Skip to content

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         │
└──────────────────────────────┴──────────────┴────────────────────────┘
ComponentBitsRangeDescription
Timestamp48~8,919 yearsMilliseconds since epoch
Worker ID100--1023Unique instance identifier
Sequence120--4095Per-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

typescript
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:

ExportTypeDescription
SnowflakeUidHelperClassThe underlying ID generator
SnowflakeConfigConstantsBit widths, max values, default epoch
IIdGeneratorOptionsInterfaceConstructor options (workerId, epoch)
ISnowflakeParsedIdInterfaceParsed ID structure (raw, timestamp, workerId, sequence)

Configuration

Environment Variables

VariableRequiredDefaultDescription
APP_ENV_SNOWFLAKE_WORKER_IDYes--Worker ID (0--1023), must be unique per service instance
APP_ENV_SNOWFLAKE_EPOCH_CHECKPOINTNo1735689600000 (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:

bash
# 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
yaml
# docker-compose.yml
services:
  api-1:
    environment:
      - APP_ENV_SNOWFLAKE_WORKER_ID=1
  api-2:
    environment:
      - APP_ENV_SNOWFLAKE_WORKER_ID=2

Usage

Import

typescript
import { IdGenerator } from '@nx/core';
// or
import { IdGenerator } from '@nx/core/utilities';

Generate IDs

typescript
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:

typescript
const parsed = generator.parseId('9du1sJXO88');
// {
//   raw: 130546360012247045n,
//   timestamp: Date,        // Date object
//   workerId: 1,
//   sequence: 0
// }

Extract Individual Components

typescript
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();
// 1

Base62 Encoding and Decoding

typescript
// Encode any bigint to Base62
const encoded = generator.encodeBase62(130546360012247045n);
// "9du1sJXO88"

// Decode Base62 back to bigint
const decoded = generator.decodeBase62('9du1sJXO88');
// 130546360012247045n

Custom Configuration

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

MethodSignatureDescription
nextId(): stringGenerate a Base62-encoded Snowflake ID (10--12 chars)
nextSnowflake(): bigintGenerate a raw 70-bit Snowflake ID
parseId(base62Id: string): ISnowflakeParsedIdParse a Base62 ID into its components
encodeBase62(num: bigint): stringEncode a bigint to Base62 string
decodeBase62(str: string): bigintDecode a Base62 string to bigint
extractTimestamp(id: bigint): DateExtract the timestamp from a raw Snowflake ID
extractWorkerId(id: bigint): numberExtract the worker ID from a raw Snowflake ID
extractSequence(id: bigint): numberExtract the sequence number from a raw Snowflake ID
getWorkerId(): numberGet the current instance's worker ID

Distributed ID Generation Flow

High-Volume Generation

Integration with Models

Drizzle Schema

typescript
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

typescript
@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

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

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

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

typescript
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

typescript
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:

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.