PseudoID
PseudoID is the core technology that enables Pseudentity’s zero-database architecture. It’s a special RFC 9562 UUID v8 that encodes all generation parameters directly into the 128-bit ID space—the world seed, type sequence, and array index.
This means any server can “inflate” a complete user profile just by decoding the ID. No database lookups. No sessions. Pure math.
Unlike random UUIDs, PseudoIDs are:
- Deterministic - Same parameters always produce the same ID
- Sortable - Organized by world, then type, then creation order
- Self-describing - Contains all metadata needed to regenerate the object
- RFC 9562 compliant - Standard UUID v8 format for vendor-specific data
- Stateless-friendly - Perfect for serverless and distributed systems
The Three Components
Section titled “The Three Components”1. World Seed
Section titled “1. World Seed”The world seed is the base seed you provide when creating a virtual array. It defines the entire “world” or “universe” of generated data.
// TypeScriptconst users = new UserArray(42n); // worldSeed = 42const user = users.at(1000);const id = user.id();// This PseudoID will contain worldSeed=42All objects from the same world seed share this value in their PseudoIDs, making it easy to group related data:
// Go - Different arrays, same worldusers := pseudata.NewUserArray(42)addresses := pseudata.NewAddressArray(42)
user := users.At(0)addr := addresses.At(0)
// Both PseudoIDs contain worldSeed=42// They belong to the same "world"Use cases:
- Separate production, staging, and test data
- Isolate data by tenant or customer
- Group related test scenarios
2. Type Sequence
Section titled “2. Type Sequence”The type sequence (typeSeq) identifies the type of object. Each complex type has a predefined sequence number:
| Type | TypeSeq | Description |
|---|---|---|
| User | 101 | User objects (OIDC-compliant) |
| Address | 110 | Address objects (locale-aware) |
| Custom Types | 1024+ | Reserved for future types |
Note: TypeSeq uses 16 bits (0-65,535), with built-in types using 0-1023 and custom types starting from 1024.
# Python - Different types have different typeSeq valuesusers = UserArray(42) # typeSeq = 101addresses = AddressArray(42) # typeSeq = 105
user_id = users[100].id() # Contains typeSeq=101address_id = addresses[100].id() # Contains typeSeq=105
# PseudoIDs are different even at same index!This means you can identify an object’s type just by looking at its PseudoID.
3. Index
Section titled “3. Index”The index is the position of the object in its virtual array. It represents the “creation order” within that type.
// JavaUserArray users = new UserArray(42L);
User first = users.at(0); // index = 0User second = users.at(1); // index = 1User thousandth = users.at(1000); // index = 1000
String id1 = first.id(); // Contains index=0String id2 = second.id(); // Contains index=1String id1000 = thousandth.id(); // Contains index=1000The index can range from 0 to 1,099,511,627,775 (40 bits), supporting over 1.1 trillion unique objects per type.
How PseudoID Works
Section titled “How PseudoID Works”PseudoIDs use the standard RFC 9562 UUID v8 format but encode the three components into the 122 “random” bits:
UUID Format: xxxxxxxx-xxxx-8xxx-yxxx-xxxxxxxxxxxx └─────────── encoded data ──────────┘ 8 = version (v8) y = variant (8-b)Bit Distribution:
- 64 bits: World seed (full uint64 range)
- 2 bits: Skip bits (reserved for future layouts, currently always 0)
- 16 bits: Type sequence (0-65,535)
- 40 bits: Index (0-1.1 trillion)
Visual Pattern:
SSSSSSSS-SSSS-8SSS-vSTT-TTIIIIIIIII
S = WorldSeed bitsT = TypeSeq bitsI = Index bitsv = Variant nibble + Skip bits (reserved)8 = Version (UUID v8)Example:
Input: worldSeed=42, typeSeq=101, index=1000Output: 00000000-0000-8002-a806-5000000003e8 └─ world ──┘ v │└ type+index ──┘Sort Order
Section titled “Sort Order”PseudoIDs are designed to sort hierarchically:
Primary: World seed (groups by world)
Secondary: Type sequence (groups types within a world)
Tertiary: Index (creation order within type)
// Go - Demonstrating sort orderid1 := EncodeID(1, 42, 101, 1000) // World 42, Users, Index 1000id2 := EncodeID(1, 42, 101, 1001) // World 42, Users, Index 1001id3 := EncodeID(1, 42, 105, 1000) // World 42, Addresses, Index 1000id4 := EncodeID(1, 43, 101, 1000) // World 43, Users, Index 1000
// Sort order (lexicographical):// id1 < id2 (same world/type, index 1000 < 1001)// id2 < id3 (same world, type 101 < 105)// id3 < id4 (world 42 < 43)This makes PseudoIDs perfect for:
- Database indexing - Natural clustering by world and type
- Analytics - Group queries by world or type
- Debugging - Find all objects from a specific world
- Testing - Isolate test data by world seed
Using PseudoIDs
Section titled “Using PseudoIDs”Accessing PseudoIDs
Section titled “Accessing PseudoIDs”Every generated object has an id() method:
// TypeScriptimport { UserArray } from '@pseudata/core';
const users = new UserArray(42n);const user = users.at(1000);
// Get the object's unique PseudoIDconst pseudoId = user.id();console.log(pseudoId); // "00000000-0000-8002-a806-5000000003e8"// Javaimport dev.pseudata.UserArray;import dev.pseudata.User;
UserArray users = new UserArray(42L);User user = users.at(1000);
String pseudoId = user.id();System.out.println(pseudoId); // "00000000-0000-8002-a806-5000000003e8"# Pythonfrom pseudata import UserArray
users = UserArray(42)user = users[1000]
pseudo_id = user.id()print(pseudo_id) # "00000000-0000-8002-a806-5000000003e8"// Goimport "github.com/pseudata/pseudata"
users := pseudata.NewUserArray(42)user := users.At(1000)
pseudoID := user.ID()fmt.Println(pseudoID) // "00000000-0000-8002-a806-5000000003e8"Creating PseudoIDs Without Virtual Arrays
Section titled “Creating PseudoIDs Without Virtual Arrays”You can generate PseudoIDs directly using utility functions:
// TypeScriptimport { encodeId } from '@pseudata/core';
const pseudoId = encodeId( 42n, // worldSeed 101, // typeSeq (Users) 1000n // index);// "00000000-0000-8002-a806-5000000003e8"// Javaimport dev.pseudata.IDUtils;
String pseudoId = IDUtils.encodeId( 42L, // worldSeed 101, // typeSeq (Users) 1000L // index);# Pythonfrom pseudata.id_utils import encode_id
pseudo_id = encode_id( world_seed=42, type_seq=101, # Users index=1000)// Goimport "github.com/pseudata/pseudata"
pseudoID := pseudata.EncodeID( 42, // worldSeed 101, // typeSeq (Users) 1000, // index)Use cases for direct encoding:
- Generate test fixtures
- Create migration scripts
- Replay specific scenarios
- Generate IDs for external systems
Decoding PseudoIDs
Section titled “Decoding PseudoIDs”Extract the components from any PseudoID:
// TypeScriptimport { decodeId } from '@pseudata/core';
const components = decodeId("00000000-0000-8002-a806-5000000003e8");if (components) { console.log(`World: ${components.worldSeed}`); // 42 console.log(`Type: ${components.typeSeq}`); // 101 console.log(`Index: ${components.index}`); // 1000}// Javaimport dev.pseudata.IDUtils;
IDUtils.IDComponents c = IDUtils.decodeId("00000000-0000-8002-a806-5000000003e8");System.out.printf("World: %d, Type: %d, Index: %d%n", c.worldSeed, c.typeSeq, c.index);# Pythonfrom pseudata.id_utils import decode_id
components = decode_id("00000000-0000-8002-a806-5000000003e8")if components: print(f"World: {components.world_seed}") # 42 print(f"Type: {components.type_seq}") # 101 print(f"Index: {components.index}") # 1000// Goimport "github.com/pseudata/pseudata"
components, err := pseudata.DecodeID("00000000-0000-8002-a806-5000000003e8")if err == nil { fmt.Printf("World: %d\n", components.WorldSeed) // 42 fmt.Printf("Type: %d\n", components.TypeSeq) // 101 fmt.Printf("Index: %d\n", components.Index) // 1000}Practical Use Cases
Section titled “Practical Use Cases”1. Database Primary Keys
Section titled “1. Database Primary Keys”Use PseudoIDs as primary keys for deterministic, reproducible databases:
// TypeScript - Generate consistent primary keysconst users = new UserArray(productionSeed);
for (let i = 0; i < 10000; i++) { const user = users.at(i); await db.insert('users', { id: user.id(), // Deterministic PseudoID name: user.name(), email: user.email(), });}Benefits:
- Same IDs across test runs
- Easy to reference in test assertions
- Natural clustering in database indexes
2. Cross-Language Test Fixtures
Section titled “2. Cross-Language Test Fixtures”Generate matching test data across different services:
# Python backend servicefrom pseudata import UserArray
users = UserArray(42)test_user = users[1000]test_user_id = test_user.id() # "10000000-0000-4000-8a80-1940000003e8"// TypeScript frontend testimport { UserArray } from '@pseudata/core';
const users = new UserArray(42n);const testUser = users.at(1000);const testUserId = testUser.id(); // "10000000-0000-4000-8a80-1940000003e8"
// Same PseudoID! Can test cross-service interactionsawait expect(page.locator(`[data-user-id="${testUserId}"]`)).toBeVisible();3. Debugging Production Issues
Section titled “3. Debugging Production Issues”Decode PseudoIDs in production logs to understand data origin:
// Go - Production debuggingsuspiciousID := "10000000-0000-4000-8a80-1940000003e8"components, _ := pseudata.DecodeID(suspiciousID)
log.Printf("Issue analysis:")log.Printf(" Environment seed: %d", components.WorldSeed) // 42log.Printf(" Object type: %d", components.TypeSeq) // 101 (User)log.Printf(" Array position: %d", components.Index) // 1000
// Reproduce the exact objectusers := pseudata.NewUserArray(components.WorldSeed)problematicUser := users.At(int(components.Index))4. Multi-Tenant Applications
Section titled “4. Multi-Tenant Applications”Use different world seeds per tenant:
// Javalong tenant1Seed = 1001;long tenant2Seed = 2002;
UserArray tenant1Users = new UserArray(tenant1Seed);UserArray tenant2Users = new UserArray(tenant2Seed);
// PseudoIDs naturally contain tenant informationString t1UserId = tenant1Users.at(0).id(); // Contains seed=1001String t2UserId = tenant2Users.at(0).id(); // Contains seed=2002
// Can identify tenant from any PseudoIDIDComponents c = IDUtils.decodeId(t1UserId);long tenantSeed = c.worldSeed; // 10015. Analytics & Reporting
Section titled “5. Analytics & Reporting”Query data by world or type:
# Python - Analytics examplefrom pseudata.id_utils import decode_id
# Analyze production PseudoIDsfor user_id in production_user_ids: components = decode_id(user_id) if components: metrics[components.world_seed]['count'] += 1 metrics[components.world_seed]['types'].add(components.type_seq)
# Report: "World 42 has 5000 Users, World 43 has 3000 Users"Cross-Language Consistency
Section titled “Cross-Language Consistency”PseudoIDs are identical across all languages for the same inputs:
// Same world, type, and index → Same PseudoID everywhere
// TypeScriptnew UserArray(42n).at(1000).id()// → "10000000-0000-4000-8a80-1940000003e8"
// PythonUserArray(42)[1000].id()// → "10000000-0000-4000-8a80-1940000003e8"
// Javanew UserArray(42L).at(1000).id()// → "10000000-0000-4000-8a80-1940000003e8"
// GoNewUserArray(42).At(1000).ID()// → "10000000-0000-4000-8a80-1940000003e8"This consistency is guaranteed by:
- Shared test vectors (
fixtures/id_test_vectors.json) - Identical bit-packing algorithms
- Comprehensive cross-language test suites
Best Practices
Section titled “Best Practices”1. Use Meaningful World Seeds
Section titled “1. Use Meaningful World Seeds”Choose world seeds that reflect your environments:
const DEV_SEED = 1n;const STAGING_SEED = 2n;const PRODUCTION_SEED = 42n;const TEST_SEED = 9999n;
// Clear separation of dataconst devUsers = new UserArray(DEV_SEED);const stagingUsers = new UserArray(STAGING_SEED);2. Document Type Sequences
Section titled “2. Document Type Sequences”Maintain a registry of your type sequences:
const ( TypeSeqUsers = 101 TypeSeqAddresses = 105 TypeSeqOrders = 201 TypeSeqPayments = 202 // Custom types start at 10000 TypeSeqMyCustom = 10000)3. Decode for Observability
Section titled “3. Decode for Observability”Add PseudoID decoding to your logging:
# Python - Enhanced loggingimport loggingfrom pseudata.id_utils import decode_id
def log_user_action(user_id: str, action: str): components = decode_id(user_id) if components: logging.info( f"Action: {action}, " f"World: {components.world_seed}, " f"Type: {components.type_seq}, " f"Index: {components.index}" )4. Validate PseudoIDs in Production
Section titled “4. Validate PseudoIDs in Production”Ensure PseudoIDs come from expected worlds:
// Java - Validationpublic boolean isValidProductionUser(String userId) { IDComponents c = IDUtils.decodeId(userId); return c.worldSeed == PRODUCTION_SEED && c.typeSeq == TypeSeq.USERS;}Comparison: PseudoID vs Random UUID
Section titled “Comparison: PseudoID vs Random UUID”| Feature | Random UUID | PseudoID |
|---|---|---|
| Deterministic | ❌ Different each time | ✅ Same across languages |
| Cross-language | ❌ Random everywhere | ✅ Identical everywhere |
| Sortable | ❌ Random order | ✅ World → Type → Index |
| Decodable | ❌ No metadata | ✅ Extract components |
| Use with virtual arrays | ❌ Not tied to data | ✅ Directly from objects |
| RFC compliant | ✅ Yes (RFC 4122) | ✅ Yes (RFC 9562) |
| Best for | Unique identifiers | Traceable, reproducible IDs |
Technical Limits
Section titled “Technical Limits”- World Seeds: 64-bit (18.4 quintillion unique worlds)
- Type Sequences: 16-bit (65,536 different types)
- Index Range: 38-bit (274 billion objects per type)
- Layout Versions: 4-bit (16 future encoding schemes)
These limits are designed to handle virtually any real-world use case.
© 2025 Pseudentity Project. Part of the Pseudata Ecosystem. Open Source under Apache License 2.0.