Skip to content

Commit 436f10d

Browse files
committed
refactor: standardize naming to camelCase for flow slugs and kebab-case for files (#442)
# Update Flow and Step Naming Conventions to camelCase This PR updates the naming conventions for flows and steps to use camelCase for slugs and kebab-case for file names. The changes include: 1. Renamed example flow file from `example_flow.ts` to `example-flow.ts` 2. Updated flow slugs from snake_case to camelCase (e.g., `example_flow` → `exampleFlow`) 3. Updated step slugs to follow camelCase convention 4. Updated file paths in imports and tests to use kebab-case 5. Added comprehensive naming convention documentation in `/concepts/naming-conventions/` 6. Updated all code examples in documentation to follow the new conventions These changes establish a consistent naming pattern across the codebase: - Flow slugs: camelCase (e.g., `greetUser`) - Step slugs: camelCase (e.g., `fetchData`) - File names: kebab-case (e.g., `greet-user.ts`) - Flow exports: PascalCase (e.g., `GreetUser`) - Worker directories: kebab-case + `-worker` (e.g., `greet-user-worker/`) This standardization improves code readability and follows JavaScript ecosystem conventions.
1 parent cde0f59 commit 436f10d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+358
-283
lines changed

pkgs/cli/__tests__/commands/install/create-flows-directory.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe('createFlowsDirectory', () => {
3535

3636
// Verify all files exist
3737
const indexPath = path.join(flowsDir, 'index.ts');
38-
const exampleFlowPath = path.join(flowsDir, 'example_flow.ts');
38+
const exampleFlowPath = path.join(flowsDir, 'example-flow.ts');
3939

4040
expect(fs.existsSync(indexPath)).toBe(true);
4141
expect(fs.existsSync(exampleFlowPath)).toBe(true);
@@ -51,26 +51,26 @@ describe('createFlowsDirectory', () => {
5151
const indexContent = fs.readFileSync(indexPath, 'utf8');
5252

5353
// Should have export for ExampleFlow
54-
expect(indexContent).toContain("export { ExampleFlow } from './example_flow.ts'");
54+
expect(indexContent).toContain("export { ExampleFlow } from './example-flow.ts';");
5555
// Should have documenting comment
5656
expect(indexContent).toContain('Re-export all flows');
5757
});
5858

59-
it('should create example_flow.ts with named export', async () => {
59+
it('should create example-flow.ts with named export', async () => {
6060
await createFlowsDirectory({
6161
supabasePath,
6262
autoConfirm: true,
6363
});
6464

65-
const exampleFlowPath = path.join(flowsDir, 'example_flow.ts');
65+
const exampleFlowPath = path.join(flowsDir, 'example-flow.ts');
6666
const exampleFlowContent = fs.readFileSync(exampleFlowPath, 'utf8');
6767

6868
// Should use named export (not default)
6969
expect(exampleFlowContent).toContain('export const ExampleFlow');
7070
// Should import Flow from @pgflow/dsl
7171
expect(exampleFlowContent).toContain("import { Flow } from '@pgflow/dsl'");
7272
// Should have correct slug
73-
expect(exampleFlowContent).toContain("slug: 'example_flow'");
73+
expect(exampleFlowContent).toContain("slug: 'exampleFlow'");
7474
// Should have input type
7575
expect(exampleFlowContent).toContain('type Input');
7676
// Should have at least one step
@@ -82,7 +82,7 @@ describe('createFlowsDirectory', () => {
8282
fs.mkdirSync(flowsDir, { recursive: true });
8383

8484
const indexPath = path.join(flowsDir, 'index.ts');
85-
const exampleFlowPath = path.join(flowsDir, 'example_flow.ts');
85+
const exampleFlowPath = path.join(flowsDir, 'example-flow.ts');
8686

8787
fs.writeFileSync(indexPath, '// existing content');
8888
fs.writeFileSync(exampleFlowPath, '// existing content');
@@ -105,7 +105,7 @@ describe('createFlowsDirectory', () => {
105105
fs.mkdirSync(flowsDir, { recursive: true });
106106

107107
const indexPath = path.join(flowsDir, 'index.ts');
108-
const exampleFlowPath = path.join(flowsDir, 'example_flow.ts');
108+
const exampleFlowPath = path.join(flowsDir, 'example-flow.ts');
109109

110110
// Only create index.ts
111111
fs.writeFileSync(indexPath, '// existing content');
@@ -115,13 +115,13 @@ describe('createFlowsDirectory', () => {
115115
autoConfirm: true,
116116
});
117117

118-
// Should return true because example_flow.ts was created
118+
// Should return true because example-flow.ts was created
119119
expect(result).toBe(true);
120120

121121
// Verify index.ts was not modified
122122
expect(fs.readFileSync(indexPath, 'utf8')).toBe('// existing content');
123123

124-
// Verify example_flow.ts was created
124+
// Verify example-flow.ts was created
125125
expect(fs.existsSync(exampleFlowPath)).toBe(true);
126126

127127
const exampleContent = fs.readFileSync(exampleFlowPath, 'utf8');
@@ -145,6 +145,6 @@ describe('createFlowsDirectory', () => {
145145

146146
// Verify files exist
147147
expect(fs.existsSync(path.join(flowsDir, 'index.ts'))).toBe(true);
148-
expect(fs.existsSync(path.join(flowsDir, 'example_flow.ts'))).toBe(true);
148+
expect(fs.existsSync(path.join(flowsDir, 'example-flow.ts'))).toBe(true);
149149
});
150150
});

pkgs/cli/src/commands/install/create-flows-directory.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import { log, confirm } from '@clack/prompts';
44
import chalk from 'chalk';
55

66
const INDEX_TS_TEMPLATE = `// Re-export all flows from this directory
7-
// Example: export { MyFlow } from './my_flow.ts';
7+
// Example: export { MyFlow } from './my-flow.ts';
88
9-
export { ExampleFlow } from './example_flow.ts';
9+
export { ExampleFlow } from './example-flow.ts';
1010
`;
1111

1212
const EXAMPLE_FLOW_TEMPLATE = `import { Flow } from '@pgflow/dsl';
1313
1414
type Input = { name: string };
1515
16-
export const ExampleFlow = new Flow<Input>({ slug: 'example_flow' })
16+
export const ExampleFlow = new Flow<Input>({ slug: 'exampleFlow' })
1717
.step({ slug: 'greet' }, (input) => \`Hello, \${input.run.name}!\`);
1818
`;
1919

@@ -27,12 +27,12 @@ export async function createFlowsDirectory({
2727
const flowsDir = path.join(supabasePath, 'flows');
2828

2929
const indexPath = path.join(flowsDir, 'index.ts');
30-
const exampleFlowPath = path.join(flowsDir, 'example_flow.ts');
30+
const exampleFlowPath = path.join(flowsDir, 'example-flow.ts');
3131

3232
// Relative paths for display
3333
const relativeFlowsDir = 'supabase/flows';
3434
const relativeIndexPath = `${relativeFlowsDir}/index.ts`;
35-
const relativeExampleFlowPath = `${relativeFlowsDir}/example_flow.ts`;
35+
const relativeExampleFlowPath = `${relativeFlowsDir}/example-flow.ts`;
3636

3737
// Check what needs to be created
3838
const filesToCreate: Array<{ path: string; relativePath: string }> = [];

pkgs/client/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ When using with `@pgflow/dsl`, you get full type safety:
230230
import { Flow } from '@pgflow/dsl';
231231

232232
// Define your flow
233-
const AnalyzeWebsite = new Flow<{ url: string }>({ slug: 'analyze_website' })
233+
const AnalyzeWebsite = new Flow<{ url: string }>({ slug: 'analyzeWebsite' })
234234
.step({ slug: 'scrape' }, async (input) => ({ content: 'html...' }))
235235
.step({ slug: 'analyze' }, async (input) => ({ sentiment: 0.8 }));
236236

pkgs/client/SECURITY.md

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ pgflow ships with NO permissions. The SQL below is a **convenience snippet** tha
2222

2323
> [!CAUTION]
2424
> This SQL grants BROAD permissions! After running this, ANY authenticated user can:
25+
>
2526
> - Start ANY flow
2627
> - View ANY run (if they know the run_id)
2728
> - See ALL flow definitions
28-
>
29+
>
2930
> It is YOUR responsibility to:
31+
>
3032
> - Tailor these permissions to your specific needs
3133
> - Implement Row Level Security policies
3234
> - Add proper access controls
@@ -51,40 +53,42 @@ This is suitable for development and trusted environments only.
5153
Since pgflow doesn't handle security yet, you might want to:
5254

5355
1. **Add Row Level Security**
54-
56+
5557
The key to implementing RLS with pgflow is to include a `user_id` field in your flow's input object. This allows you to create policies that check if the current user matches the user who started the flow.
56-
58+
5759
First, include user_id in your flow input type:
60+
5861
```typescript
5962
import { Flow } from '@pgflow/dsl';
60-
63+
6164
// Define input type with user_id
6265
type MyFlowInput = {
63-
user_id: string; // <<<<< Add this field
66+
user_id: string; // <<<<< Add this field
6467
data: string;
6568
// ... other fields
6669
};
67-
70+
6871
export const MyFlow = new Flow<MyFlowInput>({
69-
slug: 'my_secure_flow',
70-
})
72+
slug: 'mySecureFlow',
73+
});
7174
// ... rest of flow definition
7275
```
73-
76+
7477
Then create RLS policies and an index for performance:
78+
7579
```sql
7680
-- Enable RLS on tables you want to protect
7781
ALTER TABLE pgflow.runs ENABLE ROW LEVEL SECURITY;
78-
82+
7983
-- Create index for better RLS performance
8084
CREATE INDEX idx_runs_user_id ON pgflow.runs ((input->>'user_id'));
81-
85+
8286
-- Create your own policies based on your needs
8387
-- Example: Users can only see their own runs
8488
CREATE POLICY "Users see own runs" ON pgflow.runs
8589
FOR SELECT USING ((SELECT auth.uid())::text = input->>'user_id');
8690
```
87-
91+
8892
For more details about the pgflow schema and the `runs` table structure, see the [Schema Design section](../core/README.md#schema-design) in the core documentation.
8993

9094
2. **Track User Attribution**
@@ -93,4 +97,4 @@ Since pgflow doesn't handle security yet, you might want to:
9397

9498
## Questions?
9599

96-
If you have security concerns or suggestions, please share them in the [GitHub discussions](https://github.com/pgflow-dev/pgflow/discussions).
100+
If you have security concerns or suggestions, please share them in the [GitHub discussions](https://github.com/pgflow-dev/pgflow/discussions).

pkgs/dsl/README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type Input = {
3131

3232
// Define a flow with steps and dependencies
3333
export const AnalyzeWebsite = new Flow<Input>({
34-
slug: 'analyze_website',
34+
slug: 'analyzeWebsite',
3535
maxAttempts: 3,
3636
baseDelay: 5,
3737
timeout: 10,
@@ -98,13 +98,13 @@ A semantic wrapper around `.step()` that provides type enforcement for steps tha
9898
```typescript
9999
// Fetch an array of items to be processed
100100
.array(
101-
{ slug: 'fetch_items' },
101+
{ slug: 'fetchItems' },
102102
async () => [1, 2, 3, 4, 5]
103103
)
104104

105105
// With dependencies - combining data from multiple sources
106106
.array(
107-
{ slug: 'combine_results', dependsOn: ['source1', 'source2'] },
107+
{ slug: 'combineResults', dependsOn: ['source1', 'source2'] },
108108
async (input) => [...input.source1, ...input.source2]
109109
)
110110
```
@@ -131,15 +131,15 @@ Processes arrays element-by-element, similar to JavaScript's `Array.map()`. The
131131
```typescript
132132
// ROOT MAP - No array: property means use flow input
133133
// Flow input MUST be an array (e.g., ["hello", "world"])
134-
new Flow<string[]>({ slug: 'process_strings' })
134+
new Flow<string[]>({ slug: 'processStrings' })
135135
.map(
136136
{ slug: 'uppercase' }, // No array: property!
137137
(item) => item.toUpperCase()
138138
);
139139
// Each string in the input array gets uppercased in parallel
140140

141141
// DEPENDENT MAP - array: property specifies the source step
142-
new Flow<{}>({ slug: 'data_pipeline' })
142+
new Flow<{}>({ slug: 'dataPipeline' })
143143
.array({ slug: 'numbers' }, () => [1, 2, 3])
144144
.map(
145145
{ slug: 'double', array: 'numbers' }, // Processes 'numbers' output
@@ -166,7 +166,7 @@ The `.map()` method provides full TypeScript type inference for array elements:
166166
```typescript
167167
type User = { id: number; name: string };
168168

169-
new Flow<{}>({ slug: 'user_flow' })
169+
new Flow<{}>({ slug: 'userFlow' })
170170
.array({ slug: 'users' }, (): User[] => [
171171
{ id: 1, name: 'Alice' },
172172
{ id: 2, name: 'Bob' }
@@ -181,7 +181,7 @@ new Flow<{}>({ slug: 'user_flow' })
181181

182182
```typescript
183183
// Batch processing - process multiple items in parallel
184-
new Flow<number[]>({ slug: 'batch_processor' })
184+
new Flow<number[]>({ slug: 'batchProcessor' })
185185
.map({ slug: 'validate' }, (item) => {
186186
if (item < 0) throw new Error('Invalid item');
187187
return item;
@@ -192,9 +192,9 @@ new Flow<number[]>({ slug: 'batch_processor' })
192192
});
193193

194194
// Data transformation pipeline
195-
new Flow<{}>({ slug: 'etl_pipeline' })
196-
.step({ slug: 'fetch_urls' }, () => ['url1', 'url2', 'url3'])
197-
.map({ slug: 'scrape', array: 'fetch_urls' }, async (url) => {
195+
new Flow<{}>({ slug: 'etlPipeline' })
196+
.step({ slug: 'fetchUrls' }, () => ['url1', 'url2', 'url3'])
197+
.map({ slug: 'scrape', array: 'fetchUrls' }, async (url) => {
198198
return await fetchContent(url);
199199
})
200200
.map({ slug: 'extract', array: 'scrape' }, (html) => {
@@ -271,7 +271,7 @@ To use Supabase resources, import the `Flow` class from the Supabase preset:
271271
import { Flow } from '@pgflow/dsl/supabase';
272272

273273
const MyFlow = new Flow<{ userId: string }>({
274-
slug: 'my_flow',
274+
slug: 'myFlow',
275275
}).step({ slug: 'process' }, async (input, context) => {
276276
// TypeScript knows context includes Supabase resources
277277
const { data } = await context.supabase
@@ -300,7 +300,7 @@ Configure flows and steps with runtime options:
300300

301301
```typescript
302302
new Flow<Input>({
303-
slug: 'my_flow', // Required: Unique flow identifier
303+
slug: 'myFlow', // Required: Unique flow identifier
304304
maxAttempts: 3, // Optional: Maximum retry attempts (default: 1)
305305
baseDelay: 5, // Optional: Base delay in seconds for retries (default: 1)
306306
timeout: 10, // Optional: Task timeout in seconds (default: 30)

pkgs/edge-worker/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import { Flow } from 'jsr:@pgflow/dsl/supabase';
6363

6464
// Define a flow using Supabase preset for Supabase resources
6565
const AnalyzeWebsite = new Flow<{ url: string }>({
66-
slug: 'analyze_website',
66+
slug: 'analyzeWebsite',
6767
})
6868
.step({ slug: 'fetch' }, async (input, context) => {
6969
// Access Supabase resources through context
@@ -172,7 +172,7 @@ When defining flows that use Supabase resources, import `Flow` from the Supabase
172172
```typescript
173173
import { Flow } from 'jsr:@pgflow/dsl/supabase';
174174

175-
const MyFlow = new Flow<InputType>({ slug: 'my_flow' }).step(
175+
const MyFlow = new Flow<InputType>({ slug: 'myFlow' }).step(
176176
{ slug: 'process' },
177177
async (input, context) => {
178178
// TypeScript knows context includes all Supabase resources

pkgs/website/astro.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ export default defineConfig({
339339
label: 'Context object',
340340
link: '/concepts/context-object/',
341341
},
342-
{ label: 'Naming steps', link: '/concepts/naming-steps/' },
342+
{ label: 'Naming conventions', link: '/concepts/naming-conventions/' },
343343
],
344344
},
345345
],

pkgs/website/redirects.config.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ export const redirects = {
8989
'/how-to/manual-installation/': '/reference/manual-installation/',
9090
'/how-to/manually-compile-flow/': '/reference/compile-api/',
9191
'/how-to/monitor-flow-execution/': '/deploy/monitor-execution/',
92-
'/how-to/naming-steps/': '/concepts/naming-steps/',
92+
'/how-to/naming-steps/': '/concepts/naming-conventions/',
93+
'/concepts/naming-steps/': '/concepts/naming-conventions/',
9394
'/how-to/organize-flows-code/': '/build/organize-flow-code/',
9495
'/how-to/prepare-db-string/': '/deploy/connection-string/',
9596
'/how-to/prune-old-records/': '/deploy/prune-records/',

pkgs/website/src/content/docs/build/create-reusable-tasks.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ To create task functions that can be used across multiple flows without tight co
6161
}
6262

6363
// Flow uses handler to adapt context to task parameters
64-
new Flow<{ userId: string }>({ slug: 'user_flow' })
64+
new Flow<{ userId: string }>({ slug: 'userFlow' })
6565
.step({ slug: 'profile' }, async (input) =>
6666
await fetchUserProfile(input.run.userId)
6767
)

pkgs/website/src/content/docs/build/delaying-steps.mdx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,20 @@ Onboarding emails with different start delays (all are root steps with no depend
3535

3636
```typescript
3737
new Flow({
38-
slug: 'user_onboarding',
38+
slug: 'userOnboarding',
3939
maxAttempts: 3,
4040
baseDelay: 1,
4141
})
4242
.step({
43-
slug: 'send_welcome_email',
43+
slug: 'sendWelcomeEmail',
4444
// Executes immediately when flow starts
4545
}, sendWelcomeHandler)
4646
.step({
47-
slug: 'send_day_3_tips',
47+
slug: 'sendDay3Tips',
4848
startDelay: 259200, // 3 days after flow starts
4949
}, sendTipsHandler)
5050
.step({
51-
slug: 'send_week_review',
51+
slug: 'sendWeekReview',
5252
startDelay: 604800, // 7 days after flow starts
5353
}, sendReviewHandler)
5454
```
@@ -59,16 +59,16 @@ Wait period after a specific step completes:
5959

6060
```typescript
6161
new Flow({
62-
slug: 'trial_conversion',
62+
slug: 'trialConversion',
6363
maxAttempts: 3,
6464
baseDelay: 1,
6565
})
6666
.step({
67-
slug: 'provision_trial',
67+
slug: 'provisionTrial',
6868
}, provisionHandler)
6969
.step({
70-
slug: 'send_upgrade_reminder',
71-
dependsOn: ['provision_trial'],
70+
slug: 'sendUpgradeReminder',
71+
dependsOn: ['provisionTrial'],
7272
startDelay: 1209600, // 14 days after trial provisioning completes
7373
}, reminderHandler)
7474
```

0 commit comments

Comments
 (0)