From 8b195f28ab20294844f0eaeb711fc1c8ba51c430 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Mon, 25 May 2026 06:23:07 -0600 Subject: [PATCH] Correct Resource post/put/patch data type; document search-sort + content-type behaviour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * `data` parameter on `post` / `put` / `patch` is the deserialized request body, not a `Promise` — the example showing `let data = await promisedData;` is misleading because the deserializer returns synchronously for every built-in content type. Update the signatures and the example. * Add a content-type → `data`-shape table so users know what to expect on non-JSON requests. * Document the indexed-attribute + matching-condition requirement for `sort` and give the workaround that returned the only error message users see today: "X is not indexed and not combined with any other conditions". Tracks harper #774 (long-fetch abort docs) and #773 (sort error). --- reference/resources/resource-api.md | 37 +++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index 5e74eea6..e44b5c06 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -81,7 +81,7 @@ Performs a query on the resource or table. This is called by `get()` on collecti --- -### `put(target: RequestTarget | Id, data: Promise, context?: Resource | Context): Promise | Response` +### `put(target: RequestTarget | Id, data: object, context?: Resource | Context): Promise | Response` ### `put(record: object, context?): Promise` @@ -105,7 +105,7 @@ class MyResource extends Resource { --- -### `patch(target: RequestTarget | Id, data: Promise, context?: Resource | Context): Promise | Response` +### `patch(target: RequestTarget | Id, data: object, context?: Resource | Context): Promise | Response` Writes a partial record to the table, merging `data` into the existing record. @@ -127,14 +127,15 @@ class MyResource extends Resource { --- -### `post(target: RequestTarget, data: Promise, context?: Resource | Context): Promise | Response` +### `post(target: RequestTarget, data: object, context?: Resource | Context): Promise | Response` Called for HTTP POST requests. The default behavior creates a new record, but it can be overridden to implement custom actions. Prefer more explicit methods like `create()` or `update()` over calling `post` directly. +`data` is the already-deserialized request body. For an `application/json` request body it is the parsed JSON value (typically an object), so you can read fields directly — `await` is not required: + ```javascript class MyResource extends Resource { - static async post(target, promisedData) { - let data = await promisedData; + static async post(target, data) { if (data.action === 'create') { return this.create(target, data.content); } else if (data.action === 'update') { @@ -146,6 +147,17 @@ class MyResource extends Resource { } ``` +The exact shape of `data` depends on the request's `Content-Type`. The built-in deserializers produce: + +| `Content-Type` | `data` | +|---|---| +| `application/json` | parsed JSON value | +| `application/cbor`, `application/msgpack` | decoded value | +| `application/x-ndjson` | array of parsed lines | +| `text/plain` | string | + +Custom content types registered through `contentTypes.set(...)` receive whatever value the deserializer returns. + --- ### `delete(target: RequestTarget | Id, context?): Promise` @@ -661,6 +673,21 @@ Sort order object: | `descending` | Sort descending if `true` (default: `false`) | | `next` | Secondary sort to resolve ties (same structure) | +The sort `attribute` must be `@indexed` **and** narrowed by a `conditions` entry on the same attribute — Harper's query optimizer uses indexes to provide order, and refuses an unconditional ordered scan even on `@primaryKey`. A query with `sort` but no matching condition will throw: + +> `HdbError: is not indexed and not combined with any other conditions` + +To iterate a whole table in primary-key order, combine `sort` with an open-ended range condition on the same attribute: + +```javascript +Product.search({ + conditions: [{ attribute: 'id', comparator: 'greater_than', value: '' }], + sort: { attribute: 'id' }, +}); +``` + +If you just need to walk every record and order doesn't matter, omit `sort` entirely — `search({})` will iterate without an index requirement. + ### `explain` If `true`, returns conditions reordered as Harper will execute them (for debugging and optimization).