@harperfast/template-react-ts-studio 1.12.8 → 1.12.10

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.
Files changed (26) hide show
  1. package/.agents/skills/harper-best-practices/AGENTS.md +514 -445
  2. package/.agents/skills/harper-best-practices/rules/automatic-apis.md +96 -93
  3. package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +84 -58
  4. package/.agents/skills/harper-best-practices/rules/extending-tables.md +27 -35
  5. package/.agents/skills/harper-best-practices/rules/handling-binary-data.md +40 -39
  6. package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +93 -37
  7. package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +68 -66
  8. package/.agents/skills/harper-best-practices/rules/using-blob-datatype.md +24 -28
  9. package/.agents/skills/harper-best-practices/rules/v5-upgrade.md +98 -105
  10. package/.agents/skills/harper-best-practices/rules.manifest.yaml +28 -3
  11. package/.agents/skills/harper-mcp/AGENTS.md +138 -26
  12. package/.agents/skills/harper-mcp/rules/enabling-mcp.md +140 -28
  13. package/agent/skills/harper-best-practices/AGENTS.md +514 -445
  14. package/agent/skills/harper-best-practices/rules/automatic-apis.md +96 -93
  15. package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +84 -58
  16. package/agent/skills/harper-best-practices/rules/extending-tables.md +27 -35
  17. package/agent/skills/harper-best-practices/rules/handling-binary-data.md +40 -39
  18. package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +93 -37
  19. package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +68 -66
  20. package/agent/skills/harper-best-practices/rules/using-blob-datatype.md +24 -28
  21. package/agent/skills/harper-best-practices/rules/v5-upgrade.md +98 -105
  22. package/agent/skills/harper-best-practices/rules.manifest.yaml +28 -3
  23. package/agent/skills/harper-mcp/AGENTS.md +138 -26
  24. package/agent/skills/harper-mcp/rules/enabling-mcp.md +140 -28
  25. package/package.json +1 -1
  26. package/skills-lock.json +2 -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: ce0ab713d918d789bc1c9f22e461e963ccc1dff1
