Context Object
The context object is pgflow’s mechanism for providing handlers with access to platform resources like database connections, environment variables, and service clients. Instead of each handler creating its own connections, pgflow manages these resources centrally and injects them through a standardized context parameter.
Why Context Exists
Section titled “Why Context Exists”Before context, handlers faced several challenges:
- Resource Management - Each handler needed to create its own database connections and clients
- Configuration Access - No standardized way to access environment variables
- Boilerplate Code - Repeated connection setup in every handler
- Resource Leaks - Risk of not properly closing connections
- Testing Complexity - Difficult to mock resources for testing
The context object solves these problems by providing pre-configured, managed resources that handlers can use immediately.
Core Principles
Section titled “Core Principles”Context follows pgflow’s design philosophy:
- Zero Configuration - Resources are ready to use without setup
- Type Safety - Full TypeScript support with proper inference
- Pre-configured Resources - Depending on platform (Supabase only for now), established connections are provided
- Platform Agnostic - Context interface can adapt to different platforms
- Backward Compatible - Existing single-parameter handlers continue to work
How Context Works
Section titled “How Context Works”When pgflow executes a handler, it passes two parameters: the input object and the context object.
Before: Global Resources
Section titled “Before: Global Resources”import { Flow } from '@pgflow/dsl/supabase';import { sql } from '../db.js';import { supabase } from '../supabase-client.js';
const ProcessUserFlow = new Flow<{ userId: string }>({ slug: 'process_user'}) .step({ slug: 'validate' }, async (input) => { const [user] = await sql`SELECT * FROM users WHERE id = ${input.userId}`; const apiKey = Deno.env.get('EXTERNAL_API_KEY'); const { data } = await supabase.auth.getUser();
return { user }; });import { sql } from '../db.js';import { supabase } from '../supabase-client.js';
async function processOrder(input: { orderId: string }) { const [order] = await sql`SELECT * FROM orders WHERE id = ${input.orderId}`; const apiKey = Deno.env.get('EXTERNAL_API_KEY');
await sendNotification(order, apiKey); return { processed: true };}After: Using Context
Section titled “After: Using Context”import { Flow } from '@pgflow/dsl/supabase';
const ProcessUserFlow = new Flow<{ userId: string }>({ slug: 'process_user'}) .step({ slug: 'validate' }, async (input, ctx) => { const [user] = await ctx.sql`SELECT * FROM users WHERE id = ${input.userId}`; const apiKey = ctx.env.EXTERNAL_API_KEY; const { data } = await ctx.supabase.auth.getUser();
return { user }; });async function processOrder(input: { orderId: string }, ctx) { const [order] = await ctx.sql`SELECT * FROM orders WHERE id = ${input.orderId}`; const apiKey = ctx.env.EXTERNAL_API_KEY; const { data } = await ctx.supabase.auth.getUser();
await sendNotification(order, apiKey); return { processed: true };}Available Resources
Section titled “Available Resources”The context object provides access to different resources depending on your platform:
Core Resources (always available):
env- Environment variablesshutdownSignal- Worker shutdown signal for graceful cleanuprawMessage- Original pgmq message with metadatastepTask- Flow task details includingrun_id,step_slug,flow_slug,task_index, and typedinput(flow worker mode only)workerConfig- Resolved worker configuration
Supabase Platform:
sql- PostgreSQL client (postgres.js)supabase- Supabase client with service role
See the Context API Reference for complete documentation of each property.
Using Destructuring for Cleaner Code
Section titled “Using Destructuring for Cleaner Code”While examples in this documentation use ctx. for clarity, destructuring often makes your code more readable:
// Destructure only what you need.step({ slug: 'sync_data' }, async (input, { sql, supabase, stepTask }) => { console.log(`Syncing for run ${stepTask.run_id}`);
const data = await sql`SELECT * FROM pending_sync WHERE user_id = ${input.userId}`;
const synced = await supabase .from('synced_data') .upsert(data) .select();
return { syncedCount: synced.data.length };})// Destructure only what you needasync function processOrder(input, { sql, env, rawMessage }) { console.log(`Processing order ${rawMessage.msg_id}`);
const [order] = await sql` SELECT * FROM orders WHERE id = ${input.orderId} `;
await sendNotification(order, env.NOTIFICATION_API_KEY);}Summary
Section titled “Summary”The context object streamlines handler development by:
- Providing ready-to-use platform resources
- Eliminating connection boilerplate
- Centralizing resource access depending on platform
- Maintaining type safety throughout
- Supporting gradual migration of existing code
Context embodies pgflow’s philosophy of being robust yet simple - handlers get powerful capabilities without complexity.