Skip to content

PREGO ID Standard

This document is the single source of truth for external-facing entity identifiers across the PREGO platform.

English

1. Core Rule

Every major entity in PREGO must distinguish between:

  • Internal database primary key — for joins, indexing, and internal references
  • External public/system-facing ID — for APIs, logs, URLs, storage keys, and system-visible references

Internal PK Strategy

  • Keep existing INTEGER PRIMARY KEY AUTOINCREMENT or current internal PK strategy
  • Use for joins, internal indexing, and internal references
  • Never expose raw internal integer IDs externally

External ID Format

[prefix]_[nanoid]
ComponentDescription
prefix3-character lowercase identifier for entity type
_Underscore separator
nanoidURL-safe random string (6, 8, or 10 characters)

Examples:

  • usr_A7kP2x (user)
  • ten_M9qL4z (tenant)
  • ses_X2mN8vKp (session)
  • msg_A7kP2xM9qL (message)

2. Prefix Registry

Use meaningful 3-character prefixes for each entity type.

PrefixEntityID LengthExample
usruser6usr_A7kP2x
tentenant6ten_M9qL4z
appapplication6app_X2mN8v
wspworkspace6wsp_K4pR7y
memmembership6mem_J3nT5w
ctccontact6ctc_H8mS2v
empemployee6emp_L6qW9x
sessession8ses_A7kP2xM9
otpotp request8otp_X2mN8vKp
invinvite8inv_K4pR7yJ3
keyapi key8key_H8mS2vL6
devdevice8dev_M9qL4zA7
jobtask/job10job_A7kP2xM9qL
trctrace10trc_X2mN8vKp4z
paypayment10pay_K4pR7yJ3nT
ivcinvoice10ivc_H8mS2vL6qW
audaudit event10aud_M9qL4zA7kP
filfile10fil_J3nT5wX2mN
docdocument10doc_L6qW9xK4pR
conconversation10con_R7yJ3nT5wX
msgmessage10msg_S2vL6qW9xK
rstreset token10rst_T5wX2mN8vK

3. Length Rules

Default: 6-char nanoid

Apply to low-volume entities:

  • user, tenant, application, workspace, membership, contact, employee

8-char nanoid

Apply to security-sensitive or medium-volume entities:

  • session, otp request, invite, api key, device

10-char nanoid

Apply to high-volume or long-lived entities:

  • job, trace, payment, invoice, audit event, file, document, conversation, message, reset token

4. Collision Probability

LengthCombinations1% collision probability
6 chars~2.1 billion~6,500 IDs
8 chars~128 billion~1.6 million IDs
10 chars~7.6 trillion~390 million IDs

The URL-safe alphabet excludes ambiguous characters (0/O, 1/l/I) for 54 total characters.


5. Database Schema Pattern

Every external ID column must be unique.

CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
external_id TEXT NOT NULL UNIQUE,
-- other columns...
);
CREATE INDEX idx_users_external_id ON users(external_id);

For scoped uniqueness, use a compound unique key:

CREATE TABLE memberships (
id INTEGER PRIMARY KEY AUTOINCREMENT,
external_id TEXT NOT NULL,
workspace_id INTEGER NOT NULL,
-- other columns...
UNIQUE(workspace_id, external_id)
);

6. Collision Handling

All ID generation must include:

  1. Unique constraint enforcement — Database UNIQUE constraint on external_id
  2. Retry logic on collision — Up to 3 attempts
  3. Explicit failure — Throw IdCollisionError if retry budget exhausted
import { generateUniqueId, IdCollisionError } from '@platform/id-utils';
try {
const id = await generateUniqueId('user', async (id) => {
const exists = await db.query('SELECT 1 FROM users WHERE external_id = ?', [id]);
return exists.length > 0;
});
} catch (error) {
if (error instanceof IdCollisionError) {
// Handle collision exhaustion (should be extremely rare)
}
}

7. Forbidden Patterns

Do NOT use:

  • Raw UUID v4 as public-facing IDs
  • Email-derived IDs
  • Name-derived IDs
  • Raw auto-increment IDs exposed externally
  • Prefixless random IDs
  • Inconsistent entity naming

8. Storage and API Rules

Use external IDs in:

  • API request/response bodies
  • URL paths and query parameters
  • Log entries and audit traces
  • R2/KV storage paths
  • System-visible URLs

