Skip to content

Commit 7906ee8

Browse files
committed
Add ROWID record locators to fmodata
1 parent 175b0bd commit 7906ee8

21 files changed

Lines changed: 318 additions & 157 deletions

File tree

.changeset/fresh-falcons-grow.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@proofkit/fmodata": minor
3+
---
4+
5+
Add `ROWID` record locator support to `fmodata` single-record APIs.
6+
7+
- Allow `db.from(table).get({ ROWID: 2 })`
8+
- Add `update(data).byRowId(2)`
9+
- Add `delete().byRowId(2)`

apps/docs/content/docs/fmodata/crud.mdx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ if (result.data) {
6464
console.log(`Updated ${result.data.updatedCount} record(s)`);
6565
}
6666

67+
// Update by ROWID
68+
const byRowId = await db
69+
.from(users)
70+
.update({ username: "newname" })
71+
.byRowId(2)
72+
.execute();
73+
6774
// Update by filter (using ORM API)
6875
import { lt, and, eq } from "@proofkit/fmodata";
6976

@@ -85,6 +92,10 @@ const result = await db
8592
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.
8693
</Callout>
8794

95+
<Callout type="info">
96+
For webhook hydrate flows, use `db.from(table).get({ ROWID })` when you only have FileMaker `ROWID` metadata.
97+
</Callout>
98+
8899
## Delete
89100

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

111+
// Delete by ROWID
112+
const byRowId = await db.from(users).delete().byRowId(2).execute();
113+
100114
// Delete by filter (using ORM API)
101115
import { eq, and, lt } from "@proofkit/fmodata";
102116

