@sygnl/supabase-cf
v1.0.3
Published
Lightweight Supabase REST API client for Cloudflare Workers
Maintainers
Readme
@sygnl/supabase-cf
Lightweight Supabase REST API client for Cloudflare Workers. Zero dependencies, fully typed, optimized for edge runtime.
Features
- Zero Dependencies - Uses native
fetch()only - Cloudflare Workers First - Optimized for edge runtime
- Fully Typed - Complete TypeScript support
- Lightweight - < 5KB gzipped
- Simple API - Chainable, intuitive methods
- Fail-Safe - Graceful error handling
Installation
npm install @sygnl/supabase-cfQuick Start
import { SupabaseClient } from '@sygnl/supabase-cf';
// Initialize client
const supabase = new SupabaseClient({
url: env.SUPABASE_URL, // https://xxx.supabase.co
serviceKey: env.SUPABASE_SERVICE_KEY
});
// Insert data
await supabase.from('events').insert({
event_id: 'evt_123',
event_type: 'PURCHASE',
user_id: 'user_456'
});
// Query data
const { data, error } = await supabase
.from('events')
.select('*')
.eq('user_id', 'user_456')
.limit(10)
.execute();Environment Variables
Set these in your Cloudflare Worker:
# Supabase project URL (plain text Variable, not Secret)
SUPABASE_URL=https://your-project.supabase.co
# Supabase service role key (encrypted Secret)
SUPABASE_SERVICE_KEY=eyJhbG...Important: Set SUPABASE_URL as a plain text Variable, not an encrypted Secret. URLs don't need encryption.
API Reference
Client Configuration
const supabase = new SupabaseClient({
url: string, // Required: Supabase project URL
serviceKey: string, // Required: Service role key
timeout?: number, // Optional: Request timeout (default: 10000ms)
retries?: number, // Optional: Retry attempts (default: 0)
headers?: Record<string, string> // Optional: Custom headers
});Insert
// Single row
await supabase.from('events').insert({
event_id: 'evt_123',
event_type: 'PURCHASE'
});
// Multiple rows
await supabase.from('events').insert([
{ event_id: 'evt_123', event_type: 'PURCHASE' },
{ event_id: 'evt_124', event_type: 'PAGE_VIEW' }
]);Query
// Select all columns
const { data } = await supabase
.from('events')
.select()
.execute();
// Select specific columns
const { data } = await supabase
.from('events')
.select('event_id, event_type, created_at')
.execute();
// Filter
const { data } = await supabase
.from('events')
.select()
.eq('event_type', 'PURCHASE')
.gte('created_at', '2025-01-01')
.execute();
// Order and limit
const { data } = await supabase
.from('events')
.select()
.order('created_at', 'desc')
.limit(100)
.execute();
// Get single row
const { data, error } = await supabase
.from('events')
.select()
.eq('event_id', 'evt_123')
.executeSingle();Upsert
// Upsert with conflict resolution
await supabase.from('events').upsert({
event_id: 'evt_123',
event_type: 'PURCHASE',
updated_at: new Date()
}, {
onConflict: 'event_id'
});
// Batch upsert
await supabase.from('events').upsert([
{ event_id: 'evt_123', event_type: 'PURCHASE' },
{ event_id: 'evt_124', event_type: 'PAGE_VIEW' }
], {
onConflict: 'event_id'
});Filter Operators
.eq('column', value) // equals
.neq('column', value) // not equals
.gt('column', value) // greater than
.gte('column', value) // greater than or equal
.lt('column', value) // less than
.lte('column', value) // less than or equal
.filter('column', 'like', '%pattern%') // pattern matching
.filter('column', 'in', [1, 2, 3]) // in arrayTypeScript Support
// Define your table types
interface Event {
event_id: string;
event_type: string;
user_id: string;
created_at: string;
}
// Type-safe queries
const supabase = new SupabaseClient({ ... });
const { data } = await supabase
.from<Event>('events')
.select()
.eq('user_id', 'user_456')
.execute();
// data is typed as Event[]Error Handling
import { SupabaseHTTPError, SupabaseTimeoutError } from '@sygnl/supabase-cf';
const { data, error } = await supabase
.from('events')
.select()
.execute();
if (error) {
if (error instanceof SupabaseHTTPError) {
console.error(`HTTP ${error.status}:`, error.body);
} else if (error instanceof SupabaseTimeoutError) {
console.error('Request timed out');
} else {
console.error('Unknown error:', error);
}
}Examples
Basic Worker Example
See examples/worker-example.ts for a complete Cloudflare Worker implementation.
import { SupabaseClient } from '@sygnl/supabase-cf';
interface Env {
SUPABASE_URL: string;
SUPABASE_SERVICE_KEY: string;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const supabase = new SupabaseClient({
url: env.SUPABASE_URL,
serviceKey: env.SUPABASE_SERVICE_KEY
});
// Insert event asynchronously
ctx.waitUntil(
supabase.from('events').insert({
event_id: crypto.randomUUID(),
event_type: 'PAGE_VIEW',
timestamp: Date.now()
})
);
return new Response('OK');
}
};Interactive HTML Test Interface
See examples/index.html for a complete test interface with Insert/Read operations.
Setup for HTML example:
- Add assets binding to
wrangler.toml:
[assets]
directory = "./public"
binding = "ASSETS"- Serve the HTML:
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
// Serve static HTML for non-API routes
if (!url.pathname.startsWith('/api/')) {
return env.ASSETS.fetch(request);
}
// Handle API routes...
}
};- Important - Deployment vs Local Development:
- Production: Deploy to Cloudflare Workers - works out of the box
- Local Dev: Requires tunneling (e.g., Cloudflare Tunnel, ngrok) or proxy configuration
- Proxy Setup: Configure
proxyintsconfig.jsonor usewrangler dev --remotefor testing
Why? The HTML makes requests to /api/* endpoints. Locally, these requests need proper routing which requires either:
- Deploying to Cloudflare Workers (recommended for testing)
- Using
wrangler dev --remote(runs on Cloudflare's edge) - Setting up a local proxy/tunnel
Performance Tips
Batch Operations
// ❌ Slow: Multiple requests
for (const event of events) {
await supabase.from('events').insert(event);
}
// ✅ Fast: Single batched request
await supabase.from('events').insert(events);Use waitUntil for Non-Blocking Writes
// Don't block the response
ctx.waitUntil(
supabase.from('events').insert(data)
);
return new Response('OK'); // Returns immediatelyComparison with Official SDK
| Feature | @sygnl/supabase-cf | @supabase/supabase-js | |---------|-------------------|----------------------| | Size | < 5KB | ~50KB | | Dependencies | 0 | 10+ | | CF Workers | ✅ Native | ⚠️ Requires polyfills | | TypeScript | ✅ Built-in | ✅ Built-in | | Query Builder | ✅ | ✅ | | Realtime | ❌ | ✅ | | Auth | ❌ | ✅ | | Storage | ❌ | ✅ |
Use @sygnl/supabase-cf when:
- Running on Cloudflare Workers
- Only need database operations
- Want minimal bundle size
- Need zero dependencies
Use @supabase/supabase-js when:
- Need Realtime subscriptions
- Need Auth features
- Need Storage features
- Not size-constrained
Testing
The package includes comprehensive tests covering real-world scenarios:
npm test # Run tests once
npm run test:watch # Run tests in watch modeTest coverage includes:
- ✅ Configuration validation (service key format, length, JWT structure)
- ✅ Insert operations (single, batch, error handling)
- ✅ Select/Query operations (filters, ordering, limits)
- ✅ Error scenarios (401, 403, 404, 409 with helpful messages)
- ✅ Edge cases discovered during development
Tests run automatically on every build to ensure reliability.
License
Apache-2.0
Contributing
Contributions welcome! This package is part of the Sygnl SDK