API Response Format:

{
"user": {
"id": "usr_A7kP2x",
"email": "user@example.com"
},
"tenant": {
"id": "ten_M9qL4z",
"name": "Acme Corp"
}
}

KV Key Patterns:

NamespaceKey Pattern
AUTH_STOREauth:dev:{emp_id}:{dev_id}
TENANT_ORIGINS{ten_id}.pregoi.com

R2 Path Patterns:

BucketPath Pattern
prego-usage-rawusage_raw/tenant_id={ten_id}/dt={date}/...
prego-contractscontracts/{year}/{month}/{contract_id}.json

9. Migration Strategy

This is a safe, non-breaking standardization.

Phase 1: New Entities (Zero Risk)

  • Apply new ID standard to any new tables
  • Use @platform/id-utils package for generation

Phase 2: Add External ID Columns (Low Risk)

  • Add external_id column to existing tables
  • Backfill with new format
  • APIs expose new format alongside legacy

Phase 3: Tenant Migration (High Risk — Future)

  • Create mapping table
  • Dual-read period
  • Gradual external system migration

Phase 4: Legacy Cleanup (Deferred)

  • Remove legacy columns
  • Only after 90+ days of no legacy usage

10. Exceptions

These entities keep their current format:

EntityCurrent FormatReason
planSlug (basic, professional)Business-meaningful, stable
contractCTR-YYYY-CC-NNNNNNLegal/compliance requirement
billing_customercust_{tenant_id}Stripe correlation
subscriptionsub_{tenant_id}Stripe correlation
provider_eventStripe evt_*External system ID
invoice (Stripe)Stripe in_*External system ID
nodeInfrastructure-suppliedPulumi/infra naming
countryISO codeStandard

11. Implementation

Use the @platform/id-utils package:

import {
generateId,
generateUniqueId,
isValidId,
validateId,
assertValidId,
ENTITY_PREFIXES,
} from '@platform/id-utils';
// Generate IDs
const userId = generateId('user'); // 'usr_A7kP2x'
const sessionId = generateId('session'); // 'ses_X2mN8vKp'
// Validate IDs
isValidId('usr_A7kP2x', 'user'); // true
assertValidId(req.params.id, 'user'); // throws if invalid
// Parse IDs
const { entityType, prefix, nanoid } = validateId('usr_A7kP2x');

See packages/id-utils/README.md for full documentation.


한국어

핵심 규칙

PREGO의 모든 주요 엔티티는 다음을 구분해야 합니다:

  • 내부 DB 기본 키 — 조인, 인덱싱, 내부 참조용
  • 외부 공개/시스템용 ID — API, 로그, URL, 저장소 키, 시스템 표시용

외부 ID 형식

[prefix]_[nanoid]

예시:

  • usr_A7kP2x (사용자)
  • ten_M9qL4z (테넌트)
  • ses_X2mN8vKp (세션)
  • msg_A7kP2xM9qL (메시지)

접두사 규칙

  • 3자 소문자 접두사로 엔티티 타입 식별
  • 6자: 저볼륨 엔티티 (user, tenant, application 등)
  • 8자: 보안 민감 엔티티 (session, otp, api key 등)
  • 10자: 고볼륨 엔티티 (message, audit event, file 등)

데이터베이스 규칙

모든 external_id 컬럼은 고유해야 합니다:

CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
external_id TEXT NOT NULL UNIQUE,
-- 기타 컬럼...
);

충돌 처리

모든 ID 생성 로직은 다음을 포함해야 합니다:

  • 고유 제약 조건 적용
  • 충돌 시 재시도 로직 (최대 3회)
  • 재시도 예산 소진 시 명시적 실패

금지 패턴

다음을 사용하지 마세요:

  • 외부에 노출되는 원시 UUID v4
  • 이메일 기반 ID
  • 이름 기반 ID
  • 외부에 노출되는 원시 자동 증가 ID
  • 접두사 없는 랜덤 ID
  • 일관성 없는 엔티티 명명

마이그레이션 전략

이것은 안전하고 기존 기능을 깨지 않는 표준화입니다:

  • 현재 기능 유지
  • 활성 API 깨지 않음
  • 내부 PK 제거하지 않음
  • 누락된 곳에 external_id 안전하게 추가
  • 필요시 기존 레코드 백필
  • 전환 기간 동안 호환성 유지

Help