@proofkit/fmodata
Version:
FileMaker OData API client
438 lines (340 loc) • 11.9 kB
Markdown
---
name: typegen-fmodata
description: >
ENTRY POINT for /fmodata projects. Generate TypeScript table schemas
with entity IDs from FileMaker OData metadata using /typegen. Covers
proofkit-typegen-config.jsonc for OData mode, npx /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 `/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 /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 /typegen 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 /typegen
```
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 /fmodata 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 /typegen
```
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 /typegen
```
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 /typegen
```
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
```
`/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.