Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/fresh-falcons-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@proofkit/fmodata": minor
---

Add `ROWID` record locator support to `fmodata` single-record APIs.

- Allow `db.from(table).get({ ROWID: 2 })`
- Add `update(data).byRowId(2)`
- Add `delete().byRowId(2)`
14 changes: 14 additions & 0 deletions apps/docs/content/docs/fmodata/crud.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ if (result.data) {
console.log(`Updated ${result.data.updatedCount} record(s)`);
}

// Update by ROWID
const byRowId = await db
.from(users)
.update({ username: "newname" })
.byRowId(2)
.execute();

// Update by filter (using ORM API)
import { lt, and, eq } from "@proofkit/fmodata";

Expand All @@ -85,6 +92,10 @@ const result = await db
All fields are optional for updates (except read-only fields which are automatically excluded). TypeScript will enforce that you can only update fields that aren't marked as read-only.
</Callout>

<Callout type="info">
For webhook hydrate flows, use `db.from(table).get({ ROWID })` when you only have FileMaker `ROWID` metadata.
</Callout>

## Delete

Delete records by ID or filter:
Expand All @@ -97,6 +108,9 @@ if (result.data) {
console.log(`Deleted ${result.data.deletedCount} record(s)`);
}

// Delete by ROWID
const byRowId = await db.from(users).delete().byRowId(2).execute();

// Delete by filter (using ORM API)
import { eq, and, lt } from "@proofkit/fmodata";

