Context
While building a production Harper Fabric app (~15 tables, custom resources, external API integrations), we ran into several runtime behaviors around programmatic table access that aren't documented in programmatic-table-requests.md. We solved them through trial and error, but they probably belong in the skill so other builders don't hit the same walls.
Caveat: We're not 100% certain these are canonical Harper best practices — they're what we (a developer + Claude Code) encountered and how we solved them. If Harper has better patterns for any of these, the skill should document those instead.
1. Records from .get() / .search() are proxy objects
What we hit: When we retrieved records and tried to spread them, serialize them, or return them in API responses, we got unexpected behavior — missing properties, circular reference errors, or empty objects.
Why we think it happens: Harper returns proxy-wrapped objects from its data layer (presumably for reactivity / change tracking).
What we did: We wrote a toPlain() helper that unwraps via JSON.parse(JSON.stringify(record)) and call it on every record before using it in business logic. This is the single most common gotcha we hit — it caused subtle bugs that were hard to trace because the proxy looks like a normal object in console.log.
What the skill should do: Either document this behavior and the unwrap pattern, or document the correct way to work with proxy records if JSON.parse(JSON.stringify()) isn't the recommended approach.
2. search() returns async iterables, NOT arrays
What we hit: We called search() expecting an array back and tried .map(), .filter(), .length — all undefined. Silent failures.
Why we think it happens: search() is designed for streaming large result sets.
What we did: We wrote a toArray() helper that collects results via for await. The rule's example shows for await iteration but doesn't explicitly warn that the result is NOT an array and that array methods won't work.
What the skill should do: Add a clear callout: "search() returns an async iterable, not an array. Use for await to iterate, or collect into an array first." Maybe show a toArray() helper pattern.
3. The conditions structure is never fully documented
What we hit: We needed to do conditional queries (e.g., find all records matching two fields). The rule mentions conditions: [...] but never shows the object shape.
What we did: Through experimentation, we determined the shape is:
{
conditions: [
{ attribute: "status", comparator: "equals", value: "active" },
{ attribute: "categoryId", comparator: "equals", value: "abc123" }
]
}
What the skill should do: Document the full conditions object structure — attribute, comparator, value — and list available comparators (equals, greater_than, less_than, contains, starts_with, etc., whatever Harper supports). This is one of the most common operations and right now you have to guess at the API.
4. databases.dbname.TableName vs tables.TableName
What we hit: In our custom resource class, tables.TableName didn't resolve correctly when accessing tables from a named database. We had to use databases.mydb.TableName instead.
Why we think it happens: When a Harper app defines a named database (via schema), the tables live under that database namespace, not the global tables object.
What we did: We wrote a getTable() helper that accesses databases.mydb.TableName and documented the pattern. The rules consistently show tables.X everywhere, which may only work in certain contexts (table extensions? default database?).
What the skill should do: Clarify when to use tables.X vs databases.dbname.X, or if one is always preferred. If tables.X is supposed to work everywhere and we were doing something wrong, explain what.
Context
While building a production Harper Fabric app (~15 tables, custom resources, external API integrations), we ran into several runtime behaviors around programmatic table access that aren't documented in
programmatic-table-requests.md. We solved them through trial and error, but they probably belong in the skill so other builders don't hit the same walls.Caveat: We're not 100% certain these are canonical Harper best practices — they're what we (a developer + Claude Code) encountered and how we solved them. If Harper has better patterns for any of these, the skill should document those instead.
1. Records from
.get()/.search()are proxy objectsWhat we hit: When we retrieved records and tried to spread them, serialize them, or return them in API responses, we got unexpected behavior — missing properties, circular reference errors, or empty objects.
Why we think it happens: Harper returns proxy-wrapped objects from its data layer (presumably for reactivity / change tracking).
What we did: We wrote a
toPlain()helper that unwraps viaJSON.parse(JSON.stringify(record))and call it on every record before using it in business logic. This is the single most common gotcha we hit — it caused subtle bugs that were hard to trace because the proxy looks like a normal object in console.log.What the skill should do: Either document this behavior and the unwrap pattern, or document the correct way to work with proxy records if
JSON.parse(JSON.stringify())isn't the recommended approach.2.
search()returns async iterables, NOT arraysWhat we hit: We called
search()expecting an array back and tried.map(),.filter(),.length— all undefined. Silent failures.Why we think it happens:
search()is designed for streaming large result sets.What we did: We wrote a
toArray()helper that collects results viafor await. The rule's example showsfor awaititeration but doesn't explicitly warn that the result is NOT an array and that array methods won't work.What the skill should do: Add a clear callout: "
search()returns an async iterable, not an array. Usefor awaitto iterate, or collect into an array first." Maybe show atoArray()helper pattern.3. The
conditionsstructure is never fully documentedWhat we hit: We needed to do conditional queries (e.g., find all records matching two fields). The rule mentions
conditions: [...]but never shows the object shape.What we did: Through experimentation, we determined the shape is:
What the skill should do: Document the full conditions object structure —
attribute,comparator,value— and list available comparators (equals, greater_than, less_than, contains, starts_with, etc., whatever Harper supports). This is one of the most common operations and right now you have to guess at the API.4.
databases.dbname.TableNamevstables.TableNameWhat we hit: In our custom resource class,
tables.TableNamedidn't resolve correctly when accessing tables from a named database. We had to usedatabases.mydb.TableNameinstead.Why we think it happens: When a Harper app defines a named database (via schema), the tables live under that database namespace, not the global
tablesobject.What we did: We wrote a
getTable()helper that accessesdatabases.mydb.TableNameand documented the pattern. The rules consistently showtables.Xeverywhere, which may only work in certain contexts (table extensions? default database?).What the skill should do: Clarify when to use
tables.Xvsdatabases.dbname.X, or if one is always preferred. Iftables.Xis supposed to work everywhere and we were doing something wrong, explain what.