diff --git a/.README/README.md b/.README/README.md index d916abe..17a5252 100644 --- a/.README/README.md +++ b/.README/README.md @@ -74,9 +74,9 @@ import { createLightship } from 'lightship'; -const configuration: ConfigurationInputType = {}; +const configuration: ConfigurationInput = {}; -const lightship: LightshipType = createLightship(configuration); +const lightship: Lightship = createLightship(configuration); ``` @@ -86,7 +86,7 @@ The following types describe the configuration shape and the resulting Lightship /** * A teardown function called when shutdown is initialized. */ -type ShutdownHandlerType = () => Promise | void; +type ShutdownHandler = () => Promise | void; /** * @property detectKubernetes Run Lightship in local mode when Kubernetes is not detected. Default: true. @@ -97,7 +97,7 @@ type ShutdownHandlerType = () => Promise | void; * @property signals An a array of [signal events]{@link https://nodejs.org/api/process.html#process_signal_events}. Default: [SIGTERM]. * @property terminate Method used to terminate Node.js process. Default: `() => { process.exit(1) };`. */ -export type ConfigurationInputType = {| +export type ConfigurationInput = {| +detectKubernetes?: boolean, +gracefulShutdownTimeout?: number, +port?: number, @@ -115,12 +115,12 @@ export type ConfigurationInputType = {| * @property signalReady Changes server state to SERVER_IS_READY. * @property whenFirstReady Resolves the first time the service goes from `SERVER_IS_NOT_READY` to `SERVER_IS_READY` state. */ -type LightshipType = {| - +createBeacon: (context?: BeaconContextType) => BeaconControllerType, +type Lightship = {| + +createBeacon: (context?: BeaconContext) => BeaconController, +isServerReady: () => boolean, +isServerShuttingDown: () => boolean, +queueBlockingTask: (blockingTask: Promise) => void, - +registerShutdownHandler: (shutdownHandler: ShutdownHandlerType) => void, + +registerShutdownHandler: (shutdownHandler: ShutdownHandler) => void, +server: http$Server, +shutdown: () => Promise, +signalNotReady: () => void, diff --git a/.babelrc b/.babelrc index a554265..2799f39 100644 --- a/.babelrc +++ b/.babelrc @@ -6,9 +6,6 @@ ] } }, - "plugins": [ - "@babel/transform-flow-strip-types" - ], "presets": [ [ "@babel/env", @@ -17,6 +14,7 @@ "node": "10" } } - ] + ], + "@babel/preset-typescript" ] } diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/.eslintrc b/.eslintrc index cee55ae..c969698 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,19 @@ { + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], "extends": [ "canonical", - "canonical/flowtype" + "eslint:recommended", + "plugin:@typescript-eslint/recommended" ], + "settings": { + "import/resolver": { + "node": { + "extensions": [".js", ".ts", ".d.ts"] + } + } + }, "root": true } diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index 93b01a5..0000000 --- a/.flowconfig +++ /dev/null @@ -1,6 +0,0 @@ -[ignore] -.*/node_modules/.*/test/.* -/dist/.* - -[options] -types_first=false diff --git a/.gitignore b/.gitignore index 9287d60..9c250db 100755 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ node_modules !.editorconfig !.eslintignore !.eslintrc -!.flowconfig !.gitignore !.npmignore !.README diff --git a/README.md b/README.md index e8cde28..ae94cc9 100644 --- a/README.md +++ b/README.md @@ -104,9 +104,9 @@ import { createLightship } from 'lightship'; -const configuration: ConfigurationInputType = {}; +const configuration: ConfigurationInput = {}; -const lightship: LightshipType = createLightship(configuration); +const lightship: Lightship = createLightship(configuration); ``` @@ -116,7 +116,7 @@ The following types describe the configuration shape and the resulting Lightship /** * A teardown function called when shutdown is initialized. */ -type ShutdownHandlerType = () => Promise | void; +type ShutdownHandler = () => Promise | void; /** * @property detectKubernetes Run Lightship in local mode when Kubernetes is not detected. Default: true. @@ -127,7 +127,7 @@ type ShutdownHandlerType = () => Promise | void; * @property signals An a array of [signal events]{@link https://nodejs.org/api/process.html#process_signal_events}. Default: [SIGTERM]. * @property terminate Method used to terminate Node.js process. Default: `() => { process.exit(1) };`. */ -export type ConfigurationInputType = {| +export type ConfigurationInput = {| +detectKubernetes?: boolean, +gracefulShutdownTimeout?: number, +port?: number, @@ -145,12 +145,12 @@ export type ConfigurationInputType = {| * @property signalReady Changes server state to SERVER_IS_READY. * @property whenFirstReady Resolves the first time the service goes from `SERVER_IS_NOT_READY` to `SERVER_IS_READY` state. */ -type LightshipType = {| - +createBeacon: (context?: BeaconContextType) => BeaconControllerType, +type Lightship = {| + +createBeacon: (context?: BeaconContext) => BeaconController, +isServerReady: () => boolean, +isServerShuttingDown: () => boolean, +queueBlockingTask: (blockingTask: Promise) => void, - +registerShutdownHandler: (shutdownHandler: ShutdownHandlerType) => void, + +registerShutdownHandler: (shutdownHandler: ShutdownHandler) => void, +server: http$Server, +shutdown: () => Promise, +signalNotReady: () => void, diff --git a/package.json b/package.json index 0fb4943..bedeebc 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,9 @@ "url": "http://gajus.com" }, "ava": { + "extensions": [ + "ts" + ], "babel": { "compileAsTests": [ "test/helpers/**/*" @@ -14,14 +17,13 @@ "test/lightship/**/*" ], "require": [ - "@babel/register" + "ts-node/register" ] }, "dependencies": { "delay": "^4.4.0", "express": "^4.17.1", "http-terminator": "^2.0.3", - "promise-deferred": "^2.0.3", "roarr": "^2.15.4", "serialize-error": "^7.0.1" }, @@ -33,28 +35,36 @@ "@babel/node": "^7.12.10", "@babel/plugin-transform-flow-strip-types": "^7.12.10", "@babel/preset-env": "^7.12.10", - "@babel/register": "^7.12.10", + "@babel/preset-typescript": "^7.12.7", + "@types/express": "^4.17.9", + "@types/http-terminator": "^2.0.1", + "@types/node": "^14.14.14", + "@types/roarr": "^2.14.2", + "@types/sinon": "^9.0.9", + "@typescript-eslint/eslint-plugin": "^4.10.0", + "@typescript-eslint/parser": "^4.10.0", "ava": "^3.14.0", "axios": "^0.21.0", "babel-plugin-istanbul": "^6.0.0", "coveralls": "^3.1.0", "eslint": "^7.15.0", "eslint-config-canonical": "^24.4.4", - "flow-bin": "^0.140.0", - "flow-copy-source": "^2.0.9", "get-port": "^5.1.1", "gitdown": "^3.1.3", "husky": "^4.3.6", "nyc": "^15.1.0", "semantic-release": "^17.3.0", - "sinon": "^9.2.2" + "sinon": "^9.2.2", + "source-map-support": "^0.5.19", + "ts-node": "^9.1.1", + "typescript": "^4.1.3" }, "engines": { "node": ">=10" }, "husky": { "hooks": { - "pre-commit": "npm run lint && npm run test && npm run build" + "pre-commit": "npm run lint && npm run typecheck && npm run test && npm run build" } }, "keywords": [ @@ -62,29 +72,27 @@ ], "license": "BSD-3-Clause", "main": "./dist/index.js", + "types": "./dist/index.d.ts", "name": "lightship", "nyc": { "include": [ - "src/**/*.js" - ], - "instrument": false, - "reporter": [ - "text-lcov" + "src/**/*.ts" ], "require": [ - "@babel/register" - ], - "sourceMap": false + "ts-node/register", + "source-map-support/register" + ] }, "repository": { "type": "git", "url": "https://github.com/gajus/lightship" }, "scripts": { - "build": "rm -fr ./dist && NODE_ENV=production babel ./src --out-dir ./dist --copy-files --source-maps && flow-copy-source src dist", + "build": "rm -fr ./dist && NODE_ENV=production babel ./src --out-dir ./dist --extensions \".ts\" && tsc --emitDeclarationOnly", "generate-readme": "gitdown ./.README/README.md --output-file ./README.md", - "lint": "eslint ./src ./test && flow", - "test": "NODE_ENV=test ava --verbose --serial" + "lint": "eslint ./src ./test --ext .js --ext .ts", + "test": "NODE_ENV=test ava --verbose --serial", + "typecheck": "tsc --noEmit" }, "version": "1.0.0" } diff --git a/src/Logger.js b/src/Logger.ts similarity index 89% rename from src/Logger.js rename to src/Logger.ts index 5a98673..15108e8 100644 --- a/src/Logger.js +++ b/src/Logger.ts @@ -1,5 +1,3 @@ -// @flow - import Roarr from 'roarr'; export default Roarr.child({ diff --git a/src/factories/createLightship.js b/src/factories/createLightship.ts similarity index 87% rename from src/factories/createLightship.js rename to src/factories/createLightship.ts index be00d3e..a78494e 100644 --- a/src/factories/createLightship.js +++ b/src/factories/createLightship.ts @@ -1,13 +1,15 @@ -// @flow - // eslint-disable-next-line fp/no-events -import EventEmitter from 'events'; +import { + EventEmitter, +} from 'events'; +import { + AddressInfo, +} from 'net'; import delay from 'delay'; import express from 'express'; import { createHttpTerminator, } from 'http-terminator'; -import Deferred from 'promise-deferred'; import { serializeError, } from 'serialize-error'; @@ -19,10 +21,13 @@ import { SERVER_IS_SHUTTING_DOWN, } from '../states'; import type { - ConfigurationInputType, - ConfigurationType, - LightshipType, - ShutdownHandlerType, + BeaconContext, + BlockingTask, + ConfigurationInput, + Configuration, + Lightship, + ShutdownHandler, + BeaconController, } from '../types'; import { isKubernetes, @@ -49,23 +54,30 @@ const defaultConfiguration = { }, }; -export default (userConfiguration?: ConfigurationInputType): LightshipType => { - let blockingTasks = []; +interface Beacon { + context: BeaconContext +} - const deferredFirstReady = new Deferred(); +export default (userConfiguration?: ConfigurationInput): Lightship => { + let blockingTasks: BlockingTask[] = []; + + let resolveFirstReady: () => void; + const deferredFirstReady = new Promise((resolve) => { + resolveFirstReady = resolve; + }); // eslint-disable-next-line promise/always-return, promise/catch-or-return - deferredFirstReady.promise.then(() => { + deferredFirstReady.then(() => { log.info('service became available for the first time'); }); const eventEmitter = new EventEmitter(); - const beacons = []; + const beacons: Beacon[] = []; - const shutdownHandlers: Array = []; + const shutdownHandlers: Array = []; - const configuration: ConfigurationType = { + const configuration: Configuration = { ...defaultConfiguration, ...userConfiguration, }; @@ -92,7 +104,8 @@ export default (userConfiguration?: ConfigurationInputType): LightshipType => { const modeIsLocal = configuration.detectKubernetes === true && isKubernetes() === false; const server = app.listen(modeIsLocal ? undefined : configuration.port, () => { - log.info('Lightship HTTP service is running on port %s', server.address().port); + const address = server.address() as AddressInfo; + log.info('Lightship HTTP service is running on port %s', address.port); }); const httpTerminator = createHttpTerminator({ @@ -151,7 +164,7 @@ export default (userConfiguration?: ConfigurationInputType): LightshipType => { serverIsReady = true; if (blockingTasks.length === 0) { - deferredFirstReady.resolve(); + resolveFirstReady(); } }; @@ -184,12 +197,11 @@ export default (userConfiguration?: ConfigurationInputType): LightshipType => { configuration.terminate(); }, configuration.gracefulShutdownTimeout); - // $FlowFixMe gracefulShutdownTimeoutId.unref(); } if (beacons.length) { - await new Promise((resolve) => { + await new Promise((resolve) => { const check = () => { log.debug('checking if there are live beacons'); @@ -225,7 +237,6 @@ export default (userConfiguration?: ConfigurationInputType): LightshipType => { configuration.terminate(); }, configuration.shutdownHandlerTimeout); - // $FlowFixMe shutdownHandlerTimeoutId.unref(); } @@ -255,7 +266,6 @@ export default (userConfiguration?: ConfigurationInputType): LightshipType => { configuration.terminate(); }, 1_000) - // $FlowFixMe .unref(); }; @@ -273,7 +283,7 @@ export default (userConfiguration?: ConfigurationInputType): LightshipType => { } } - const createBeacon = (context) => { + const createBeacon = (context?: BeaconContext): BeaconController => { const beacon = { context: context || {}, }; @@ -301,7 +311,7 @@ export default (userConfiguration?: ConfigurationInputType): LightshipType => { isServerShuttingDown: () => { return serverIsShuttingDown; }, - queueBlockingTask: (blockingTask) => { + queueBlockingTask: (blockingTask: BlockingTask) => { blockingTasks.push(blockingTask); // eslint-disable-next-line promise/catch-or-return @@ -311,7 +321,7 @@ export default (userConfiguration?: ConfigurationInputType): LightshipType => { }); if (blockingTasks.length === 0 && serverIsReady === true) { - deferredFirstReady.resolve(); + resolveFirstReady(); } return result; @@ -327,7 +337,7 @@ export default (userConfiguration?: ConfigurationInputType): LightshipType => { signalNotReady, signalReady, whenFirstReady: () => { - return deferredFirstReady.promise; + return deferredFirstReady; }, }; }; diff --git a/src/index.js b/src/index.ts similarity index 56% rename from src/index.js rename to src/index.ts index fb00811..17536b2 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,8 +1,4 @@ -// @flow - export { default as createLightship, } from './factories/createLightship'; -export type { - LightshipType, -} from './types'; +export * from './types'; diff --git a/src/states.js b/src/states.ts similarity index 76% rename from src/states.js rename to src/states.ts index 6a611de..2b63857 100644 --- a/src/states.js +++ b/src/states.ts @@ -1,11 +1,8 @@ -// @flow - -import type { - StateType, +import { + State, } from './types'; -const createState = (subject: *): StateType => { - // $FlowFixMe +const createState = (subject: TState): TState => { return subject; }; diff --git a/src/types.js b/src/types.ts similarity index 56% rename from src/types.js rename to src/types.ts index 501aea9..1be5465 100644 --- a/src/types.js +++ b/src/types.ts @@ -1,13 +1,11 @@ -// @flow - -import type { +import { Server, } from 'http'; /** * A teardown function called when shutdown is initialized. */ -export type ShutdownHandlerType = () => Promise | void; +export type ShutdownHandler = () => Promise | void; /** * @property detectKubernetes Run Lightship in local mode when Kubernetes is not detected. Default: true. @@ -18,38 +16,37 @@ export type ShutdownHandlerType = () => Promise | void; * @property signals An a array of [signal events]{@link https://nodejs.org/api/process.html#process_signal_events}. Default: [SIGTERM]. * @property terminate Method used to terminate Node.js process. Default: `() => { process.exit(1) };`. */ -export type ConfigurationInputType = {| - +detectKubernetes?: boolean, - +gracefulShutdownTimeout?: number, - +port?: number, - +shutdownDelay?: number, - +shutdownHandlerTimeout?: number, - +signals?: $ReadOnlyArray, - +terminate?: () => void, -|}; +export type ConfigurationInput = { + readonly detectKubernetes?: boolean; + readonly gracefulShutdownTimeout?: number; + readonly port?: number; + readonly shutdownDelay?: number; + readonly shutdownHandlerTimeout?: number; + readonly signals?: ReadonlyArray; + readonly terminate?: () => void; +}; + +export type Configuration = { + readonly detectKubernetes: boolean; + readonly gracefulShutdownTimeout: number; + readonly port: number; + readonly shutdownDelay: number; + readonly shutdownHandlerTimeout: number; + readonly signals: ReadonlyArray; + readonly terminate: () => void; +}; -export type ConfigurationType = {| - +detectKubernetes: boolean, - +gracefulShutdownTimeout: number, - +port: number, - +shutdownDelay: number, - +shutdownHandlerTimeout: number, - +signals: $ReadOnlyArray, - +terminate: () => void, -|}; +export type BeaconContext = { + [key: string]: unknown; +}; -// eslint-disable-next-line flowtype/no-weak-types -export type BeaconContextType = Object; +export type BeaconController = { + readonly die: () => Promise; +}; -export type BeaconControllerType = {| - +die: () => Promise, -|}; +export type BlockingTask = Promise -export opaque type StateType = - 'SERVER_IS_NOT_READY' | - 'SERVER_IS_NOT_SHUTTING_DOWN' | - 'SERVER_IS_READY' | - 'SERVER_IS_SHUTTING_DOWN'; +export type State = 'SERVER_IS_NOT_READY' | 'SERVER_IS_NOT_SHUTTING_DOWN' | 'SERVER_IS_READY' | 'SERVER_IS_SHUTTING_DOWN'; /** * @property registerShutdownHandler Registers teardown functions that are called when shutdown is initialized. All registered shutdown handlers are executed in the order they have been registered. After all shutdown handlers have been executed, Lightship asks `process.exit()` to terminate the process synchronously. @@ -58,17 +55,15 @@ export opaque type StateType = * @property signalReady Changes server state to SERVER_IS_READY. * @property queueBlockingTask Forces service state to SERVER_IS_NOT_READY until all promises are resolved. */ -export type LightshipType = {| - +createBeacon: (context?: BeaconContextType) => BeaconControllerType, - +isServerReady: () => boolean, - +isServerShuttingDown: () => boolean, - - // eslint-disable-next-line flowtype/no-weak-types - +queueBlockingTask: (blockingTask: Promise) => void, - +registerShutdownHandler: (shutdownHandler: ShutdownHandlerType) => void, - +server: Server, - +shutdown: () => Promise, - +signalNotReady: () => void, - +signalReady: () => void, - +whenFirstReady: () => Promise, -|}; +export type Lightship = { + readonly createBeacon: (context?: BeaconContext) => BeaconController; + readonly isServerReady: () => boolean; + readonly isServerShuttingDown: () => boolean; + readonly queueBlockingTask: (blockingTask: BlockingTask) => void; + readonly registerShutdownHandler: (shutdownHandler: ShutdownHandler) => void; + readonly server: Server; + readonly shutdown: () => Promise; + readonly signalNotReady: () => void; + readonly signalReady: () => void; + readonly whenFirstReady: () => Promise +}; diff --git a/src/utilities/index.js b/src/utilities/index.ts similarity index 85% rename from src/utilities/index.js rename to src/utilities/index.ts index b74a5f1..704489e 100644 --- a/src/utilities/index.js +++ b/src/utilities/index.ts @@ -1,5 +1,3 @@ -// @flow - export { default as isKubernetes, } from './isKubernetes'; diff --git a/src/utilities/isKubernetes.js b/src/utilities/isKubernetes.ts similarity index 93% rename from src/utilities/isKubernetes.js rename to src/utilities/isKubernetes.ts index 4e74645..14f3738 100644 --- a/src/utilities/isKubernetes.js +++ b/src/utilities/isKubernetes.ts @@ -1,5 +1,3 @@ -// @flow - export default (): boolean => { // eslint-disable-next-line no-process-env return Boolean(process.env.KUBERNETES_SERVICE_HOST); diff --git a/test/.eslintrc b/test/.eslintrc index 9c55099..ebd3e3b 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -1,5 +1,6 @@ { "extends": [ + "../.eslintrc", "canonical/ava" ] } diff --git a/test/lightship/factories/createLightship.js b/test/lightship/factories/createLightship.ts similarity index 77% rename from test/lightship/factories/createLightship.js rename to test/lightship/factories/createLightship.ts index 8fc6c19..6bf6899 100644 --- a/test/lightship/factories/createLightship.js +++ b/test/lightship/factories/createLightship.ts @@ -1,30 +1,40 @@ -// @flow - +import { + AddressInfo, +} from 'net'; import test from 'ava'; import axios from 'axios'; import delay from 'delay'; -import Deferred from 'promise-deferred'; -import sinon from 'sinon'; +import { + stub, spy, +} from 'sinon'; import createLightship from '../../../src/factories/createLightship'; import { - SERVER_IS_NOT_READY, - SERVER_IS_NOT_SHUTTING_DOWN, - SERVER_IS_READY, - SERVER_IS_SHUTTING_DOWN, + SERVER_IS_NOT_READY, SERVER_IS_NOT_SHUTTING_DOWN, SERVER_IS_READY, SERVER_IS_SHUTTING_DOWN, } from '../../../src/states'; +import { + Lightship, +} from '../../../src/types'; -type ProbeStateType = {| - +message: string, - +status: number, -|}; +type ProbeState = { + readonly message: string; + readonly status: number; +}; + +type ServiceState = { + readonly health: ProbeState; + readonly live: ProbeState; + readonly ready: ProbeState; +}; -type ServiceStateType = {| - +health: ProbeStateType, - +live: ProbeStateType, - +ready: ProbeStateType, -|}; +const getLightshipPort = (lightship: Lightship) => { + const address = lightship.server.address() as AddressInfo; + + return address.port; +}; + +const getServiceState = async (lightship: Lightship): Promise => { + const port = getLightshipPort(lightship); -const getServiceState = async (port: number = 9_000): Promise => { const health = await axios('http://127.0.0.1:' + port + '/health', { validateStatus: () => { return true; @@ -60,7 +70,7 @@ const getServiceState = async (port: number = 9_000): Promise }; test('server starts in SERVER_IS_NOT_READY state', async (t) => { - const terminate = sinon.stub(); + const terminate = stub(); const lightship = createLightship({ shutdownDelay: 0, @@ -70,7 +80,7 @@ test('server starts in SERVER_IS_NOT_READY state', async (t) => { t.is(lightship.isServerReady(), false); t.is(lightship.isServerShuttingDown(), false); - const serviceState = await getServiceState(lightship.server.address().port); + const serviceState = await getServiceState(lightship); t.is(serviceState.health.status, 500); t.is(serviceState.health.message, SERVER_IS_NOT_READY); @@ -87,7 +97,7 @@ test('server starts in SERVER_IS_NOT_READY state', async (t) => { }); test('calling `signalReady` changes server state to SERVER_IS_READY', async (t) => { - const terminate = sinon.stub(); + const terminate = stub(); const lightship = createLightship({ shutdownDelay: 0, @@ -99,7 +109,7 @@ test('calling `signalReady` changes server state to SERVER_IS_READY', async (t) t.is(lightship.isServerReady(), true); t.is(lightship.isServerShuttingDown(), false); - const serviceState = await getServiceState(lightship.server.address().port); + const serviceState = await getServiceState(lightship); t.is(serviceState.health.status, 200); t.is(serviceState.health.message, SERVER_IS_READY); @@ -116,7 +126,7 @@ test('calling `signalReady` changes server state to SERVER_IS_READY', async (t) }); test('calling `signalReady` resolves `whenFirstReady`', async (t) => { - const terminate = sinon.stub(); + const terminate = stub(); const lightship = createLightship({ shutdownDelay: 0, @@ -135,22 +145,25 @@ test('calling `signalReady` resolves `whenFirstReady`', async (t) => { }); test('`queueBlockingTask` forces service into SERVER_IS_NOT_READY until blocking tasks are resolved', async (t) => { - const terminate = sinon.stub(); + const terminate = stub(); const lightship = createLightship({ shutdownDelay: 0, terminate, }); - const blockingTask = new Deferred(); + let resolveBlockingTask: () => void; + const blockingTask = new Promise((resolve) => { + resolveBlockingTask = resolve; + }); - lightship.queueBlockingTask(blockingTask.promise); + lightship.queueBlockingTask(blockingTask); lightship.signalReady(); t.is(lightship.isServerReady(), false); - blockingTask.resolve(); + resolveBlockingTask!(); await delay(0); @@ -158,7 +171,7 @@ test('`queueBlockingTask` forces service into SERVER_IS_NOT_READY until blocking }); test('`whenFirstReady` resolves when all blocking tasks are resolved', async (t) => { - const terminate = sinon.stub(); + const terminate = stub(); const lightship = createLightship({ shutdownDelay: 0, @@ -166,7 +179,7 @@ test('`whenFirstReady` resolves when all blocking tasks are resolved', async (t) }); lightship.queueBlockingTask( - new Promise((resolve) => { + new Promise((resolve) => { setTimeout(() => { resolve(); }, 200); @@ -185,7 +198,7 @@ test('`whenFirstReady` resolves when all blocking tasks are resolved', async (t) }); test('calling `signalNotReady` changes server state to SERVER_IS_NOT_READY', async (t) => { - const terminate = sinon.stub(); + const terminate = stub(); const lightship = createLightship({ shutdownDelay: 0, @@ -198,7 +211,7 @@ test('calling `signalNotReady` changes server state to SERVER_IS_NOT_READY', asy t.is(lightship.isServerReady(), false); t.is(lightship.isServerShuttingDown(), false); - const serviceState = await getServiceState(lightship.server.address().port); + const serviceState = await getServiceState(lightship); t.is(serviceState.health.status, 500); t.is(serviceState.health.message, SERVER_IS_NOT_READY); @@ -215,14 +228,14 @@ test('calling `signalNotReady` changes server state to SERVER_IS_NOT_READY', asy }); test('calling `shutdown` changes server state to SERVER_IS_SHUTTING_DOWN', async (t) => { - const terminate = sinon.stub(); + const terminate = stub(); const lightship = createLightship({ shutdownDelay: 0, terminate, }); - let shutdown; + let shutdown: (() => void) | undefined; lightship.registerShutdownHandler(() => { return new Promise((resolve) => { @@ -235,7 +248,7 @@ test('calling `shutdown` changes server state to SERVER_IS_SHUTTING_DOWN', async t.is(lightship.isServerReady(), false); t.is(lightship.isServerShuttingDown(), true); - const serviceState = await getServiceState(lightship.server.address().port); + const serviceState = await getServiceState(lightship); t.is(serviceState.health.status, 500); t.is(serviceState.health.message, SERVER_IS_SHUTTING_DOWN); @@ -256,39 +269,37 @@ test('calling `shutdown` changes server state to SERVER_IS_SHUTTING_DOWN', async }); test('invoking `shutdown` using a signal causes SERVER_IS_READY', (t) => { - const terminate = sinon.stub(); + const terminate = stub(); const lightship = createLightship({ detectKubernetes: false, shutdownDelay: 0, - signals: [ - 'LIGHTSHIP_TEST', - ], + signals: ['LIGHTSHIP_TEST'], terminate, }); - process.emit('LIGHTSHIP_TEST'); + process.emit('LIGHTSHIP_TEST' as any); t.is(lightship.isServerReady(), false); t.is(lightship.isServerShuttingDown(), true); }); test('error thrown from within a shutdown handler does not interrupt the shutdown sequence', async (t) => { - const terminate = sinon.stub(); + const terminate = stub(); const lightship = createLightship({ shutdownDelay: 0, terminate, }); - const shutdownHandler0 = sinon.spy(async () => { + const shutdownHandler0 = spy(async () => { throw new Error('test'); }); - let shutdown; + let shutdown: (() => void) | undefined; - const shutdownHandler1 = sinon.spy(() => { - return new Promise((resolve) => { + const shutdownHandler1 = spy(() => { + return new Promise((resolve) => { shutdown = resolve; }); }); @@ -313,17 +324,17 @@ test('error thrown from within a shutdown handler does not interrupt the shutdow }); test('calling `shutdown` multiple times results in shutdown handlers called once', async (t) => { - const terminate = sinon.stub(); + const terminate = stub(); const lightship = createLightship({ shutdownDelay: 0, terminate, }); - let shutdown; + let shutdown: (() => void) | undefined; - const shutdownHandler = sinon.spy(() => { - return new Promise((resolve) => { + const shutdownHandler = spy(() => { + return new Promise((resolve) => { shutdown = resolve; }); }); @@ -350,17 +361,17 @@ test('calling `shutdown` multiple times results in shutdown handlers called once }); test('presence of live beacons suspend the shutdown routine', async (t) => { - const terminate = sinon.stub(); + const terminate = stub(); const lightship = createLightship({ shutdownDelay: 0, terminate, }); - let shutdown; + let shutdown: (() => void) | undefined; - const shutdownHandler = sinon.spy(() => { - return new Promise((resolve) => { + const shutdownHandler = spy(() => { + return new Promise((resolve) => { shutdown = resolve; }); }); @@ -389,17 +400,17 @@ test('presence of live beacons suspend the shutdown routine', async (t) => { }); test('delays shutdown handlers', async (t) => { - const terminate = sinon.stub(); + const terminate = stub(); const lightship = createLightship({ shutdownDelay: 1_000, terminate, }); - let shutdown; + let shutdown: (() => void) | undefined; - const shutdownHandler = sinon.spy(() => { - return new Promise((resolve) => { + const shutdownHandler = spy(() => { + return new Promise((resolve) => { shutdown = resolve; }); }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d31f002 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,71 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "exclude": ["./test"] +}