apps/docs/content/docs/fmodata/extra-properties.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ const result = await db.from(users).list().execute({
3434
});
3535
```
3636

37+
Use `ROWID` to hydrate a record when a webhook payload only gives you system columns:
38+
39+
```typescript
40+
const result = await db.from(users).get({ ROWID: 2 }).execute();
41+
```
42+
3743
<Callout type="warning">
3844
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.
3945
</Callout>

apps/docs/content/docs/fmodata/methods.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Quick reference for all available methods and operators in `@proofkit/fmodata`.
1313
|--------|-------------|---------|
1414
| `list()` | Retrieve multiple records | `db.from(users).list().execute()` |
1515
| `get(id)` | Get a single record by ID | `db.from(users).get("user-123").execute()` |
16+
| `get({ ROWID })` | Get a single record by FileMaker `ROWID` | `db.from(users).get({ ROWID: 2 }).execute()` |
1617
| `getSingleField(column)` | Get a single field value | `db.from(users).get("user-123").getSingleField(users.email).execute()` |
1718
| `single()` | Ensure exactly one record | `db.from(users).list().where(eq(...)).single().execute()` |
1819
| `maybeSingle()` | Get at most one record (returns null if none) | `db.from(users).list().where(eq(...)).maybeSingle().execute()` |
@@ -24,6 +25,7 @@ Quick reference for all available methods and operators in `@proofkit/fmodata`.
2425
| `insert(data)` | Insert a new record | `db.from(users).insert({ username: "john" }).execute()` |
2526
| `update(data)` | Update records | `db.from(users).update({ active: true }).byId("user-123").execute()` |
2627
| `delete()` | Delete records | `db.from(users).delete().byId("user-123").execute()` |
28+
| `byRowId(rowId)` | Target a single record by FileMaker `ROWID` | `db.from(users).update({ active: true }).byRowId(2).execute()` |
2729

2830
## Query Modifiers
2931

apps/docs/src/app/(home)/page.tsx

Lines changed: 26 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -26,51 +26,34 @@ export default function HomePage() {
2626
<div className="mx-auto flex w-full max-w-screen-lg flex-col items-center justify-center">
2727
<div className="relative flex h-[500px] w-full flex-col items-center justify-center overflow-hidden rounded-lg bg-background">
2828
<InteractiveGridPattern
29-
className={cn(
30-
"absolute inset-0 [mask-image:radial-gradient(400px_circle_at_center,white,transparent)]",
31-
)}
29+
className={cn("absolute inset-0 [mask-image:radial-gradient(400px_circle_at_center,white,transparent)]")}
3230
height={40}
3331
squares={[80, 80]}
3432
squaresClassName="hover:fill-brand/50"
3533
style={{ zIndex: 0 }}
3634
width={40}
3735
/>
38-
<Image
39-
alt="ProofKit Logo"
40-
className="pointer-events-none z-10"
41-
src={ProofKitLogo}
42-
width={400}
43-
/>
36+
<Image alt="ProofKit Logo" className="pointer-events-none z-10" src={ProofKitLogo} width={400} />
4437
</div>
4538

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

5646
<Cards className="px-4 text-left">
5747
<Card href="/docs/cli" icon={<Terminal />} title="ProofKit CLI">
58-
A command line tool to start a new project, or easily apply
59-
templates and common patterns with{" "}
60-
<span className="underline">no JavaScript experience</span>{" "}
61-
required.
48+
A command line tool to start a new project, or easily apply templates and common patterns with{" "}
49+
<span className="underline">no JavaScript experience</span> required.
6250
</Card>
6351
<Card href="/docs/typegen" icon={<Code />} title={"Typegen"}>
64-
Automatically generate runtime validators and TypeScript files
65-
from your own FileMaker layouts or table occurrences.
52+
Automatically generate runtime validators and TypeScript files from your own FileMaker layouts or table
53+
occurrences.
6654
</Card>
67-
<Card
68-
href="/docs/fmdapi"
69-
icon={<WebhookIcon />}
70-
title="Filemaker Data API"
71-
>
72-
A type-safe API for your FileMaker layouts. Easily connect without
73-
worrying about token management.
55+
<Card href="/docs/fmdapi" icon={<WebhookIcon />} title="Filemaker Data API">
56+
A type-safe API for your FileMaker layouts. Easily connect without worrying about token management.
7457
</Card>
7558
<Card
7659
href="/docs/fmodata"
@@ -84,16 +67,11 @@ export default function HomePage() {
8467
</span>
8568
}
8669
>
87-
A strongly-typed OData API client with full TypeScript inference,
88-
runtime validation, and a fluent query builder.
70+
A strongly-typed OData API client with full TypeScript inference, runtime validation, and a fluent query
71+
builder.
8972
</Card>
90-
<Card
91-
href="/docs/webviewer"
92-
icon={<Globe />}
93-
title="FileMaker Webviewer"
94-
>
95-
Use async functions in WebViewer code to execute and get the
96-
result of a FileMaker script.
73+
<Card href="/docs/webviewer" icon={<Globe />} title="FileMaker Webviewer">
74+
Use async functions in WebViewer code to execute and get the result of a FileMaker script.
9775
</Card>
9876
<Card
9977
href="/docs/better-auth"
@@ -107,8 +85,7 @@ export default function HomePage() {
10785
</span>
10886
}
10987
>
110-
Own your authentication with FileMaker and the extensible
111-
Better-Auth framework.
88+
Own your authentication with FileMaker and the extensible Better-Auth framework.
11289
</Card>
11390
</Cards>
11491

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

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

@@ -167,9 +141,8 @@ export default function HomePage() {
167141
Expert knowledge built in
168142
</div>
169143
<p className="text-gray-500 text-sm">
170-
Agent skills cover API patterns, edge cases, and common
171-
mistakes so your AI agent avoids the pitfalls that trip up
172-
even experienced developers.
144+
Agent skills cover API patterns, edge cases, and common mistakes so your AI agent avoids the pitfalls
145+
that trip up even experienced developers.
173146
</p>
174147
</div>
175148
<div className="flex flex-col gap-2 rounded-lg border p-4">
@@ -178,9 +151,8 @@ export default function HomePage() {
178151
Type-safe by default
179152
</div>
180153
<p className="text-gray-500 text-sm">
181-
Schemas generated from your FileMaker field names plus runtime
182-
validators catch bugs early — whether code is written by you
183-
or your AI agent.
154+
Schemas generated from your FileMaker field names plus runtime validators catch bugs early — whether
155+
code is written by you or your AI agent.
184156
</p>
185157
</div>
186158
<div className="flex flex-col gap-2 rounded-lg border p-4">
@@ -189,9 +161,8 @@ export default function HomePage() {
189161
Works with any agent
190162
</div>
191163
<p className="text-gray-500 text-sm">
192-
Skills are bundled with each package — just install and your
193-
AI coding tool picks them up automatically. Compatible with
194-
Claude Code, Cursor, Windsurf, and more.
164+
Skills are bundled with each package — just install and your AI coding tool picks them up
165+
automatically. Compatible with Claude Code, Cursor, Windsurf, and more.
195166
</p>
196167
</div>
197168
</div>

apps/docs/src/app/docs/(docs)/layout.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@ export default function Layout({ children }: { children: ReactNode }) {
1616
<div className="mt-2 flex items-center justify-center text-muted-foreground text-xs">
1717
<p>
1818
Made with ❤️ by{" "}
19-
<a
20-
className="underline"
21-
href="http://proof.sh"
22-
rel="noopener"
23-
target="_blank"
24-
>
19+
<a className="underline" href="http://proof.sh" rel="noopener" target="_blank">
2520
Proof
2621
</a>
2722
</p>

apps/docs/src/app/docs/templates/layout.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,7 @@ export default async function Layout({ children }: { children: ReactNode }) {
8686
<div className="mt-2 flex items-center justify-center text-muted-foreground text-xs">
8787
<p>
8888
Made with ❤️ by{" "}
89-
<a
90-
className="underline"
91-
href="http://proof.sh"
92-
rel="noopener noreferrer"
93-
target="_blank"
94-
>
89+
<a className="underline" href="http://proof.sh" rel="noopener noreferrer" target="_blank">
9590
Proof
9691
</a>
9792
</p>

packages/fmodata/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,16 @@ if (result.data) {
258258
}
259259
```
260260

