Skip to content

TypeScript Support

The @nhtio/web-re-active-record library is built with TypeScript from the ground up, providing comprehensive type safety and excellent developer experience. This guide will help you understand the type system and how to effectively use types in your application.

Type Safety

The library provides strong type safety through:

  • Compile-time Type Checking: All operations on models and relationships are fully typed
  • Generic Type Parameters: Models and queries maintain type information throughout the chain
  • Type Inference: TypeScript can automatically infer types in most cases
  • Strict Null Checks: The library is designed to work well with TypeScript's strict mode
  • Runtime Type Guards: Built-in error classes support instanceof checks for robust error handling

Essential Types

Core Model Types

  • ReactiveModel<T, PK, R>: The base interface for all reactive models
    • T: The shape of your model data
    • PK: The primary key type
    • R: The shape of your model's relationships
  • ReactiveModelConstructor: The constructor interface for reactive models
  • BaseReactiveModel: The abstract base class that implements core model functionality
  • PlainObject: Represents a basic object with string keys and unknown values

Data Types

  • DataValues<T, PK>: Represents model properties excluding the primary key
  • DataProps<T>: Extracts only the data properties (non-methods) from a type
  • PendingStateChange: Represents pending changes to a model's state

Query and Database Types

  • ReactiveQueryBuilder<T, PK, R>: Type for building and executing queries
  • ReactiveDatabaseOptions: Configuration options for the database
  • ReactiveDatabaseModelDefinition: Type for defining model schemas
  • ReactiveDatabaseInitialOptions: Initial database configuration
  • ReActiveDatabaseDexie<T>: Extended Dexie database type with entity tables
  • ReactiveStateTypedEventMap: Map of all possible state change events in the system

Event and Change Tracking Types

  • ReactiveModelChangeEmitterEvent: Events emitted by models
  • ReactiveModelChangeEmitterEventMap: Map of all possible model events
  • ReactiveModelChangeDelta: Represents changes in model data
  • LogBusEvent: Type for logging events

Where to Find Types

The library exports its types from several locations:

  1. Types Module:

    typescript
    import type {
      ReactiveModel,
      PlainObject,
      DataValues
    } from '@nhtio/web-re-active-record/types'
  2. Errors Module:

    typescript
    import {
      ReactiveModelQueryException,
      NoReactiveModelRecordError
    } from '@nhtio/web-re-active-record/errors'

Working with Query Builder Types

typescript
import type { ReactiveQueryBuilder } from '@nhtio/web-re-active-record/types'

// The query builder maintains type information
const query: ReactiveQueryBuilder<User, 'id', {}> = UserModel.query()
const user = await query
  .where('email', 'user@example.com')
  .first()
// user is typed as User | undefined

Error Handling

The library exports error classes from the errors module that support instanceof checks. Here are some common error scenarios:

Database Operations

typescript
import { 
  NoReactiveModelRecordError,
  MissingReactiveModelRecordError,
  ReactiveModelQueryException
} from '@nhtio/web-re-active-record/errors'

try {
  const user = await UserModel.findOrFail(id)
} catch (error) {
  if (error instanceof NoReactiveModelRecordError) {
    // Handle no records found
  } else if (error instanceof ReactiveModelQueryException) {
    // Handle query execution errors
  }
}

Model Operations

typescript
import {
  ReactiveModelDeletedException,
  ReactiveModelNoSuchPropertyException,
  ReactiveModelUnacceptableValueException
} from '@nhtio/web-re-active-record/errors'

try {
  model.someProperty = value
} catch (error) {
  if (error instanceof ReactiveModelNoSuchPropertyException) {
    // Handle invalid property access
  } else if (error instanceof ReactiveModelUnacceptableValueException) {
    // Handle invalid value assignment
  } else if (error instanceof ReactiveModelDeletedException) {
    // Handle operations on deleted model
  }
}

Relationship Handling

typescript
import {
  UnpreparedRelationshipException,
  RelationshipNotBootedException
} from '@nhtio/web-re-active-record/errors'

try {
  await model.related('author')
} catch (error) {
  if (error instanceof UnpreparedRelationshipException) {
    // Handle unprepared relationship access
  } else if (error instanceof RelationshipNotBootedException) {
    // Handle unbooted relationship
  }
}

Type Utilities

The library provides several utility types to help with common patterns:

  • StringKeyOf<T>: Extracts string keys from an object type
  • DataProps<T>: Extracts only the data properties (non-methods) from a type
  • IsStrictlyAny<T>: Type guard for checking if a type is strictly 'any'
  • NonMethodKeys<T>: Extracts keys that don't represent methods
  • OnlyMethodKeys<T>: Extracts keys that represent methods

These utilities can be helpful when working with advanced type patterns in your application.

Type Safety Best Practices

  1. Use type inference with query builders:

    typescript
    const users = await UserModel.query()
      .where('active', true)
      .get() // users is typed as UserModel[]
  2. Leverage error classes for type-safe error handling:

    typescript
    import { ReactiveModelQueryException } from '@nhtio/web-re-active-record/errors'
    
    try {
      await model.save()
    } catch (error) {
      if (error instanceof ReactiveModelQueryException) {
        // Handle database operation error
      }
    }
  3. Use relationship types for better type safety:

    typescript
    const post = await PostModel.query()
      .with('author') // Relationship is type-checked
      .first()