# Command Implementation

## Unified Pattern: `run` + generated shell

All commands follow the same pattern — export a `run` function, and the generated shell wraps it with `defineCommand`:

**Implementation file** (`command/myCommand.ts`):

```typescript
export async function run(db: DB, input: MyCommandInput, ctx: CommandContext) {
  // validate → query → mutate
  return ok({ entity });
}
```

**Generated shell** (`command/myCommand.generated.ts`):

```typescript
export const myCommand = defineCommand(permissions.myCommand, run);
```

## Custom Fields (generic CF)

Commands that **write** (insert or update) into a table with user-extensible fields make `run` generic:

- Generic `CF extends Record<string, unknown>` on the `run` function
- Input type: `CreateXInput & CF` or `UpdateXInput & Partial<CF>`
- Destructure known fields, rest-spread custom fields
- Cast custom fields: `...(customFields as Record<string, unknown>)`
- module.ts locks CF via instantiation expression: `const createXTyped = createX<FieldsToInsertable<F>>;`

### Rule: when to use generic CF

> If a model uses `fields?: F` for custom fields, **every command that writes those fields** must have a generic `CF`. This includes create, update, and any other write command. The determining factor is whether the command writes to a table with custom fields, not just whether it inserts new rows.

## Implementation Considerations

- **Validation**: Check referenced entities exist before operating
- **Idempotency**: For assign/revoke, return existing instead of throwing
- **Return format**: Wrap in object `{ entity }` not just `entity`

## Conventions

- Input types: exported interfaces (`export interface MyFunctionInput`)
- Use `.executeTakeFirst()` for single results
- Include JSDoc: `/** Function: name \n Description */`

## State Transitions

For commands that transition between statuses, accept `from?: string[]` with a default:

```typescript
from?: string[];  // Default: ["ACTIVE"]

const validFromStatuses = input.from ?? ["ACTIVE"];
if (!validFromStatuses.includes(user.status)) {
  return err(new InvalidStatusTransitionError(user.status, targetStatus));
}
```

- Default `from` contains the base valid source status
- Parent modules can override to allow transitions from additional statuses