10
- inputHash: 19738fbc732e0a1a
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 add computed properties, intercept writes, enforce validation, or otherwise customize the behavior of a Harper table resource beyond what the default generated endpoints provide. Use it any time a `@table` type needs server-side logic attached to its REST handlers.
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`**: Declare the table type in `schema.graphql` and omit the `@export` directive. Leaving `@export` on the schema while also exporting a subclass with the same name produces conflicting endpoints. Let the JavaScript class own the URL instead.
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**: In `resources.js`, extend from the `tables.<TypeName>` global. The class name you export becomes the URL path. The exported class extends tables.
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` to preserve default behavior**: When delegating to `super`, match the argument form to the operation:
49
- - Reads/deletes: `super.get(target)` / `super.delete(target)`
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
- Omit the `super` call only if you intend to replace the default behavior entirely.
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
- 5. **Configure Harper to load both files**: Ensure your configuration references the schema and resource files.
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 end-to-end example — schema, resource class, and error handling:
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
- ```javascript
104
- if (!authorized) {
105
- const error = new Error('Forbidden');
106
- error.statusCode = 403;
107
- throw error;
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 JavaScript subclass is exporting the same name. The two registrations conflict.
114
- - `super` must be called with the correct arguments for each operation type — mismatched arguments will not behave as expected.
115
- - `statusCode` is the only recognized property for controlling HTTP status on thrown errors; `.status` is ignored.
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: ce0ab713d918d789bc1c9f22e461e963ccc1dff1
11
- inputHash: fa06480e6fae7614
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 REST clients send `base64`-encoded data inside JSON, when raw binary is uploaded via `PUT`/`POST`, or when a resource must stream binary back to the client with the correct `Content-Type`.
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**: Decode the incoming `base64` string with `Buffer.from` and wrap it using `createBlob`, recording the MIME type. Override `post` in your resource class:
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
- if (record.data) {
32
- record.data = createBlob(Buffer.from(record.data, record.encoding || 'base64'), {
33
- type: record.contentType || 'application/octet-stream',
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, record);
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. **Upload raw binary with a non-standard content type**: Make a `PUT` or `POST` with any non-standard `Content-Type` header. Harper automatically stores the body as a record with `contentType` and `data` properties:
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
- ```http
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
- **End-to-end: accept base64 JSON, store as blob, serve as binary**
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
- if (record.data) {
98
- record.data = createBlob(Buffer.from(record.data, record.encoding || 'base64'), {
99
- type: record.contentType || 'application/octet-stream',
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, record);
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
- - `createBlob` takes a `Buffer` as its first argument and an options object with a `type` property for the MIME type. See [using-blob-datatype.md](using-blob-datatype.md) for full details on the blob data type.
123
- - Always fall back to `application/octet-stream` when no MIME type is known, both when creating and when serving blobs.
124
- - When Harper retrieves a record that has both `contentType` and `data` properties, it automatically sets the response `Content-Type` and body — no custom `get` override is required for that case unless you need additional logic.
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: 677ad213d67822e109c83619e181ca23a59823db
9
- inputHash: 0f8efee293628a52
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 Harper REST API collections using URL query parameters.
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 building or modifying code that queries Harper REST collection endpoints. Use it when you need to filter records by attribute values, apply comparison operators, sort or paginate results, or join across related tables. See [automatic-apis.md](automatic-apis.md) for how Harper exposes tables as REST endpoints.
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
- 2. **Apply comparison operators (FIQL syntax)**: Use FIQL operators in the query string for numeric, string, and date comparisons.
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&lt=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 of operations. Prefer square brackets when constructing queries from user input, since standard URI encoding safely encodes `[` and `]`.
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
- Construct from JavaScript:
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(...)` as a query function separated by `&`.
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
- 8. **Paginate with `limit(`**: Use `limit(end)` or `limit(start,end)` to control result count and offset.
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
- 9. **Sort with `sort(`**: Use `sort(property)` or `sort(+property,-property,...)`. Prefix `+` or no prefix = ascending; `-` = descending.
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
- 10. **Query across relationships**: Use dot-syntax to filter by related table attributes. Relationships must be defined in the schema using `@relationship`. Relationship attributes are not included by default — use `select()` to include them.
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
- 11. **Query for null values**: Use `=null` as the value to match null or non-null records.
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 /Product/?discount=null
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
- **Filter with comparison operators and select:**
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
- **Paginate and sort:**
189
+ **Paginated request with exact count:**
131
190
 
132
191
  ```
133
- GET /Product/?rating=gt=3&inStock=true&select(rating,name)&limit(20)
134
- GET /Product/?rating=gt=3&limit(10,30)
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
- **OR logic with grouping:**
196
+ Response headers:
140
197
 
141
198
  ```
142
- GET /Product/?price=lt=100|[rating=5&[tag=fast|tag=scalable|tag=efficient]&inStock=true]
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 join with nested select:**
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
- **Access a specific property by record ID:**
240
+ **OR grouping with user-supplied tags:**
186
241
 
187
- ```
188
- GET /MyTable/123.propertyName
242
+ ```javascript
243
+ let url = `/Product/?rating=5&[${tags.map(encodeURIComponent).join('|')}]`;
189
244
  ```
190
245
 
191
246
  ## Notes
192
247
 
193
- - Only indexed attributes can be used as the primary filter attribute; when combining multiple attributes, only one needs to be indexed.
194
- - Relationship attributes are excluded from responses by default. Always use `select(` to include them.
195
- - When selecting a related attribute without filtering on it, the behavior is a LEFT JOIN — the property is omitted if the foreign key is null or references a non-existent record.
196
- - The suffixes `.json`, `.cbor`, `.msgpack`, and `.csv` in URL paths are reserved as content-type selectors and take precedence over a property of the same name.
197
- - Square brackets are preferred over parentheses when building grouped queries programmatically, because `[` and `]` are safely URL-encoded by standard encoding functions while `(` is not.
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`.