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 inferredconst userStub: Stub<UserManager> = userManager.get('user-123');
// Type-safe remote callsconst 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 asyncconst sum = await calc.add(5, 3); // Promise<number>
// Already async method stays asyncconst 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.