npx skills add https://github.com/proofgeist/proofkit --skill proofkit-fmodataSKILL.md
ProofKit FMOData
Type-safe ORM for FileMaker's OData API with TypeScript code generation.
Up-to-Date Documentation
For the latest docs, fetch from proofkit.dev:
- FMOData:
https://proofkit.dev/llms/fmodata - Typegen:
https://proofkit.dev/llms/typegen - All packages:
https://proofkit.dev/llms-full.txt
Quick Setup
# 1. Install packages
pnpm add @proofkit/fmodata@beta @proofkit/typegen
# 2. Create config (proofkit-typegen.config.jsonc)
npx @proofkit/typegen init
# 3. Set env vars
FM_SERVER=https://your-server.com
FM_DATABASE=YourDatabase.fmp12
OTTO_API_KEY=your-api-key # or FM_USERNAME/FM_PASSWORD
# 4. Generate types
npx @proofkit/typegen generate
# 5. Or use interactive UI
npx @proofkit/typegen ui
Define Tables
import { fmTableOccurrence, textField, numberField, timestampField } from "@proofkit/fmodata";
import { z } from "zod";
export const Users = fmTableOccurrence("Users", {
id: textField().primaryKey().entityId("FMFID:100001"),
name: textField().notNull(),
email: textField().notNull(),
active: numberField()
.readValidator(z.coerce.boolean())
.writeValidator(z.boolean().transform(v => v ? 1 : 0)),
createdAt: timestampField().readOnly(),
}, {
entityId: "FMTID:1000001",
navigationPaths: ["Contacts", "Orders"],
});
Query Patterns
import { FMServerConnection, eq, and, gt, asc, contains } from "@proofkit/fmodata";
const connection = new FMServerConnection({
serverUrl: process.env.FM_SERVER,
auth: { apiKey: process.env.OTTO_API_KEY }
});
const db = connection.database("MyDatabase.fmp12");
// List with filters
const result = await db.from(Users).list()
.where(and(eq(Users.active, true), gt(Users.age, 18)))
.orderBy(asc(Users.name))
.top(10)
.execute();
// Get single record
const user = await db.from(Users).get("user-123").execute();
// Select specific fields
const result = await db.from(Users).list()
.select({ userId: Users.id, userName: Users.name })
.execute();
// String filters
.where(contains(Users.email, "@example.com"))
.where(startsWith(Users.name, "John"))
CRUD Operations
// Insert
const result = await db.from(Users)
.insert({ name: "John", email: "john@example.com" })
.execute();
// Update
const result = await db.from(Users)
.update({ name: "Jane" })
.byId("user-123")
.execute();
// Delete
const result = await db.from(Users)
.delete()
.byId("user-123")
.execute();
// Batch operations (atomic)
const result = await db.batch([
db.from(Users).list().top(10),
db.from(Users).insert({ name: "Alice", email: "alice@example.com" }),
]).execute();
Relationships
// Expand related records
const result = await db.from(Users).list()
.expand(Contacts, (b) =>
b.select({ name: Contacts.name })
.where(eq(Contacts.active, true))
)
.execute();
// Navigate from a record
const result = await db.from(Contacts).get("contact-123")
.navigate(Users)
.select({ username: Users.username })
.execute();
Error Handling
import { isHTTPError, ValidationError, TimeoutError } from "@proofkit/fmodata";
const result = await db.from(Users).list().execute();
if (result.error) {
if (isHTTPError(result.error)) {
if (result.error.isNotFound()) console.log("Not found");
if (result.error.is5xx()) console.log("Server error");
} else if (result.error instanceof ValidationError) {
console.log("Validation failed:", result.error.issues);
} else if (result.error instanceof TimeoutError) {
console.log("Request timed out");
}
}
Troubleshooting
Connection Issues
"Unauthorized" or 401 errors
- Verify
OTTO_API_KEYorFM_USERNAME/FM_PASSWORDenv vars - Ensure FM account has
fmodataprivilege enabled - Check OData service is enabled on FM Server
"Not Found" or 404 errors
- Verify database name includes
.fmp12extension - Check table/layout name matches exactly (case-sensitive)
- Ensure OData is enabled for the table occurrence
Type Generation Issues
typegen can't connect
- Run
npx @proofkit/typegen uito debug interactively - Check connection health indicator in UI
- Verify env vars are loaded (check
--env-pathflag)
Generated types don't match FM schema
- Re-run
npx @proofkit/typegen generateafter FM schema changes - Use
--reset-overridesto recreate override files - Check field type mappings in config
Query Issues
"Field not found" errors
- Ensure field is defined in
fmTableOccurrence - Check
entityIdmatches FM field ID (use typegen to auto-generate) - Verify field is on the OData-exposed table occurrence
Validation errors on read/write
- Check
readValidator/writeValidatorschemas match FM data types - FM stores booleans as 0/1 numbers - use coercion validators
- Empty strings may need
.catch("")or.nullable()
Performance Issues
Slow queries
- Add
.top(n)to limit results - Use
.select()to fet
...
Repository
proofgeist/proofkitParent repository
Repository Stats
Stars8
Forks2
LicenseMIT License