Comparing version 7.2.0 to 7.3.0
@@ -8,17 +8,11 @@ { | ||
"ava": { | ||
"extensions": [ | ||
"ts" | ||
], | ||
"files": [ | ||
"test/lightship/**/*" | ||
], | ||
"require": [ | ||
"ts-node/register/transpile-only" | ||
] | ||
"extensions": { | ||
"ts": "module" | ||
} | ||
}, | ||
"dependencies": { | ||
"delay": "^5.0.0", | ||
"fastify": "^4.10.2", | ||
"roarr": "^7.14.0", | ||
"serialize-error": "^8.1.0" | ||
"fastify": "^4.17.0", | ||
"roarr": "^7.15.0", | ||
"serialize-error": "^11.0.0" | ||
}, | ||
@@ -35,7 +29,7 @@ "description": "Abstracts readiness, liveness and startup checks and graceful shutdown of Node.js services running in Kubernetes.", | ||
"@types/sinon": "^10.0.13", | ||
"ava": "^3.15.0", | ||
"axios": "^0.24.0", | ||
"ava": "^5.2.0", | ||
"axios": "^1.4.0", | ||
"coveralls": "^3.1.1", | ||
"eslint": "^8.28.0", | ||
"eslint-config-canonical": "^37.0.3", | ||
"eslint": "^8.40.0", | ||
"eslint-config-canonical": "^41.0.4", | ||
"gitdown": "^3.1.5", | ||
@@ -47,7 +41,18 @@ "husky": "^5.0.0", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^4.9.3" | ||
"tsx": "^3.12.7", | ||
"typescript": "^5.0.4" | ||
}, | ||
"engines": { | ||
"node": ">=14" | ||
"node": ">=18" | ||
}, | ||
"exports": { | ||
".": { | ||
"import": "./dist/index.js", | ||
"types": "./dist/index.d.ts" | ||
} | ||
}, | ||
"files": [ | ||
"src", | ||
"dist" | ||
], | ||
"husky": { | ||
@@ -62,4 +67,2 @@ "hooks": { | ||
"license": "BSD-3-Clause", | ||
"main": "./dist/src/index.js", | ||
"types": "./dist/src/index.d.ts", | ||
"name": "lightship", | ||
@@ -71,9 +74,10 @@ "repository": { | ||
"scripts": { | ||
"build": "rm -fr ./dist && tsc", | ||
"build": "rm -fr ./dist && tsc --project ./tsconfig.build.json", | ||
"generate-readme": "gitdown ./.README/README.md --output-file ./README.md", | ||
"lint": "eslint ./src ./test", | ||
"test": "NODE_ENV=test ava --verbose --serial", | ||
"test": "NODE_OPTIONS='--loader=tsx --no-warnings' ava --verbose --serial", | ||
"typecheck": "tsc --noEmit" | ||
}, | ||
"version": "7.2.0" | ||
"type": "module", | ||
"version": "7.3.0" | ||
} |
/* eslint-disable promise/prefer-await-to-then */ | ||
import { Logger } from '../Logger.js'; | ||
import { | ||
EventEmitter, | ||
} from 'events'; | ||
import delay from 'delay'; | ||
import createFastify from 'fastify'; | ||
import { | ||
serializeError, | ||
} from 'serialize-error'; | ||
import Logger from '../Logger'; | ||
import { | ||
SERVER_IS_NOT_READY, | ||
@@ -17,15 +9,17 @@ SERVER_IS_NOT_SHUTTING_DOWN, | ||
SERVER_IS_SHUTTING_DOWN, | ||
} from '../states'; | ||
} from '../states.js'; | ||
import { | ||
type BeaconContext, | ||
type BeaconController, | ||
type BlockingTask, | ||
type Configuration, | ||
type ConfigurationInput, | ||
type Configuration, | ||
type Lightship, | ||
type ShutdownHandler, | ||
type BeaconController, | ||
} from '../types'; | ||
import { | ||
isKubernetes, | ||
} from '../utilities'; | ||
} from '../types.js'; | ||
import { isKubernetes } from '../utilities/isKubernetes.js'; | ||
import delay from 'delay'; | ||
import createFastify from 'fastify'; | ||
import { EventEmitter } from 'node:events'; | ||
import { serializeError } from 'serialize-error'; | ||
@@ -38,3 +32,3 @@ const log = Logger.child({ | ||
LIGHTSHIP_PORT, | ||
// eslint-disable-next-line node/no-process-env | ||
// eslint-disable-next-line node/no-process-env | ||
} = process.env; | ||
@@ -48,7 +42,3 @@ | ||
shutdownHandlerTimeout: 5_000, | ||
signals: [ | ||
'SIGTERM', | ||
'SIGHUP', | ||
'SIGINT', | ||
], | ||
signals: ['SIGTERM', 'SIGHUP', 'SIGINT'], | ||
terminate: () => { | ||
@@ -61,6 +51,8 @@ // eslint-disable-next-line node/no-process-exit | ||
type Beacon = { | ||
context: BeaconContext, | ||
context: BeaconContext; | ||
}; | ||
export default async (userConfiguration?: ConfigurationInput): Promise<Lightship> => { | ||
export const createLightship = async ( | ||
userConfiguration?: ConfigurationInput, | ||
): Promise<Lightship> => { | ||
let blockingTasks: BlockingTask[] = []; | ||
@@ -87,3 +79,4 @@ | ||
const modeIsLocal = configuration.detectKubernetes === true && isKubernetes() === false; | ||
const modeIsLocal = | ||
configuration.detectKubernetes === true && isKubernetes() === false; | ||
@@ -94,3 +87,5 @@ if (modeIsLocal) { | ||
if (userConfiguration?.shutdownDelay === undefined) { | ||
log.info('detected local mode and shutdownDelay is not configured, defaulting shutdownDelay to 0ms'); | ||
log.info( | ||
'detected local mode and shutdownDelay is not configured, defaulting shutdownDelay to 0ms', | ||
); | ||
@@ -102,4 +97,8 @@ // @ts-expect-error overriding read-only value | ||
if (configuration.gracefulShutdownTimeout < configuration.shutdownHandlerTimeout) { | ||
throw new Error('gracefulShutdownTimeout cannot be lesser than shutdownHandlerTimeout.'); | ||
if ( | ||
configuration.gracefulShutdownTimeout < configuration.shutdownHandlerTimeout | ||
) { | ||
throw new Error( | ||
'gracefulShutdownTimeout cannot be lesser than shutdownHandlerTimeout.', | ||
); | ||
} | ||
@@ -125,5 +124,8 @@ | ||
app.addHook('onError', (request, reply, error, done) => { | ||
log.error({ | ||
error: serializeError(error), | ||
}, 'lightship error'); | ||
log.error( | ||
{ | ||
error: serializeError(error), | ||
}, | ||
'lightship error', | ||
); | ||
@@ -135,12 +137,7 @@ done(); | ||
if (serverIsShuttingDown) { | ||
await reply | ||
.code(500) | ||
.send(SERVER_IS_SHUTTING_DOWN); | ||
await reply.code(500).send(SERVER_IS_SHUTTING_DOWN); | ||
} else if (serverIsReady) { | ||
await reply | ||
.send(SERVER_IS_READY); | ||
await reply.send(SERVER_IS_READY); | ||
} else { | ||
await reply | ||
.code(500) | ||
.send(SERVER_IS_NOT_READY); | ||
await reply.code(500).send(SERVER_IS_NOT_READY); | ||
} | ||
@@ -151,8 +148,5 @@ }); | ||
if (serverIsShuttingDown) { | ||
await reply | ||
.code(500) | ||
.send(SERVER_IS_SHUTTING_DOWN); | ||
await reply.code(500).send(SERVER_IS_SHUTTING_DOWN); | ||
} else { | ||
await reply | ||
.send(SERVER_IS_NOT_SHUTTING_DOWN); | ||
await reply.send(SERVER_IS_NOT_SHUTTING_DOWN); | ||
} | ||
@@ -163,8 +157,5 @@ }); | ||
if (isServerReady()) { | ||
await reply | ||
.send(SERVER_IS_READY); | ||
await reply.send(SERVER_IS_READY); | ||
} else { | ||
await reply | ||
.code(500) | ||
.send(SERVER_IS_NOT_READY); | ||
await reply.code(500).send(SERVER_IS_NOT_READY); | ||
} | ||
@@ -198,3 +189,5 @@ }); | ||
if (blockingTasks.length > 0) { | ||
log.debug('service will not become immediately ready because there are blocking tasks'); | ||
log.debug( | ||
'service will not become immediately ready because there are blocking tasks', | ||
); | ||
} | ||
@@ -224,3 +217,6 @@ | ||
if (configuration.shutdownDelay) { | ||
log.debug('delaying shutdown handler by %d seconds', configuration.shutdownDelay / 1_000); | ||
log.debug( | ||
'delaying shutdown handler by %d seconds', | ||
configuration.shutdownDelay / 1_000, | ||
); | ||
@@ -248,7 +244,12 @@ await delay(configuration.shutdownDelay); | ||
if (beacons.length > 0) { | ||
log.info({ | ||
beacons, | ||
} as {}, 'program termination is on hold because there are live beacons'); | ||
log.info( | ||
{ | ||
beacons, | ||
} as {}, | ||
'program termination is on hold because there are live beacons', | ||
); | ||
} else { | ||
log.info('there are no live beacons; proceeding to terminate the Node.js process'); | ||
log.info( | ||
'there are no live beacons; proceeding to terminate the Node.js process', | ||
); | ||
@@ -289,5 +290,8 @@ eventEmitter.off('beaconStateChange', check); | ||
} catch (error) { | ||
log.error({ | ||
error: serializeError(error), | ||
}, 'shutdown handler produced an error'); | ||
log.error( | ||
{ | ||
error: serializeError(error), | ||
}, | ||
'shutdown handler produced an error', | ||
); | ||
} | ||
@@ -300,3 +304,5 @@ } | ||
log.debug('all shutdown handlers have run to completion; proceeding to terminate the Node.js process'); | ||
log.debug( | ||
'all shutdown handlers have run to completion; proceeding to terminate the Node.js process', | ||
); | ||
@@ -306,8 +312,8 @@ void app.close(); | ||
setTimeout(() => { | ||
log.warn('process did not exit on its own; investigate what is keeping the event loop active'); | ||
log.warn( | ||
'process did not exit on its own; investigate what is keeping the event loop active', | ||
); | ||
configuration.terminate(); | ||
}, 1_000) | ||
.unref(); | ||
}, 1_000).unref(); | ||
}; | ||
@@ -320,5 +326,8 @@ | ||
process.on(signal, () => { | ||
log.debug({ | ||
signal, | ||
}, 'received a shutdown signal'); | ||
log.debug( | ||
{ | ||
signal, | ||
}, | ||
'received a shutdown signal', | ||
); | ||
@@ -339,5 +348,8 @@ void shutdown(false); | ||
die: async () => { | ||
log.trace({ | ||
beacon, | ||
} as {}, 'beacon has been killed'); | ||
log.trace( | ||
{ | ||
beacon, | ||
} as {}, | ||
'beacon has been killed', | ||
); | ||
@@ -353,11 +365,16 @@ beacons.splice(beacons.indexOf(beacon), 1); | ||
void deferredFirstReady.then(() => { | ||
log.info('service became available for the first time'); | ||
}).catch(async (error) => { | ||
log.error({ | ||
error: serializeError(error), | ||
}, 'service failed to become available for the first time'); | ||
void deferredFirstReady | ||
.then(() => { | ||
log.info('service became available for the first time'); | ||
}) | ||
.catch(async (error) => { | ||
log.error( | ||
{ | ||
error: serializeError(error), | ||
}, | ||
'service failed to become available for the first time', | ||
); | ||
await shutdown(false); | ||
}); | ||
await shutdown(false); | ||
}); | ||
@@ -373,11 +390,13 @@ return { | ||
void blockingTask.then(() => { | ||
blockingTasks = blockingTasks.filter((maybeTargetBlockingTask) => { | ||
return maybeTargetBlockingTask !== blockingTask; | ||
}); | ||
void blockingTask | ||
.then(() => { | ||
blockingTasks = blockingTasks.filter((maybeTargetBlockingTask) => { | ||
return maybeTargetBlockingTask !== blockingTask; | ||
}); | ||
if (blockingTasks.length === 0 && serverIsReady === true) { | ||
resolveFirstReady(); | ||
} | ||
}).catch(rejectFirstReady); | ||
if (blockingTasks.length === 0 && serverIsReady === true) { | ||
resolveFirstReady(); | ||
} | ||
}) | ||
.catch(rejectFirstReady); | ||
}, | ||
@@ -384,0 +403,0 @@ registerShutdownHandler: (shutdownHandler) => { |
@@ -1,8 +0,6 @@ | ||
export { | ||
default as createLightship, | ||
} from './factories/createLightship'; | ||
export { createLightship } from './factories/createLightship.js'; | ||
export type { | ||
BeaconController, | ||
ConfigurationInput, | ||
Lightship, | ||
ConfigurationInput, | ||
BeaconController, | ||
} from './types'; | ||
} from './types.js'; |
@@ -1,7 +0,5 @@ | ||
import { | ||
Roarr, | ||
} from 'roarr'; | ||
import { Roarr } from 'roarr'; | ||
export default Roarr.child({ | ||
export const Logger = Roarr.child({ | ||
package: 'lightship', | ||
}); |
@@ -1,4 +0,2 @@ | ||
import { | ||
type State, | ||
} from './types'; | ||
import { type State } from './types.js'; | ||
@@ -10,4 +8,6 @@ const createState = <TState extends State>(subject: TState): TState => { | ||
export const SERVER_IS_NOT_READY = createState('SERVER_IS_NOT_READY'); | ||
export const SERVER_IS_NOT_SHUTTING_DOWN = createState('SERVER_IS_NOT_SHUTTING_DOWN'); | ||
export const SERVER_IS_NOT_SHUTTING_DOWN = createState( | ||
'SERVER_IS_NOT_SHUTTING_DOWN', | ||
); | ||
export const SERVER_IS_READY = createState('SERVER_IS_READY'); | ||
export const SERVER_IS_SHUTTING_DOWN = createState('SERVER_IS_SHUTTING_DOWN'); |
@@ -1,4 +0,2 @@ | ||
import { | ||
type Server, | ||
} from 'http'; | ||
import { type Server } from 'node:http'; | ||
@@ -20,27 +18,27 @@ /** | ||
export type ConfigurationInput = { | ||
readonly detectKubernetes?: boolean, | ||
readonly gracefulShutdownTimeout?: number, | ||
readonly port?: number, | ||
readonly shutdownDelay?: number, | ||
readonly shutdownHandlerTimeout?: number, | ||
readonly signals?: readonly string[], | ||
readonly terminate?: () => void, | ||
readonly detectKubernetes?: boolean; | ||
readonly gracefulShutdownTimeout?: number; | ||
readonly port?: number; | ||
readonly shutdownDelay?: number; | ||
readonly shutdownHandlerTimeout?: number; | ||
readonly signals?: readonly string[]; | ||
readonly terminate?: () => void; | ||
}; | ||
export type Configuration = { | ||
readonly detectKubernetes: boolean, | ||
readonly gracefulShutdownTimeout: number, | ||
readonly port: number, | ||
readonly shutdownDelay: number, | ||
readonly shutdownHandlerTimeout: number, | ||
readonly signals: readonly string[], | ||
readonly terminate: () => void, | ||
readonly detectKubernetes: boolean; | ||
readonly gracefulShutdownTimeout: number; | ||
readonly port: number; | ||
readonly shutdownDelay: number; | ||
readonly shutdownHandlerTimeout: number; | ||
readonly signals: readonly string[]; | ||
readonly terminate: () => void; | ||
}; | ||
export type BeaconContext = { | ||
[key: string]: unknown, | ||
[key: string]: unknown; | ||
}; | ||
export type BeaconController = { | ||
readonly die: () => Promise<void>, | ||
readonly die: () => Promise<void>; | ||
}; | ||
@@ -50,3 +48,7 @@ | ||
export type State = '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'; | ||
@@ -61,12 +63,12 @@ /** | ||
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<void>, | ||
readonly signalNotReady: () => void, | ||
readonly signalReady: () => void, | ||
readonly whenFirstReady: () => Promise<void>, | ||
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<void>; | ||
readonly signalNotReady: () => void; | ||
readonly signalReady: () => void; | ||
readonly whenFirstReady: () => Promise<void>; | ||
}; |
@@ -1,4 +0,4 @@ | ||
export default (): boolean => { | ||
export const isKubernetes = (): boolean => { | ||
// eslint-disable-next-line node/no-process-env | ||
return Boolean(process.env.KUBERNETES_SERVICE_HOST); | ||
}; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
0
Yes
40381
21
9
405
1
+ Addedserialize-error@11.0.3(transitive)
+ Addedtype-fest@2.19.0(transitive)
- Removedserialize-error@8.1.0(transitive)
- Removedtype-fest@0.20.2(transitive)
Updatedfastify@^4.17.0
Updatedroarr@^7.15.0
Updatedserialize-error@^11.0.0