Expand Down
6 changes: 6 additions & 0 deletions apps/docs/content/docs/fmodata/extra-properties.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ const result = await db.from(users).list().execute({
});
```

Use `ROWID` to hydrate a record when a webhook payload only gives you system columns:

```typescript
const result = await db.from(users).get({ ROWID: 2 }).execute();
```

<Callout type="warning">
Special columns are only included when no `$select` query is applied (per OData specification). When using `.select()`, special columns are excluded even if `includeSpecialColumns` is enabled.
</Callout>
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/content/docs/fmodata/methods.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Quick reference for all available methods and operators in `@proofkit/fmodata`.
|--------|-------------|---------|
| `list()` | Retrieve multiple records | `db.from(users).list().execute()` |
| `get(id)` | Get a single record by ID | `db.from(users).get("user-123").execute()` |
| `get({ ROWID })` | Get a single record by FileMaker `ROWID` | `db.from(users).get({ ROWID: 2 }).execute()` |
| `getSingleField(column)` | Get a single field value | `db.from(users).get("user-123").getSingleField(users.email).execute()` |
| `single()` | Ensure exactly one record | `db.from(users).list().where(eq(...)).single().execute()` |
| `maybeSingle()` | Get at most one record (returns null if none) | `db.from(users).list().where(eq(...)).maybeSingle().execute()` |
Expand All @@ -24,6 +25,7 @@ Quick reference for all available methods and operators in `@proofkit/fmodata`.
| `insert(data)` | Insert a new record | `db.from(users).insert({ username: "john" }).execute()` |
| `update(data)` | Update records | `db.from(users).update({ active: true }).byId("user-123").execute()` |
| `delete()` | Delete records | `db.from(users).delete().byId("user-123").execute()` |
| `byRowId(rowId)` | Target a single record by FileMaker `ROWID` | `db.from(users).update({ active: true }).byRowId(2).execute()` |

## Query Modifiers

Expand Down
81 changes: 26 additions & 55 deletions apps/docs/src/app/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,51 +26,34 @@ export default function HomePage() {
<div className="mx-auto flex w-full max-w-screen-lg flex-col items-center justify-center">
<div className="relative flex h-[500px] w-full flex-col items-center justify-center overflow-hidden rounded-lg bg-background">
<InteractiveGridPattern
className={cn(
"absolute inset-0 [mask-image:radial-gradient(400px_circle_at_center,white,transparent)]",
)}
className={cn("absolute inset-0 [mask-image:radial-gradient(400px_circle_at_center,white,transparent)]")}
height={40}
squares={[80, 80]}
squaresClassName="hover:fill-brand/50"
style={{ zIndex: 0 }}
width={40}
/>
<Image
alt="ProofKit Logo"
className="pointer-events-none z-10"
src={ProofKitLogo}
width={400}
/>
<Image alt="ProofKit Logo" className="pointer-events-none z-10" src={ProofKitLogo} width={400} />
</div>

<div className="mt-8 w-full space-y-8 text-center">
<h1 className="font-bold text-4xl">
A collection of tools for FileMaker-aware TypeScript applications
</h1>
<h1 className="font-bold text-4xl">A collection of tools for FileMaker-aware TypeScript applications</h1>
<p className="font-medium text-gray-500 text-xl">
For new and experienced developers alike, the ProofKit toolset is
the best way to build web apps connected to FileMaker data, or rich,
interactive interfaces in a FileMaker webviewer.
For new and experienced developers alike, the ProofKit toolset is the best way to build web apps connected
to FileMaker data, or rich, interactive interfaces in a FileMaker webviewer.
</p>

<Cards className="px-4 text-left">
<Card href="/docs/cli" icon={<Terminal />} title="ProofKit CLI">
A command line tool to start a new project, or easily apply
templates and common patterns with{" "}
<span className="underline">no JavaScript experience</span>{" "}
required.
A command line tool to start a new project, or easily apply templates and common patterns with{" "}
<span className="underline">no JavaScript experience</span> required.
</Card>
<Card href="/docs/typegen" icon={<Code />} title={"Typegen"}>
Automatically generate runtime validators and TypeScript files
from your own FileMaker layouts or table occurrences.
Automatically generate runtime validators and TypeScript files from your own FileMaker layouts or table
occurrences.
</Card>
<Card
href="/docs/fmdapi"
icon={<WebhookIcon />}
title="Filemaker Data API"
>
A type-safe API for your FileMaker layouts. Easily connect without
worrying about token management.
<Card href="/docs/fmdapi" icon={<WebhookIcon />} title="Filemaker Data API">
A type-safe API for your FileMaker layouts. Easily connect without worrying about token management.
</Card>
<Card
href="/docs/fmodata"
Expand All @@ -84,16 +67,11 @@ export default function HomePage() {
</span>
}
>
A strongly-typed OData API client with full TypeScript inference,
runtime validation, and a fluent query builder.
A strongly-typed OData API client with full TypeScript inference, runtime validation, and a fluent query
builder.
</Card>
<Card
href="/docs/webviewer"
icon={<Globe />}
title="FileMaker Webviewer"
>
Use async functions in WebViewer code to execute and get the
result of a FileMaker script.
<Card href="/docs/webviewer" icon={<Globe />} title="FileMaker Webviewer">
Use async functions in WebViewer code to execute and get the result of a FileMaker script.
</Card>
<Card
href="/docs/better-auth"
Expand All @@ -107,8 +85,7 @@ export default function HomePage() {
</span>
}
>
Own your authentication with FileMaker and the extensible
Better-Auth framework.
Own your authentication with FileMaker and the extensible Better-Auth framework.
</Card>
</Cards>

Expand All @@ -118,8 +95,7 @@ export default function HomePage() {
<div className="flex flex-col text-left">
<h2 className="mb-4 font-bold text-3xl">Quick Start</h2>
<p className="mb-0 text-gray-600 text-lg">
Use the ProofKit CLI to launch a full-featured Next.js app in
minutes—no prior experience required.
Use the ProofKit CLI to launch a full-featured Next.js app in minutes—no prior experience required.
</p>
</div>

Expand Down Expand Up @@ -152,11 +128,9 @@ export default function HomePage() {
Built for AI Agents
</h2>
<p className="mb-0 text-gray-600 text-lg">
Every ProofKit package ships with agent skills — built from
decades of combined FileMaker integration experience at Proof —
that give AI coding tools like Claude Code and Cursor the
context they need to write correct, production-ready FileMaker
code from day one.
Every ProofKit package ships with agent skills — built from decades of combined FileMaker integration
experience at Proof — that give AI coding tools like Claude Code and Cursor the context they need to
write correct, production-ready FileMaker code from day one.
</p>
</div>

Expand All @@ -167,9 +141,8 @@ export default function HomePage() {
Expert knowledge built in
</div>
<p className="text-gray-500 text-sm">
Agent skills cover API patterns, edge cases, and common
mistakes so your AI agent avoids the pitfalls that trip up
even experienced developers.
Agent skills cover API patterns, edge cases, and common mistakes so your AI agent avoids the pitfalls
that trip up even experienced developers.
</p>
</div>
<div className="flex flex-col gap-2 rounded-lg border p-4">
Expand All @@ -178,9 +151,8 @@ export default function HomePage() {
Type-safe by default
</div>
<p className="text-gray-500 text-sm">
Schemas generated from your FileMaker field names plus runtime
validators catch bugs early — whether code is written by you
or your AI agent.
Schemas generated from your FileMaker field names plus runtime validators catch bugs early — whether
code is written by you or your AI agent.
</p>
</div>
<div className="flex flex-col gap-2 rounded-lg border p-4">
Expand All @@ -189,9 +161,8 @@ export default function HomePage() {
Works with any agent
</div>
<p className="text-gray-500 text-sm">
Skills are bundled with each package — just install and your
AI coding tool picks them up automatically. Compatible with
Claude Code, Cursor, Windsurf, and more.
Skills are bundled with each package — just install and your AI coding tool picks them up
automatically. Compatible with Claude Code, Cursor, Windsurf, and more.
</p>
</div>
</div>
Expand Down
7 changes: 1 addition & 6 deletions apps/docs/src/app/docs/(docs)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@ export default function Layout({ children }: { children: ReactNode }) {
<div className="mt-2 flex items-center justify-center text-muted-foreground text-xs">
<p>
Made with ❤️ by{" "}
<a
className="underline"
href="http://proof.sh"
rel="noopener"
target="_blank"
>
<a className="underline" href="http://proof.sh" rel="noopener" target="_blank">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use HTTPS here as well.

This footer link also uses http://proof.sh. Please switch to HTTPS to avoid insecure redirects/content tampering.

Suggested fix
-              <a className="underline" href="http://proof.sh" rel="noopener" target="_blank">
+              <a className="underline" href="https://proof.sh" rel="noopener" target="_blank">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a className="underline" href="http://proof.sh" rel="noopener" target="_blank">
<a className="underline" href="https://proof.sh" rel="noopener" target="_blank">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs/src/app/docs/`(docs)/layout.tsx at line 19, The footer anchor using
href="http://proof.sh" should use HTTPS; update the anchor element (the <a> with
className "underline" and target="_blank") in layout.tsx to use
"https://proof.sh" so the link is loaded securely and avoids insecure redirects
or content tampering.

