@alexpopovme/deadline-timer
v0.1.0
Published
A small TypeScript utility for counting down to an absolute date based on server time
Downloads
142
Maintainers
Readme
DeadlineTimer
A small TypeScript utility that counts down to an absolute date using server time. It is UI-framework-agnostic and makes no network requests.
Installation
pnpm add @alexpopovme/deadline-timerThe package is distributed as ESM and has no runtime dependencies.
Usage
import { createDeadlineTimer } from '@alexpopovme/deadline-timer'
const timer = createDeadlineTimer({
expiresAt: '2026-08-06T12:30:00Z',
serverNow: '2026-08-06T12:10:00Z',
})
if (timer) {
const unsubscribe = timer.subscribe((snapshot) => {
console.log(snapshot.remainingSeconds)
console.log(snapshot.status)
})
// Когда подписка больше не нужна
unsubscribe()
}Input
expiresAt— the absolute expiration date ornull. When it isnull, the factory returnsnull.serverNow— the server time received together with the deadline. You do not need to pass the device's local time.expirationSafetySeconds— ends the countdown slightly early to account for delay between the server and the client. It defaults to5seconds;0disables the margin.
Dates are parsed with Date.parse. Always specify the time zone explicitly; the recommended format is 2026-08-14T07:38:51.923Z. An invalid object, type, date, or option causes a synchronous TypeError. Additional properties in input objects are allowed.
The backend must still verify whether an action is available.
Snapshot
type DeadlineTimerSnapshot = Readonly<{
remainingSeconds: number
status: 'active' | 'expired'
}>remainingSeconds is the non-negative number of seconds remaining, rounded up. The status is active when the remaining time is positive and expired when it reaches zero. Previously returned snapshot objects are not mutated.
Methods
getSnapshot()returns the current state without starting updates or notifying subscribers.subscribe(listener)immediately calls the listener synchronously with the current state. It then calls the listener only when the remaining time or status changes.- The returned function removes only its own subscription and is safe to call repeatedly. Updates stop after the last subscription is removed.
- For an already expired timer, the listener is called once. The listener must be a function and must not throw.
How time is calculated
The timer stores serverNow when it is created and adds the actual elapsed time to it. After a delay or returning to the tab, it therefore calculates the current remaining time immediately without replaying missed values.
The timer does not contact the server again. If the backend provides new expiresAt or serverNow values, create a new instance. The server remains the source of truth for deciding whether an action is available.
API
type DeadlineTimerInput = Readonly<{
expiresAt: string | null
serverNow: string
}>
type DeadlineTimerOptions = Readonly<{
expirationSafetySeconds?: number
}>
type DeadlineTimerStatus = 'active' | 'expired'
type DeadlineTimerSnapshot = Readonly<{
remainingSeconds: number
status: DeadlineTimerStatus
}>
type DeadlineTimerListener = (snapshot: DeadlineTimerSnapshot) => void
type DeadlineTimer = Readonly<{
getSnapshot(): DeadlineTimerSnapshot
subscribe(listener: DeadlineTimerListener): () => void
}>
declare const createDeadlineTimer: (
input: DeadlineTimerInput,
options?: DeadlineTimerOptions,
) => DeadlineTimer | nullSpecifications
The normative project specifications are currently available in Russian:
