diff --git a/.changeset/auto-compile-http-control.md b/.changeset/auto-compile-http-control.md index 788f598a0..3cabcecea 100644 --- a/.changeset/auto-compile-http-control.md +++ b/.changeset/auto-compile-http-control.md @@ -3,4 +3,24 @@ '@pgflow/edge-worker': minor --- -Add auto-compilation flow and HTTP control plane server for edge worker +## @pgflow/edge-worker + +- Add ControlPlane HTTP server for flow compilation (`ControlPlane.serve()`) +- Support namespace imports for flow registration + +## @pgflow/cli + +### Breaking Changes + +- `pgflow compile` now takes flow slug instead of file path +- Compilation happens via HTTP to ControlPlane (local Deno no longer required) +- Deprecate `--deno-json` flag (will be removed in v1.0) + +### New Features + +- `pgflow install` now scaffolds complete setup: + - Creates `supabase/flows/` with example GreetUser flow + - Creates `supabase/functions/pgflow/` Control Plane + - Creates `supabase/functions/greet-user-worker/` example worker +- Add `--control-plane-url` option to compile command +- Dynamic version injection in generated deno.json files diff --git a/PLAN_auth-verification.md b/PLAN_auth-verification.md new file mode 100644 index 000000000..4d0e59279 --- /dev/null +++ b/PLAN_auth-verification.md @@ -0,0 +1,108 @@ +# PLAN: Auth Verification for Control Plane & Workers + +**Created**: 2025-11-28 +**Status**: Future Work +**Related**: PLAN_control-plane-edge-worker-compilation.md + +--- + +## Goal + +Control plane and workers verify the secret key to protect sensitive operations. + +## Supabase Key Context + +Supabase is transitioning from JWT-based keys to new API keys: + +| Old Name | New Name | Format | Use Case | +|----------|----------|--------|----------| +| `anon` | publishable key | `sb_publishable_...` | Client-side (RLS-restricted) | +| `service_role` | secret key | `sb_secret_...` | Server-side (bypasses RLS) | + +**Important notes:** +- **CLI/local dev** (`supabase start`): Only has legacy `anon`/`service_role` keys +- **Hosted Supabase**: Has both old and new key formats +- **Edge Functions**: Have `SUPABASE_SERVICE_ROLE_KEY` env var available by default +- **Timeline**: Legacy keys deprecated late 2026 +- **Reference**: https://supabase.com/docs/guides/api/api-keys + +**Supabase docs explicitly list "periodic jobs, queue processors, topic subscribers" as use cases for secret/service_role keys.** + +--- + +## Why Auth is Required + +Both pgflow functions need **service_role/secret key** protection: + +| Function | Why Secret Key Required | +|----------|------------------------| +| **Control Plane** (`/pgflow/`) | Enumerate flows, compile flows - dangerous operations | +| **Workers** (`/greet-user-worker/`) | Full DB access, execute arbitrary handlers | + +--- + +## Important: New Keys Require Manual Verification + +From Supabase docs: +> "Edge Functions only support JWT verification via the anon and service_role JWT-based API keys. You will need to use the --no-verify-jwt option when using publishable and secret keys. Implement your own apikey-header authorization logic inside the Edge Function code itself." + +**Key is sent in `apikey` header (NOT Authorization)** - new secret keys are not JWTs. + +--- + +## Implementation + +### Code Changes + +**Files to modify:** +- `pkgs/edge-worker/src/ControlPlane.ts` (or equivalent) +- `pkgs/edge-worker/src/EdgeWorker.ts` (or equivalent) + +**Implementation:** +```typescript +// At the start of request handling +const apiKey = req.headers.get('apikey'); +const expectedKey = Deno.env.get('PGFLOW_SECRET_KEY'); // custom env var set by user + +if (!apiKey || apiKey !== expectedKey) { + return new Response(JSON.stringify({ error: 'Invalid or missing secret key' }), { + status: 401, + }); +} +``` + +### User Setup Required + +For production with new secret keys: +1. Create secret key in Supabase dashboard +2. Store as Edge Function secret: `PGFLOW_SECRET_KEY` +3. Pass same key to CLI: `pgflow compile --secret-key ` + +### Documentation + +**Add to docs:** +- Auth model explanation (both functions need service_role key) +- How to pass apikey header +- Production deployment considerations + +**Example:** +```bash +curl http://localhost:54321/functions/v1/greet-user-worker \ + -H "apikey: $PGFLOW_SECRET_KEY" +``` + +--- + +## When to Implement + +This should be implemented when: +- Flow enumeration is added to control plane +- Other sensitive operations are exposed +- Before production deployments become common + +--- + +## References + +- Supabase API Keys docs: https://supabase.com/docs/guides/api/api-keys +- Edge Function secrets: https://supabase.com/docs/guides/functions/secrets diff --git a/PLAN_control-plane-edge-worker-compilation.md b/PLAN_control-plane-edge-worker-compilation.md index 29a91a290..ed20853c9 100644 --- a/PLAN_control-plane-edge-worker-compilation.md +++ b/PLAN_control-plane-edge-worker-compilation.md @@ -193,6 +193,10 @@ class EdgeWorker { - Advisory locks in ControlPlane - Proper error messages with actionable fixes - Deployment mode detection (dev/prod) +- **Auth verification for ControlPlane endpoints** (see PLAN_auth-verification.md) + - Verify `apikey` header against `PGFLOW_SECRET_KEY` env var + - Required for production deployments + - Protects flow enumeration and compilation endpoints --- diff --git a/PLAN_workers-start-command.md b/PLAN_workers-start-command.md new file mode 100644 index 000000000..ff7efbed9 --- /dev/null +++ b/PLAN_workers-start-command.md @@ -0,0 +1,164 @@ +# PLAN: pgflow workers start CLI Command + +**Created**: 2025-11-28 +**Status**: Future Work +**Related**: PLAN_auth-verification.md + +--- + +## Goal + +Provide a CLI command to start workers with proper authentication, eliminating the need for manual curl commands. + +--- + +## Command Design + +```bash +# Uses SUPABASE_SERVICE_ROLE_KEY from env or .env +pgflow workers start greet-user-worker + +# Or explicit key +pgflow workers start greet-user-worker --secret-key + +# Multiple workers +pgflow workers start greet-user-worker payment-worker +``` + +--- + +## Behavior + +### Key Resolution + +1. Check `--secret-key` flag +2. Check `SUPABASE_SERVICE_ROLE_KEY` env var +3. Check `.env` file in current directory +4. Check `supabase/.env` file +5. Error if no key found + +### Request Handling + +- Call worker endpoint with proper `apikey` header +- Handle HTTP responses and errors +- Display worker status/logs +- Handle reconnection on disconnect + +### URL Resolution + +- Default: `http://localhost:54321/functions/v1/` +- Support `--url` flag for custom endpoints +- Support `SUPABASE_URL` env var for hosted + +--- + +## Implementation + +### Files to Create/Modify + +- `pkgs/cli/src/commands/workers/start.ts` - New command +- `pkgs/cli/src/commands/workers/index.ts` - Command group + +### Command Structure + +```typescript +import { Command } from 'commander'; + +export const workersStartCommand = new Command('start') + .description('Start a worker to process tasks') + .argument('', 'Worker function name(s)') + .option('--secret-key ', 'Service role / secret key') + .option('--url ', 'Supabase functions URL') + .action(async (workers, options) => { + const secretKey = resolveSecretKey(options); + + for (const worker of workers) { + await startWorker(worker, secretKey, options.url); + } + }); + +async function startWorker(name: string, secretKey: string, baseUrl?: string) { + const url = `${baseUrl || getDefaultUrl()}/functions/v1/${name}`; + + const response = await fetch(url, { + headers: { + 'apikey': secretKey, + }, + }); + + // Handle response, reconnection, etc. +} +``` + +### Key Resolution Function + +```typescript +function resolveSecretKey(options: { secretKey?: string }): string { + // 1. CLI flag + if (options.secretKey) return options.secretKey; + + // 2. Env var + if (process.env.SUPABASE_SERVICE_ROLE_KEY) { + return process.env.SUPABASE_SERVICE_ROLE_KEY; + } + + // 3. .env file + const envPath = findEnvFile(); + if (envPath) { + const env = dotenv.parse(fs.readFileSync(envPath)); + if (env.SUPABASE_SERVICE_ROLE_KEY) { + return env.SUPABASE_SERVICE_ROLE_KEY; + } + } + + throw new Error('No secret key found. Provide --secret-key or set SUPABASE_SERVICE_ROLE_KEY'); +} +``` + +--- + +## UX Considerations + +### Output Format + +``` +$ pgflow workers start greet-user-worker + +Starting worker: greet-user-worker + URL: http://localhost:54321/functions/v1/greet-user-worker + Auth: Using SUPABASE_SERVICE_ROLE_KEY from environment + +Worker started successfully. Press Ctrl+C to stop. +``` + +### Error Messages + +``` +$ pgflow workers start greet-user-worker + +Error: No secret key found. + +To fix this, either: + 1. Set SUPABASE_SERVICE_ROLE_KEY environment variable + 2. Add SUPABASE_SERVICE_ROLE_KEY to your .env file + 3. Use --secret-key flag: pgflow workers start greet-user-worker --secret-key +``` + +--- + +## When to Implement + +After: +- Auth verification is implemented (PLAN_auth-verification.md) +- Basic worker functionality is stable +- User feedback indicates need for easier worker management + +--- + +## Future Enhancements + +- `pgflow workers list` - List available workers +- `pgflow workers status` - Show running workers +- `pgflow workers stop` - Stop running workers +- Watch mode for development +- Multiple worker instances with `--concurrency` flag diff --git a/pkgs/cli/__tests__/commands/compile/index.test.ts b/pkgs/cli/__tests__/commands/compile/index.test.ts index 6aa86d5f1..08e850888 100644 --- a/pkgs/cli/__tests__/commands/compile/index.test.ts +++ b/pkgs/cli/__tests__/commands/compile/index.test.ts @@ -43,7 +43,7 @@ describe('fetchFlowSQL', () => { const result = await fetchFlowSQL( 'test_flow', 'http://127.0.0.1:50621/functions/v1/pgflow', - 'test-publishable-key' + 'test-secret-key' ); expect(result).toEqual({ @@ -58,8 +58,8 @@ describe('fetchFlowSQL', () => { 'http://127.0.0.1:50621/functions/v1/pgflow/flows/test_flow', { headers: { - 'Authorization': 'Bearer test-publishable-key', - 'apikey': 'test-publishable-key', + 'Authorization': 'Bearer test-secret-key', + 'apikey': 'test-secret-key', 'Content-Type': 'application/json', }, } @@ -142,7 +142,7 @@ describe('fetchFlowSQL', () => { const result = await fetchFlowSQL( 'test_flow', 'http://127.0.0.1:50621', - 'test-publishable-key' + 'test-secret-key' ); expect(result).toHaveProperty('flowSlug'); @@ -224,7 +224,7 @@ describe('fetchFlowSQL', () => { await fetchFlowSQL( 'my_complex_flow_123', 'http://127.0.0.1:50621/functions/v1/pgflow', - 'test-publishable-key' + 'test-secret-key' ); expect(global.fetch).toHaveBeenCalledWith( @@ -233,7 +233,7 @@ describe('fetchFlowSQL', () => { ); }); - it('should pass publishable key in Authorization header', async () => { + it('should pass secret key in Authorization header', async () => { const mockResponse = { ok: true, status: 200, @@ -248,13 +248,13 @@ describe('fetchFlowSQL', () => { await fetchFlowSQL( 'test_flow', 'http://127.0.0.1:50621/functions/v1/pgflow', - 'my-special-publishable-key' + 'my-special-secret-key' ); expect(global.fetch).toHaveBeenCalledWith(expect.any(String), { headers: { - 'Authorization': 'Bearer my-special-publishable-key', - 'apikey': 'my-special-publishable-key', + 'Authorization': 'Bearer my-special-secret-key', + 'apikey': 'my-special-secret-key', 'Content-Type': 'application/json', }, }); @@ -276,7 +276,7 @@ describe('fetchFlowSQL', () => { const result = await fetchFlowSQL( 'empty_flow', 'http://127.0.0.1:50621/functions/v1/pgflow', - 'test-publishable-key' + 'test-secret-key' ); expect(result.sql).toEqual([]); diff --git a/pkgs/cli/project.json b/pkgs/cli/project.json index ab926c1e6..17d221ca5 100644 --- a/pkgs/cli/project.json +++ b/pkgs/cli/project.json @@ -67,7 +67,7 @@ "cwd": "{projectRoot}", "readyWhen": "Serving functions on http://", "streamOutput": true, - "command": "../../scripts/supabase-start-locked.sh . && ./scripts/sync-e2e-deps.sh && supabase functions serve --import-map supabase/functions/pgflow/deno.json" + "command": "../../scripts/supabase-start-locked.sh . && ./scripts/sync-e2e-deps.sh && supabase functions serve --no-verify-jwt --import-map supabase/functions/pgflow/deno.json" } }, "hang": { diff --git a/pkgs/cli/src/commands/compile/index.ts b/pkgs/cli/src/commands/compile/index.ts index 5034f61d3..994842b0a 100644 --- a/pkgs/cli/src/commands/compile/index.ts +++ b/pkgs/cli/src/commands/compile/index.ts @@ -1,27 +1,24 @@ -import { type Command } from 'commander'; +import { type Command, Option } from 'commander'; import chalk from 'chalk'; import { intro, log, outro } from '@clack/prompts'; import path from 'path'; import fs from 'fs'; -// Default Supabase local development publishable key (same for all local projects) -const DEFAULT_PUBLISHABLE_KEY = 'sb_publishable_ACJWlzQHlZjBrEguHvfOxg_3BJgxAaH'; - /** * Fetch flow SQL from ControlPlane HTTP endpoint */ export async function fetchFlowSQL( flowSlug: string, controlPlaneUrl: string, - publishableKey: string + secretKey: string ): Promise<{ flowSlug: string; sql: string[] }> { const url = `${controlPlaneUrl}/flows/${flowSlug}`; try { const response = await fetch(url, { headers: { - 'Authorization': `Bearer ${publishableKey}`, - 'apikey': publishableKey, + 'Authorization': `Bearer ${secretKey}`, + 'apikey': secretKey, 'Content-Type': 'application/json', }, }); @@ -91,20 +88,19 @@ export default (program: Command) => { .command('compile') .description('Compiles a flow into SQL migration via ControlPlane HTTP') .argument('', 'Flow slug to compile (e.g., my_flow)') - .option( - '--deno-json ', - '[DEPRECATED] No longer used. Will be removed in v1.0' - ) .option('--supabase-path ', 'Path to the Supabase folder') .option( '--control-plane-url ', 'Control plane URL', 'http://127.0.0.1:54321/functions/v1/pgflow' ) + .addOption( + new Option('--secret-key [key]', 'Supabase anon/service_role key') + .hideHelp() + ) .option( - '--publishable-key ', - 'Supabase publishable key (legacy anon keys also work)', - DEFAULT_PUBLISHABLE_KEY + '--deno-json ', + '[DEPRECATED] No longer used. Will be removed in v1.0' ) .action(async (flowSlug, options) => { intro('pgflow - Compile Flow to SQL'); @@ -162,7 +158,7 @@ export default (program: Command) => { const result = await fetchFlowSQL( flowSlug, options.controlPlaneUrl, - options.publishableKey + options.secretKey ); // Validate result @@ -201,7 +197,7 @@ export default (program: Command) => { // Display next steps with outro outro( [ - chalk.bold('Flow compilation completed successfully!'), + chalk.green.bold('✓ Flow compilation completed successfully!'), '', `- Run ${chalk.cyan('supabase migration up')} to apply the migration`, '', diff --git a/pkgs/cli/src/index.ts b/pkgs/cli/src/index.ts index 8c74585b9..0e2b8a855 100755 --- a/pkgs/cli/src/index.ts +++ b/pkgs/cli/src/index.ts @@ -42,19 +42,20 @@ const g = chalk.hex('#9ece6a'); // vibrant green const l = chalk.hex('#2ac3de'); // bright teal/cyan // const o = chalk.hex('#ff9e64'); // orange // const w = chalk.hex('#f7768e'); // magenta/pink +const d = chalk.dim; // dim for secondary text const banner = [ - ` ${l('__ _')} `, - ` ${g('_ __ __ _')} ${l('/ _| | _____ __')} `, - ` ${g("| '_ \\ / _'")} ${l('| |_| |/ _ \\ \\ /\\ / /')} `, - ` ${g('| |_) | (_|')} ${l('| _| | (_) \\ V V /')} `, - ` ${g('| .__/ \\__,')} ${l('|_| |_|\\___/ \\_/\\_/')} `, - ` ${g('|_| |___/')}`, + ` ${l('__ _')}`, + ` ${g('_ __ __ _')} ${l('/ _| | _____ __')}`, + ` ${g("| '_ \\ / _'")} ${l('| |_| |/ _ \\ \\ /\\ / /')}`, + ` ${g('| |_) | (_|')} ${l('| _| | (_) \\ V V /')}`, + ` ${g('| .__/ \\__,')} ${l('|_| |_|\\___/ \\_/\\_/')}`, + ` ${g('|_| |___/')} ${d('v' + getVersion())}`, + ``, + ` ${l('Workflows in Supabase')} ${d('|')} ${g.underline('pgflow.dev')}`, ].join('\n'); console.log(banner); console.log(); -console.log(); -console.log(); // Use a promise-aware approach to parse arguments async function main() { diff --git a/pkgs/website/src/content/docs/build/organize-flow-code.mdx b/pkgs/website/src/content/docs/build/organize-flow-code.mdx index 9b1852714..d666e168e 100644 --- a/pkgs/website/src/content/docs/build/organize-flow-code.mdx +++ b/pkgs/website/src/content/docs/build/organize-flow-code.mdx @@ -47,7 +47,7 @@ This structure enables: - **First-class concepts**: Flows and tasks are not hidden in underscore-prefixed folders - **Self-documenting**: Structure clearly shows where things go -- **Clean imports**: `../../flows/index.ts` from Control Plane, `../../tasks/` from flows +- **Clean imports**: `../../flows/index.ts` from Control Plane, `../tasks/` from flows - **Scalable**: Ready for multiple flows and workers - **Barrel exports**: Each directory has an `index.ts` for clean re-exports diff --git a/pkgs/website/src/content/docs/comparisons/inngest.mdx b/pkgs/website/src/content/docs/comparisons/inngest.mdx index e311c9da3..e5489bf78 100644 --- a/pkgs/website/src/content/docs/comparisons/inngest.mdx +++ b/pkgs/website/src/content/docs/comparisons/inngest.mdx @@ -115,7 +115,7 @@ The difference is architectural: ### pgflow - **Native integration** - Built specifically for Supabase ecosystem - **Zero infrastructure** - Uses Supabase Edge Functions and PostgreSQL -- **Simple setup** - Single command (`npx pgflow install`) sets up all required components +- **Simple setup** - Single command (`npx pgflow@latest install`) sets up all required components - **Direct database access** - All workflow state directly accessible in your Supabase database ### Inngest diff --git a/pkgs/website/src/content/docs/comparisons/trigger.mdx b/pkgs/website/src/content/docs/comparisons/trigger.mdx index ce12c913f..d356132a8 100644 --- a/pkgs/website/src/content/docs/comparisons/trigger.mdx +++ b/pkgs/website/src/content/docs/comparisons/trigger.mdx @@ -120,7 +120,7 @@ The difference is architectural: ### pgflow - **Native integration** - Built specifically for Supabase ecosystem - **Zero infrastructure** - Uses Supabase Edge Functions and PostgreSQL -- **Simple setup** - Single command (`npx pgflow install`) sets up all required components +- **Simple setup** - Single command (`npx pgflow@latest install`) sets up all required components - **Direct database access** - All workflow state directly accessible in your Supabase database ### Trigger.dev diff --git a/pkgs/website/src/content/docs/comparisons/vercel-workflows.mdx b/pkgs/website/src/content/docs/comparisons/vercel-workflows.mdx index 0bc3bccba..0ece7425a 100644 --- a/pkgs/website/src/content/docs/comparisons/vercel-workflows.mdx +++ b/pkgs/website/src/content/docs/comparisons/vercel-workflows.mdx @@ -130,7 +130,7 @@ The difference is architectural: ### pgflow - **Native integration** - Built specifically for Supabase ecosystem - **Zero infrastructure** - Uses Supabase Edge Functions and PostgreSQL -- **Simple setup** - Single command (`npx pgflow install`) sets up all required components +- **Simple setup** - Single command (`npx pgflow@latest install`) sets up all required components - **Direct database access** - All workflow state directly accessible in your Supabase database ### Vercel Workflows diff --git a/pkgs/website/src/content/docs/deploy/supabase/deploy-first-flow.mdx b/pkgs/website/src/content/docs/deploy/supabase/deploy-first-flow.mdx index b1acde706..c812a3182 100644 --- a/pkgs/website/src/content/docs/deploy/supabase/deploy-first-flow.mdx +++ b/pkgs/website/src/content/docs/deploy/supabase/deploy-first-flow.mdx @@ -87,7 +87,7 @@ curl "https://your-project.supabase.co/functions/v1/your-worker-name" \ 3. Copy the `anon` `public` key :::note -Production Edge Functions have JWT verification enabled by default. +Production Edge Functions have JWT verification enabled by default. Currently, workers use the `anon` key for self-respawning. Enhanced authentication using a dedicated secret key is planned for future releases. ::: The worker starts polling for tasks and continues running until it's stopped or times out. diff --git a/pkgs/website/src/content/docs/get-started/flows/quickstart.mdx b/pkgs/website/src/content/docs/get-started/flows/quickstart.mdx index 6f7f08a65..afa737214 100644 --- a/pkgs/website/src/content/docs/get-started/flows/quickstart.mdx +++ b/pkgs/website/src/content/docs/get-started/flows/quickstart.mdx @@ -22,9 +22,13 @@ See pgflow in action using the GreetUser flow scaffolded during installation. Open a terminal and start the edge functions server: ```bash frame="none" - npx supabase functions serve + npx supabase functions serve --no-verify-jwt ``` + + Keep this terminal open. 2. ### Compile the flow @@ -50,6 +54,10 @@ See pgflow in action using the GreetUser flow scaffolded during installation. npx supabase migrations up ``` + + 4. ### Start the worker Send an HTTP request to start the scaffolded worker: @@ -103,6 +111,14 @@ See pgflow in action using the GreetUser flow scaffolded during installation. The GreetUser flow demonstrates the core pgflow pattern - steps with dependencies that pass data between them. +## Iterating on your flow + +Want to modify the flow structure? Flows are immutable once registered - this protects running workflows. + +**During development:** [Delete the flow](/build/delete-flows/) and recompile. This removes all run data, so only use locally. + +**In production:** Create a [versioned flow](/build/version-flows/) (e.g., `greetUserV2`) to preserve history. + ## Next steps @@ -116,4 +132,9 @@ The GreetUser flow demonstrates the core pgflow pattern - steps with dependencie href="/concepts/how-pgflow-works/" description="Understand the execution model behind the scenes" /> + diff --git a/pkgs/website/src/content/docs/get-started/installation.mdx b/pkgs/website/src/content/docs/get-started/installation.mdx index e6d6a2a5a..88f9d10bc 100644 --- a/pkgs/website/src/content/docs/get-started/installation.mdx +++ b/pkgs/website/src/content/docs/get-started/installation.mdx @@ -71,10 +71,6 @@ import { Card } from '@astrojs/starlight/components'; - - For single-step tasks without orchestration - just reliable task queue processing on Supabase with retries and observability. +### Need simple background jobs? - - Use background jobs - - +For single-step tasks without orchestration, see [background jobs](/get-started/background-jobs/create-worker/). diff --git a/pkgs/website/src/content/docs/news/pgflow-0-9-0-control-plane-and-http-based-compilation.mdx b/pkgs/website/src/content/docs/news/pgflow-0-9-0-control-plane-and-http-based-compilation.mdx index c34534211..556854e21 100644 --- a/pkgs/website/src/content/docs/news/pgflow-0-9-0-control-plane-and-http-based-compilation.mdx +++ b/pkgs/website/src/content/docs/news/pgflow-0-9-0-control-plane-and-http-based-compilation.mdx @@ -2,7 +2,7 @@ draft: false title: 'pgflow 0.9.0: Control Plane and HTTP-Based Compilation' description: 'Simpler flow compilation via ControlPlane edge function - no local Deno required' -date: 2025-11-27 +date: 2025-11-28 authors: - jumski tags: @@ -30,6 +30,66 @@ This simplifies the development setup and lays groundwork for future auto-compil This release prepares for upcoming changes where edge workers will automatically verify flow shapes and trigger compilation when needed - making `pgflow compile` optional for development workflows. +## Breaking Changes + +### Compile Command + +The `pgflow compile` command now takes a **flow slug** instead of a file path: + +**Before (0.8.x):** +```bash frame="none" +pgflow compile path/to/my_flow.ts --deno-json supabase/functions/deno.json +``` + +**After (0.9.0):** +```bash frame="none" +pgflow compile my_flow +``` + +The `--deno-json` flag is deprecated and will be removed in v1.0. + + + +## New Install Experience + +The installer now scaffolds a complete working setup: + +```bash frame="none" +npx pgflow@0.9.0 install +``` + +This creates: +- `supabase/flows/` - Directory for flow definitions with namespace imports +- `supabase/flows/greet-user.ts` - Example GreetUser flow +- `supabase/functions/pgflow/` - Control Plane for compilation +- `supabase/functions/greet-user-worker/` - Example worker ready to run + +The installer shows a clear summary and asks for a single confirmation before making changes. + +## Namespace Imports for Flows + +ControlPlane now supports namespace imports, the recommended pattern: + +```typescript +import { ControlPlane } from '@pgflow/edge-worker'; +import * as flows from '../../flows/index.ts'; + +ControlPlane.serve(flows); +``` + +Add flows by exporting from `flows/index.ts`: + +```typescript +export { GreetUser } from './greet-user.ts'; +export { MyOtherFlow } from './my-other-flow.ts'; +``` + ## Upgrading Update your packages following the [Update pgflow guide](/deploy/update-pgflow/), then run: @@ -38,7 +98,7 @@ Update your packages following the [Update pgflow guide](/deploy/update-pgflow/) npx pgflow@0.9.0 install ``` -This creates the ControlPlane edge function at `supabase/functions/control-plane/`. +This creates the ControlPlane edge function at `supabase/functions/pgflow/`.