261+
Get a specific record by `ROWID`:
262+
263+
```typescript
264+
const result = await db.from(users).get({ ROWID: 2 }).execute();
265+
266+
if (result.data) {
267+
console.log(result.data.username);
268+
}
269+
```
270+
261271
Get a single field value:
262272

263273
```typescript
@@ -531,6 +541,13 @@ if (result.data) {
531541
console.log(`Updated ${result.data.updatedCount} record(s)`);
532542
}
533543

544+
// Update by ROWID
545+
const byRowId = await db
546+
.from(users)
547+
.update({ username: "newname" })
548+
.byRowId(2)
549+
.execute();
550+
534551
// Update by filter (using new ORM API)
535552
import { lt, and, eq } from "@proofkit/fmodata";
536553

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

587+
// Delete by ROWID
588+
const byRowId = await db.from(users).delete().byRowId(2).execute();
589+
570590
// Delete by filter (using new ORM API)
571591
import { eq, and, lt } from "@proofkit/fmodata";
572592

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

1494+
Use `ROWID` to hydrate a single record when you only have webhook metadata:
1495+
1496+
```typescript
1497+
const result = await db
1498+
.from(users)
1499+
.get({ ROWID: 2 })
1500+
.execute();
1501+
```
1502+
14741503
**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.
14751504

14761505
### Error Handling

packages/fmodata/skills/fmodata-client/SKILL.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ if (result.data) {
129129
// Get single record by ID
130130
const one = await db.from(contacts).get("abc-123").execute();
131131

132+
// Get single record by FileMaker ROWID
133+
const byRowId = await db.from(contacts).get({ ROWID: 2 }).execute();
134+
132135
// single() -- error if != 1 result; maybeSingle() -- null if 0, error if > 1
133136
const exact = await db
134137
.from(contacts)
@@ -171,6 +174,13 @@ const updated = await db
171174
.byId("abc-123")
172175
.execute();
173176

177+
// Update by ROWID
178+
const updatedByRowId = await db
179+
.from(contacts)
180+
.update({ phone: "+1-555-0100" })
181+
.byRowId(2)
182+
.execute();
183+
174184
// Update by filter
175185
const bulk = await db
176186
.from(contacts)
@@ -181,6 +191,9 @@ const bulk = await db
181191
// Delete by ID
182192
const deleted = await db.from(contacts).delete().byId("abc-123").execute();
183193

194+
// Delete by ROWID
195+
const deletedByRowId = await db.from(contacts).delete().byRowId(2).execute();
196+
184197
// Delete by filter
185198
const bulkDel = await db
186199
.from(contacts)
@@ -230,6 +243,13 @@ const orders = await db
230243
.navigate(invoices)
231244
.execute();
232245

246+
// Navigate starting from a ROWID-located record
247+
const ordersByRowId = await db
248+
.from(contacts)
249+
.get({ ROWID: 2 })
250+
.navigate(invoices)
251+
.execute();
252+
233253
// expand -- includes related records inline
234254
const withInvoices = await db
235255
.from(contacts)

0 commit comments

Comments
 (0)