npm package: @flatfile/implementation-utils-zod-adapter
Description: Provides a mechanism for passing Zod schemas to a bulkRecordHook for transformation and validation as well as general purpose utilities for working with Zod schemas.
Author: Flatfile
Latest version: 0.0.9

# @flatfile/implementation-utils-zod-adapter

Provides a mechanism for passing Zod schemas to a bulkRecordHook for transformation and validation as well as general purpose utilities for working with Zod schemas.

## Why Zod?

Zod provides a composable, type-safe, runtime schema system that maps cleanly onto TypeScript types. We can use Zod to:

1. Coerce and transform raw Flatfile record data into strongly-typed objects.
2. Validate business rules (length, pattern, enum membership, cross-field constraints).
3. Report errors/warnings back to Flatfile so end-users receive immediate feedback.
4. Keep compile-time types (`z.infer<Schema>`) in sync with runtime validation logic.

## Features

### The `zodRecordHookAdapter`

Location: `src/utils/zod/adapter.ts`

```ts
export const zodRecordHookAdapter = (
  transformSchema: z.ZodObject<…>,
  validationSchema: z.ZodObject,
): ((records: FlatfileRecord[]) => FlatfileRecord[]) => { … }
```

#### Responsibilities

| Stage         | Responsibility                                                                                                                                                                                                                 |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Transform** | • `partialSafeParse` runs against `transformSchema`.<br>• Valid fields are **immediately written** back to the Flatfile `record` via `record.set(key, value)`.<br>• Invalid fields are collected but do _not_ halt processing. |
| **Validate**  | • A second pass executes `validationSchema.safeParse(record.obj)`.<br>• All issues are converted into `record.addError` or `record.addWarning` calls.                                                                          |

#### Issue Handling

`adapter.ts` inspects every `ZodIssue`:

- `issue.code === "custom" && issue.params?.isWarning` → `addWarning`
- `issue.code === "custom" && issue.params?.isSilent` → skipped (useful for silent refinements)
- otherwise → `addError`

This allows blueprints to downgrade non-blocking rules to warnings without losing the rich error metadata Zod provides.

#### Partial-success Strategy

`partialSafeParse` categorises results as:

- `full` – every field valid
- `partial` – some fields valid; invalid ones returned separately
- `none` – fatal object-level issues

For `full | partial`, valid values are persisted so downstream hooks always operate on the _cleanest possible_ record shape.

### Helper: `partial-safe-parse.ts`

`partialSafeParse` is a small wrapper around Zod’s `safeParse` that lets us retrieve **only the good fields** when a record is partially invalid. This enables the transform-then-validate pattern described above.

Key utilities exported:

- `omit` / `pick` – thin Object helpers used internally.
- `safeParseResult` – ergonomic result-shaping helper.
- `partialSafeParse` – the workhorse that returns `{ successType, validData, invalidData }`.

## Usage

### Blueprint Conventions

Every sheet defines **two schemas**:

| File         | Variable                   | Purpose                                                                                                                     |
| ------------ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `*.sheet.ts` | `XYZSheetTransformSchema`  | Lightweight transformations (trimming, coercion, defaulting). Must **never** reject on its own unless absolutely necessary. |
| `*.sheet.ts` | `XYZSheetValidationSchema` | Strict field-level & cross-field rules. Rejects invalid data and surfaces meaningful messages.                              |

These schemas are then passed into the adapter inside the sheet’s `recordHook`:

```ts
space.sheet("Prescriber Information", {
  …,
  recordHook: zodRecordHookAdapter(
    prescriberInformationSheetTransformSchema,
    prescriberInformationSheetValidationSchema,
  ),
});
```

### Example snippet (Prescriber)

```ts
const prescriberInformationSheetValidationSchema = z.object({
  [NPI_NUMBER]: z.string().length(10),
  [START_DATE]: z.string().pipe(dateTransform),
  [END_DATE]: z.string().pipe(dateTransform).nullable(),
  [DEA_NUMBER]: recommendedSchema(
    z
      .string()
      .length(9)
      .regex(/^[A-Z]{2}\d{7}$/i)
      .nullable(),
    "DEA number is recommended"
  ),
});
```

Notes:

- `recommendedSchema` is a thin wrapper that attaches `{ isWarning: true }` so the adapter logs **warnings** instead of errors.
- `dateTransform` is applied in _validation_ to guarantee canonical `Date` objects for downstream jobs.

### Adding a New Sheet / Schema

1. **Create** `XYZSheetTransformSchema` & `XYZSheetValidationSchema` in the sheet file.
2. **Pipe** any common transforms (trim, upper-case, etc.) _before_ validation.
3. Prefer **literal enums** (`z.enum([...])`) over regex where possible for maintainability.
4. Pass both schemas into `zodRecordHookAdapter`.

## Future Enhancements

- Implement checks that rely on additional context/data from other sheets by dynamically constructing schemas.
- Implement checks that rely on metadata and additional FlatfileRecord context via `check` on the outermost object schema.

## References

- [Zod documentation](https://zod.dev)