proofkit-fmodata
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseProofKit FMOData
ProofKit FMOData
Type-safe ORM for FileMaker's OData API with TypeScript code generation.
为FileMaker的OData API打造的类型安全ORM,支持TypeScript代码生成。
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
如需获取最新文档,请访问proofkit.dev:
- FMOData:
https://proofkit.dev/llms/fmodata - Typegen:
https://proofkit.dev/llms/typegen - 所有包:
https://proofkit.dev/llms-full.txt
Quick Setup
快速设置
bash
undefinedbash
undefined1. Install packages
1. 安装包
pnpm add @proofkit/fmodata@beta @proofkit/typegen
pnpm add @proofkit/fmodata@beta @proofkit/typegen
2. Create config (proofkit-typegen.config.jsonc)
2. 创建配置文件(proofkit-typegen.config.jsonc)
npx @proofkit/typegen init
npx @proofkit/typegen init
3. Set env vars
3. 设置环境变量
FM_SERVER=https://your-server.com
FM_DATABASE=YourDatabase.fmp12
OTTO_API_KEY=your-api-key # or FM_USERNAME/FM_PASSWORD
FM_SERVER=https://your-server.com
FM_DATABASE=YourDatabase.fmp12
OTTO_API_KEY=your-api-key # 或使用FM_USERNAME/FM_PASSWORD
4. Generate types
4. 生成类型
npx @proofkit/typegen generate
npx @proofkit/typegen generate
5. Or use interactive UI
5. 或使用交互式UI
npx @proofkit/typegen ui
undefinednpx @proofkit/typegen ui
undefinedDefine Tables
定义表
typescript
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"],
});typescript
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
查询模式
typescript
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"))typescript
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");
// 带筛选条件的列表查询
const result = await db.from(Users).list()
.where(and(eq(Users.active, true), gt(Users.age, 18)))
.orderBy(asc(Users.name))
.top(10)
.execute();
// 获取单条记录
const user = await db.from(Users).get("user-123").execute();
// 选择特定字段
const result = await db.from(Users).list()
.select({ userId: Users.id, userName: Users.name })
.execute();
// 字符串筛选器
.where(contains(Users.email, "@example.com"))
.where(startsWith(Users.name, "John"))CRUD Operations
CRUD操作
typescript
// 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();typescript
// 插入记录
const result = await db.from(Users)
.insert({ name: "John", email: "john@example.com" })
.execute();
// 更新记录
const result = await db.from(Users)
.update({ name: "Jane" })
.byId("user-123")
.execute();
// 删除记录
const result = await db.from(Users)
.delete()
.byId("user-123")
.execute();
// 批量操作(原子性)
const result = await db.batch([
db.from(Users).list().top(10),
db.from(Users).insert({ name: "Alice", email: "alice@example.com" }),
]).execute();Relationships
关系操作
typescript
// 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();typescript
// 展开关联记录
const result = await db.from(Users).list()
.expand(Contacts, (b) =>
b.select({ name: Contacts.name })
.where(eq(Contacts.active, true))
)
.execute();
// 从记录导航到关联对象
const result = await db.from(Contacts).get("contact-123")
.navigate(Users)
.select({ username: Users.username })
.execute();Error Handling
错误处理
typescript
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");
}
}typescript
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("未找到资源");
if (result.error.is5xx()) console.log("服务器错误");
} else if (result.error instanceof ValidationError) {
console.log("验证失败:", result.error.issues);
} else if (result.error instanceof TimeoutError) {
console.log("请求超时");
}
}Troubleshooting
故障排查
Connection Issues
连接问题
"Unauthorized" or 401 errors
- Verify or
OTTO_API_KEY/FM_USERNAMEenv varsFM_PASSWORD - Ensure FM account has privilege enabled
fmodata - Check OData service is enabled on FM Server
"Not Found" or 404 errors
- Verify database name includes extension
.fmp12 - Check table/layout name matches exactly (case-sensitive)
- Ensure OData is enabled for the table occurrence
"Unauthorized"或401错误
- 验证或
OTTO_API_KEY/FM_USERNAME环境变量是否正确FM_PASSWORD - 确保FileMaker账户已启用权限
fmodata - 检查FM Server上的OData服务是否已启用
"Not Found"或404错误
- 验证数据库名称是否包含扩展名
.fmp12 - 检查表/布局名称是否完全匹配(区分大小写)
- 确保该表布局已启用OData访问
Type Generation Issues
类型生成问题
typegen can't connect
- Run to debug interactively
npx @proofkit/typegen ui - Check connection health indicator in UI
- Verify env vars are loaded (check flag)
--env-path
Generated types don't match FM schema
- Re-run after FM schema changes
npx @proofkit/typegen generate - Use to recreate override files
--reset-overrides - Check field type mappings in config
typegen无法连接
- 运行进行交互式调试
npx @proofkit/typegen ui - 检查UI中的连接健康状态指示器
- 验证环境变量是否已加载(可使用参数指定路径)
--env-path
生成的类型与FM模式不匹配
- 在FM模式变更后重新运行
npx @proofkit/typegen generate - 使用参数重新创建覆盖文件
--reset-overrides - 检查配置中的字段类型映射
Query Issues
查询问题
"Field not found" errors
- Ensure field is defined in
fmTableOccurrence - Check matches FM field ID (use typegen to auto-generate)
entityId - Verify field is on the OData-exposed table occurrence
Validation errors on read/write
- Check /
readValidatorschemas match FM data typeswriteValidator - FM stores booleans as 0/1 numbers - use coercion validators
- Empty strings may need or
.catch("").nullable()
"Field not found"错误
- 确保字段已在中定义
fmTableOccurrence - 检查是否与FileMaker字段ID匹配(可使用typegen自动生成)
entityId - 验证该字段是否在OData暴露的表布局中
读写操作时出现验证错误
- 检查/
readValidator模式是否与FileMaker数据类型匹配writeValidator - FileMaker将布尔值存储为0/1数字 - 使用强制转换验证器
- 空字符串可能需要使用或
.catch("")处理.nullable()
Performance Issues
性能问题
Slow queries
- Add to limit results
.top(n) - Use to fetch only needed fields
.select() - Avoid expanding large related record sets
查询缓慢
- 添加限制结果数量
.top(n) - 使用仅获取所需字段
.select() - 避免展开大量关联记录集
References
参考资料
- fmodata-api.md - Complete API reference: field builders, operators, query methods
- typegen-config.md - Configuration options and examples
- fmodata-api.md - 完整API参考:字段构建器、操作符、查询方法
- typegen-config.md - 配置选项及示例