npm package: @info-lounge/firestore-typed
Description: Type-safe, low-level wrapper for Firebase Firestore with enhanced validation and developer experience
Author: Info Lounge Corporation
Latest version: 0.5.5

# @info-lounge/firestore-typed – Type-safe Firestore wrapper with runtime validation

[![npm version](https://badge.fury.io/js/@info-lounge%2Ffirestore-typed.svg)](https://badge.fury.io/js/@info-lounge%2Ffirestore-typed)
[![npm downloads](https://img.shields.io/npm/dm/@info-lounge/firestore-typed.svg)](https://www.npmjs.com/package/@info-lounge/firestore-typed)
[![codecov](https://codecov.io/gh/InfoLoungeLLC/firestore-typed/branch/main/graph/badge.svg)](https://codecov.io/gh/InfoLoungeLLC/firestore-typed)
[![license](https://img.shields.io/npm/l/@info-lounge/firestore-typed.svg)](https://github.com/InfoLoungeLLC/firestore-typed/blob/main/LICENSE)

A type-safe, low-level wrapper for Firebase Firestore with **mandatory runtime validation**. This package ensures that **all data is validated using typia validators during both read and write operations**, providing comprehensive type safety, data integrity, and improved developer experience for Firestore operations.

> **[日本語のREADMEはこちら / Japanese README here →](README.ja.md)**

## Quick Example - Why Use FirestoreTyped?

```typescript
// ❌ Raw Firestore - No validation, runtime errors possible
const db = getFirestore()
await db.collection('users').doc('123').set({
  name: 123,      // Wrong type but no error until runtime
  email: null     // Missing required field
})

// ✅ FirestoreTyped - Automatic validation prevents errors
import { getFirestoreTyped } from '@info-lounge/firestore-typed'
import typia from 'typia'

interface User {
  name: string
  email: string
}

const userValidator = typia.createAssert<User>()
const db = getFirestoreTyped()
const users = db.collection<User>('users', userValidator)

await users.doc('123').set({
  name: 'John',              // ✓ Validated at runtime
  email: 'john@example.com'  // ✓ All fields checked
})
// Throws validation error if data doesn't match User interface
```

> ⚠️ **EXPERIMENTAL RELEASE**: This package is currently in beta. APIs may change in future releases. Use with caution in production environments.

## Key Features

- **🛡️ Mandatory Runtime Validation**: All data is automatically validated using typia validators on read/write operations
- **🔒 Type Safety**: Full TypeScript compile-time and runtime type checking
- **⚡ Performance Optimized**: Minimal overhead with maximum data integrity
- **🎯 Firebase Native**: Direct mapping to Firestore's native API patterns
- **🔧 Configurable**: Flexible validation settings per operation or globally

## Table of Contents

1. [Installation](#installation)
2. [Quick Start](#quick-start)
3. [Core Architecture](#core-architecture)
4. [Configuration](#configuration)
5. [CRUD Operations](#crud-operations)
6. [Query Builder](#query-builder)
7. [Validation](#validation)
8. [Automatic Type Conversion](#automatic-type-conversion)
9. [Type-Safe Document References](#type-safe-document-references)
10. [Error Handling](#error-handling)
11. [Performance](#performance)
12. [Best Practices](#best-practices)
13. [Advanced Usage](#advanced-usage)
14. [API Reference](#api-reference)

## Installation

This is a private npm package within the workspace:

```bash
npm install @info-lounge/firestore-typed
```

### Dependencies

This package requires the following peer dependencies:

```bash
# Required
npm install firebase-admin

# Choose one validation library (or both):
npm install typia     # Recommended for best performance
# OR
npm install zod       # Alternative validation library
```

**Note**: You must install either `typia` or `zod` (or both) as a validation library. The package will work with either one.


### Typia Transform Configuration

**Important**: Typia requires TypeScript transform plugin configuration to work properly. Use `ts-patch` for the easiest setup.

#### 1. Install ts-patch

```bash
npm install -D ts-patch
```

#### 2. Patch TypeScript compiler

```bash
npx ts-patch install
```

#### 3. Configure tsconfig.json

```json
{
  "compilerOptions": {
    "strict": true,
    "plugins": [
      {
        "transform": "typia/lib/transform"
      }
    ]
  }
}
```

#### 4. Use patched TypeScript compiler

```bash
# Instead of tsc
npx tsc

# For ts-node
npx ts-node your-file.ts
```

> **Note**: Without proper transformer configuration, `typia.createAssert<T>()` will not generate validation code and may throw runtime errors. The ts-patch approach is the most reliable method for most projects.

## Quick Start

### Why FirestoreTyped?

**FirestoreTyped's core principle: Every piece of data is validated.** Unlike raw Firestore operations, FirestoreTyped ensures data integrity by requiring validators for all operations.

```typescript
// ❌ Raw Firestore - No validation, potential runtime errors
const db = getFirestore()
await db.collection('users').doc('123').set({
  name: 123, // Wrong type, but no error until runtime
  email: null // Missing required field
})

// ✅ FirestoreTyped - Automatic validation prevents errors
const db = getFirestoreTyped()
const users = db.collection<UserEntity>('users', userValidator)
await users.doc('123').set({
  name: 'John', // Validated at runtime
  email: 'john@example.com' // All fields checked
})
// Throws validation error if data doesn't match UserEntity
```

### Basic Usage

```typescript
import { getFirestoreTyped } from '@info-lounge/firestore-typed'
import typia from 'typia'

// 1. Define your data structure
interface UserEntity {
  name: string
  email: string
  createdAt: Date
  updatedAt: Date
}

// 2. Create validator (REQUIRED - this is what makes data safe)
const userEntityValidator = typia.createAssert<UserEntity>()

// 3. Create FirestoreTyped instance
const db = getFirestoreTyped(undefined, { 
  validateOnWrite: true,  // Default: validates all writes
  validateOnRead: false   // Optional: validates all reads
})

// 4. Create typed collection with validator
const usersCollection = db.collection<UserEntity>('users', userEntityValidator)

// ✅ This data will be validated before write
await usersCollection.doc('user-001').set({
  name: 'John Doe',
  email: 'john@example.com',
  createdAt: new Date(),
  updatedAt: new Date()
})

// ✅ This data will be validated after read (if validateOnRead: true)
const user = await usersCollection.doc('user-001').get()
// user.data is guaranteed to match UserEntity or throw validation error
```

### Using Custom Firestore Instances

When you need to use a different Firestore instance (e.g., different project, different database), you can pass a custom Firestore instance:

```typescript
import { getFirestore } from 'firebase-admin/firestore'
import { initializeApp, getApps } from 'firebase-admin/app'
import { getFirestoreTyped } from '@info-lounge/firestore-typed'

// Initialize custom Firebase app
const customApp = getApps().find(app => app.name === 'custom-app') || 
  initializeApp({
    projectId: 'custom-project-id',
    // other configuration...
  }, 'custom-app')

// Get custom Firestore instance
const customFirestore = getFirestore(customApp)

// Initialize FirestoreTyped with custom Firestore instance
const customDb = getFirestoreTyped(customFirestore, {
  validateOnWrite: true,
  validateOnRead: false
})

// Use as normal
const usersCollection = customDb.collection<UserEntity>('users', userValidator)
await usersCollection.doc('user-001').set(userData)

// Using named databases
const namedDatabase = getFirestore(customApp, '(named-database)')
const namedDb = getFirestoreTyped(namedDatabase, {
  validateOnWrite: true
})
```

**Use Cases:**
- **Multi-project**: Working with multiple Firebase projects
- **Environment separation**: Different databases for development, staging, production
- **Named databases**: Using Firestore's multiple database feature
- **Testing**: Using dedicated Firestore emulator instances

### Complete CRUD Example

```typescript
// Define user data structure
interface User {
  name: string
  email: string
  createdAt: Date
  updatedAt: Date
}

// Create validator function
const userValidator = typia.createAssert<User>()

// Initialize FirestoreTyped
const db = getFirestoreTyped(undefined, {
  validateOnRead: true,
  validateOnWrite: true
})

const usersCollection = db.collection<User>('users', userValidator)

// CREATE: Add document with auto-generated ID
const newUser = {
  name: 'John Doe',
  email: 'john@example.com',
  createdAt: new Date(),
  updatedAt: new Date()
}

const docRef = await usersCollection.add(newUser)
console.log('Created user with ID:', docRef.id)

// CREATE: Set document with specific ID
const specificUser = {
  name: 'Jane Smith',
  email: 'jane@example.com',
  createdAt: new Date(),
  updatedAt: new Date()
}

await usersCollection.doc('user-123').set(specificUser)

// READ: Get single document
const userSnapshot = await usersCollection.doc('user-123').get()
if (userSnapshot.metadata.exists) {
  const user = userSnapshot.data!
  console.log(`User: ${user.name} (${user.email})`)
}

// READ: Get all documents
const allUsers = await usersCollection.get()
const userList = allUsers.docs.map(doc => ({
  id: doc.metadata.id, // Use Firestore document ID
  ...doc.data!
}))

// UPDATE: Merge partial data
await usersCollection.doc('user-123').merge({
  email: 'jane.updated@example.com',
  updatedAt: new Date()
})

// UPDATE: Complete replacement
const updatedUser: User = {
  name: 'Jane Smith',
  email: 'jane.smith@example.com',
  createdAt: new Date('2024-01-01'),
  updatedAt: new Date()
}
await usersCollection.doc('user-123').set(updatedUser)

// DELETE: Remove document
await usersCollection.doc('user-123').delete()
```

## Core Architecture

### FirestoreTyped Instance

FirestoreTyped provides a low-level, type-safe interface to Firestore with built-in validation. The main entry point is the `getFirestoreTyped()` factory function:

```typescript
const db = getFirestoreTyped(firestore?: Firestore, options?: FirestoreTypedOptions)
```

### Architecture Philosophy

FirestoreTyped follows a **low-level, collection-specific validation** approach:
- **Type Safety**: Generic types ensure compile-time safety
- **Flexible Validation**: Collection-level validators for different entity types
- **Firebase Native**: Direct mapping to Firestore's native API patterns
- **Performance Focused**: Minimal overhead with maximum type safety

### Collection Reference

Collections are accessed through the `collection()` method with type-specific validator:

```typescript
// Validator is provided per collection for maximum flexibility
const collection = db.collection<T>(path: string, validator: (data: unknown) => T)
```

### Document Reference

Documents are accessed through the `doc()` method on collections:

```typescript
const doc = collection.doc(id: string)
```

### Document Snapshot

Read operations return a `DocumentSnapshot<T>` with type-safe data access:

```typescript
const snapshot = await doc.get()
const data = snapshot.data // Type-safe data access with automatic validation
const exists = snapshot.metadata.exists
```

## Configuration

### Global Options

```typescript
interface FirestoreTypedOptions {
  validateOnRead?: boolean   // Default: false
  validateOnWrite?: boolean  // Default: true
}
```

### Validator Configuration

FirestoreTyped uses collection-level validators for flexible entity type support:

```typescript
// Using typia for compile-time type generation
import typia from 'typia'

interface UserEntity {
  name: string
  email: string
  createdAt: Date
  updatedAt: Date
}

// Create validator function
const userValidator = typia.createAssert<UserEntity>()

// Initialize FirestoreTyped
const db = getFirestoreTyped(undefined, {
  validateOnRead: true,
  validateOnWrite: true
})

// Create typed collection with validator
const usersCollection = db.collection<UserEntity>('users', userValidator)
```

### Operation Options

```typescript
interface ReadOptions {
  validateOnRead?: boolean  // Override global setting
}

interface WriteOptions {
  validateOnWrite?: boolean  // Override global setting
  failIfExists?: boolean    // Prevent overwriting existing documents
}
```

## CRUD Operations

All CRUD operations require a typed collection created with a validator:

```typescript
// Setup: Create typed collection
const db = getFirestoreTyped()
const collection = db.collection<UserEntity>('users', userValidator)
```

### Create Documents

```typescript
// Add with auto-generated ID
await collection.add(data)

// Set with specific ID
await collection.doc('specific-id').set(data)

// Set with write options
await collection.doc('id').set(data, { validateOnWrite: false })

// Ensure document creation without overwriting
await collection.doc('id').set(data, { failIfExists: true })
// Throws DocumentAlreadyExistsError if document already exists
```

### Read Documents

```typescript
// Get single document
const snapshot = await collection.doc('id').get()
const data = snapshot.data

// Get single document with read options
const snapshot = await collection.doc('id').get({ validateOnRead: true })

// Get all documents in collection
const querySnapshot = await collection.get()
const documents = querySnapshot.docs.map(doc => doc.data)
```

### Merge Partial Data

**Important**: 
- FirestoreTyped uses a dedicated `merge()` method instead of `set(data, { merge: true })`
- The `merge` operation validates the **complete merged data**, not just the partial data being merged
- **The document must exist** - throws `DocumentNotFoundError` if the document doesn't exist
- Native Firestore's `set(..., { merge: true })` is not available in FirestoreTyped

```typescript
// Partial merge with existing data
// 1. Retrieves existing document data
// 2. Merges with new partial data
// 3. Validates the COMPLETE merged result against the entity type
await collection.doc('id').merge({ field: 'newValue' })

// Example with validation
interface User {
  name: string
  email: string
  age: number
}

// Existing document: { name: 'John', email: 'john@example.com', age: 30 }
// Merge operation:
await usersCollection.doc('user123').merge({ 
  email: 'john.doe@example.com' 
})
// Result validated: { name: 'John', email: 'john.doe@example.com', age: 30 }
// ✅ Passes validation as complete User entity

// ❌ This would fail validation:
// await usersCollection.doc('user123').merge({ invalidField: 'value' })
// Because merged result would have extra field not in User interface

// Merge with validation options
await collection.doc('id').merge(data, { validateOnWrite: true })

// ❌ NOT available in FirestoreTyped (use merge() instead):
// await collection.doc('id').set(data, { merge: true }) // This pattern is NOT supported

// ✅ Use dedicated merge() method:
await collection.doc('id').merge(data) // This is the correct FirestoreTyped way

// Handle non-existent document error
try {
  await collection.doc('non-existent').merge({ field: 'value' })
} catch (error) {
  if (error instanceof DocumentNotFoundError) {
    console.log('Document does not exist:', error.documentPath)
    // Create the document instead
    await collection.doc('non-existent').set(fullData)
  }
}
```

### Delete Documents

```typescript
await collection.doc('id').delete()
```


## Query Builder

FirestoreTyped provides a complete type-safe query builder that allows you to use Firebase Firestore's powerful query capabilities with full type safety.

### Basic Queries

```typescript
// Setup: Create typed collection
const db = getFirestoreTyped()
const usersCollection = db.collection<UserEntity>('users', userValidator)

// Single condition search
const johnUsers = await usersCollection
  .where('name', '==', 'John Doe')
  .get()

// Sorting functionality
const sortedUsers = await usersCollection
  .orderBy('createdAt', 'desc')
  .get()

// Result limiting
const latestUsers = await usersCollection
  .orderBy('updatedAt', 'desc')
  .limit(10)
  .get()
```

### Complex Queries and Method Chaining

```typescript
// Multiple condition combined query
const filteredUsers = await usersCollection
  .where('email', '>=', 'a@example.com')
  .where('email', '<=', 'z@example.com')
  .orderBy('name', 'asc')
  .limit(5)
  .get()

// Product type-based search
const cellProducts = await productsCollection
  .where('category', '==', 'electronics')
  .where('price', '<=', 1000)
  .orderBy('name')
  .get()
```

### Advanced Pagination

```typescript
// Cursor-based pagination
const firstPage = await usersCollection
  .orderBy('createdAt', 'desc')
  .limit(20)
  .get()

if (firstPage.docs.length > 0) {
  const lastDoc = firstPage.docs[firstPage.docs.length - 1]
  
  // Get next page
  const secondPage = await usersCollection
    .orderBy('createdAt', 'desc')
    .startAfter(lastDoc.data!.createdAt)
    .limit(20)
    .get()
}

// Range queries
const rangeQuery = await usersCollection
  .orderBy('name')
  .startAt('Alice')
  .endAt('John')
  .get()
```

### Type Safety

The query builder provides type safety for field names and basic operations:

```typescript
// ✅ Valid usage - field names are type-checked
const validQuery = usersCollection.where('name', '==', 'John Doe')
const sortedQuery = usersCollection.orderBy('createdAt', 'desc')

// ❌ Compile errors for invalid field names
// const invalidQuery = usersCollection.where('invalidField', '==', 'value')

// ⚠️ Note: Value types and pagination parameters are not fully type-checked
// const query = usersCollection.where('name', '==', 123) // May not catch type errors
// const paginated = usersCollection.startAt('any', 'values') // Parameters are unknown[]
```

### Validation Integration

```typescript
// Query results automatically validated based on global configuration
const validatedResults = await usersCollection
  .where('name', '!=', '')
  .orderBy('name')
  .get()

// Override global validation settings for specific operations
const fastResults = await usersCollection
  .limit(100)
  .get({ validateOnRead: false })
```

### Native Firebase Access

For advanced Firestore features, you can access the native query object:

```typescript
const query = usersCollection.where('name', '!=', '').orderBy('name')
const nativeQuery = query.native // Firebase Query object
const snapshot = await nativeQuery.get()
```


## Validation

### Runtime Validation with Typia

FirestoreTyped integrates with [typia](https://github.com/samchon/typia) for runtime validation:

```typescript
import typia from 'typia'

interface UserEntity {
  name: string
  email: string
  createdAt: Date
  updatedAt: Date
}

const userEntityValidator = typia.createAssert<UserEntity>()
const db = getFirestoreTyped()
const usersCollection = db.collection<UserEntity>('users', userEntityValidator)

// This will throw a validation error if data doesn't match UserEntity
await usersCollection.doc('user-001').set(invalidData)
```

### Advanced Typia Features

Typia supports JSDoc comment-based type annotations for more precise validation:

```typescript
interface UserEntity {
  /**
   * User ID with specific format
   * @format uuid
   */
  id: string

  /**
   * User name with length constraints
   * @minLength 2
   * @maxLength 50
   */
  name: string

  /**
   * Email address with format validation
   * @format email
   */
  email: string

  /**
   * User age as integer
   * @type integer
   * @minimum 0
   * @maximum 120
   */
  age: number

  /**
   * Phone number with regex pattern
   * @pattern ^\+?[1-9]\d{1,14}$
   */
  phone: string

  /**
   * User status from specific values
   * @items.enum ["active", "inactive", "pending"]
   */
  status: "active" | "inactive" | "pending"

  createdAt: Date
  updatedAt: Date
}

const userValidator = typia.createAssert<UserEntity>()
const db = getFirestoreTyped()

// This will validate all the constraints:
// - id must be valid UUID format
// - name must be 2-50 characters
// - email must be valid email format
// - age must be integer between 0-120
// - phone must match the regex pattern
// - status must be one of the allowed values
await db.collection<UserEntity>('users', userValidator).doc('user123').set({
  id: '550e8400-e29b-41d4-a716-446655440000',
  name: 'John Doe',
  email: 'john@example.com',
  age: 30,
  phone: '+1234567890',
  status: 'active',
  createdAt: new Date(),
  updatedAt: new Date()
})
```

**Supported JSDoc Tags:**
- `@type integer` - Validates integer numbers
- `@format email|uuid|date|uri` - Format validation
- `@pattern <regex>` - Regular expression validation
- `@minimum/@maximum` - Numeric range validation
- `@minLength/@maxLength` - String length validation
- `@items.enum` - Enum value validation

This provides much more precise validation than basic TypeScript types alone.

### Alternative Validation Libraries

**Note**: FirestoreTyped accepts any validator function with signature `(data: unknown) => T`. Besides typia, we have also verified compatibility with Zod:

```typescript
// Zod example
import { z } from 'zod'

const UserSchema = z.object({
  name: z.string(),
  email: z.string(),
  createdAt: z.date(),
  updatedAt: z.date()
})

const zodValidator = (data: unknown): UserEntity => {
  return UserSchema.parse(data) // Throws on validation failure
}

const db = getFirestoreTyped()
const users = db.collection<UserEntity>('users', zodValidator)

// Joi example (untested)
import Joi from 'joi'

const userJoiSchema = Joi.object({
  name: Joi.string().required(),
  email: Joi.string().required(),
  createdAt: Joi.date().required(),
  updatedAt: Joi.date().required()
})

const joiValidator = (data: unknown): UserEntity => {
  const { error, value } = userJoiSchema.validate(data)
  if (error) throw error
  return value
}

const db2 = getFirestoreTyped()
const users2 = db2.collection<UserEntity>('users', joiValidator)
```

⚠️ **Important**: We have verified compatibility with **typia** and **Zod**. Other validation libraries like Joi should work in theory but have not been verified. Please test thoroughly and report any issues.

### Validation Control

```typescript
// Enable validation for a specific operation
await usersCollection.doc('user-001').set(data, { validateOnWrite: true })

// Disable validation for a specific operation
await usersCollection.doc('user-001').set(data, { validateOnWrite: false })
```

### Validation Errors

```typescript
import { FirestoreTypedValidationError } from '@info-lounge/firestore-typed'

try {
  await usersCollection.doc('user-001').set(invalidData)
} catch (error) {
  if (error instanceof FirestoreTypedValidationError) {
    console.log('Validation failed:', error.message)
    console.log('Original error:', error.originalError)
  }
}
```

## Error Handling

```typescript
// Setup: Create typed collection
const db = getFirestoreTyped()
const usersCollection = db.collection<UserEntity>('users', userValidator)
```

### Document Not Found

```typescript
try {
  const snapshot = await usersCollection.doc('99999').get()
  if (!snapshot.metadata.exists) {
    console.log('User does not exist')
  }
} catch (error) {
  console.error('Error retrieving user:', error)
}
```

### Validation Errors

```typescript
try {
  await usersCollection.doc('user-001').set(invalidData)
} catch (error) {
  if (error instanceof FirestoreTypedValidationError) {
    // Handle validation error
  }
}
```

### Document Already Exists

```typescript
import { DocumentAlreadyExistsError } from '@info-lounge/firestore-typed'

try {
  // This will fail if document already exists
  await usersCollection.doc('user-001').set(data, { failIfExists: true })
} catch (error) {
  if (error instanceof DocumentAlreadyExistsError) {
    console.log('Document already exists at:', error.documentPath)
    // Handle by updating instead of creating
    await usersCollection.doc('user-001').merge(data)
  }
}
```


## Automatic Type Conversion

FirestoreTyped automatically handles the conversion of JavaScript types to Firestore special types during write operations:

### Supported Type Conversions

| JavaScript Type | Firestore Type | Description |
|----------------|----------------|-------------|
| `Date` | `Timestamp` | DateTime data conversion (see precision note below) |
| `SerializedGeoPoint` | `GeoPoint` | Geographic location data conversion |
| `SerializedDocumentReference<TCollection, TDocument>` | `DocumentReference` | Type-safe document reference restoration |

> **⚠️ Important Note on Date/Timestamp Precision**: JavaScript `Date` objects have millisecond precision, while Firestore `Timestamp` objects support nanosecond precision. When converting from `Date` to `Timestamp`, the nanosecond portion will always be `000000` (zero). This means any nanosecond-level precision from the original Firestore data will be lost during the conversion process.

### Example Usage

```typescript
import { GeoPoint, Timestamp } from 'firebase-admin/firestore'
import type { StoreEntity } from '@common/types/store.types'

// JavaScript types are automatically converted to Firestore types
const storeData = {
  id: 'store-001',
  name: 'Tokyo Electronics Store',
  address: 'Shibuya, Tokyo',
  phone: '+81-3-1234-5678',
  category: {
    type: 'DocumentReference',
    path: 'categories/electronics',
    collectionId: 'categories',
    documentId: 'electronics'
  } as SerializedDocumentReference<'categories', CategoryEntity>,
  location: {
    type: 'GeoPoint',
    latitude: 35.6762,
    longitude: 139.6503
  },
  createdAt: new Date('2024-01-01'),
  updatedAt: new Date()
}

// Setup: Create typed collection and automatically converts during write:
// - Date → Timestamp
// - SerializedGeoPoint → GeoPoint  
// - SerializedDocumentReference → DocumentReference
const db = getFirestoreTyped()
const storesCollection = db.collection<StoreEntity>('stores', storeValidator)
await storesCollection.doc('store-001').set(storeData)
```

### Processing Order

1. **Validation**: Data is validated using typia assertions
2. **Type Conversion**: JavaScript types converted to Firestore types
3. **Write**: Data written to Firestore with proper types

## Type-Safe Document References

FirestoreTyped provides enhanced type safety for document references through the `SerializedDocumentReference` interface:

### Generic Type Parameters

```typescript
import { SerializedDocumentReference } from '@info-lounge/firestore-typed'

// TCollection: Collection name (literal type)
// TDocument: Referenced document type (phantom type)
type ProductRef = SerializedDocumentReference<'products', ProductEntity>

// Type-safe reference with compile-time validation
const parentRef: ProductRef = {
  type: 'DocumentReference',
  path: 'products/parent-001',
  collectionId: 'categories', // Must match 'products'
  documentId: 'parent-001'
}
```

### Type Safety Benefits

- **Collection Name Validation**: `collectionId` must match the generic type
- **Type Safety**: TypeScript enforces correct collection-document type pairs
- **Runtime Checking**: typia validates the structure and values
- **IDE Support**: Full IntelliSense and autocomplete

### Usage Example

```typescript
// Creating products with parent references
const childProduct: ProductEntity = {
  id: 'child-001',
  name: 'Child Product',
  category: 'electronics',
  userId: 'user-001',
  parent: {
    type: 'DocumentReference',
    path: 'products/parent-001',
    collectionId: 'categories', // ✅ Type-safe
    documentId: 'parent-001'
  } as SerializedDocumentReference<'products', ProductEntity>,
  createdAt: new Date(),
  updatedAt: new Date()
}
```

## Performance

### Benchmarks

**Note**: These performance benchmarks are based on comprehensive testing using **typia validators**. Performance may vary with other validation libraries.

Based on comprehensive performance testing:

#### Core Operations Performance
- **Create Operations**: High-performance document creation (avg. 57ms)
- **Read Operations**: Optimized data retrieval (avg. 17ms)  
- **Merge Operations**: Efficient partial updates (avg. 44ms)
- **Batch Operations**: Optimized bulk operations (avg. 72ms)

#### Type Conversion Performance
- **SerializedDocumentReference conversion**: <0.1ms overhead per reference
- **Complex nested structure processing**: Maintains ~98% of baseline performance
- **Type guard validation**: Efficient type checking with minimal performance impact

### Validation Overhead

- Runtime validation adds approximately 1.2% overhead (0.58ms)
- SerializedDocumentReference validation adds <0.1ms per reference  
- Validation can be disabled per operation for performance-critical paths
- Type guards ensure safe conversion without performance penalties

### Memory Usage

- Minimal memory footprint (271KB/operation)
- Efficient handling of large datasets with document references
- No memory leaks in continuous operations
- SerializedDocumentReference conversion uses minimal additional memory


## Advanced Usage

### Options Management

```typescript
// Initialize with default options
const db = getFirestoreTyped()

// Get current options
const currentOptions = db.getOptions()

// Create new instance with different options
const strictDb = db.withOptions({ validateOnRead: true })
```

### Type Safety

```typescript
// Setup: Create typed collection
const db = getFirestoreTyped()
const users = db.collection<UserEntity>('users', userValidator)

// TypeScript will enforce correct types
await users.doc('user-001').set({
  name: 'John Doe',
  email: 'john@example.com',
  createdAt: new Date(),
  updatedAt: new Date()
})

// This will cause a compile-time error
// await users.doc('user-001').set({
//   id: 'user-001',
//   name: 'John Doe',
//   email: 123 // Type error: number is not assignable to string
// })
```

### Working with Existing Types

```typescript
// Integration with your application types including document references
import { SerializedDocumentReference } from '@info-lounge/firestore-typed'
import typia from 'typia'

// Define your entity types
interface UserEntity {
  name: string
  email: string
  createdAt: Date
  updatedAt: Date
}

interface ProductEntity {
  id: string
  name: string
  category: string
  price: number
  parentCategory?: SerializedDocumentReference<'categories', CategoryEntity>
  createdAt: Date
  updatedAt: Date
}

// Create validators
const userValidator = typia.createAssert<UserEntity>()
const productValidator = typia.createAssert<ProductEntity>()

// Create single FirestoreTyped instance that can handle multiple entity types
const db = getFirestoreTyped()

// Create type-safe collections with their respective validators
const users = db.collection<UserEntity>('users', userValidator)
const products = db.collection<ProductEntity>('products', productValidator)

// Working with products that have parent references
const productWithParent: ProductEntity = {
  id: 'prod-001',
  name: 'Test Product',
  category: 'electronics',
  userId: 'user-001',
  parent: {
    type: 'DocumentReference',
    path: 'categories/electronics-main',
    collectionId: 'categories',
    documentId: 'electronics-main'
  } as SerializedDocumentReference<'products', ProductEntity>,
  createdAt: new Date(),
  updatedAt: new Date()
}
```

## Best Practices

### 1. Always Use Validators

FirestoreTyped requires validators for type safety and data integrity:

```typescript
// ✅ Good: Always provide a validator per collection
const userValidator = typia.createAssert<UserEntity>()
const db = getFirestoreTyped()
const users = db.collection<UserEntity>('users', userValidator)

// ❌ Bad: Never skip validation
// This is not possible with FirestoreTyped's design
```

### 2. Type Your Data Properly

```typescript
// ✅ Good: Use specific types
interface UserEntity {
  name: string
  email: string
  createdAt: Date
  updatedAt: Date
}

// ❌ Bad: Avoid generic types
// Don't use 'any' or overly broad types
```

### 3. Handle Errors Appropriately

```typescript
import { FirestoreTypedValidationError } from '@info-lounge/firestore-typed'

// Setup: Create typed collection
const db = getFirestoreTyped()
const userCollection = db.collection<UserEntity>('users', userValidator)

try {
  await userCollection.doc('user-id').set(userData)
} catch (error) {
  if (error instanceof FirestoreTypedValidationError) {
    // Handle validation error
    console.error('Data validation failed:', error.message)
  } else {
    // Handle other errors
    throw error
  }
}
```

### 4. Optimize Validation Settings

```typescript
// For read-heavy operations, consider disabling read validation
const fastDb = getFirestoreTyped(undefined, {
  validateOnRead: false,  // Skip validation on reads for performance
  validateOnWrite: true   // Always validate writes for data integrity
})

const userCollection = fastDb.collection<UserEntity>('users', userValidator)

// Override per operation when needed
const data = await userCollection.doc('user-id').get({ validateOnRead: true })
```

### 5. Use Collection Group Queries Efficiently

```typescript
// ✅ Good: Use collection group queries for cross-collection searches
const allProducts = await db.queryCollectionGroup('products', (query) =>
  query.where('category', '==', 'electronics').orderBy('name')
)

// ✅ Good: Regular collection queries for single collection
const userProducts = await db.collection('users/user-001/products').get()
```

### 6. Performance Considerations

```typescript
// ✅ For high-frequency operations, consider validation overhead
const performanceDb = getFirestoreTyped(undefined, {
  validateOnRead: false,   // Skip validation for read-heavy logging
  validateOnWrite: false   // Skip validation for high-frequency writes
})
const logs = performanceDb.collection<LogEntry>('logs', logValidator)

// ✅ Use batch operations for multiple writes (planned feature)
// const batch = db.batch() // ⚠️ Not yet implemented
// batch.set(userCollection.doc('user1'), userData1)
// batch.set(userCollection.doc('user2'), userData2)
// await batch.commit()

// For now, use native Firestore batch:
const batch = db.native.batch()
batch.set(db.native.collection('users').doc('user1'), userData1)
batch.set(db.native.collection('users').doc('user2'), userData2)
await batch.commit()
```

## API Reference

### Factory Function

```typescript
/**
 * Creates a FirestoreTyped instance for multi-entity type support
 * @param options - Global configuration options
 * @returns Configured FirestoreTyped instance
 * @example
 * ```typescript
 * import { getFirestoreTyped } from '@info-lounge/firestore-typed'
 * 
 * const db = getFirestoreTyped(firestore, options)
 * ```
 */
function getFirestoreTyped(
  options?: FirestoreTypedOptions
): FirestoreTyped
```

### FirestoreTyped Instance

```typescript
/**
 * Main FirestoreTyped instance providing type-safe Firestore operations
 */
class FirestoreTyped {
  /**
   * Gets a typed collection reference with validator
   * @param path - Firestore collection path
   * @param validator - Runtime validation function for the entity type
   * @returns Type-safe collection reference
   * @example
   * ```typescript
   * const usersCollection = db.collection<UserEntity>('users', userValidator)
   * const userProductsCollection = db.collection<ProductEntity>('users/user-001/products', productValidator)
   * ```
   */
  collection<T>(path: string, validator: (data: unknown) => T): CollectionReference<T>

  /**
   * Gets a typed collection group reference with validator
   * @param collectionId - Collection group ID to query across multiple parent documents
   * @param validator - Runtime validation function for the entity type
   * @returns Type-safe collection group reference
   * @example
   * ```typescript
   * const allPosts = db.collectionGroup<PostEntity>('posts', postValidator)
   * const userPosts = allPosts.where('userId', '==', 'user123')
   * ```
   */
  collectionGroup<T>(collectionId: string, validator: (data: unknown) => T): CollectionGroup<T>

  /**
   * Gets current configuration options
   * @returns Current FirestoreTyped options
   * @example
   * ```typescript
   * const currentOptions = db.getOptions()
   * console.log(`Validation on read: ${currentOptions.validateOnRead}`)
   * ```
   */
  getOptions(): ResolvedFirestoreTypedOptions

  /**
   * Creates new instance with modified options
   * @param options - Partial options to override
   * @returns New FirestoreTyped instance with updated options
   * @example
   * ```typescript
   * const strictDb = db.withOptions({ validateOnRead: true })
   * const fastDb = db.withOptions({ validateOnWrite: false })
   * ```
   */
  withOptions(options: Partial<FirestoreTypedOptions>): FirestoreTyped

  /**
   * Access to native Firestore instance for advanced operations
   * @returns Native Firestore instance
   * @example
   * ```typescript
   * const nativeFirestore = db.native
   * const batch = nativeFirestore.batch()
   * ```
   */
  get native(): Firestore

  /**
   * Performs collection group query across multiple collections
   * @param collectionId - Collection ID to search across
   * @param queryFn - Optional query builder function
   * @returns Query results from all matching collections
   * @throws FirestoreTypedValidationError if validation fails
   * @example
   * ```typescript
   * // Find all products across all users
   * const allProducts = await db.queryCollectionGroup('products')
   * 
   * // With query constraints
   * const electronicsProducts = await db.queryCollectionGroup('products', (query) =>
   *   query.where('category', '==', 'electronics').orderBy('name')
   * )
   * ```
   */
  queryCollectionGroup<T>(
    collectionId: string, 
    queryFn?: (query: Query) => Query
  ): Promise<QuerySnapshot<T>>

  /**
   * Finds specific document across collection groups
   * @param collectionId - Collection ID to search in
   * @param documentId - Document ID to find
   * @returns Document data if found, null otherwise
   * @throws FirestoreTypedValidationError if validation fails
   * @example
   * ```typescript
   * // Find user across all users/products
   * const user = await db.findDocumentInCollectionGroup('publicUsers', 'user123')
   * if (user) {
   *   console.log(`Found user: ${user.name}`)
   * }
   * ```
   */
  findDocumentInCollectionGroup<T>(
    collectionId: string, 
    documentId: string
  ): Promise<T | null>
}
```

### CollectionReference Class

```typescript
/**
 * Type-safe collection reference with query capabilities
 */
class CollectionReference<T> {
  /**
   * Collection ID (last segment of path)
   * @example 'users', 'products'
   */
  get id(): string

  /**
   * Full collection path
   * @example 'users', 'users/user-001/products'
   */
  get path(): string

  /**
   * Gets a document reference within this collection
   * @param id - Document ID
   * @returns Type-safe document reference
   * @example
   * ```typescript
   * const userDoc = usersCollection.doc('user123')
   * const userData = await userDoc.get()
   * ```
   */
  doc(id: string): DocumentReference<T>

  /**
   * Adds a new document with auto-generated ID
   * @param data - Document data to add
   * @param options - Write options (validation settings)
   * @returns Promise resolving to document reference
   * @throws FirestoreTypedValidationError if validation fails
   * @example
   * ```typescript
   * const docRef = await usersCollection.add({
   *   name: 'John Doe',
   *   email: 'john@example.com',
   *   createdAt: new Date(),
   *   updatedAt: new Date()
   * })
   * console.log(`Created with ID: ${docRef.id}`)
   * ```
   */
  add(data: T, options?: WriteOptions): Promise<DocumentReference<T>>

  /**
   * Gets all documents in the collection
   * @param options - Read options (validation settings)
   * @returns Promise resolving to query snapshot
   * @throws FirestoreTypedValidationError if validation fails
   * @example
   * ```typescript
   * const snapshot = await usersCollection.get()
   * const users = snapshot.docs.map(doc => doc.data!)
   * console.log(`Found ${users.length} users`)
   * ```
   */
  get(options?: ReadOptions): Promise<QuerySnapshot<T>>

  /**
   * Creates a query with where clause
   * @param field - Field to filter on
   * @param operator - Comparison operator
   * @param value - Value to compare against
   * @returns Query builder for method chaining
   * @example
   * ```typescript
   * const activeUsers = await usersCollection
   *   .where('status', '==', 'active')
   *   .get()
   * ```
   */
  where(field: keyof T, operator: WhereFilterOp, value: any): Query<T>

  /**
   * Orders query results by specified field
   * @param field - Field to order by
   * @param direction - Sort direction ('asc' | 'desc')
   * @returns Query builder for method chaining
   * @example
   * ```typescript
   * const sortedUsers = await usersCollection
   *   .orderBy('createdAt', 'desc')
   *   .limit(10)
   *   .get()
   * ```
   */
  orderBy(field: keyof T, direction?: 'asc' | 'desc'): Query<T>

  /**
   * Limits number of results
   * @param limit - Maximum number of documents to return
   * @returns Query builder for method chaining
   */
  limit(limit: number): Query<T>
}
```

### DocumentReference Class

```typescript
/**
 * Type-safe document reference for individual document operations
 */
class DocumentReference<T> {
  /**
   * Document ID
   */
  get id(): string

  /**
   * Full document path
   * @example 'users/user123', 'users/user-001/products/prod456'
   */
  get path(): string

  /**
   * Gets document data
   * @param options - Read options (validation settings)
   * @returns Promise resolving to document snapshot
   * @throws FirestoreTypedValidationError if validation fails
   * @example
   * ```typescript
   * const snapshot = await userDoc.get()
   * if (snapshot.metadata.exists) {
   *   console.log(`User: ${snapshot.data!.name}`)
   * } else {
   *   console.log('User not found')
   * }
   * ```
   */
  get(options?: ReadOptions): Promise<DocumentSnapshot<T>>

  /**
   * Sets document data (creates or replaces)
   * @param data - Complete document data
   * @param options - Write options (validation settings, failIfExists)
   * @returns Promise resolving when operation completes
   * @throws FirestoreTypedValidationError if validation fails
   * @throws DocumentAlreadyExistsError if failIfExists is true and document exists
   * @example
   * ```typescript
   * // Normal set operation
   * await userDoc.set({
   *   id: 'user123',
   *   name: 'John Doe',
   *   email: 'john@example.com',
   *   createdAt: new Date(),
   *   updatedAt: new Date()
   * })
   * 
   * // Ensure document doesn't exist
   * await userDoc.set(data, { failIfExists: true })
   * ```
   */
  set(data: T, options?: WriteOptions): Promise<void>

  /**
   * Merges partial data with existing document
   * @param data - Partial document data to merge
   * @param options - Write options (validation settings)
   * @returns Promise resolving when operation completes
   * @throws DocumentNotFoundError if document doesn't exist
   * @throws FirestoreTypedValidationError if validation fails
   * @example
   * ```typescript
   * await userDoc.merge({
   *   email: 'newemail@example.com',
   *   updatedAt: new Date()
   * })
   * 
   * // Error handling
   * try {
   *   await userDoc.merge(data)
   * } catch (error) {
   *   if (error instanceof DocumentNotFoundError) {
   *     // Document doesn't exist, create it instead
   *     await userDoc.set(fullData)
   *   }
   * }
   * ```
   */
  merge(data: Partial<T>, options?: WriteOptions): Promise<void>

  /**
   * Deletes the document
   * @returns Promise resolving when deletion completes
   * @example
   * ```typescript
   * await userDoc.delete()
   * console.log('User deleted successfully')
   * ```
   */
  delete(): Promise<void>
}
```

### Types

```typescript
interface FirestoreTypedOptions {
  validateOnRead?: boolean
  validateOnWrite?: boolean
}

interface CollectionOptions<T> {
  validator?: (data: unknown) => T
}

interface ReadOptions {
  validateOnRead?: boolean
}

interface WriteOptions {
  validateOnWrite?: boolean
  failIfExists?: boolean    // If true, throws error if document already exists
}

interface DocumentSnapshot<T> {
  metadata: DocumentMetadata
  data?: T
}

interface QuerySnapshot<T> {
  docs: DocumentSnapshot<T>[]
  empty: boolean
  size: number
}
```