JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 140
  • Score
    100M100P100Q133298F
  • License ISC

UBSI 2026 Clock package - Simulated time management for testing asynchronous behaviors

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/clock

Configuration

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); // true

Key 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

  1. Always use Clock for time: Replace Date.now() and new Date() with clock.getCurrentTime()
// ❌ Don't do this
const now = new Date();

// ✅ Do this instead
const now = clock.getCurrentTime();
  1. 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", {});
  1. 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.

  1. 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.

  1. 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);
});
  1. 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);
}
  1. Disable jobs during development: Use the disabled option 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 install

To build the package:

bun run build

To generate the API documentation:

bun run docs

To use the package locally in another project:

bun link

And in the other project:

bun link @ubsi-2026/clock