Skip to content

Commit 6348583

Browse files
committed
refactor: consolidate flow registration in index.ts and remove flows.ts (#437)
# Update Flow Registration to Use Single File This PR simplifies the pgflow architecture by consolidating flow registration into a single file. Instead of using a separate `flows.ts` file, flows are now registered directly in the `index.ts` file of the pgflow edge function. Key changes: - Removed `flows.ts` and moved flow registration directly into `index.ts` - Updated error messages and documentation to reference `index.ts` instead of `flows.ts` - Added a new documentation page explaining the auto-compilation architecture - Updated the getting started guide to reflect the new registration pattern - Modified tests to match the new file structure This change makes flow registration more straightforward for users and reduces the number of files they need to manage. The documentation now clearly explains how to register flows and how the auto-compilation process works.
1 parent a9850f4 commit 6348583

File tree

21 files changed

+366
-127
lines changed

21 files changed

+366
-127
lines changed

pkgs/cli/__tests__/commands/compile/index.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ describe('fetchFlowSQL', () => {
7272
status: 404,
7373
json: async () => ({
7474
error: 'Flow Not Found',
75-
message: "Flow 'unknown_flow' not found. Did you add it to flows.ts?",
75+
message: "Flow 'unknown_flow' not found. Did you add it to supabase/functions/pgflow/index.ts?",
7676
}),
7777
};
7878

@@ -83,7 +83,7 @@ describe('fetchFlowSQL', () => {
8383
).rejects.toThrow("Flow 'unknown_flow' not found");
8484
await expect(
8585
fetchFlowSQL('unknown_flow', 'http://127.0.0.1:50621/functions/v1/pgflow', 'test-publishable-key')
86-
).rejects.toThrow('Add your flow to supabase/functions/pgflow/flows.ts');
86+
).rejects.toThrow('Add your flow to supabase/functions/pgflow/index.ts');
8787
});
8888

8989
it('should handle ECONNREFUSED with startup instructions', async () => {
@@ -167,7 +167,7 @@ describe('fetchFlowSQL', () => {
167167
).rejects.toThrow("Flow 'unknown_flow' not found");
168168
await expect(
169169
fetchFlowSQL('unknown_flow', 'http://127.0.0.1:50621/functions/v1/pgflow', 'test-publishable-key')
170-
).rejects.toThrow('Did you add it to flows.ts');
170+
).rejects.toThrow('Did you add it to supabase/functions/pgflow/index.ts');
171171
});
172172

173173
it('should construct correct URL with flow slug', async () => {

pkgs/cli/__tests__/commands/install/create-edge-function.test.ts

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('createEdgeFunction', () => {
2222
fs.rmSync(tempDir, { recursive: true, force: true });
2323
});
2424

25-
it('should create all three files when none exist', async () => {
25+
it('should create both files when none exist', async () => {
2626
const result = await createEdgeFunction({
2727
supabasePath,
2828
autoConfirm: true,
@@ -36,23 +36,16 @@ describe('createEdgeFunction', () => {
3636

3737
// Verify all files exist
3838
const indexPath = path.join(pgflowFunctionDir, 'index.ts');
39-
const flowsPath = path.join(pgflowFunctionDir, 'flows.ts');
4039
const denoJsonPath = path.join(pgflowFunctionDir, 'deno.json');
4140

4241
expect(fs.existsSync(indexPath)).toBe(true);
43-
expect(fs.existsSync(flowsPath)).toBe(true);
4442
expect(fs.existsSync(denoJsonPath)).toBe(true);
4543

46-
// Verify index.ts content
44+
// Verify index.ts content (inline flow registration, no flows.ts)
4745
const indexContent = fs.readFileSync(indexPath, 'utf8');
4846
expect(indexContent).toContain("import { ControlPlane } from '@pgflow/edge-worker'");
49-
expect(indexContent).toContain("import { flows } from './flows.ts'");
50-
expect(indexContent).toContain('ControlPlane.serve(flows)');
51-
52-
// Verify flows.ts content
53-
const flowsContent = fs.readFileSync(flowsPath, 'utf8');
54-
expect(flowsContent).toContain('export const flows = [');
55-
expect(flowsContent).toContain('// Import your flows here');
47+
expect(indexContent).toContain('ControlPlane.serve([');
48+
expect(indexContent).toContain('// Import your flows here');
5649

5750
// Verify deno.json content
5851
const denoJsonContent = fs.readFileSync(denoJsonPath, 'utf8');
@@ -66,11 +59,9 @@ describe('createEdgeFunction', () => {
6659
fs.mkdirSync(pgflowFunctionDir, { recursive: true });
6760

6861
const indexPath = path.join(pgflowFunctionDir, 'index.ts');
69-
const flowsPath = path.join(pgflowFunctionDir, 'flows.ts');
7062
const denoJsonPath = path.join(pgflowFunctionDir, 'deno.json');
7163

7264
fs.writeFileSync(indexPath, '// existing content');
73-
fs.writeFileSync(flowsPath, '// existing content');
7465
fs.writeFileSync(denoJsonPath, '// existing content');
7566

7667
const result = await createEdgeFunction({
@@ -83,7 +74,6 @@ describe('createEdgeFunction', () => {
8374

8475
// Verify files still exist with original content
8576
expect(fs.readFileSync(indexPath, 'utf8')).toBe('// existing content');
86-
expect(fs.readFileSync(flowsPath, 'utf8')).toBe('// existing content');
8777
expect(fs.readFileSync(denoJsonPath, 'utf8')).toBe('// existing content');
8878
});
8979

@@ -92,7 +82,6 @@ describe('createEdgeFunction', () => {
9282
fs.mkdirSync(pgflowFunctionDir, { recursive: true });
9383

9484
const indexPath = path.join(pgflowFunctionDir, 'index.ts');
95-
const flowsPath = path.join(pgflowFunctionDir, 'flows.ts');
9685
const denoJsonPath = path.join(pgflowFunctionDir, 'deno.json');
9786

9887
// Only create index.ts
@@ -103,19 +92,15 @@ describe('createEdgeFunction', () => {
10392
autoConfirm: true,
10493
});
10594

106-
// Should return true because some files were created
95+
// Should return true because deno.json was created
10796
expect(result).toBe(true);
10897

10998
// Verify index.ts was not modified
11099
expect(fs.readFileSync(indexPath, 'utf8')).toBe('// existing content');
111100

112-
// Verify flows.ts and deno.json were created
113-
expect(fs.existsSync(flowsPath)).toBe(true);
101+
// Verify deno.json was created
114102
expect(fs.existsSync(denoJsonPath)).toBe(true);
115103

116-
const flowsContent = fs.readFileSync(flowsPath, 'utf8');
117-
expect(flowsContent).toContain('export const flows = [');
118-
119104
const denoJsonContent = fs.readFileSync(denoJsonPath, 'utf8');
120105
expect(denoJsonContent).toContain('"imports"');
121106
});
@@ -138,7 +123,6 @@ describe('createEdgeFunction', () => {
138123

139124
// Verify files exist
140125
expect(fs.existsSync(path.join(pgflowFunctionDir, 'index.ts'))).toBe(true);
141-
expect(fs.existsSync(path.join(pgflowFunctionDir, 'flows.ts'))).toBe(true);
142126
expect(fs.existsSync(path.join(pgflowFunctionDir, 'deno.json'))).toBe(true);
143127
});
144128

pkgs/cli/src/commands/compile/index.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export async function fetchFlowSQL(
3030
const errorData = await response.json();
3131
throw new Error(
3232
`Flow '${flowSlug}' not found.\n\n` +
33-
`${errorData.message || 'Did you add it to flows.ts?'}\n\n` +
33+
`${errorData.message || 'Did you add it to supabase/functions/pgflow/index.ts?'}\n\n` +
3434
`Fix:\n` +
35-
`1. Add your flow to supabase/functions/pgflow/flows.ts\n` +
35+
`1. Add your flow to supabase/functions/pgflow/index.ts\n` +
3636
`2. Restart edge functions: supabase functions serve`
3737
);
3838
}
@@ -45,11 +45,6 @@ export async function fetchFlowSQL(
4545
return await response.json();
4646
} catch (error) {
4747
if (error instanceof Error) {
48-
// Debug: show actual error and URL
49-
console.error(`[DEBUG] Fetch failed for URL: ${url}`);
50-
console.error(`[DEBUG] Error message: ${error.message}`);
51-
console.error(`[DEBUG] Error cause:`, (error as any).cause);
52-
5348
// Check for connection refused errors
5449
if (
5550
error.message.includes('ECONNREFUSED') ||

pkgs/cli/src/commands/install/create-edge-function.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,13 @@ import chalk from 'chalk';
55
import { getVersion } from '../../utils/get-version.js';
66

77
const INDEX_TS_TEMPLATE = `import { ControlPlane } from '@pgflow/edge-worker';
8-
import { flows } from './flows.ts';
9-
10-
ControlPlane.serve(flows);
11-
`;
12-
13-
const FLOWS_TS_TEMPLATE = `// Import your flows here
8+
// Import your flows here:
149
// import { MyFlow } from '../_flows/my_flow.ts';
1510
16-
// Export flows array for ControlPlane
17-
export const flows = [
11+
ControlPlane.serve([
12+
// Add your flows here:
1813
// MyFlow,
19-
];
14+
]);
2015
`;
2116

2217
const DENO_JSON_TEMPLATE = (version: string) => `{
@@ -44,7 +39,6 @@ export async function createEdgeFunction({
4439
const pgflowFunctionDir = path.join(functionsDir, 'pgflow');
4540

4641
const indexPath = path.join(pgflowFunctionDir, 'index.ts');
47-
const flowsPath = path.join(pgflowFunctionDir, 'flows.ts');
4842
const denoJsonPath = path.join(pgflowFunctionDir, 'deno.json');
4943

5044
// Check what needs to be created
@@ -54,10 +48,6 @@ export async function createEdgeFunction({
5448
filesToCreate.push({ path: indexPath, name: 'index.ts' });
5549
}
5650

57-
if (!fs.existsSync(flowsPath)) {
58-
filesToCreate.push({ path: flowsPath, name: 'flows.ts' });
59-
}
60-
6151
if (!fs.existsSync(denoJsonPath)) {
6252
filesToCreate.push({ path: denoJsonPath, name: 'deno.json' });
6353
}
@@ -69,7 +59,6 @@ export async function createEdgeFunction({
6959
const detailedMsg = [
7060
'Existing files:',
7161
` ${chalk.dim('•')} ${chalk.bold('supabase/functions/pgflow/index.ts')}`,
72-
` ${chalk.dim('•')} ${chalk.bold('supabase/functions/pgflow/flows.ts')}`,
7362
` ${chalk.dim('•')} ${chalk.bold('supabase/functions/pgflow/deno.json')}`,
7463
].join('\n');
7564

@@ -113,10 +102,6 @@ export async function createEdgeFunction({
113102
fs.writeFileSync(indexPath, INDEX_TS_TEMPLATE);
114103
}
115104

116-
if (filesToCreate.some((f) => f.path === flowsPath)) {
117-
fs.writeFileSync(flowsPath, FLOWS_TS_TEMPLATE);
118-
}
119-
120105
if (filesToCreate.some((f) => f.path === denoJsonPath)) {
121106
fs.writeFileSync(denoJsonPath, DENO_JSON_TEMPLATE(getVersion()));
122107
}

pkgs/cli/supabase/functions/pgflow/flows.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import { ControlPlane } from '@pgflow/edge-worker';
2-
import { flows } from './flows.ts';
2+
import { TestFlowE2E } from '../_flows/test_flow_e2e.ts';
33

4-
ControlPlane.serve(flows);
4+
ControlPlane.serve([TestFlowE2E]);

pkgs/edge-worker/src/control-plane/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
* @example
88
* ```typescript
99
* import { ControlPlane } from '@pgflow/edge-worker';
10-
* import { flows } from './flows.ts';
10+
* import { MyFlow } from '../_flows/my_flow.ts';
1111
*
12-
* ControlPlane.serve(flows);
12+
* ControlPlane.serve([MyFlow]);
1313
* ```
1414
*/
1515

pkgs/edge-worker/src/control-plane/server.ts

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,15 @@ export function serveControlPlane(flows: AnyFlow[]): void {
8585
/**
8686
* Handles GET /flows/:slug requests
8787
*/
88-
function handleGetFlow(
89-
registry: Map<string, AnyFlow>,
90-
slug: string
91-
): Response {
88+
function handleGetFlow(registry: Map<string, AnyFlow>, slug: string): Response {
9289
try {
9390
const flow = registry.get(slug);
9491

9592
if (!flow) {
9693
return jsonResponse(
9794
{
9895
error: 'Flow Not Found',
99-
message: `Flow '${slug}' not found. Did you add it to flows.ts?`,
96+
message: `Flow '${slug}' not found. Did you add it to supabase/functions/pgflow/index.ts?`,
10097
},
10198
404
10299
);
@@ -134,17 +131,3 @@ function jsonResponse(data: unknown, status: number): Response {
134131
},
135132
});
136133
}
137-
138-
/**
139-
* ControlPlane class for serving flow compilation HTTP API
140-
*/
141-
export class ControlPlane {
142-
/**
143-
* Serves the ControlPlane HTTP API for flow compilation
144-
* @param flows Array of flow definitions to register
145-
*/
146-
static serve(flows: AnyFlow[]): void {
147-
const handler = createControlPlaneHandler(flows);
148-
Deno.serve({}, handler);
149-
}
150-
}

pkgs/website/astro.config.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,10 @@ export default defineConfig({
321321
link: '/concepts/three-layer-architecture/',
322322
},
323323
{ label: 'Data model', link: '/concepts/data-model/' },
324+
{
325+
label: 'Compilation',
326+
link: '/concepts/compilation/',
327+
},
324328
],
325329
},
326330
{
@@ -366,6 +370,10 @@ export default defineConfig({
366370
},
367371
{ label: 'Context API', link: '/reference/context/' },
368372
{ label: 'Compile API', link: '/reference/compile-api/' },
373+
{
374+
label: 'ControlPlane API',
375+
link: '/reference/control-plane-api/',
376+
},
369377
{
370378
label: 'Manual installation',
371379
link: '/reference/manual-installation/',

pkgs/website/src/content/docs/build/index.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ Now that you've created your first flow, learn how to structure your code, integ
3535
href="/build/process-arrays-in-parallel/"
3636
description="Process arrays of data in parallel using map steps"
3737
/>
38+
<LinkCard
39+
title="Validation steps"
40+
href="/build/validation-steps/"
41+
description="Use explicit validation steps to fail fast on invalid input"
42+
/>
3843
</CardGrid>
3944

4045
## Starting Flows

0 commit comments

Comments
 (0)