@guillaumecisco/terminus-lifecycle
v1.0.9
Published
Kubernetes-ready lifecycle management with graceful shutdown and beacon tracking
Maintainers
Readme
terminus-lifecycle
Kubernetes-ready lifecycle management with graceful shutdown and beacon tracking
A modern, TypeScript-first library for managing application lifecycle in Kubernetes environments. Built on top of @godaddy/terminus with enhanced features:
- 🎯 Beacon tracking - Track ongoing operations during shutdown
- 🏥 Health checks -
/health,/live, and/readyendpoints - 🔄 Graceful shutdown - Wait for in-flight operations to complete
- 📊 Structured logging - Built-in pino logger with pretty printing
- 🐳 Kubernetes-native - Designed for K8s readiness/liveness probes
- 🔌 Default instance - Access lifecycle state from anywhere in your app
Installation
$> npm install @guillaumecisco/terminus-lifecycle
# or
$> yarn add @guillaumecisco/terminus-lifecycleFor pretty logs in development:
$> npm install --save-dev pino-prettyQuick Start
import { createLifecycleServer } from '@guillaumecisco/terminus-lifecycle'
const lifecycle = createLifecycleServer({
port: 9000,
onReadyPromises: YourAsyncInitializationTasksConnectToDatabase().startMessageQueue(),
onShutdown: async () => {
// Custom cleanup logic await closeConnections()
},
})
// Start your main application
app.listen(3000, async () => { await lifecycle.setReady(true) })Features
Default Lifecycle Instance
The first lifecycle server created becomes the default instance, accessible from anywhere in your application:
import { createLifecycleServer, getDefaultLifecycle } from '@guillaumecisco/terminus-lifecycle'
// In your main file
createLifecycleServer({ port: 9000 })
// In any other file
import { getDefaultLifecycle } from '@guillaumecisco/terminus-lifecycle'
async function processJobs(jobs) {
for (const job of jobs) {
if (getDefaultLifecycle().isServerShuttingDown()) {
console.log('Shutdown detected, stopping job processing')
break
}
await processJob(job)
}
}Beacon Manager
Track ongoing operations to ensure graceful shutdown:
import { beaconManager } from '@guillaumecisco/terminus-lifecycle'
async function processJob(job) {
const beacon = beaconManager.createBeacon({ name: 'processJob', jobId: job.id })
try {
await doWork(job)
}
finally {
await beacon.die()
}
}Custom Logger
import { createLifecycleServer, createLogger } from '@guillaumecisco/terminus-lifecycle'
const logger = createLogger({
level: 'debug',
prettify: true, // pretty print in development
})
const lifecycle = createLifecycleServer({
port: 9000,
logger,
onShutdown: async () => {
logger.info('Shutting down gracefully')
}
})Health Check Endpoints
The lifecycle server automatically creates three endpoints:
GET /- Simple OK responseGET /health- Overall health (ready and not shutting down)GET /live- Liveness probe (not shutting down)GET /ready- Readiness probe (initialization complete and not shutting down)
API
createLifecycleServer(config)
Creates and initializes a lifecycle server.
Config Options:
{
port?: number // Default: 9000
onReadyPromises?: Promise[] // Promises to wait for before marking ready
onShutdown?: () => Promise // Custom shutdown handler
readinessPeriodSeconds?: number // K8s readiness period (default: 10)
readinessFailureThreshold?: number // K8s failure threshold (default: 3)
logger?: Logger // Custom pino logger instance
loggerOptions?: LoggerOptions // Or logger options
}Returns: LifecycleServer instance
getDefaultLifecycle()
Returns the default lifecycle server instance (the first one created).
Throws: Error if no lifecycle server has been created yet.
Example:
import { getDefaultLifecycle } from '@guillaumecisco/terminus-lifecycle'
if (getDefaultLifecycle().isServerShuttingDown()) {
// Stop accepting new work
}LifecycleServer Methods
setReady(value: boolean)- Mark server as ready/not readygetReady()- Check if server is readyisServerShuttingDown()- Check if shutdown has startedgetBeaconManager()- Get the beacon manager instance
beaconManager
Singleton beacon manager for convenience.
Methods:
createBeacon(context?: object)- Create a new beacon- Returns:
{ die: () => Promise<void> }
- Returns:
waitForBeacons()- Wait for all beacons to completegetBeaconsCount()- Get current beacon count
createLogger(options)
Create a structured logger.
Options:
{
level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'
enabled?: boolean // Default: true
prettify?: boolean // Default: NODE_ENV !== 'production'
}Kubernetes Example
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: app
image: myapp:latest
ports:
- name: http
containerPort: 3000
- name: lifecycle
containerPort: 9000
livenessProbe:
httpGet:
path: /live
port: lifecycle
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: lifecycle
initialDelaySeconds: 5
periodSeconds: 5Complete Example
import express from 'express'
import { createLifecycleServer, createLogger, beaconManager } from '@guillaumecisco/terminus-lifecycle'
const logger = createLogger({ level: 'info' })
const app = express()
// Your routes
app.get('/api/data', async (req, res) => {
const beacon = beaconManager.createBeacon({
endpoint: '/api/data'
})
try {
const data = await fetchData()
res.json(data)
} finally {
await beacon.die()
}
})
// Initialize dependencies
const dbPromise = connectToDatabase()
const redisPromise = connectToRedis()
// Create lifecycle server
const lifecycle = createLifecycleServer({
port: 9000,
onReadyPromises: [dbPromise, redisPromise],
logger,
onShutdown: async () => {
logger.info('Cleaning up resources')
const [db, redis] = await Promise.all([dbPromise, redisPromise])
await db.close()
await redis.disconnect()
}
})
// Start main application
const server = app.listen(3000, async () => {
logger.info('Application started on port 3000')
await lifecycle.setReady(true)
})License
MIT © [Guillaume Cisco]
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Releasing
1. Update the version in package.json
$> npm version patch # or minor, or major2. Push with tags
$> git push && git push --tags3. Create the release on GitHub
$> gh release create v1.0.0 --title "Release 1.0.0" --notes "Initial release"