IdGenerator
Overview
The IdGenerator utility is a singleton wrapper around the IGNIS Framework's SnowflakeUidHelper. It provides globally unique, time-ordered, distributed IDs without requiring central coordination. Every service in BANA uses IdGenerator for primary key generation.
Source: packages/core/src/utilities/id-generator.utility.ts (81 lines)
Snowflake ID Structure
┌──────────────────────────────────────────────────────────────────────┐
│ 70-bit Snowflake ID │
├──────────────────────────────┬──────────────┬────────────────────────┤
│ Timestamp │ Worker ID │ Sequence │
│ (48 bits) │ (10 bits) │ (12 bits) │
├──────────────────────────────┼──────────────┼────────────────────────┤
│ ~8,919 years range │ 0 - 1023 │ 0 - 4095 │
└──────────────────────────────┴──────────────┴────────────────────────┘| Component | Bits | Range | Description |
|---|---|---|---|
| Timestamp | 48 | ~8,919 years | Milliseconds since epoch |
| Worker ID | 10 | 0--1023 | Unique instance identifier |
| Sequence | 12 | 0--4095 | Per-millisecond counter |
The output is a compact Base62-encoded string (10--12 characters), suitable for use as database primary keys and URL-safe identifiers.
Class Definition
import { SnowflakeUidHelper } from '@venizia/ignis';
export class IdGenerator {
private static instance: SnowflakeUidHelper;
// Get or create the singleton instance
static getInstance(opts?: {
workerId?: number;
epoch?: bigint;
}): SnowflakeUidHelper;
// Reset instance (for testing only)
static resetInstance(): void;
}Re-exports from IGNIS
The module re-exports these types from @venizia/ignis for convenience:
| Export | Type | Description |
|---|---|---|
SnowflakeUidHelper | Class | The underlying ID generator |
SnowflakeConfig | Constants | Bit widths, max values, default epoch |
IIdGeneratorOptions | Interface | Constructor options (workerId, epoch) |
ISnowflakeParsedId | Interface | Parsed ID structure (raw, timestamp, workerId, sequence) |
Configuration
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
APP_ENV_SNOWFLAKE_WORKER_ID | Yes | -- | Worker ID (0--1023), must be unique per service instance |
APP_ENV_SNOWFLAKE_EPOCH_CHECKPOINT | No | 1735689600000 (2025-01-01 UTC) | Custom epoch timestamp in milliseconds |
Worker ID Assignment
Each service instance must have a unique Worker ID to prevent ID collisions:
# Development
APP_ENV_SNOWFLAKE_WORKER_ID=1
# Production (per instance)
# Instance 1: APP_ENV_SNOWFLAKE_WORKER_ID=1
# Instance 2: APP_ENV_SNOWFLAKE_WORKER_ID=2
# Instance 3: APP_ENV_SNOWFLAKE_WORKER_ID=3# docker-compose.yml
services:
api-1:
environment:
- APP_ENV_SNOWFLAKE_WORKER_ID=1
api-2:
environment:
- APP_ENV_SNOWFLAKE_WORKER_ID=2Usage
Import
import { IdGenerator } from '@nx/core';
// or
import { IdGenerator } from '@nx/core/utilities';Generate IDs
const generator = IdGenerator.getInstance();
// Generate a Base62-encoded ID (recommended)
const id = generator.nextId();
// "9du1sJXO88"
// Generate a raw Snowflake bigint
const snowflakeId = generator.nextSnowflake();
// 130546360012247045n
// Generate multiple IDs
const ids = Array.from({ length: 10 }, () => generator.nextId());Parse an ID
Extract the embedded timestamp, worker ID, and sequence from an existing ID:
const parsed = generator.parseId('9du1sJXO88');
// {
// raw: 130546360012247045n,
// timestamp: Date, // Date object
// workerId: 1,
// sequence: 0
// }Extract Individual Components
const snowflakeId = generator.nextSnowflake();
// Extract timestamp
const timestamp = generator.extractTimestamp(snowflakeId);
// Date object
// Extract worker ID
const workerId = generator.extractWorkerId(snowflakeId);
// 1
// Extract sequence
const sequence = generator.extractSequence(snowflakeId);
// 0-4095
// Get current instance's worker ID
const currentWorkerId = generator.getWorkerId();
// 1Base62 Encoding and Decoding
// Encode any bigint to Base62
const encoded = generator.encodeBase62(130546360012247045n);
// "9du1sJXO88"
// Decode Base62 back to bigint
const decoded = generator.decodeBase62('9du1sJXO88');
// 130546360012247045nCustom Configuration
// First call initializes with config
const generator = IdGenerator.getInstance({
workerId: 100,
epoch: BigInt(1609459200000), // Custom epoch: 2021-01-01
});
// Subsequent calls return the same instance (options are ignored)
const sameGenerator = IdGenerator.getInstance();API Summary
| Method | Signature | Description |
|---|---|---|
nextId | (): string | Generate a Base62-encoded Snowflake ID (10--12 chars) |
nextSnowflake | (): bigint | Generate a raw 70-bit Snowflake ID |
parseId | (base62Id: string): ISnowflakeParsedId | Parse a Base62 ID into its components |
encodeBase62 | (num: bigint): string | Encode a bigint to Base62 string |
decodeBase62 | (str: string): bigint | Decode a Base62 string to bigint |
extractTimestamp | (id: bigint): Date | Extract the timestamp from a raw Snowflake ID |
extractWorkerId | (id: bigint): number | Extract the worker ID from a raw Snowflake ID |
extractSequence | (id: bigint): number | Extract the sequence number from a raw Snowflake ID |
getWorkerId | (): number | Get the current instance's worker ID |
Distributed ID Generation Flow
High-Volume Generation
Integration with Models
Drizzle Schema
import { pgTable, text } from 'drizzle-orm/pg-core';
import { IdGenerator } from '@nx/core';
export const User = pgTable('User', {
id: text('id')
.primaryKey()
.$defaultFn(() => IdGenerator.getInstance().nextId()),
// ... other columns
});Repository Usage
@repository({ dataSource: PostgresCoreDataSource, model: User })
export class UserRepository extends SoftDeletableRepository<TUserSchema, TUser> {
async createUser(data: TUserCreate): Promise<TUser> {
const id = IdGenerator.getInstance().nextId();
return this.create({
data: {
id,
...data,
createdAt: new Date(),
},
});
}
}Error Handling
Missing Worker ID
// APP_ENV_SNOWFLAKE_WORKER_ID is not set
try {
IdGenerator.getInstance();
} catch (error) {
// [IdGenerator][getWorkerIdFromEnv] Missing required environment variable
// APP_ENV_SNOWFLAKE_WORKER_ID | hint: Set APP_ENV_SNOWFLAKE_WORKER_ID
// in between 0 and 1023 for each service instance
}Invalid Worker ID
// APP_ENV_SNOWFLAKE_WORKER_ID="abc"
try {
IdGenerator.getInstance();
} catch (error) {
// [IdGenerator][getWorkerIdFromEnv] Invalid APP_ENV_SNOWFLAKE_WORKER_ID value
// received: abc | expected: number between 0 and 1023
}Invalid Epoch
// APP_ENV_SNOWFLAKE_EPOCH_CHECKPOINT="not-a-number"
try {
IdGenerator.getInstance();
} catch (error) {
// [IdGenerator][getEpochFromEnv] Invalid APP_ENV_SNOWFLAKE_EPOCH_CHECKPOINT value
// received: not-a-number | expected: timestamp in milliseconds
}Testing
Reset for Clean State
describe('UserService', () => {
beforeEach(() => {
// Reset singleton for clean state
IdGenerator.resetInstance();
// Set test worker ID
process.env.APP_ENV_SNOWFLAKE_WORKER_ID = '999';
});
it('generates user with Snowflake ID', async () => {
const user = await userService.create({ name: 'Test' });
expect(user.id).toBeTruthy();
});
});Mock the Generator
jest.mock('@nx/core/utilities', () => ({
IdGenerator: {
getInstance: () => ({
nextId: jest.fn().mockReturnValue('mockId123'),
}),
},
}));WARNING
Never call IdGenerator.resetInstance() in production. It is designed exclusively for test isolation.
IGNIS Framework Reference
IdGenerator wraps the IGNIS SnowflakeUidHelper. For the full API, including clock drift handling, sequence exhaustion behavior, and all configuration constants, see the IGNIS UID Helper reference: