---
name: typegen-fmodata
description: >
  ENTRY POINT for @proofkit/fmodata projects. Generate TypeScript table schemas
  with entity IDs from FileMaker OData metadata using @proofkit/typegen. Covers
  proofkit-typegen-config.jsonc for OData mode, npx @proofkit/typegen setup,
  fmTableOccurrence generation, entity IDs (FMFID/FMTID), generated output
  structure, field exclusion, type overrides, InferTableSchema, env var
  configuration, OData prerequisites, fmodata privilege, and why typegen is
  required for entity ID correctness.
metadata:
  type: core
  library: proofkit
  library_version: "0.1.2"
sources:
  - "proofsh/proofkit:packages/typegen/src/cli.ts"
  - "proofsh/proofkit:packages/typegen/src/fmodata/typegen.ts"
  - "proofsh/proofkit:apps/docs/content/docs/typegen/*.mdx"
  - "proofsh/proofkit:apps/docs/content/docs/fmodata/quick-start.mdx"
---

> **ALWAYS use `@proofkit/typegen` to set up an @proofkit/fmodata project.**
> Typegen generates `fmTableOccurrence` definitions with correct field types
> and entity IDs (`FMFID`/`FMTID`) from FileMaker metadata. These entity IDs
> are opaque identifiers that cannot be guessed — they MUST come from typegen.
> Without typegen, queries using `useEntityIds: true` will silently fail or
> return wrong data. Before writing any fmodata query code, run typegen first.

## Prerequisites

- **FileMaker Server 22.0.4+** accessible via port 443
- **OttoFMS 4.11+** installed on the FM server (only required for API key auth)
- **FileMaker account** with the `fmodata` extended privilege enabled
- **OData API enabled** on the FileMaker server

## Setup

### 1. Install the runtime package

```bash
npm add @proofkit/fmodata
```

### 2. Set environment variables

```bash
# .env
FM_SERVER=https://your-server.com     # must start with https://
FM_DATABASE=MyFile.fmp12              # must end with .fmp12

# Option A: API key auth (requires OttoFMS 4.11+)
OTTO_API_KEY=dk_123456...789
# Option B: username/password auth
FM_USERNAME=admin
FM_PASSWORD=password
```

### 3. Initialize and run typegen

```bash
npx @proofkit/typegen@beta init
```

This creates `proofkit-typegen-config.jsonc`. Configure for OData mode:

```jsonc
{
  "$schema": "https://proofkit.proof.sh/typegen-config-schema.json",
  "config": {
    "type": "fmodata",
    "path": "schema/odata",
    "tables": [
      {
        "tableName": "Customers",
        "fields": [
          { "fieldName": "InternalID", "exclude": true },
          { "fieldName": "Status", "typeOverride": "boolean" }
        ]
      },
      { "tableName": "Orders", "variableName": "OrdersTable" }
    ]
  }
}
```

Run typegen to generate schemas:

```bash
npx @proofkit/typegen@beta
```

Add a convenience script to `package.json`:

```json
{
  "scripts": {
    "typegen": "npx @proofkit/typegen@beta"
    "typegen:ui": "npm run tyepgen ui"
  }
}
```

### 4. Create a server connection

```ts
import { FMServerConnection } from "@proofkit/fmodata";

export const connection = new FMServerConnection({
  serverUrl: process.env.FM_SERVER,
  auth: {
    apiKey: process.env.OTTO_API_KEY,
  },
});
```

### 5. First query

```ts
import { connection } from "./connection";
import { Customers } from "./schema/odata/generated/Customers";

const db = connection.database(process.env.FM_DATABASE);

const { data, error } = await db.from(Customers).list().execute();

if (error) {
  console.error(error);
} else {
  console.log(data);
}
```

## Core Patterns

### Generated output structure

For OData configs (`"type": "fmodata"`), typegen generates `fmTableOccurrence` definitions:

```text
schema/odata/
  generated/      # Auto-generated. NEVER edit fields or entity IDs.
    Customers.ts  # fmTableOccurrence with fields, entity IDs from FM metadata
    Orders.ts
```

### What typegen generates

Each generated file exports a `fmTableOccurrence` with:
- Field definitions with correct types (`textField()`, `numberField()`, etc.)
- Entity IDs (`FMFID`/`FMTID`) from FileMaker metadata
- Primary key designation
- Read-only markers for auto-enter fields

```ts
// schema/odata/generated/Customers.ts — generated by typegen, do NOT edit
import { fmTableOccurrence, textField, numberField, timestampField } from "@proofkit/fmodata";

export const Customers = fmTableOccurrence(
  "Customers",
  {
    id: textField().primaryKey().entityId("FMFID:1039485"),
    name: textField().notNull().entityId("FMFID:3432343"),
    email: textField().notNull().entityId("FMFID:1223242"),
    age: numberField().entityId("FMFID:4323435"),
    createdAt: timestampField().readOnly().entityId("FMFID:5938271"),
  },
  {
    entityId: "FMTID:1243253",
    defaultSelect: "schema",
  },
);
```

### Safe customizations on generated schemas

You may add or edit these options on generated schemas — they will NOT be overwritten by typegen:
- `readValidator` — transform data on read (e.g., `z.coerce.boolean()`)
- `writeValidator` — transform data on write
- `defaultSelect` — control default field selection
- `navigationPaths` — define relationship navigation

```ts
// Safe to add to generated file:
active: numberField()
  .readValidator(z.coerce.boolean())
  .writeValidator(z.boolean().transform((v) => (v ? 1 : 0)))
  .entityId("FMFID:6"),
```

Do NOT manually add new fields or change entity IDs — these must come from typegen.

### Type inference

Use `InferTableSchema` to extract row types from generated tables:

```ts
import type { InferTableSchema } from "@proofkit/fmodata";
import { Customers } from "./schema/odata/generated/Customers";

type CustomerRow = InferTableSchema<typeof Customers>;
```

### Custom env variable names

```jsonc
{
  "config": {
    "type": "fmodata",
    "envNames": {
      "server": "MY_FM_SERVER",
      "db": "MY_FM_DATABASE",
      "auth": {
        "apiKey": "MY_OTTO_KEY"
      }
    },
    "tables": [
      { "tableName": "Customers" }
    ]
  }
}
```

### Multiple configs

The `config` key can be an array mixing `fmdapi` and `fmodata` entries, each with its own `path` and `envNames`.

### Config options for OData tables

```jsonc
{
  "config": {
    "type": "fmodata",
    "path": "schema/odata",
    "reduceMetadata": true,
    "tables": [
      {
        "tableName": "Customers",
        "variableName": "CustomersTable",
        "fields": [
          { "fieldName": "InternalID", "exclude": true },
          { "fieldName": "Status", "typeOverride": "boolean" },
          { "fieldName": "Notes", "typeOverride": "string" }
        ]
      }
    ]
  }
}
```

- `variableName` — custom export name (default: table name)
- `fields[].exclude` — omit field from generated schema
- `fields[].typeOverride` — override the inferred field type

## Common Mistakes

### CRITICAL: Inventing entity IDs or guessing field names

Wrong:
```ts
// Agent adds a field with a guessed entity ID
const contacts = fmTableOccurrence("contacts", {
  ...existingFields,
  newField: textField().entityId("FMFID:99"), // guessed ID — will silently fail
});
```

Entity IDs (`FMFID`/`FMTID`) are opaque and MUST come from FileMaker metadata via typegen. Guessed entity IDs cause silent query failures — wrong data or empty results with no error.

To add new fields, use the fmodata CLI to discover real field names programmatically, then add them to the `tables[].fields` array in `proofkit-typegen-config.jsonc`, and re-run typegen to generate the correct definitions with entity IDs:

```bash
# 1. Discover available fields via the CLI
npx @proofkit/fmodata@beta metadata fields --table <TableName>
# Add --details for field types, nullability, etc.

# 2. Edit proofkit-typegen-config.jsonc to include the new field(s)

# 3. Re-run typegen to regenerate schemas with correct entity IDs
npx @proofkit/typegen@beta
```

Never manually edit entity IDs or field definitions in generated files — always re-run typegen after config changes.

Source: packages/typegen/src/fmodata/typegen.ts

### CRITICAL: Manually redefining types instead of using generated/inferred types

Wrong:
```ts
interface Customer {
  name: string;
  email: string;
  phone: string;
}
```

Correct:
```ts
import type { InferTableSchema } from "@proofkit/fmodata";
import { Customers } from "./schema/odata/generated/Customers";

type CustomerRow = InferTableSchema<typeof Customers>;
```

fmodata infers all types from `fmTableOccurrence` definitions. Use `InferTableSchema<typeof table>` if you need an explicit type alias.

Source: packages/typegen/src/buildSchema.ts

### CRITICAL: Writing fmTableOccurrence from scratch without typegen

Wrong:
```ts
// Manually defining schema without entity IDs
const users = fmTableOccurrence("users", {
  id: textField().primaryKey(),
  name: textField().notNull(),
  email: textField().notNull(),
});
```

Correct:
```bash
npx @proofkit/typegen@beta
```

Manual schemas lack entity IDs, meaning `useEntityIds: true` (the recommended database option) won't work. Queries will use field names instead of stable IDs, breaking silently when fields are renamed in FileMaker. Always generate schemas with typegen.

Source: apps/docs/content/docs/fmodata/entity-ids.mdx

### HIGH: Omitting type discriminator for OData config

Wrong:
```jsonc
{
  "config": {
    "tables": [
      { "tableName": "Customers" }
    ]
  }
}
```

Correct:
```jsonc
{
  "config": {
    "type": "fmodata",
    "tables": [
      { "tableName": "Customers" }
    ]
  }
}
```

Without `"type": "fmodata"`, the config defaults to `"fmdapi"` and expects a `layouts` array instead of `tables`, causing a validation error.

Source: packages/typegen/src/types.ts:238-243

### HIGH: Not running typegen after FileMaker schema changes or config edits

Wrong:
```ts
// Manually editing a generated file to add a new field
// OR editing proofkit-typegen-config.jsonc without re-running typegen
```

Correct:
```bash
npx @proofkit/typegen@beta
```

After changing table schemas in FileMaker or editing `proofkit-typegen-config.jsonc`, re-run typegen to regenerate types. The generated schemas are the source of truth for field names, types, and entity IDs.

Source: packages/typegen/src/fmodata/typegen.ts

### CRITICAL: Putting env var values instead of names in config

Wrong:
```jsonc
{
  "envNames": {
    "server": "https://my-server.com",
    "auth": {
      "apiKey": "dk_abc123secret"
    }
  }
}
```

Correct:
```jsonc
{
  "envNames": {
    "server": "MY_FM_SERVER",
    "auth": {
      "apiKey": "MY_OTTO_KEY"
    }
  }
}
```

`envNames` expects the names of environment variables, not their secret values; typegen reads the actual values from the environment at runtime.

Source: apps/docs/content/docs/typegen/config.mdx

### HIGH: Mixing Zod v3 and v4 in the same project

Use one Zod version consistently (v4 recommended). Zod v3 and v4 have incompatible APIs; mixing them causes runtime validation failures.

Source: apps/docs/content/docs/typegen/config.mdx

### CRITICAL: FM_SERVER without https:// prefix

Wrong:
```bash
FM_SERVER=filemaker.example.com
```

Correct:
```bash
FM_SERVER=https://filemaker.example.com
```

`@proofkit/fmodata` expects `FM_SERVER` to be a full URL including the `https://` protocol prefix.

Source: apps/docs/content/docs/fmdapi/quick-start.mdx

### CRITICAL: Missing fmodata privilege on FM account

The OData API requires the `fmodata` extended privilege on the FileMaker account. Without it, all API calls return authorization errors. Enable via File > Manage > Security > Privilege Sets > Extended Privileges.

Source: apps/docs/content/docs/cli/guides/getting-started.mdx

## References

- **fmodata-client**: After generating schemas with typegen, see the fmodata-client skill for full query builder usage, CRUD operations, filter operators, relationships, batch operations, and error handling.
- **odata-query-optimization**: Performance patterns for fmodata queries — defaultSelect, pagination, batch operations, and entity ID usage.
