# Resolver & Executor Patterns

## Resolver Patterns

All resolvers export a default `createResolver()` from `@tailor-platform/sdk`.

### Verify command input types

Before implementing a resolver, read the command's type definition:

```
node_modules/@tailor-platform/erp-kit/src/modules/<module>/command/<commandName>.ts
```

The command source is the ground truth. Do not invent fields that don't exist, and preserve the required/optional distinction.

### SDK type system

See [Resolver docs](https://raw.githubusercontent.com/tailor-platform/sdk/refs/heads/main/packages/sdk/docs/services/resolver.md) for the `t` namespace.

`t.array()` and `t.boolean()` do **not exist**. Never attempt to use them.

### Command-based mutation

The most common pattern. Key points:

```ts
import { createContext } from "@tailor-platform/erp-kit/app";

body: async (context) => {
  const db = getDB("main-db");
  const result = await imCommands.createItem(db, { ...context.input }, createContext(context));
  if (!result.ok) throw result.error;
  return { id: result.value.item.id, ... };
},
```

- `context.input.optionalField ?? undefined` — Convert null to undefined for command inputs
- `result.value.entity.nullableField ?? ""` — Handle nullable return values
- `createContext(context)` wraps the resolver context for erp-kit commands
- `getDB("main-db")` — Namespace must match `tailor.config.ts`

### Error handling

Resolver specs document error codes the command can return. When codes are documented, add a `case` for every documented code — generic `throw result.error` loses context for the frontend. The error code is a discriminated union, so TypeScript enforces exhaustive handling without a `default` branch.

**Do this:**

```ts
if (!result.ok) {
  switch (result.error.code) {
    case "INSUFFICIENT_STOCK":
      throw new Error(`Stock insufficient for item ${context.input.itemId}`);
    case "ORDER_ALREADY_CONFIRMED":
      throw new Error("Cannot modify a confirmed order");
  }
}
```

**Not this:**

```ts
if (!result.ok) throw result.error;
```

Do not directly mutate module-owned tables via Kysely — always use module commands.

---

## Executor Patterns

Executors re-export module-provided event handlers. One file per executor, no custom logic:

```ts
import { rolePermissionCreated } from "@/modules";
export default rolePermissionCreated;
```
