HarperFast / HarperFast/skills

programmatic-table-requests: Document proxy objects, async iterables, and conditions structure

Open
#18 3 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
3
Forks
0
Avg merge
3d 7h
Merged PRs (30d)
8

Description

## 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:
```js
{
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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.