Skip to content

Commit 199fbe1

Browse files
authored
fix: improve local environment detection using SUPABASE_URL instead of API keys (#580)
# Fix Local Environment Detection and Optimize Step Output Storage This PR fixes the local environment detection in pgflow by using `SUPABASE_URL` instead of API keys, which resolves compatibility issues with recent Supabase CLI versions. The previous detection method relied on matching specific API key patterns, but this became unreliable as Supabase CLI evolved. Key changes: - Changed local environment detection to check for `SUPABASE_URL: 'http://kong:8000'` instead of matching API keys - Updated all tests to use the new URL-based detection method - Renamed release notes from 0.13.0 to 0.13.1 since 0.13.0 was yanked due to the detection issue - Added step output storage optimization that makes Map chains run 2x faster by storing outputs in step_states.output instead of aggregating them on-demand This release (0.13.1) includes both the fix for local environment detection and the performance improvements for Map chains that were originally intended for 0.13.0.
1 parent 039f189 commit 199fbe1

File tree

11 files changed

+129
-213
lines changed

11 files changed

+129
-213
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@pgflow/edge-worker': patch
3+
'@pgflow/core': patch
4+
---
5+
6+
Note: Version 0.13.0 was yanked due to broken local environment detection. This release (0.13.1) includes both the fix and the features from 0.13.0.
7+
8+
- Fix local environment detection to use SUPABASE_URL instead of API keys
9+
- Add step output storage optimization for 2x faster Map chains (outputs now stored in step_states.output instead of aggregated on-demand)
Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,19 @@
1-
// Old HS256 JWT keys (Supabase CLI v1)
2-
export const KNOWN_LOCAL_ANON_KEY =
3-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0';
4-
5-
export const KNOWN_LOCAL_SERVICE_ROLE_KEY =
6-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU';
7-
8-
// New opaque keys (Supabase CLI v2+)
9-
// Source: https://github.com/supabase/cli/blob/develop/pkg/config/apikeys.go
10-
export const KNOWN_LOCAL_PUBLISHABLE_KEY =
11-
'sb_publishable_ACJWlzQHlZjBrEguHvfOxg_3BJgxAaH';
12-
13-
export const KNOWN_LOCAL_SECRET_KEY = 'sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz';
1+
/**
2+
* Local Supabase CLI sets SUPABASE_URL to this Docker-internal API gateway.
3+
*/
4+
export const LOCAL_SUPABASE_HOST = 'kong:8000';
145

156
/**
16-
* Checks if the provided environment indicates local Supabase.
17-
* Use when you have access to an env record (e.g., from PlatformAdapter).
18-
* Supports both old HS256 JWT keys and new opaque keys from Supabase CLI v2+.
7+
* Checks if the provided environment indicates local Supabase development.
8+
* Uses SUPABASE_URL to detect local environment (more reliable than key matching).
199
*/
2010
export function isLocalSupabaseEnv(env: Record<string, string | undefined>): boolean {
21-
const anonKey = env['SUPABASE_ANON_KEY'];
22-
const serviceRoleKey = env['SUPABASE_SERVICE_ROLE_KEY'];
23-
24-
const isLocalAnonKey =
25-
anonKey === KNOWN_LOCAL_ANON_KEY || anonKey === KNOWN_LOCAL_PUBLISHABLE_KEY;
26-
const isLocalServiceRoleKey =
27-
serviceRoleKey === KNOWN_LOCAL_SERVICE_ROLE_KEY ||
28-
serviceRoleKey === KNOWN_LOCAL_SECRET_KEY;
11+
const supabaseUrl = env['SUPABASE_URL'];
12+
if (!supabaseUrl) return false;
2913

30-
return isLocalAnonKey || isLocalServiceRoleKey;
14+
try {
15+
return new URL(supabaseUrl).host === LOCAL_SUPABASE_HOST;
16+
} catch {
17+
return false;
18+
}
3119
}

pkgs/edge-worker/supabase/functions/auth_test/index.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,21 @@ import { EdgeWorker } from '@pgflow/edge-worker';
22
import { configurePlatform } from '@pgflow/edge-worker/testing';
33
import { sql } from '../utils.ts';
44

5-
// Production-like keys (NOT the known local demo keys)
6-
// These must match the values used in tests/e2e/authorization.test.ts
7-
export const PRODUCTION_ANON_KEY = 'test-production-anon-key-abc123';
5+
// Production-like service role key for auth validation (must match test's Bearer token)
86
export const PRODUCTION_SERVICE_ROLE_KEY = 'test-production-service-role-key-xyz789';
97

108
// Docker-internal URL for Supabase transaction pooler
119
const DOCKER_POOLER_URL = 'postgresql://postgres.pooler-dev:postgres@pooler:6543/postgres';
1210

1311
// Override environment BEFORE EdgeWorker.start() to enable auth validation
1412
// This simulates production mode where auth is NOT bypassed
15-
// Note: We must include EDGE_WORKER_DB_URL because with production keys,
13+
// Note: We must include EDGE_WORKER_DB_URL because with production SUPABASE_URL,
1614
// isLocalSupabaseEnv() returns false and the docker pooler fallback won't trigger
1715
configurePlatform({
1816
getEnv: () => ({
1917
...Deno.env.toObject(),
20-
SUPABASE_ANON_KEY: PRODUCTION_ANON_KEY,
21-
SUPABASE_SERVICE_ROLE_KEY: PRODUCTION_SERVICE_ROLE_KEY,
18+
SUPABASE_URL: 'https://test-project.supabase.co', // Production URL
19+
SUPABASE_SERVICE_ROLE_KEY: PRODUCTION_SERVICE_ROLE_KEY, // For auth validation
2220
EDGE_WORKER_DB_URL: DOCKER_POOLER_URL,
2321
}),
2422
});

pkgs/edge-worker/tests/unit/platform/SupabasePlatformAdapter.test.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -214,16 +214,14 @@ Deno.test({
214214
// ============================================================
215215

216216
Deno.test({
217-
name: 'isLocalEnvironment returns true for local Supabase keys',
217+
name: 'isLocalEnvironment returns true for local Supabase URL (kong)',
218218
sanitizeResources: false,
219219
fn: () => {
220-
const KNOWN_LOCAL_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0';
221-
222220
const deps = createMockDeps({
223221
getEnv: () => ({
224-
SUPABASE_URL: 'http://test.supabase.co',
225-
SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY,
226-
SUPABASE_SERVICE_ROLE_KEY: 'test-service-key',
222+
SUPABASE_URL: 'http://kong:8000', // Local dev URL
223+
SUPABASE_ANON_KEY: 'any-anon-key',
224+
SUPABASE_SERVICE_ROLE_KEY: 'any-service-key',
227225
SB_EXECUTION_ID: 'test-exec-id',
228226
EDGE_WORKER_DB_URL: 'postgresql://postgres:postgres@localhost:54322/postgres',
229227
}),
@@ -236,14 +234,14 @@ Deno.test({
236234
});
237235

238236
Deno.test({
239-
name: 'isLocalEnvironment returns false for production keys',
237+
name: 'isLocalEnvironment returns false for production URL',
240238
sanitizeResources: false,
241239
fn: () => {
242240
const deps = createMockDeps({
243241
getEnv: () => ({
244-
SUPABASE_URL: 'http://test.supabase.co',
245-
SUPABASE_ANON_KEY: 'production-anon-key',
246-
SUPABASE_SERVICE_ROLE_KEY: 'production-service-key',
242+
SUPABASE_URL: 'https://abc123.supabase.co', // Production URL
243+
SUPABASE_ANON_KEY: 'any-anon-key',
244+
SUPABASE_SERVICE_ROLE_KEY: 'any-service-key',
247245
SB_EXECUTION_ID: 'test-exec-id',
248246
EDGE_WORKER_DB_URL: 'postgresql://postgres:postgres@localhost:54322/postgres',
249247
}),

pkgs/edge-worker/tests/unit/platform/connectionPriority.test.ts

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
import { assertEquals, assertThrows } from '@std/assert';
2-
import {
3-
KNOWN_LOCAL_ANON_KEY,
4-
KNOWN_LOCAL_SERVICE_ROLE_KEY,
5-
} from '../../../src/shared/localDetection.ts';
62
import {
73
resolveConnectionString,
84
assertConnectionAvailable,
@@ -11,31 +7,28 @@ import {
117
} from '../../../src/platform/resolveConnection.ts';
128
import postgres from 'postgres';
139

10+
// Local URL for test envs (triggers isLocalSupabaseEnv = true)
11+
const LOCAL_SUPABASE_URL = 'http://kong:8000';
12+
1413
// ============================================================
1514
// Local environment tests
1615
// ============================================================
1716

1817
Deno.test('connection priority - local env uses Docker pooler URL by default', () => {
19-
const env = { SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY };
20-
const result = resolveConnectionString(env);
21-
assertEquals(result, DOCKER_TRANSACTION_POOLER_URL);
22-
});
23-
24-
Deno.test('connection priority - local env with service role key uses Docker pooler URL', () => {
25-
const env = { SUPABASE_SERVICE_ROLE_KEY: KNOWN_LOCAL_SERVICE_ROLE_KEY };
18+
const env = { SUPABASE_URL: LOCAL_SUPABASE_URL };
2619
const result = resolveConnectionString(env);
2720
assertEquals(result, DOCKER_TRANSACTION_POOLER_URL);
2821
});
2922

3023
Deno.test('connection priority - local env respects config.connectionString override', () => {
31-
const env = { SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY };
24+
const env = { SUPABASE_URL: LOCAL_SUPABASE_URL };
3225
const options = { connectionString: 'postgresql://custom:5432/db' };
3326
const result = resolveConnectionString(env, options);
3427
assertEquals(result, 'postgresql://custom:5432/db');
3528
});
3629

3730
Deno.test('connection priority - local env respects config.sql override', () => {
38-
const env = { SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY };
31+
const env = { SUPABASE_URL: LOCAL_SUPABASE_URL };
3932
const options = { hasSql: true };
4033
const result = resolveConnectionString(env, options);
4134
// When sql is provided, we don't use the local pooler URL
@@ -45,7 +38,7 @@ Deno.test('connection priority - local env respects config.sql override', () =>
4538

4639
Deno.test('connection priority - local env with EDGE_WORKER_DB_URL uses it instead of docker pooler', () => {
4740
const env = {
48-
SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY,
41+
SUPABASE_URL: LOCAL_SUPABASE_URL,
4942
EDGE_WORKER_DB_URL: 'postgresql://custom-local:5432/db',
5043
};
5144
const result = resolveConnectionString(env);
@@ -58,7 +51,7 @@ Deno.test('connection priority - local env with EDGE_WORKER_DB_URL uses it inste
5851

5952
Deno.test('connection priority - production uses EDGE_WORKER_DB_URL', () => {
6053
const env = {
61-
SUPABASE_ANON_KEY: 'prod-anon-key',
54+
SUPABASE_URL: 'https://abc123.supabase.co',
6255
EDGE_WORKER_DB_URL: 'postgresql://prod:5432/db',
6356
};
6457
const result = resolveConnectionString(env);
@@ -67,7 +60,7 @@ Deno.test('connection priority - production uses EDGE_WORKER_DB_URL', () => {
6760

6861
Deno.test('connection priority - production config.connectionString overrides env var', () => {
6962
const env = {
70-
SUPABASE_ANON_KEY: 'prod-anon-key',
63+
SUPABASE_URL: 'https://abc123.supabase.co',
7164
EDGE_WORKER_DB_URL: 'postgresql://prod:5432/db',
7265
};
7366
const options = { connectionString: 'postgresql://override:5432/db' };
@@ -76,7 +69,7 @@ Deno.test('connection priority - production config.connectionString overrides en
7669
});
7770

7871
Deno.test('connection priority - production returns undefined when nothing configured', () => {
79-
const env = { SUPABASE_ANON_KEY: 'prod-anon-key' };
72+
const env = { SUPABASE_URL: 'https://abc123.supabase.co' };
8073
const result = resolveConnectionString(env);
8174
assertEquals(result, undefined);
8275
});
@@ -86,7 +79,7 @@ Deno.test('connection priority - production returns undefined when nothing confi
8679
// ============================================================
8780

8881
Deno.test('connection validation - throws when no connection available on production', () => {
89-
const env = { SUPABASE_ANON_KEY: 'prod-anon-key' };
82+
const env = { SUPABASE_URL: 'https://abc123.supabase.co' };
9083
const connectionString = resolveConnectionString(env);
9184

9285
assertThrows(
@@ -108,7 +101,7 @@ Deno.test('connection validation - does not throw when sql is provided', () => {
108101
});
109102

110103
Deno.test('connection validation - error message lists all options', () => {
111-
const env = { SUPABASE_ANON_KEY: 'prod-anon-key' };
104+
const env = { SUPABASE_URL: 'https://abc123.supabase.co' };
112105
const connectionString = resolveConnectionString(env);
113106

114107
try {
@@ -128,7 +121,7 @@ Deno.test('connection validation - error message lists all options', () => {
128121
Deno.test('connection priority - preview branch fallback pattern works', () => {
129122
// Simulates: connectionString: Deno.env.get('EDGE_WORKER_DB_URL') || Deno.env.get('SUPABASE_DB_URL')
130123
const env = {
131-
SUPABASE_ANON_KEY: 'prod-anon-key',
124+
SUPABASE_URL: 'https://abc123.supabase.co',
132125
// EDGE_WORKER_DB_URL not set (preview branch)
133126
};
134127

@@ -146,7 +139,7 @@ Deno.test('connection priority - preview branch fallback pattern works', () => {
146139

147140
Deno.test('resolveSqlConnection - priority 1: returns provided sql directly', () => {
148141
const mockSql = postgres('postgresql://mock:5432/db');
149-
const env = { SUPABASE_ANON_KEY: 'prod-anon-key' };
142+
const env = { SUPABASE_URL: 'https://abc123.supabase.co' };
150143

151144
const result = resolveSqlConnection(env, { sql: mockSql });
152145

@@ -155,7 +148,7 @@ Deno.test('resolveSqlConnection - priority 1: returns provided sql directly', ()
155148
});
156149

157150
Deno.test('resolveSqlConnection - priority 2: creates sql from connectionString', () => {
158-
const env = { SUPABASE_ANON_KEY: 'prod-anon-key' };
151+
const env = { SUPABASE_URL: 'https://abc123.supabase.co' };
159152
const options = { connectionString: 'postgresql://custom:5432/db' };
160153

161154
const result = resolveSqlConnection(env, options);
@@ -166,7 +159,7 @@ Deno.test('resolveSqlConnection - priority 2: creates sql from connectionString'
166159

167160
Deno.test('resolveSqlConnection - priority 3: creates sql from EDGE_WORKER_DB_URL', () => {
168161
const env = {
169-
SUPABASE_ANON_KEY: 'prod-anon-key',
162+
SUPABASE_URL: 'https://abc123.supabase.co',
170163
EDGE_WORKER_DB_URL: 'postgresql://env-var:5432/db',
171164
};
172165

@@ -177,7 +170,7 @@ Deno.test('resolveSqlConnection - priority 3: creates sql from EDGE_WORKER_DB_UR
177170
});
178171

179172
Deno.test('resolveSqlConnection - priority 4: creates sql from Docker URL in local env', () => {
180-
const env = { SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY };
173+
const env = { SUPABASE_URL: LOCAL_SUPABASE_URL };
181174

182175
const result = resolveSqlConnection(env);
183176

@@ -186,7 +179,7 @@ Deno.test('resolveSqlConnection - priority 4: creates sql from Docker URL in loc
186179
});
187180

188181
Deno.test('resolveSqlConnection - throws when nothing configured in production', () => {
189-
const env = { SUPABASE_ANON_KEY: 'prod-anon-key' };
182+
const env = { SUPABASE_URL: 'https://abc123.supabase.co' };
190183

191184
assertThrows(
192185
() => resolveSqlConnection(env),
@@ -197,7 +190,7 @@ Deno.test('resolveSqlConnection - throws when nothing configured in production',
197190

198191
Deno.test('resolveSqlConnection - connectionString takes priority over EDGE_WORKER_DB_URL', () => {
199192
const env = {
200-
SUPABASE_ANON_KEY: 'prod-anon-key',
193+
SUPABASE_URL: 'https://abc123.supabase.co',
201194
EDGE_WORKER_DB_URL: 'postgresql://env-var:5432/db',
202195
};
203196
const options = { connectionString: 'postgresql://explicit:5432/db' };
@@ -212,7 +205,7 @@ Deno.test('resolveSqlConnection - connectionString takes priority over EDGE_WORK
212205
Deno.test('resolveSqlConnection - sql takes priority over everything', () => {
213206
const mockSql = postgres('postgresql://mock:5432/db');
214207
const env = {
215-
SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY,
208+
SUPABASE_URL: LOCAL_SUPABASE_URL,
216209
EDGE_WORKER_DB_URL: 'postgresql://env-var:5432/db',
217210
};
218211
const options = {

0 commit comments

Comments
 (0)