# 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, use the generated lifecycle and `executeTransition` helper from `@tailor-platform/erp-kit/core`:

```typescript
const result = await executeTransition({
  db,
  tableName: "User",
  statusField: "status",
  id: input.id,
  transition: "activate",
  lifecycle: userLifecycle,
  errors: { notFound: UserNotFoundError, invalidTransition: InvalidStateTransitionError },
});
if (!result.ok) return result;
return ok({ user: result.value });
```

For transitions that need extra validation or touch other tables, use `lifecycle.tryTransition(currentState, transition)` inline and handle the update manually. Valid source states are defined once in the `State Transitions` table of the model doc and cannot be overridden at call time.