Proof
</a>
</p>
Expand Down
7 changes: 1 addition & 6 deletions apps/docs/src/app/docs/templates/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,7 @@ export default async function Layout({ children }: { children: ReactNode }) {
<div className="mt-2 flex items-center justify-center text-muted-foreground text-xs">
<p>
Made with ❤️ by{" "}
<a
className="underline"
href="http://proof.sh"
rel="noopener noreferrer"
target="_blank"
>
<a className="underline" href="http://proof.sh" rel="noopener noreferrer" target="_blank">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use HTTPS for the external footer link.

href="http://proof.sh" downgrades transport security and allows interception/redirect risks. Switch to HTTPS.

Suggested fix
-              <a className="underline" href="http://proof.sh" rel="noopener noreferrer" target="_blank">
+              <a className="underline" href="https://proof.sh" rel="noopener noreferrer" target="_blank">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a className="underline" href="http://proof.sh" rel="noopener noreferrer" target="_blank">
<a className="underline" href="https://proof.sh" rel="noopener noreferrer" target="_blank">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs/src/app/docs/templates/layout.tsx` at line 89, Update the external
footer anchor in layout.tsx to use HTTPS by changing the href value on the
anchor element (the <a className="underline" ...> tag) from "http://proof.sh" to
"https://proof.sh"; keep the existing rel="noopener noreferrer" and
target="_blank" attributes intact to preserve security and behavior.

Proof
</a>
</p>
Expand Down
29 changes: 29 additions & 0 deletions packages/fmodata/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,16 @@ if (result.data) {
}
```

Get a specific record by `ROWID`:

```typescript
const result = await db.from(users).get({ ROWID: 2 }).execute();

if (result.data) {
console.log(result.data.username);
}
```

Get a single field value:

```typescript
Expand Down Expand Up @@ -531,6 +541,13 @@ if (result.data) {
console.log(`Updated ${result.data.updatedCount} record(s)`);
}

// Update by ROWID
const byRowId = await db
.from(users)
.update({ username: "newname" })
.byRowId(2)
.execute();

// Update by filter (using new ORM API)
import { lt, and, eq } from "@proofkit/fmodata";

Expand Down Expand Up @@ -567,6 +584,9 @@ if (result.data) {
console.log(`Deleted ${result.data.deletedCount} record(s)`);
}

// Delete by ROWID
const byRowId = await db.from(users).delete().byRowId(2).execute();

// Delete by filter (using new ORM API)
import { eq, and, lt } from "@proofkit/fmodata";

Expand Down Expand Up @@ -1471,6 +1491,15 @@ const result = await db.from(users).list().execute({
});
```

Use `ROWID` to hydrate a single record when you only have webhook metadata:

```typescript
const result = await db
.from(users)
.get({ ROWID: 2 })
.execute();
```

**Important:** Special columns are only included when no `$select` query is applied (per OData specification). When using `.select()`, special columns are excluded even if `includeSpecialColumns` is enabled.

### Error Handling
Expand Down
20 changes: 20 additions & 0 deletions packages/fmodata/skills/fmodata-client/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ if (result.data) {
// Get single record by ID
const one = await db.from(contacts).get("abc-123").execute();

// Get single record by FileMaker ROWID
const byRowId = await db.from(contacts).get({ ROWID: 2 }).execute();

// single() -- error if != 1 result; maybeSingle() -- null if 0, error if > 1
const exact = await db
.from(contacts)
Expand Down Expand Up @@ -171,6 +174,13 @@ const updated = await db
.byId("abc-123")
.execute();

// Update by ROWID
const updatedByRowId = await db
.from(contacts)
.update({ phone: "+1-555-0100" })
.byRowId(2)
.execute();

// Update by filter
const bulk = await db
.from(contacts)
Expand All @@ -181,6 +191,9 @@ const bulk = await db
// Delete by ID
const deleted = await db.from(contacts).delete().byId("abc-123").execute();

// Delete by ROWID
const deletedByRowId = await db.from(contacts).delete().byRowId(2).execute();

// Delete by filter
const bulkDel = await db
.from(contacts)
Expand Down Expand Up @@ -230,6 +243,13 @@ const orders = await db
.navigate(invoices)
.execute();

// Navigate starting from a ROWID-located record
const ordersByRowId = await db
.from(contacts)
.get({ ROWID: 2 })
.navigate(invoices)
.execute();

// expand -- includes related records inline
const withInvoices = await db
.from(contacts)
Expand Down
Loading
Loading