Package Exports
- @ubsi-2026/clock
Readme
@ubsi-2026/clock
The @ubsi-2026/clock package provides an abstraction for managing simulated time in your backend applications, allowing you to test asynchronous behaviors without waiting for real-time.
This client maintains a real-time connection to the Clock API via WebSocket and keeps the current simulated time synchronized automatically. All time queries are served from the cached state, eliminating the need for repeated API calls.
Note: This package is designed for the backend applications.
Installation
npm install @ubsi-2026/clockConfiguration
import { ClockClient } from "@ubsi-2026/clock";
// Create the singleton instance (async)
const clock = await ClockClient.create({
baseUrl: process.env.KONG_API_GATEWAY!,
apiKey: process.env.KONG_API_KEY,
appName: process.env.APP_NAME!, // Unique identifier for your app
rabbitMQUrl: process.env.RABBITMQ_URL!, // RabbitMQ connection URL
});
// Subsequent calls return the same instance
const sameClock = await ClockClient.create(options);
console.log(clock === sameClock); // trueKey Features:
- Singleton Pattern: Only one instance per application
- Async Initialization: Waits for WebSocket and RabbitMQ connections before returning
- Auto-reconnect: Automatically reconnects if connections are lost
API Reference
The complete API reference is available here.
Best Practices
- Always use Clock for time: Replace
Date.now()andnew Date()withclock.getCurrentTime()
// ❌ Don't do this
const now = new Date();
// ✅ Do this instead
const now = clock.getCurrentTime();- Prefer @Cron decorator for scheduled tasks: Use the decorator for cleaner, more maintainable code
// ✅ Recommended - using decorator
class EmailService {
@Cron("0 3 * * *", clock, { name: "daily-cleanup" })
async cleanupOldEmails(metadata: any) {
// Cleanup logic
}
}
// ⚠️ Alternative - manual registration (only when you need dynamic scheduling)
clock.registerHandler("send-welcome-email", async (data) => {
await emailService.send(data.userId, data.template);
});
await clock.scheduleCron("0 3 * * *", "send-welcome-email", {});- Use meaningful action names: Choose descriptive names for action types that clearly indicate what they do
// ✅ Good names
@Cron('0 3 * * *', clock, { name: 'send-welcome-email' })
// ❌ Bad names
@Cron('0 3 * * *', clock, { name: 'do-stuff' })This will make debugging and monitoring easier for you and for us.
- Handle errors in action handlers: Implement proper error handling within your handlers
@Cron('0 3 * * *', clock, { name: 'send-email' })
async sendEmail(data: any) {
try {
await emailService.send(data.userId, data.template);
} catch (error) {
logger.error("Failed to send email:", error);
// Optionally implement retry logic here
throw error; // Re-throw to send to DLQ
}
}This will make debugging and monitoring easier for you and for us.
- Use event listeners for reactive updates: Don't poll
getCurrentTime()repeatedly
// ❌ Don't poll
setInterval(() => {
const time = clock.getCurrentTime();
processTimeBasedLogic(time);
}, 1000);
// ✅ Use event listeners
clock.on("update", (time) => {
processTimeBasedLogic(time);
});- Store job IDs for cancellation: Keep track of job IDs if you need to cancel them later
// Store job ID for potential cancellation
const jobId = await clock.scheduleOnce(futureDate, "action", data);
await db.jobs.create({ id: jobId, type: "welcome-email", userId });
// Cancel later if needed
const job = await db.jobs.findByUserId(userId);
if (job) {
await clock.cancelJob(job.id);
}- Disable jobs during development: Use the
disabledoption to temporarily disable jobs
@Cron('0 3 * * *', clock, {
name: 'expensive-operation',
disabled: process.env.NODE_ENV === 'development'
})
async expensiveOperation(metadata: any) {
// This won't run in development
}Important Notes
- Singleton Pattern: Only one ClockClient instance exists per application
- Async Initialization: Always use
await ClockClient.create()to ensure connections are established - WebSocket connection is automatic - waits for connection before returning from
create() - RabbitMQ connection is automatic - waits for connection before returning from
create() - Database timestamps must use simulated time from
getCurrentTime() - Time acceleration affects all connected services simultaneously
- Event listeners run on every WebSocket update (every ~100ms)
- Cleanup: Call
ClockClient.destroyInstance()when shutting down your application
Example with Cleanup
// Application startup
const clock = await ClockClient.create({
baseUrl: process.env.KONG_API_GATEWAY!,
apiKey: process.env.KONG_API_KEY,
appName: "ecommerce",
rabbitMQUrl: process.env.RABBITMQ_URL!,
});
// Handle graceful shutdown
process.on("SIGTERM", async () => {
console.log("Shutting down...");
ClockClient.destroyInstance();
process.exit(0);
});Development
To install dependencies:
bun installTo build the package:
bun run buildTo generate the API documentation:
bun run docsTo use the package locally in another project:
bun linkAnd in the other project:
bun link @ubsi-2026/clock