@baphy/dora
v1.1.0
Published
DORA metrics calculator — Deployment Frequency, Lead Time, Change Failure Rate, MTTR
Maintainers
Readme
@baphy/dora
Calculate the four DORA engineering performance metrics from any data source
Overview
@baphy/dora calculates the four DORA engineering performance metrics:
| Metric | Measures | |---|---| | Deployment Frequency | How often you deploy to production | | Lead Time for Changes | Time from first commit to production | | Change Failure Rate | Percentage of deployments that cause incidents | | MTTR | Time to restore service after an incident |
Each metric returns a level (elite · high · medium · low) based on the Google DORA 2023 thresholds, which are fully overridable.
Platform-agnostic — the library defines normalized input types. Map your own data (GitHub, GitLab, Bitbucket, Jira, or anything else) to those types before calling the functions. No adapters, no platform lock-in.
Zero dependencies · Synchronous · O(n) · Configurable labels and thresholds
Installation
npm install @baphy/doraUsage
All four metrics at once
import { calcDora } from '@baphy/dora'
import type { DeploymentEvent, ChangeEvent, IncidentEvent } from '@baphy/dora'
const result = calcDora({
deployments, // DeploymentEvent[]
changes, // ChangeEvent[]
incidents, // IncidentEvent[]
period: { start, end },
})
console.log(result)
// {
// deploymentFrequency: { value: 1.4, level: 'elite', label: '1.4 deploys/day' },
// leadTime: { value: 3, level: 'high', label: '3 hours' },
// changeFailureRate: { value: 8.9, level: 'high', label: '8.9%' },
// mttr: { value: 0.5, level: 'elite', label: '30 minutes' },
// overall: 'high',
// }Only provide the data you have — any omitted input means that metric is absent from the result:
// only deployments → only deploymentFrequency + changeFailureRate are computed
calcDora({ deployments })Individual metric functions
import { calcDeploymentFrequency, calcLeadTime, calcChangeFailureRate, calcMttr } from '@baphy/dora'
const df = calcDeploymentFrequency(deployments, period)
const lt = calcLeadTime(changes)
const cfr = calcChangeFailureRate(deployments)
const mtr = calcMttr(incidents)Input Types
Map your data to these normalized types before calling any function:
interface DeploymentEvent {
deployedAt: Date
success: boolean // false if the deploy triggered an incident / was rolled back
}
interface ChangeEvent {
startedAt: Date // first commit, PR open, ticket created — whatever fits your workflow
deployedAt: Date // when it reached production
}
interface IncidentEvent {
failedAt: Date
restoredAt: Date
}
interface Period {
start: Date
end: Date
}Mapping from GitHub data
import { calcDora } from '@baphy/dora'
import type { DeploymentEvent } from '@baphy/dora'
// GitHub Deployments API → DeploymentEvent[]
const deployments: DeploymentEvent[] = githubDeployments.map((d) => ({
deployedAt: new Date(d.created_at),
success: d.statuses.some((s) => s.state === 'success'),
}))
calcDora({ deployments, period: { start, end } })API
calcDora(input)
function calcDora(input: CalcDoraInput): DoraResult
interface CalcDoraInput {
deployments?: DeploymentEvent[]
changes?: ChangeEvent[]
incidents?: IncidentEvent[]
period?: Period // defaults to last 90 days
thresholds?: Partial<DoraThresholds>
labels?: CalcDoraLabels // per-metric label formatter overrides
}
interface DoraResult {
deploymentFrequency?: DoraLevelResult
leadTime?: DoraLevelResult
changeFailureRate?: DoraLevelResult
mttr?: DoraLevelResult
overall?: DoraLevel // worst level among computed metrics
}Individual metric functions
function calcDeploymentFrequency(
events: DeploymentEvent[],
period: Period,
options?: { thresholds?: Partial<DeploymentFrequencyThresholds>; label?: LabelFormatter }
): DoraLevelResult
function calcLeadTime(
changes: ChangeEvent[],
options?: { thresholds?: Partial<LeadTimeThresholds>; label?: LabelFormatter }
): DoraLevelResult
function calcChangeFailureRate(
events: DeploymentEvent[],
options?: { thresholds?: Partial<ChangeFailureRateThresholds>; label?: LabelFormatter }
): DoraLevelResult
function calcMttr(
incidents: IncidentEvent[],
options?: { thresholds?: Partial<MttrThresholds>; label?: LabelFormatter }
): DoraLevelResultReturn type
type DoraLevel = 'elite' | 'high' | 'medium' | 'low'
interface DoraLevelResult {
value: number // raw computed value (deploys/day, hours, or percentage)
level: DoraLevel
label: string // human-readable string produced by the formatter
}Empty-input contract — all functions return { value: 0, level: 'low', label: '—' } when given an empty array. They never throw.
Thresholds
Default thresholds follow the Google DORA 2023 Research Program:
| Metric | Elite | High | Medium | Low | |---|---|---|---|---| | Deployment Frequency | ≥ 1/day | ≥ 1/week | ≥ 1/month | < 1/month | | Lead Time for Changes | ≤ 1 hour | ≤ 1 week | ≤ 1 month | > 1 month | | Change Failure Rate | ≤ 5% | ≤ 10% | ≤ 15% | > 15% | | MTTR | ≤ 1 hour | ≤ 24 hours | ≤ 1 week | > 1 week |
Override any threshold globally via calcDora({ thresholds }) or per-call via each function's options.thresholds:
import { mergeThresholds, DEFAULT_THRESHOLDS } from '@baphy/dora'
// partial override — unspecified fields keep the defaults
const thresholds = mergeThresholds({
deploymentFrequency: { elite: 2, high: 0.5, medium: 0.1 },
})
calcDora({ deployments, thresholds })Configurable Labels
Each metric has a built-in English label formatter. Provide your own LabelFormatter to customise the output — including locale, units, or appending extra context:
type LabelFormatter = (value: number, level: DoraLevel) => stringConsumer with Spanish locale:
import { calcDeploymentFrequency } from '@baphy/dora'
const n = new Intl.NumberFormat('es-MX', { maximumFractionDigits: 1 })
calcDeploymentFrequency(events, period, {
label: (v) => v >= 1
? `${n.format(v)} despliegues/día`
: `${n.format(v * 7)} despliegues/semana`,
})
// → "1,4 despliegues/día"Extending the built-in formatter:
import { calcDeploymentFrequency, labelDeploymentFrequency } from '@baphy/dora'
calcDeploymentFrequency(events, period, {
label: (v, l) => `${labelDeploymentFrequency(v, l)} · ${l.toUpperCase()}`,
})
// → "1.4 deploys/day · ELITE"Per-metric overrides via calcDora:
calcDora({
deployments,
labels: {
deploymentFrequency: (v) => `${v.toFixed(2)} dep/day`,
changeFailureRate: (v, l) => `${v.toFixed(1)}% (${l})`,
},
})The built-in default formatters are also exported for direct use:
import { labelDeploymentFrequency, labelLeadTime, labelChangeFailureRate, labelMttr } from '@baphy/dora'All default formatters use Intl.NumberFormat('en-US') with an explicit locale — output is consistent and predictable regardless of the runtime environment.
Performance
Benchmarked with vitest bench. Each metric function is a single O(n) pass. Reproduce locally:
npm run benchcalcDeploymentFrequency
| Input size | Mean time | |---|---| | 100 events | ~0.013 ms | | 1,000 events | ~0.12 ms | | 10,000 events | ~1.2 ms | | 100,000 events | ~13 ms |
calcLeadTime
| Input size | Mean time | |---|---| | 100 changes | ~0.008 ms | | 1,000 changes | ~0.073 ms | | 10,000 changes | ~0.73 ms | | 100,000 changes | ~9.1 ms |
calcChangeFailureRate
| Input size | Mean time | |---|---| | 100 events | ~0.005 ms | | 1,000 events | ~0.046 ms | | 10,000 events | ~0.46 ms | | 100,000 events | ~5 ms |
calcMttr
| Input size | Mean time | |---|---| | 100 incidents | ~0.007 ms | | 1,000 incidents | ~0.071 ms | | 10,000 incidents | ~0.71 ms | | 100,000 incidents | ~9.3 ms |
calcDora (all four metrics)
| Input size (each) | Mean time | |---|---| | 100 | ~0.03 ms | | 1,000 | ~0.27 ms | | 10,000 | ~2.7 ms |
License
MIT © pilmee
