Enforce lowercase snake_case for all machine identifiers#245
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…se naming Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…case naming Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…ke_case) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This pull request enforces lowercase snake_case for all machine identifiers across the ObjectStack specification to ensure cross-platform compatibility, URL-friendliness, database consistency, and security.
Changes:
- Introduces three new shared identifier schemas (
SystemIdentifierSchema,SnakeCaseIdentifierSchema,EventNameSchema) with strict validation rules that reject leading underscores, uppercase letters, and other invalid characters - Updates machine identifier fields across UI (app, page, view, action), Auth (role), Permission (permission set, territory), Automation (workflow, webhook), System (events), and Data (field, mapping) modules to use the new schemas
- Adds auto-conversion logic to
Field.select()helper to automatically transform option values to lowercase snake_case
Reviewed changes
Copilot reviewed 70 out of 70 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/shared/identifiers.zod.ts | New centralized schemas for system identifiers with comprehensive documentation |
| src/shared/index.ts | Exports the new identifier schemas |
| src/ui/*.zod.ts | Updates app, page, view, and action schemas to use SnakeCaseIdentifierSchema |
| src/auth/role.zod.ts | Updates role names to use SnakeCaseIdentifierSchema |
| src/permission/*.zod.ts | Updates permission set and territory names to use SnakeCaseIdentifierSchema |
| src/automation/*.zod.ts | Updates workflow and webhook names to use SnakeCaseIdentifierSchema |
| src/system/events.zod.ts | Updates event names to use EventNameSchema (allows dots) |
| src/data/field.zod.ts | Updates SelectOption.value to use SystemIdentifierSchema and adds auto-conversion in Field.select() |
| src/data/mapping.zod.ts | Updates mapping names to use SnakeCaseIdentifierSchema |
| *.test.ts | Updates tests to verify leading underscores are now rejected |
| json-schema/* | Updates generated JSON schemas with consistent patterns and descriptions |
| content/docs/* | Updates documentation to reflect new naming requirements |
| options = optionsOrConfig.map(o => | ||
| typeof o === 'string' | ||
| ? { label: o, value: toSnakeCase(o) } // Auto-convert string to snake_case | ||
| : { ...o, value: o.value.toLowerCase() } // Ensure value is lowercase |
There was a problem hiding this comment.
When a SelectOption object is provided (not a string), the code only applies .toLowerCase() to the value. This won't convert spaces, hyphens, or other invalid characters to valid snake_case format. For example, { label: 'Test', value: 'My Value' } becomes { label: 'Test', value: 'my value' }, which will fail the SystemIdentifierSchema validation because spaces are not allowed.
Consider applying the same toSnakeCase() transformation to object values: { ...o, value: toSnakeCase(o.value) }
| options = (optionsOrConfig.options || []).map(o => | ||
| typeof o === 'string' | ||
| ? { label: o, value: toSnakeCase(o) } // Auto-convert string to snake_case | ||
| : { ...o, value: o.value.toLowerCase() } // Ensure value is lowercase |
There was a problem hiding this comment.
Same issue as line 282: When a SelectOption object is provided (not a string), only .toLowerCase() is applied. This won't convert invalid characters like spaces or hyphens to valid snake_case format, causing validation failures.
Consider applying the toSnakeCase() transformation: { ...o, value: toSnakeCase(o.value) }
| const toSnakeCase = (str: string): string => { | ||
| return str | ||
| .toLowerCase() | ||
| .replace(/\s+/g, '_') // Replace spaces with underscores | ||
| .replace(/[^a-z0-9_]/g, ''); // Remove invalid characters (keeping underscores only) |
There was a problem hiding this comment.
The toSnakeCase helper removes all non-alphanumeric characters except underscores (line 268), which means it strips dots. However, SelectOption.value uses SystemIdentifierSchema which explicitly allows dots in its pattern /^[a-z][a-z0-9_.]*$/.
This creates an inconsistency: if someone passes a string like "user.status", it gets converted to "userstatus" by this function, even though "user.status" would be valid according to the schema.
Consider whether SelectOption values should allow dots (like events), or if they should strictly be snake_case without dots. If dots should not be allowed for select options, use SnakeCaseIdentifierSchema instead of SystemIdentifierSchema for the value field. If dots should be allowed, update the toSnakeCase function to preserve them.
| label: z.string().describe('Display label'), | ||
| value: z.string().describe('Stored value'), | ||
| label: z.string().describe('Display label (human-readable, any case allowed)'), | ||
| value: SystemIdentifierSchema.describe('Stored value (lowercase machine identifier)'), |
There was a problem hiding this comment.
The value field uses SystemIdentifierSchema, which allows dots in identifiers (pattern: /^[a-z][a-z0-9_.]*$/). However, select option values are typically stored in database columns and should use strict snake_case without dots (e.g., 'in_progress', 'closed_won' as shown in the examples above).
Consider using SnakeCaseIdentifierSchema instead, which enforces lowercase snake_case without dots. This would be more appropriate for database-stored enum values and would align with the documented examples.
| * @example Invalid | ||
| * - 'UserCreated' (camelCase) | ||
| * - 'user_created' (should use dots for namespacing) |
There was a problem hiding this comment.
The comment states that 'user_created' is invalid and "should use dots for namespacing", but the regex pattern /^[a-z][a-z0-9_.]*$/ actually accepts underscores, so 'user_created' would pass validation.
This comment is misleading. If the intent is to strongly encourage dots for namespacing in event names, consider either:
- Changing this to an "@example Discouraged" section rather than "Invalid"
- Or if dots should be required, update the regex to enforce at least one dot (e.g.,
/^[a-z][a-z0-9_]*\.[a-z0-9_.]+$/)
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Machine identifiers (object names, field names, roles, permissions, actions, etc.) now require lowercase snake_case. Event names use dot notation (
user.created).Changes
New schemas (
src/shared/identifiers.zod.ts):SystemIdentifierSchema- allows underscores and dots:/^[a-z][a-z0-9_.]*$/SnakeCaseIdentifierSchema- strict snake_case:/^[a-z][a-z0-9_]*$/EventNameSchema- event names with dots:/^[a-z][a-z0-9_.]*$/Updated schemas:
SelectOption.value,Mapping.name,Field.select()helperRole.name,PermissionSet.name,Territory.nameWorkflowRule.name,Webhook.name,Event.nameApp.name,Page.name,Action.name,NavigationItem.id,ListView.nameField.select() auto-conversion:
Examples
Impact
All machine identifiers must now be lowercase. Labels remain case-insensitive for display.
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.