@harperfast/template-react-ts-studio 1.12.7 → 1.12.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/harper-best-practices/AGENTS.md +514 -445
- package/.agents/skills/harper-best-practices/rules/automatic-apis.md +96 -93
- package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +84 -58
- package/.agents/skills/harper-best-practices/rules/extending-tables.md +27 -35
- package/.agents/skills/harper-best-practices/rules/handling-binary-data.md +40 -39
- package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +93 -37
- package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +68 -66
- package/.agents/skills/harper-best-practices/rules/using-blob-datatype.md +24 -28
- package/.agents/skills/harper-best-practices/rules/v5-upgrade.md +98 -105
- package/.agents/skills/harper-best-practices/rules.manifest.yaml +28 -3
- package/.agents/skills/harper-local-dev/AGENTS.md +197 -0
- package/.agents/skills/harper-local-dev/SKILL.md +65 -0
- package/.agents/skills/harper-local-dev/rules/running-dev-instances-in-worktrees.md +198 -0
- package/.agents/skills/harper-local-dev/rules.manifest.yaml +20 -0
- package/.agents/skills/harper-local-dev/scripts/harper-dev.mjs +345 -0
- package/.agents/skills/harper-mcp/AGENTS.md +138 -26
- package/.agents/skills/harper-mcp/rules/enabling-mcp.md +140 -28
- package/agent/skills/harper-best-practices/AGENTS.md +514 -445
- package/agent/skills/harper-best-practices/rules/automatic-apis.md +96 -93
- package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +84 -58
- package/agent/skills/harper-best-practices/rules/extending-tables.md +27 -35
- package/agent/skills/harper-best-practices/rules/handling-binary-data.md +40 -39
- package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +93 -37
- package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +68 -66
- package/agent/skills/harper-best-practices/rules/using-blob-datatype.md +24 -28
- package/agent/skills/harper-best-practices/rules/v5-upgrade.md +98 -105
- package/agent/skills/harper-best-practices/rules.manifest.yaml +28 -3
- package/agent/skills/harper-local-dev/AGENTS.md +197 -0
- package/agent/skills/harper-local-dev/SKILL.md +64 -0
- package/agent/skills/harper-local-dev/rules/running-dev-instances-in-worktrees.md +198 -0
- package/agent/skills/harper-local-dev/rules.manifest.yaml +20 -0
- package/agent/skills/harper-local-dev/scripts/harper-dev.mjs +345 -0
- package/agent/skills/harper-mcp/AGENTS.md +138 -26
- package/agent/skills/harper-mcp/rules/enabling-mcp.md +140 -28
- package/package.json +1 -1
- package/skills-lock.json +8 -2
|
@@ -6,8 +6,8 @@ metadata:
|
|
|
6
6
|
sources:
|
|
7
7
|
- reference/v5/resources/overview.md#Extending a Table
|
|
8
8
|
- reference/v5/resources/resource-api.md#Throwing Errors
|
|
9
|
-
sourceCommit:
|
|
10
|
-
inputHash:
|
|
9
|
+
sourceCommit: 2cfb81318f17e0aca2d600109e6ad813d2d0443e
|
|
10
|
+
inputHash: 925aa3c817b6c9f1
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Extending Tables
|
|
@@ -16,11 +16,11 @@ Instructions for the agent to follow when adding custom logic to automatically g
|
|
|
16
16
|
|
|
17
17
|
## When to Use
|
|
18
18
|
|
|
19
|
-
Apply this rule when you need to
|
|
19
|
+
Apply this rule when you need to override or augment the default HTTP handler behavior (GET, POST, PUT, PATCH, DELETE) for a Harper table resource. Use it when a table needs computed properties, input transformation, authorization checks, or custom error responses.
|
|
20
20
|
|
|
21
21
|
## How It Works
|
|
22
22
|
|
|
23
|
-
1. **Define the schema without `@export`**:
|
|
23
|
+
1. **Define the table schema without `@export`**: In `schema.graphql`, declare the table type but omit the `@export` directive. Leaving `@export` on the schema while also exporting a subclass with the same name produces conflicting endpoints.
|
|
24
24
|
|
|
25
25
|
```graphql
|
|
26
26
|
# Omit the `@export` directive
|
|
@@ -30,7 +30,7 @@ Apply this rule when you need to add computed properties, intercept writes, enfo
|
|
|
30
30
|
}
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
2. **Extend the generated table class
|
|
33
|
+
2. **Extend the generated table class in `resources.js`**: Use `extends tables.<TableName>` to subclass the auto-generated resource. The exported JavaScript class owns the URL instead of the schema type.
|
|
34
34
|
|
|
35
35
|
```javascript
|
|
36
36
|
export class MyTable extends tables.MyTable {
|
|
@@ -40,19 +40,17 @@ Apply this rule when you need to add computed properties, intercept writes, enfo
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
static async post(target, data) {
|
|
43
|
-
this.create({ ...(await data), status: 'pending' });
|
|
43
|
+
return this.create({ ...(await data), status: 'pending' });
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
3. **Call `super`
|
|
49
|
-
- Reads
|
|
48
|
+
3. **Call `super` with the correct argument form per operation**: When delegating to the default behavior, match arguments to the operation type:
|
|
49
|
+
- Reads and deletes: `super.get(target)` / `super.delete(target)`
|
|
50
50
|
- Collection create: `super.post(target, record)` — target carries no id
|
|
51
51
|
- Updates: `super.put(target, data)` / `super.patch(target, data)`
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
4. **Set `statusCode` on thrown errors to control HTTP responses**: Uncaught errors are caught by the protocol handler and produce error responses for REST. Use `.statusCode` — a plain `.status` property is ignored.
|
|
53
|
+
4. **Set `statusCode` on thrown errors to control HTTP status**: A plain `.status` property is ignored. Use `.statusCode` on the error object:
|
|
56
54
|
|
|
57
55
|
```javascript
|
|
58
56
|
const error = new Error('Name is required');
|
|
@@ -60,7 +58,9 @@ Apply this rule when you need to add computed properties, intercept writes, enfo
|
|
|
60
58
|
throw error;
|
|
61
59
|
```
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
Uncaught errors are caught by the protocol handler and produce error responses for REST.
|
|
62
|
+
|
|
63
|
+
5. **Configure Harper to load both files**: Ensure your configuration references the schema and resource files:
|
|
64
64
|
|
|
65
65
|
```yaml
|
|
66
66
|
rest: true
|
|
@@ -72,17 +72,9 @@ Apply this rule when you need to add computed properties, intercept writes, enfo
|
|
|
72
72
|
|
|
73
73
|
## Examples
|
|
74
74
|
|
|
75
|
-
Full
|
|
76
|
-
|
|
77
|
-
```graphql
|
|
78
|
-
# schema.graphql — omit @export so the JS class owns the endpoint
|
|
79
|
-
type MyTable @table {
|
|
80
|
-
id: Long @primaryKey
|
|
81
|
-
}
|
|
82
|
-
```
|
|
75
|
+
Full example extending a table with a computed GET response, a custom POST, and a guarded handler that throws a typed error:
|
|
83
76
|
|
|
84
77
|
```javascript
|
|
85
|
-
// resources.js
|
|
86
78
|
export class MyTable extends tables.MyTable {
|
|
87
79
|
static async get(target) {
|
|
88
80
|
// get the record from the database
|
|
@@ -92,24 +84,24 @@ export class MyTable extends tables.MyTable {
|
|
|
92
84
|
}
|
|
93
85
|
|
|
94
86
|
static async post(target, data) {
|
|
95
|
-
// custom action on POST
|
|
96
|
-
this.create({ ...(await data), status: 'pending' });
|
|
87
|
+
// custom action on POST; return the write so the response waits for the commit
|
|
88
|
+
return this.create({ ...(await data), status: 'pending' });
|
|
97
89
|
}
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Throwing a controlled HTTP error:
|
|
102
90
|
|
|
103
|
-
|
|
104
|
-
if (!authorized) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
91
|
+
static async delete(target) {
|
|
92
|
+
if (!authorized) {
|
|
93
|
+
const error = new Error('Forbidden');
|
|
94
|
+
error.statusCode = 403;
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
return super.delete(target);
|
|
98
|
+
}
|
|
108
99
|
}
|
|
109
100
|
```
|
|
110
101
|
|
|
111
102
|
## Notes
|
|
112
103
|
|
|
113
|
-
- Always omit `@export` from the schema type when a
|
|
114
|
-
- `super`
|
|
115
|
-
- `statusCode`
|
|
104
|
+
- Always omit `@export` from the schema type when you export a subclass with the same name in JavaScript. The exported class owns the endpoint registration.
|
|
105
|
+
- Call `super.get/post/put/patch/delete` to preserve Harper's default behavior unless you intend to replace it entirely.
|
|
106
|
+
- `statusCode` on an error object controls the HTTP response status for REST. A `.status` property is ignored.
|
|
107
|
+
- See [`Database / Schema`](/reference/v5/database/schema.md) for full schema API details.
|
|
@@ -7,8 +7,8 @@ metadata:
|
|
|
7
7
|
- reference/v5/database/api.md#Accepting Binary in JSON Requests
|
|
8
8
|
- reference/v5/database/api.md#Serving Binary from a Resource
|
|
9
9
|
- reference/v5/rest/content-types.md#Storing Arbitrary Content Types
|
|
10
|
-
sourceCommit:
|
|
11
|
-
inputHash:
|
|
10
|
+
sourceCommit: 2cfb81318f17e0aca2d600109e6ad813d2d0443e
|
|
11
|
+
inputHash: 8be9e31f009e2d27
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# Handling Binary Data
|
|
@@ -17,28 +17,30 @@ Instructions for the agent to follow when storing and serving binary data (image
|
|
|
17
17
|
|
|
18
18
|
## When to Use
|
|
19
19
|
|
|
20
|
-
Apply this rule when a Harper resource needs to accept, store, or serve binary payloads such as images, audio files, or calendar data. Use it when
|
|
20
|
+
Apply this rule when a Harper resource needs to accept, store, or serve binary payloads such as images, audio files, or calendar data. Use it when clients send raw binary via `PUT`/`POST`, or when they send `base64`-encoded data inside a JSON body.
|
|
21
21
|
|
|
22
22
|
## How It Works
|
|
23
23
|
|
|
24
|
-
1. **Accept base64-encoded binary from JSON clients**:
|
|
24
|
+
1. **Accept base64-encoded binary from JSON clients**: REST clients that cannot post raw binary send `base64` inside a JSON body. In the resource override, `await` the `record` promise before reading any fields — reading fields off the unresolved promise silently yields `undefined` and stores the raw base64 string. Decode the field with `Buffer.from` and wrap it with `createBlob`, recording the MIME type.
|
|
25
25
|
|
|
26
26
|
```typescript
|
|
27
27
|
import { type RequestTargetOrId, tables, createBlob } from 'harper';
|
|
28
28
|
|
|
29
29
|
export class Photo extends tables.Photo {
|
|
30
30
|
static async post(target: RequestTargetOrId, record: any) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const body = await record;
|
|
32
|
+
if (!body) return new Response('A JSON body is required', { status: 400 });
|
|
33
|
+
if (body.data) {
|
|
34
|
+
body.data = createBlob(Buffer.from(body.data, body.encoding || 'base64'), {
|
|
35
|
+
type: body.contentType || 'application/octet-stream',
|
|
34
36
|
});
|
|
35
37
|
}
|
|
36
|
-
return super.post(target,
|
|
38
|
+
return super.post(target, body);
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
```
|
|
40
42
|
|
|
41
|
-
2. **Serve binary from a resource**: Override `get` to return a response object with the blob's MIME type in the `Content-Type` header and the blob as the body. Harper streams it to the client
|
|
43
|
+
2. **Serve binary from a resource**: Override `get` to return a response object with the blob's MIME type in the `Content-Type` header and the blob as the body. Harper streams it to the client.
|
|
42
44
|
|
|
43
45
|
```typescript
|
|
44
46
|
export class Photo extends tables.Photo {
|
|
@@ -56,28 +58,11 @@ Apply this rule when a Harper resource needs to accept, store, or serve binary p
|
|
|
56
58
|
}
|
|
57
59
|
```
|
|
58
60
|
|
|
59
|
-
3. **
|
|
61
|
+
3. **Store arbitrary content types via raw PUT/POST**: When a `PUT` or `POST` arrives with a non-standard `Content-Type` (e.g., `text/calendar`, `image/gif`), Harper automatically stores the content as a record with `contentType` and `data` properties. Retrieving that record returns the response with the original `Content-Type` and body. If the content type is not from the `text` family, the data is treated as binary (a Node.js `Buffer`).
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
PUT /my-resource/33
|
|
63
|
-
Content-Type: text/calendar
|
|
63
|
+
Use `application/octet-stream` for generic binary data, or target a specific property:
|
|
64
64
|
|
|
65
|
-
BEGIN:VCALENDAR
|
|
66
|
-
VERSION:2.0
|
|
67
|
-
...
|
|
68
65
|
```
|
|
69
|
-
|
|
70
|
-
Harper stores this as:
|
|
71
|
-
|
|
72
|
-
```json
|
|
73
|
-
{ "contentType": "text/calendar", "data": "BEGIN:VCALENDAR\nVERSION:2.0\n..." }
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Retrieving that record returns the response with the stored `Content-Type` and body. If the content type is not from the `text` family, the data is treated as binary (a Node.js `Buffer`).
|
|
77
|
-
|
|
78
|
-
4. **Upload binary to a specific property**: Use `application/octet-stream` (or any image/binary MIME type) and target a sub-path to store binary directly on a property:
|
|
79
|
-
|
|
80
|
-
```http
|
|
81
66
|
PUT /my-resource/33/image
|
|
82
67
|
Content-Type: image/gif
|
|
83
68
|
|
|
@@ -86,23 +71,40 @@ Apply this rule when a Harper resource needs to accept, store, or serve binary p
|
|
|
86
71
|
|
|
87
72
|
## Examples
|
|
88
73
|
|
|
89
|
-
**
|
|
74
|
+
**Storing a calendar entry with a raw PUT:**
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
PUT /my-resource/33
|
|
78
|
+
Content-Type: text/calendar
|
|
79
|
+
|
|
80
|
+
BEGIN:VCALENDAR
|
|
81
|
+
VERSION:2.0
|
|
82
|
+
...
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Harper stores this as:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{ "contentType": "text/calendar", "data": "BEGIN:VCALENDAR\nVERSION:2.0\n..." }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Full resource class handling base64 JSON upload and binary serving:**
|
|
90
92
|
|
|
91
93
|
```typescript
|
|
92
94
|
import { type RequestTargetOrId, tables, createBlob } from 'harper';
|
|
93
95
|
|
|
94
96
|
export class Photo extends tables.Photo {
|
|
95
|
-
// Accept base64-encoded uploads in JSON
|
|
96
97
|
static async post(target: RequestTargetOrId, record: any) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
const body = await record;
|
|
99
|
+
if (!body) return new Response('A JSON body is required', { status: 400 });
|
|
100
|
+
if (body.data) {
|
|
101
|
+
body.data = createBlob(Buffer.from(body.data, body.encoding || 'base64'), {
|
|
102
|
+
type: body.contentType || 'application/octet-stream',
|
|
100
103
|
});
|
|
101
104
|
}
|
|
102
|
-
return super.post(target,
|
|
105
|
+
return super.post(target, body);
|
|
103
106
|
}
|
|
104
107
|
|
|
105
|
-
// Stream the blob back with the correct Content-Type
|
|
106
108
|
static async get(target: RequestTargetOrId) {
|
|
107
109
|
const record = await super.get(target);
|
|
108
110
|
if (record?.data) {
|
|
@@ -119,7 +121,6 @@ export class Photo extends tables.Photo {
|
|
|
119
121
|
|
|
120
122
|
## Notes
|
|
121
123
|
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
-
|
|
125
|
-
- Non-`text` content types cause `data` to be stored and returned as a Node.js `Buffer`.
|
|
124
|
+
- Always `await` the `record` parameter before accessing its properties; accessing fields on the unresolved promise yields `undefined`.
|
|
125
|
+
- `createBlob` accepts a `Buffer` and an options object with a `type` property for the MIME type. Fall back to `application/octet-stream` when no MIME type is provided.
|
|
126
|
+
- For schema-level blob field definitions, see [using-blob-datatype.md](using-blob-datatype.md).
|
|
@@ -5,17 +5,17 @@ metadata:
|
|
|
5
5
|
mode: generate
|
|
6
6
|
sources:
|
|
7
7
|
- reference/v5/rest/querying.md
|
|
8
|
-
sourceCommit:
|
|
9
|
-
inputHash:
|
|
8
|
+
sourceCommit: 2cfb81318f17e0aca2d600109e6ad813d2d0443e
|
|
9
|
+
inputHash: 25b9daabc0a28fd5
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Querying REST APIs
|
|
13
13
|
|
|
14
|
-
Instructions for the agent to filter, sort, select, and paginate
|
|
14
|
+
Instructions for the agent to filter, sort, select, and paginate records through Harper's URL-based REST query language.
|
|
15
15
|
|
|
16
16
|
## When to Use
|
|
17
17
|
|
|
18
|
-
Apply this rule whenever
|
|
18
|
+
Apply this rule whenever you need to construct or handle GET requests against Harper collection endpoints that require filtering by attribute value, comparison operators, sorting, field selection, or paginated results. This rule also covers type coercion syntax and relationship joins via dot notation. See [automatic-apis.md](automatic-apis.md) for how REST endpoints are generated from schemas.
|
|
19
19
|
|
|
20
20
|
## How It Works
|
|
21
21
|
|
|
@@ -26,7 +26,13 @@ Apply this rule whenever building or modifying code that queries Harper REST col
|
|
|
26
26
|
GET /Product/?category=software&inStock=true
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
To filter for null values:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
GET /Product/?discount=null
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2. **Apply comparison operators (FIQL syntax)**: Use FIQL operators in the query string for range and pattern matching.
|
|
30
36
|
|
|
31
37
|
| Operator | Meaning |
|
|
32
38
|
| -------------------- | -------------------------------------- |
|
|
@@ -61,7 +67,7 @@ Apply this rule whenever building or modifying code that queries Harper REST col
|
|
|
61
67
|
GET /Product/?price=gt=100<=200
|
|
62
68
|
```
|
|
63
69
|
|
|
64
|
-
4. **Apply type conversion**: For FIQL comparators, Harper converts values automatically. Use explicit prefixes to force a type.
|
|
70
|
+
4. **Apply type conversion**: For FIQL comparators (`==`, `!=`, `=gt=`, etc.), Harper converts values automatically. Use explicit type prefixes to force a specific type.
|
|
65
71
|
|
|
66
72
|
| Syntax | Behavior |
|
|
67
73
|
| ----------------------------------------- | ------------------------------------------- |
|
|
@@ -81,20 +87,20 @@ Apply this rule whenever building or modifying code that queries Harper REST col
|
|
|
81
87
|
GET /Product/?rating=5|featured=true
|
|
82
88
|
```
|
|
83
89
|
|
|
84
|
-
6. **Group conditions**: Use parentheses or square brackets to control order
|
|
90
|
+
6. **Group conditions**: Use parentheses or square brackets to control evaluation order. Prefer square brackets when building queries from user input, since standard URI encoding safely encodes `[` and `]`.
|
|
85
91
|
|
|
86
92
|
```
|
|
87
93
|
GET /Product/?rating=5|(price=gt=100&price=lt=200)
|
|
88
94
|
GET /Product/?rating=5&[tag=fast|tag=scalable|tag=efficient]
|
|
89
95
|
```
|
|
90
96
|
|
|
91
|
-
|
|
97
|
+
Build grouped queries in JavaScript:
|
|
92
98
|
|
|
93
99
|
```javascript
|
|
94
100
|
let url = `/Product/?rating=5&[${tags.map(encodeURIComponent).join('|')}]`;
|
|
95
101
|
```
|
|
96
102
|
|
|
97
|
-
7. **Select specific properties with `select(`**: Append `select(
|
|
103
|
+
7. **Select specific properties with `select()`**: Append `select()` as a query function separated by `&`.
|
|
98
104
|
|
|
99
105
|
| Syntax | Returns |
|
|
100
106
|
| -------------------------------------- | ------------------------------------------- |
|
|
@@ -104,47 +110,99 @@ Apply this rule whenever building or modifying code that queries Harper REST col
|
|
|
104
110
|
| `?select(property1,)` | Objects with a single specified property |
|
|
105
111
|
| `?select(property{subProp1,subProp2})` | Nested objects with specific sub-properties |
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
```
|
|
114
|
+
GET /Product/?category=software&select(name)
|
|
115
|
+
GET /Product/?brand.name=Microsoft&select(name,brand{name})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
8. **Limit results with `limit(end)` or `limit(start,end)`**: Append as a query function.
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
GET /Product/?rating=gt=3&inStock=true&select(rating,name)&limit(20)
|
|
122
|
+
GET /Product/?rating=gt=3&limit(10,30)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
9. **Sort results with `sort(property)` or `sort(+property,-property,...)`**: Prefix `+` or no prefix = ascending; `-` = descending. List multiple properties to break ties in order.
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
GET /Product/?rating=gt=3&sort(+name)
|
|
129
|
+
GET /Product/?sort(+rating,-price)
|
|
130
|
+
```
|
|
108
131
|
|
|
109
|
-
|
|
132
|
+
10. **Paginate with a total count**: Use `limit(start,end)` for paging and send a `Prefer` request header to receive a total match count.
|
|
110
133
|
|
|
111
|
-
|
|
134
|
+
| `Prefer` value | Meaning |
|
|
135
|
+
| ----------------- | ---------------------------------------- |
|
|
136
|
+
| `count=exact` | Exact count of matching records (opt-in) |
|
|
137
|
+
| `count=estimated` | Fast approximate count |
|
|
112
138
|
|
|
113
139
|
```
|
|
140
|
+
GET /Product/?category=software&limit(0,25)
|
|
141
|
+
Prefer: count=exact
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The server responds with:
|
|
145
|
+
|
|
146
|
+
| Header | Example | Description |
|
|
147
|
+
| -------------------- | ----------------- | ------------------------------------------------------ |
|
|
148
|
+
| `Content-Range` | `items 0-24/1234` | 0-based inclusive range out of total matching records |
|
|
149
|
+
| `Range-Unit` | `items` | Unit used by `Content-Range` |
|
|
150
|
+
| `Preference-Applied` | `count=exact` | Count mode the server applied (`exact` or `estimated`) |
|
|
151
|
+
|
|
152
|
+
The response status is always `200`. When the total cannot be produced, it is reported as `*` (e.g., `Content-Range: items 0-24/*`).
|
|
153
|
+
|
|
154
|
+
Enable exact counts in the application's REST configuration:
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
rest:
|
|
158
|
+
exactCount: true
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Without this, a `count=exact` request is served as an estimate.
|
|
162
|
+
|
|
163
|
+
11. **Query across relationships using dot syntax**: Define relationships in the schema with `@relationship`, then filter and select across them.
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
GET /Product/?brand.name=Microsoft
|
|
167
|
+
GET /Brand/?products.name=Keyboard
|
|
114
168
|
GET /Product/?brand.name=Microsoft&select(name,brand{name})
|
|
115
169
|
```
|
|
116
170
|
|
|
117
|
-
|
|
171
|
+
Filtering on a related attribute produces INNER JOIN behavior. Selecting a relationship without filtering produces LEFT JOIN behavior — the property is omitted if the foreign key is null or references a non-existent record.
|
|
172
|
+
|
|
173
|
+
12. **Access a specific property by URL**: Append the property name with dot syntax to the record ID.
|
|
174
|
+
|
|
118
175
|
```
|
|
119
|
-
GET /
|
|
176
|
+
GET /MyTable/123.propertyName
|
|
120
177
|
```
|
|
121
178
|
|
|
179
|
+
This only works for declared schema properties. The suffixes `.json`, `.cbor`, `.msgpack`, and `.csv` are reserved as content-type selectors.
|
|
180
|
+
|
|
122
181
|
## Examples
|
|
123
182
|
|
|
124
|
-
**
|
|
183
|
+
**Range filter with select and sort:**
|
|
125
184
|
|
|
126
185
|
```
|
|
127
|
-
GET /Product/?category=software&price=gt=100&price=lt=200&select(name,price)
|
|
186
|
+
GET /Product/?category=software&price=gt=100&price=lt=200&select(name,price)&sort(+price)
|
|
128
187
|
```
|
|
129
188
|
|
|
130
|
-
**
|
|
189
|
+
**Paginated request with exact count:**
|
|
131
190
|
|
|
132
191
|
```
|
|
133
|
-
GET /Product/?
|
|
134
|
-
|
|
135
|
-
GET /Product/?rating=gt=3&sort(+name)
|
|
136
|
-
GET /Product/?sort(+rating,-price)
|
|
192
|
+
GET /Product/?category=software&limit(0,25)
|
|
193
|
+
Prefer: count=exact
|
|
137
194
|
```
|
|
138
195
|
|
|
139
|
-
|
|
196
|
+
Response headers:
|
|
140
197
|
|
|
141
198
|
```
|
|
142
|
-
|
|
199
|
+
HTTP/1.1 200 OK
|
|
200
|
+
Content-Range: items 0-24/1234
|
|
201
|
+
Range-Unit: items
|
|
202
|
+
Preference-Applied: count=exact
|
|
143
203
|
```
|
|
144
204
|
|
|
145
|
-
**Relationship
|
|
146
|
-
|
|
147
|
-
Define the schema:
|
|
205
|
+
**Relationship schema and join query:**
|
|
148
206
|
|
|
149
207
|
```graphql
|
|
150
208
|
type Product @table @export {
|
|
@@ -160,11 +218,8 @@ type Brand @table @export {
|
|
|
160
218
|
}
|
|
161
219
|
```
|
|
162
220
|
|
|
163
|
-
Query with join:
|
|
164
|
-
|
|
165
221
|
```
|
|
166
222
|
GET /Product/?brand.name=Microsoft&select(name,brand{name,id})
|
|
167
|
-
GET /Brand/?products.name=Keyboard
|
|
168
223
|
```
|
|
169
224
|
|
|
170
225
|
**Many-to-many relationship:**
|
|
@@ -182,16 +237,17 @@ type Product @table @export {
|
|
|
182
237
|
GET /Product/?resellers.name=Cool Shop&select(id,name,resellers{name,id})
|
|
183
238
|
```
|
|
184
239
|
|
|
185
|
-
**
|
|
240
|
+
**OR grouping with user-supplied tags:**
|
|
186
241
|
|
|
187
|
-
```
|
|
188
|
-
|
|
242
|
+
```javascript
|
|
243
|
+
let url = `/Product/?rating=5&[${tags.map(encodeURIComponent).join('|')}]`;
|
|
189
244
|
```
|
|
190
245
|
|
|
191
246
|
## Notes
|
|
192
247
|
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
-
|
|
248
|
+
- The queried attribute must be indexed for basic attribute filtering to execute. For multi-attribute queries, only one attribute needs to be indexed.
|
|
249
|
+
- Null indexing requires indexes created after the feature was introduced. Rebuild existing indexes (remove and re-add) to support `name==null` queries.
|
|
250
|
+
- `limit()` must be a non-negative integer no larger than **10,000**, and the requested window (offset + limit) no larger than **1,000,000**, for count headers to be returned.
|
|
251
|
+
- A `HEAD` request with a `Prefer: count=exact` header returns count headers with no body — it saves bandwidth but still scans the matched set.
|
|
252
|
+
- When CORS is enabled, `Content-Range`, `Range-Unit`, and `Preference-Applied` are added to `Access-Control-Expose-Headers` automatically.
|
|
253
|
+
- Operators `!=` and `=ct=` (contains) do not produce cardinality estimates; the total is reported as `*` when these are used with `count=exact`.
|