Skip to content

Shared Types

Overview

The shared module provides fundamental TypeScript types that form the foundation of Raindrop’s execution model and actor communication system. These types enable consistent runtime behavior across services, actors, and tasks while providing type-safe communication patterns.

Key benefits:

  • Runtime Context: Standardized execution environment across all framework components
  • Type Safety: Generic utilities ensure compile-time correctness for actor communication
  • Async Coordination: Built-in support for background task coordination

Prerequisites

  • Basic TypeScript knowledge
  • Understanding of generic types and utility types
  • Familiarity with Promise-based async patterns

Core Types

ExecutionContext

The ExecutionContext interface provides runtime capabilities to framework components during execution.

type ExecutionContext = {
waitUntil(promise: Promise<any>): void;
};

Properties:

  • waitUntil: Registers background promises to keep the execution context alive

Usage in Services

Services receive an execution context for managing background operations:

class MyService extends Service<Env> {
async handleRequest(): Promise<Response> {
// Start background task
const backgroundWork = this.processAsyncTask();
// Register with execution context
this.ctx.waitUntil(backgroundWork);
// Return response immediately
return new Response('Request processed');
}
private async processAsyncTask(): Promise<void> {
// Background processing continues after response
await this.updateCache();
await this.sendNotifications();
}
}

Usage in Tasks and Observers

Tasks and observers use execution context for coordination:

class DataProcessor extends Task<Env> {
async run(): Promise<void> {
// Process primary data
const result = await this.processData();
// Continue background cleanup
this.ctx.waitUntil(this.cleanupTempFiles());
return result;
}
}

Stub<T>

The Stub<T> utility type transforms a class interface into a remote procedure call interface, converting all methods to return Promises.

type Stub<T> = {
[P in keyof T as T[P] extends (...args: any[]) => any ? P : never]:
T[P] extends (...args: infer A) => infer R
? (...args: A) => Promise<Awaited<R>>
: never;
};

Type transformation:

  • Preserves method names and parameter types
  • Wraps return types in Promise<Awaited<R>>
  • Filters to include only methods (not properties)

Actor Communication

Stub<T> enables type-safe remote actor calls:

class UserManager extends Actor {
async createUser(name: string, email: string): Promise<User> {
return { id: generateId(), name, email };
}
async getUser(id: string): Promise<User | null> {
return this.database.users.find(id);
}
}
// Stub type automatically inferred
const userStub: Stub<UserManager> = userManager.get('user-123');
// Type-safe remote calls
const user = await userStub.createUser('Alice', 'alice@example.com');
const existing = await userStub.getUser('user-456');

Type Safety Benefits

The stub system prevents common RPC errors:

class Calculator extends Actor {
add(a: number, b: number): number {
return a + b;
}
async divide(a: number, b: number): Promise<number> {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
}
const calc: Stub<Calculator> = calculator.get('calc-1');
// Synchronous method becomes async
const sum = await calc.add(5, 3); // Promise<number>
// Already async method stays async
const quotient = await calc.divide(10, 2); // Promise<number>
// TypeScript prevents incorrect usage
// calc.add(5, 3).then(result => ...); // Error: missing await

Framework Integration

Service Base Class

All services automatically receive execution context:

abstract class Service<Env> {
ctx: ExecutionContext; // Provided by runtime
env: Env; // Environment bindings
constructor(ctx: ExecutionContext, env: Env) {
this.ctx = ctx;
this.env = env;
}
}

Actor Communication

Actors use stubs for type-safe remote calls:

class OrderService extends Service<Env> {
async processOrder(order: Order): Promise<void> {
// Get typed actor stub
const userActor = this.env.USER_MANAGER.get(order.userId);
// Type-safe method calls
const user = await userActor.getUser(order.userId);
await userActor.updateLastOrder(order.id);
// Background processing
this.ctx.waitUntil(this.sendConfirmationEmail(user));
}
}

Best Practices

Execution Context Usage

Do:

  • Use waitUntil for cleanup operations
  • Register long-running background tasks
  • Keep response times fast by deferring non-critical work

Don’t:

  • Use for critical response data
  • Assume background tasks always complete
  • Block response on waitUntil promises

Stub Type Patterns

Do:

  • Leverage TypeScript inference for stub types
  • Use descriptive actor IDs for debugging
  • Handle Promise rejections from remote calls

Don’t:

  • Bypass the stub system for direct actor calls
  • Assume synchronous semantics with remote actors
  • Ignore network failure scenarios

raindrop.manifest

Shared types are used implicitly in resource definitions:

resource "actor" "user_manager" {
class_name = "UserManager"
# Stub<UserManager> automatically available in bindings
}
resource "service" "order_api" {
class_name = "OrderService"
# ExecutionContext provided automatically
}

The framework automatically provides execution context and creates stub types based on your resource definitions, enabling type-safe communication across your distributed system.