@harperfast/template-react-ts-studio 1.11.3 → 1.11.5

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 (48) hide show
  1. package/.agents/skills/harper-best-practices/AGENTS.md +567 -490
  2. package/.agents/skills/harper-best-practices/rules/automatic-apis.md +46 -78
  3. package/.agents/skills/harper-best-practices/rules/defining-relationships.md +41 -63
  4. package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +59 -45
  5. package/.agents/skills/harper-best-practices/rules/load-env.md +24 -23
  6. package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +62 -78
  7. package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +92 -65
  8. package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +53 -55
  9. package/.agents/skills/harper-best-practices/rules/v5-upgrade.md +98 -62
  10. package/.agents/skills/harper-best-practices/rules/vector-indexing.md +113 -42
  11. package/.agents/skills/harper-mcp/AGENTS.md +501 -0
  12. package/.agents/skills/harper-mcp/SKILL.md +91 -0
  13. package/.agents/skills/harper-mcp/rules/automatic-verb-tools.md +50 -0
  14. package/.agents/skills/harper-mcp/rules/connecting-clients.md +59 -0
  15. package/.agents/skills/harper-mcp/rules/custom-mcp-prompts.md +52 -0
  16. package/.agents/skills/harper-mcp/rules/custom-mcp-resources.md +64 -0
  17. package/.agents/skills/harper-mcp/rules/custom-mcp-tools.md +72 -0
  18. package/.agents/skills/harper-mcp/rules/durable-quotas.md +73 -0
  19. package/.agents/skills/harper-mcp/rules/enabling-mcp.md +63 -0
  20. package/.agents/skills/harper-mcp/rules/rate-limiting.md +62 -0
  21. package/.agents/skills/harper-mcp/rules/resources-surface.md +44 -0
  22. package/.agents/skills/harper-mcp/rules/security-posture.md +36 -0
  23. package/.agents/skills/harper-mcp/rules.manifest.yaml +203 -0
  24. package/agent/skills/harper-best-practices/AGENTS.md +567 -490
  25. package/agent/skills/harper-best-practices/rules/automatic-apis.md +46 -78
  26. package/agent/skills/harper-best-practices/rules/defining-relationships.md +41 -63
  27. package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +59 -45
  28. package/agent/skills/harper-best-practices/rules/load-env.md +24 -23
  29. package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +62 -78
  30. package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +92 -65
  31. package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +53 -55
  32. package/agent/skills/harper-best-practices/rules/v5-upgrade.md +98 -62
  33. package/agent/skills/harper-best-practices/rules/vector-indexing.md +113 -42
  34. package/agent/skills/harper-mcp/AGENTS.md +501 -0
  35. package/agent/skills/harper-mcp/SKILL.md +83 -0
  36. package/agent/skills/harper-mcp/rules/automatic-verb-tools.md +50 -0
  37. package/agent/skills/harper-mcp/rules/connecting-clients.md +59 -0
  38. package/agent/skills/harper-mcp/rules/custom-mcp-prompts.md +52 -0
  39. package/agent/skills/harper-mcp/rules/custom-mcp-resources.md +64 -0
  40. package/agent/skills/harper-mcp/rules/custom-mcp-tools.md +72 -0
  41. package/agent/skills/harper-mcp/rules/durable-quotas.md +73 -0
  42. package/agent/skills/harper-mcp/rules/enabling-mcp.md +63 -0
  43. package/agent/skills/harper-mcp/rules/rate-limiting.md +62 -0
  44. package/agent/skills/harper-mcp/rules/resources-surface.md +44 -0
  45. package/agent/skills/harper-mcp/rules/security-posture.md +36 -0
  46. package/agent/skills/harper-mcp/rules.manifest.yaml +203 -0
  47. package/package.json +2 -2
  48. package/skills-lock.json +7 -1
@@ -7,8 +7,8 @@ metadata:
7
7
  mode: generate
8
8
  sources:
9
9
  - reference/v5/environment-variables/overview.md
10
- sourceCommit: 42231db17c025a4a455e3d91875fad4084a01cb5
11
- inputHash: fe610fc245226357
10
+ sourceCommit: 3749d0c54be457a2a65d9a63c738a5dc88989ecd
11
+ inputHash: b4db5ede6b93d426
12
12
  ---
13
13
 
14
14
  # Load Environment Variables with loadEnv
@@ -17,18 +17,18 @@ Instructions for the agent to follow when loading environment variables from `.e
17
17
 
18
18
  ## When to Use
19
19
 
20
- Apply this rule when a Harper application needs to load secrets or configuration values from `.env` files into `process.env` at startup. Use it whenever hardcoding values must be avoided and environment-specific configuration must be supplied to Harper components.
20
+ Apply this rule when a Harper application needs to supply secrets, API endpoints, or other configuration values to component code via `process.env` without hardcoding them. Use `loadEnv` any time you need to load one or more `.env` files at application startup.
21
21
 
22
22
  ## How It Works
23
23
 
24
- 1. **Declare `loadEnv` in `config.yaml`**: Add `loadEnv` as a top-level key. It is built into Harper and requires no installation.
24
+ 1. **Declare `loadEnv` in `config.yaml`**: Add `loadEnv` as the first entry in `config.yaml`. It is built into Harper and requires no installation.
25
25
 
26
26
  ```yaml
27
27
  loadEnv:
28
28
  files: '.env'
29
29
  ```
30
30
 
31
- 2. **Place `loadEnv` first**: Always list `loadEnv` before any other components in `config.yaml` so that environment variables are available on `process.env` before dependent components start.
31
+ 2. **Place `loadEnv` first**: Harper is a single-process application. List `loadEnv` before all other components so that environment variables are available on `process.env` before dependent components start.
32
32
 
33
33
  ```yaml
34
34
  # config.yaml — loadEnv must come first
@@ -41,9 +41,9 @@ Apply this rule when a Harper application needs to load secrets or configuration
41
41
  files: './src/*.js'
42
42
  ```
43
43
 
44
- 3. **Configure the `files` option**: Provide one or more paths or glob patterns pointing to the env files to load. This option is required.
44
+ 3. **Access loaded values in component code**: After `loadEnv` runs, all loaded values are available on `process.env` and shared across all components.
45
45
 
46
- 4. **Set `override` if needed**: By default, existing environment variables take precedence over values in `.env` files. Set `override: true` to reverse this and have loaded values win.
46
+ 4. **Control override behavior**: By default, existing environment variables take precedence over values in `.env` files. Set `override: true` to make loaded values win instead.
47
47
 
48
48
  ```yaml
49
49
  loadEnv:
@@ -51,14 +51,17 @@ Apply this rule when a Harper application needs to load secrets or configuration
51
51
  override: true
52
52
  ```
53
53
 
54
- 5. **Load multiple files when required**: Supply a list of files or a glob pattern. Files are loaded in the order specified.
54
+ 5. **Load multiple files**: Pass an array of paths or a glob pattern to `files`. Files are loaded in the order specified.
55
+
55
56
  ```yaml
56
57
  loadEnv:
57
58
  files:
58
59
  - '.env'
59
60
  - '.env.local'
60
61
  ```
61
- or
62
+
63
+ or with a glob:
64
+
62
65
  ```yaml
63
66
  loadEnv:
64
67
  files: 'env-vars/*'
@@ -73,17 +76,23 @@ Apply this rule when a Harper application needs to load secrets or configuration
73
76
 
74
77
  ## Examples
75
78
 
76
- **Minimal setup — single `.env` file:**
79
+ **Single file, default behavior:**
77
80
 
78
81
  ```yaml
82
+ # config.yaml
79
83
  loadEnv:
80
84
  files: '.env'
85
+
86
+ rest: true
87
+
88
+ myApp:
89
+ files: './src/*.js'
81
90
  ```
82
91
 
83
- **Full `config.yaml` with load order, multiple files, and override:**
92
+ **Multiple files with override:**
84
93
 
85
94
  ```yaml
86
- # config.yaml — loadEnv must come first
95
+ # config.yaml
87
96
  loadEnv:
88
97
  files:
89
98
  - '.env'
@@ -96,16 +105,8 @@ myApp:
96
105
  files: './src/*.js'
97
106
  ```
98
107
 
99
- **Glob pattern:**
100
-
101
- ```yaml
102
- loadEnv:
103
- files: 'env-vars/*'
104
- ```
105
-
106
108
  ## Notes
107
109
 
108
- - `loadEnv` is built into Harper — do not install it separately; only declare it in `config.yaml`.
109
- - Because Harper is a single-process application, variables loaded onto `process.env` are shared across all components.
110
- - Without `override: true`, variables already set in the shell or container environment will not be overwritten by values in `.env` files.
111
- - `files` is the only required option; omitting it will produce an invalid configuration.
110
+ - `loadEnv` loads values into `process.env` for **application** code only — it does not configure Harper itself.
111
+ - Harper's own instance-wide configuration is composed at startup **before** any component's `loadEnv` runs. Variables such as `HARPER_CONFIG`, `HARPER_SET_CONFIG`, and `HARPER_DEFAULT_CONFIG` delivered through a `.env` file are read too late and are ignored. Set Harper configuration directly in the configuration file or export variables in the real process/container environment before Harper starts.
112
+ - For production credentials, prefer the encrypted secrets store over a committed `.env` file. Secrets are also delivered to components via `process.env`.
@@ -14,8 +14,8 @@ metadata:
14
14
  reference/v5/resources/resource-api.md#`addTo(property: string, value:
15
15
  number)`
16
16
  - reference/v5/components/javascript-environment.md#Module Loading
17
- sourceCommit: be709f9978319dcdb669c05d794effc82bcda8b7
18
- inputHash: 3d2417fbff687c42
17
+ sourceCommit: 3749d0c54be457a2a65d9a63c738a5dc88989ecd
18
+ inputHash: ace5c3f4a0ad4519
19
19
  ---
20
20
 
21
21
  # Programmatic Table Requests
@@ -24,11 +24,11 @@ Instructions for the agent to interact with Harper tables programmatically using
24
24
 
25
25
  ## When to Use
26
26
 
27
- Apply this rule when writing server-side Harper code that reads from or writes to tables directly — for example, in request handlers, background jobs, or SSR entry points — instead of going through the REST API. Use it whenever you need to construct queries with `conditions`, paginate results, select specific fields, or perform CRDT-safe mutations with `addTo`.
27
+ Apply this rule when writing server-side code that reads from or writes to Harper tables directly — for example, in request handlers, background jobs, or SSR rendering — without going through the REST API. Use it whenever you need to construct queries with `conditions`, `select`, `sort`, or `search(`.
28
28
 
29
29
  ## How It Works
30
30
 
31
- 1. **Import `tables`**: Import from the `harper` package. Each table defined in `schema.graphql` with `@table` is available as a named property.
31
+ 1. **Import `tables`**: Pull `tables` from the `harper` package. Each property on `tables` corresponds to a table defined in `schema.graphql`.
32
32
 
33
33
  ```javascript
34
34
  import { tables } from 'harper';
@@ -36,10 +36,9 @@ Apply this rule when writing server-side Harper code that reads from or writes t
36
36
  // same as: databases.data.Product
37
37
  ```
38
38
 
39
- 2. **Define your schema**: Declare tables in `schema.graphql` using `@table` and `@primaryKey`.
39
+ 2. **Define your schema**: Declare tables with `@table` in `schema.graphql`. Each type becomes a property on `tables`.
40
40
 
41
41
  ```graphql
42
- # schema.graphql
43
42
  type Product @table {
44
43
  id: Long @primaryKey
45
44
  name: String
@@ -47,57 +46,75 @@ Apply this rule when writing server-side Harper code that reads from or writes t
47
46
  }
48
47
  ```
49
48
 
50
- 3. **Create and mutate records**: Use `create`, `patch`, `get`, and `update` on the table class.
49
+ 3. **Create and modify records**: Use `create`, `patch`, and `get` for basic CRUD.
51
50
 
52
51
  ```javascript
53
- // Create a new record (id auto-generated)
54
52
  const created = await Product.create({ name: 'Shirt', price: 9.5 });
55
-
56
- // Modify the record
57
53
  await Product.patch(created.id, { price: Math.round(created.price * 0.8 * 100) / 100 });
58
-
59
- // Retrieve by primary key
60
54
  const record = await Product.get(created.id);
61
55
  ```
62
56
 
63
- 4. **Query with `search(` and `conditions`**: Pass a query object to `search()` to filter records. Iterate the async result.
57
+ 4. **Query with `search(`**: Pass a query object to `Product.search(query)`. It returns an async iterable.
64
58
 
65
59
  ```javascript
66
60
  const query = {
67
61
  conditions: [{ attribute: 'price', comparator: 'less_than', value: 8.0 }],
68
62
  };
69
63
  for await (const record of Product.search(query)) {
70
- // ...
64
+ // process record
71
65
  }
72
66
  ```
73
67
 
74
- 5. **Use `select` to shape results**: Pass a `select` array to return only specific properties, including nested relationship fields.
68
+ 5. **Build `conditions`**: Each condition object filters records. Nest conditions with `operator` for boolean logic.
69
+
70
+ | Property | Description |
71
+ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
72
+ | `attribute` | Property name, or array for chained/joined properties (e.g. `['brand', 'name']`) |
73
+ | `value` | The value to match |
74
+ | `comparator` | `equals` (default), `greater_than`, `greater_than_equal`, `less_than`, `less_than_equal`, `starts_with`, `contains`, `ends_with`, `between`, `not_equal` |
75
+ | `conditions` | Nested conditions array |
76
+ | `operator` | `and` (default) or `or` for the nested `conditions` |
77
+
78
+ 6. **Use `select`** to control which properties are returned. Accepts an array of property names, a string for a single property, or nested objects for relationships.
75
79
 
76
80
  ```javascript
77
- const book = await Book.get({ id: 42, select: ['id', 'title', 'author'] });
78
- book.author.name; // full related Author record
81
+ // Array of names
82
+ Product.search({ select: ['name', 'price'] });
79
83
 
80
- // Partial related record
81
- const book = await Book.get({
82
- id: 42,
83
- select: ['id', 'title', { name: 'author', select: ['name'] }],
84
- });
84
+ // Nested relationship select
85
+ Book.get({ id: 42, select: ['id', 'title', { name: 'author', select: ['name'] }] });
85
86
  ```
86
87
 
87
- 6. **Use `addTo` for concurrent-safe increments**: Call `addTo` on a mutable resource instance obtained via `update()`. This uses CRDT incrementation, safe across threads and nodes.
88
+ Special `select` values:
89
+ - `$id` — returns the primary key regardless of its name
90
+ - `$updatedtime` — returns the last-updated timestamp
91
+ - `$distance` — returns the computed distance when querying a vector index
92
+
93
+ 7. **Use `addTo`** for concurrent-safe numeric increments (CRDT incrementation, safe across threads and nodes).
88
94
 
89
95
  ```javascript
90
- static async post(target, data) {
91
- const record = await this.update(target.id);
92
- record.addTo('quantity', -1); // decrement safely across nodes
93
- }
96
+ const record = await Product.update(target.id);
97
+ record.addTo('quantity', -1);
94
98
  ```
95
99
 
96
- 7. **Scope destructive operations carefully**: `update`, `patch`, and `delete` operate directly on stored data. Always use specific `conditions`, validate the affected set before writing, and gate behind authorization controls.
100
+ 8. **Apply `sort`, `limit`, and `offset`** for ordering and pagination. A `sort` attribute must be `@indexed`, or at least one `conditions` entry must be present. Pass `allowFullScan: true` to permit an unconditional ordered scan.
101
+
102
+ ```javascript
103
+ Product.search({
104
+ conditions: [{ attribute: 'id', comparator: 'greater_than', value: '' }],
105
+ sort: { attribute: 'id' },
106
+ });
107
+ ```
108
+
109
+ | Sort property | Description |
110
+ | ------------- | ---------------------------------------------------------- |
111
+ | `attribute` | Property name (or array for chained relationship property) |
112
+ | `descending` | Sort descending if `true` (default: `false`) |
113
+ | `next` | Secondary sort to resolve ties (same structure) |
97
114
 
98
115
  ## Examples
99
116
 
100
- ### Nested conditions query
117
+ **Nested conditions with `or`:**
101
118
 
102
119
  ```javascript
103
120
  Product.search({
@@ -114,23 +131,25 @@ Product.search({
114
131
  });
115
132
  ```
116
133
 
117
- ### Chained attribute reference (relationship join)
134
+ **Chained attribute reference (relationship join):**
118
135
 
119
136
  ```javascript
120
137
  Product.search({ conditions: [{ attribute: ['brand', 'name'], value: 'Harper' }] });
121
138
  ```
122
139
 
123
- ### Deep nested `select`
140
+ **Deep nested `select` across multiple relationships:**
124
141
 
125
142
  ```javascript
126
- select: [
127
- 'id',
128
- 'name',
129
- { name: 'segments', select: ['id', 'name', { name: 'client', select: ['id', 'name'] }] },
130
- ];
143
+ Product.search({
144
+ select: [
145
+ 'id',
146
+ 'name',
147
+ { name: 'segments', select: ['id', 'name', { name: 'client', select: ['id', 'name'] }] },
148
+ ],
149
+ });
131
150
  ```
132
151
 
133
- ### SSR usage
152
+ **SSR usage — read directly from `tables` in a render function:**
134
153
 
135
154
  ```typescript
136
155
  import { tables } from 'harper';
@@ -143,44 +162,9 @@ export async function render(url: string): Promise<string> {
143
162
 
144
163
  ## Notes
145
164
 
146
- ### `conditions` comparator values
147
-
148
- | Comparator | Description |
149
- | -------------------- | ---------------------- |
150
- | `equals` | Default equality match |
151
- | `greater_than` | Strictly greater |
152
- | `greater_than_equal` | Greater than or equal |
153
- | `less_than` | Strictly less |
154
- | `less_than_equal` | Less than or equal |
155
- | `starts_with` | String prefix match |
156
- | `contains` | String contains |
157
- | `ends_with` | String suffix match |
158
- | `between` | Range match |
159
- | `not_equal` | Inequality match |
160
-
161
- ### Query object options
162
-
163
- | Property | Description |
164
- | ----------------------- | ----------------------------------------------------------------------- |
165
- | `conditions` | Array of condition objects to filter records |
166
- | `operator` | Top-level `and` (default) or `or` for the `conditions` array |
167
- | `limit` | Maximum number of records to return |
168
- | `offset` | Number of records to skip (for pagination) |
169
- | `select` | Properties to include in each returned record |
170
- | `sort` | Sort order object with `attribute`, `descending`, and `next` properties |
171
- | `explain` | If `true`, returns conditions reordered as Harper will execute them |
172
- | `enforceExecutionOrder` | If `true`, forces conditions to execute in the order supplied |
173
-
174
- ### `select` special properties
175
-
176
- - `$id` — Returns the primary key regardless of its name
177
- - `$updatedtime` — Returns the last-updated timestamp
178
- - `$distance` — Returns the computed distance from the target vector when the query uses a vector index
179
-
180
- ### Relationship join behavior
181
-
182
- - Selecting a relationship **without** filtering on it behaves as a **LEFT JOIN** — records with no related row are still returned.
183
- - Adding a `conditions` entry on a related attribute (e.g. `attribute: ['author', 'name']`) behaves as an **INNER JOIN** — only records with a matching related row are returned.
184
-
185
- - Keep `harper` external when bundling for SSR (e.g. `ssr: { external: ['harper'] }` in `vite.config`) so it resolves to the runtime instead of being bundled.
186
- - `tables`, `databases`, and other Harper APIs are the same live, process-wide objects regardless of whether accessed as globals or via `import { tables } from 'harper'`.
165
+ - Scope destructive operations (`update`, `patch`, `delete`) with specific `conditions` and validate the affected set before writing. These operate on live data and are not easily reversible.
166
+ - Sorting by a bare `@primaryKey` with no conditions raises `HdbError: <attribute> is not indexed and not combined with any other conditions`. Add an open-ended condition or pass `allowFullScan: true`.
167
+ - Selecting a relationship field without filtering on it behaves as a **LEFT JOIN**. Adding a condition on a related attribute (e.g. `attribute: ['author', 'name']`) behaves as an **INNER JOIN**.
168
+ - A to-many relationship resolves to an array; `await` the property before iterating when needed.
169
+ - `tables` and `databases.data` reference the same live objects — a record written through one component is immediately visible to all others.
170
+ - Keep `harper` external when bundling for SSR (e.g. `ssr: { external: ['harper'] }` in `vite.config`) so it resolves to the runtime.
@@ -5,17 +5,17 @@ metadata:
5
5
  mode: generate
6
6
  sources:
7
7
  - reference/v5/rest/querying.md
8
- sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
9
- inputHash: 9f8c981a629ef606
8
+ sourceCommit: 3749d0c54be457a2a65d9a63c738a5dc88989ecd
9
+ inputHash: d5ed0e937cd2d7e0
10
10
  ---
11
11
 
12
- # Querying REST APIs
12
+ # Querying Harper 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 using Harper's URL-based query language on REST collection endpoints.
15
15
 
16
16
  ## When to Use
17
17
 
18
- Apply this rule when building or modifying code that queries Harper REST endpoints with filtering, sorting, field selection, or pagination. Use it whenever constructing URLs against collection paths exposed by Harper's automatic REST interface (see [automatic-apis.md](automatic-apis.md)).
18
+ Apply this rule when building or debugging REST API calls against Harper tables that require filtering by attribute values, comparison ranges, sorting, field selection, or result pagination. This rule also covers OR logic, grouping, and querying across related tables via dot-syntax joins. See [automatic-apis.md](automatic-apis.md) for how Harper exposes tables as REST endpoints.
19
19
 
20
20
  ## How It Works
21
21
 
@@ -26,21 +26,27 @@ Apply this rule when building or modifying code that queries Harper REST endpoin
26
26
  GET /Product/?category=software&inStock=true
27
27
  ```
28
28
 
29
- 2. **Apply comparison operators (FIQL syntax)**: Use FIQL operators directly in query parameter values.
29
+ 2. **Filter for null values**: Use `=null` as the value to match null or non-null records.
30
30
 
31
- | Operator | Meaning |
32
- | ------------ | -------------------------------------- |
33
- | `==` | Equal |
34
- | `=lt=` | Less than |
35
- | `=le=` | Less than or equal |
36
- | `=gt=` | Greater than |
37
- | `=ge=` | Greater than or equal |
38
- | `=ne=`, `!=` | Not equal |
39
- | `=ct=` | Contains (strings) |
40
- | `=sw=` | Starts with (strings) |
41
- | `=ew=` | Ends with (strings) |
42
- | `=`, `===` | Strict equality (no type conversion) |
43
- | `!==` | Strict inequality (no type conversion) |
31
+ ```
32
+ GET /Product/?discount=null
33
+ ```
34
+
35
+ 3. **Apply comparison operators (FIQL syntax)**: Use FIQL operators in query parameters for range and string matching.
36
+
37
+ | Operator | Meaning |
38
+ | -------------------- | -------------------------------------- |
39
+ | `==` | Equal |
40
+ | `=lt=` | Less than |
41
+ | `=le=` | Less than or equal |
42
+ | `=gt=` | Greater than |
43
+ | `=ge=` | Greater than or equal |
44
+ | `=ne=`, `!=` | Not equal |
45
+ | `=ct=` | Contains (strings) |
46
+ | `=sw=`, `==<value>*` | Starts with (strings) |
47
+ | `=ew=` | Ends with (strings) |
48
+ | `=`, `===` | Strict equality (no type conversion) |
49
+ | `!==` | Strict inequality (no type conversion) |
44
50
 
45
51
  ```
46
52
  GET /Product/?price=gt=100
@@ -55,32 +61,52 @@ Apply this rule when building or modifying code that queries Harper REST endpoin
55
61
  GET /Product/?listDate=gt=2017-03-08T09%3A30%3A00.000Z
56
62
  ```
57
63
 
58
- 3. **Chain conditions for range queries**: Omit the attribute name on the second condition to apply it to the same attribute. Only `gt`/`ge` combined with `lt`/`le` is supported.
64
+ 4. **Chain conditions for range queries**: Omit the attribute name on the second condition to apply it to the same attribute. Only `gt`/`ge` combined with `lt`/`le` is supported for chaining.
59
65
 
60
66
  ```
61
67
  GET /Product/?price=gt=100&lt=200
62
68
  ```
63
69
 
64
- 4. **Combine conditions with OR logic**: Use `|` instead of `&`.
70
+ 5. **Apply type conversion**: For FIQL comparators, Harper converts values automatically. Use explicit prefixes to force a type.
71
+
72
+ | Syntax | Behavior |
73
+ | ----------------------------------------- | ------------------------------------------- |
74
+ | `name==null` | Converts to `null` |
75
+ | `name==123` | Converts to number if attribute is untyped |
76
+ | `name==true` | Converts to boolean if attribute is untyped |
77
+ | `name==number:123` | Explicit number conversion |
78
+ | `name==boolean:true` | Explicit boolean conversion |
79
+ | `name==string:some%20text` | Keep as string with URL decode |
80
+ | `name==date:2024-01-05T20%3A07%3A27.955Z` | Explicit Date conversion |
81
+
82
+ For strict operators (`=`, `===`, `!==`), no automatic type conversion is applied.
83
+
84
+ 6. **Combine conditions with OR logic**: Use `|` instead of `&` to express OR between conditions.
65
85
 
66
86
  ```
67
87
  GET /Product/?rating=5|featured=true
68
88
  ```
69
89
 
70
- 5. **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
+ 7. **Group conditions**: Use parentheses or square brackets to control evaluation order. Prefer square brackets when building queries from user input, since `[` and `]` are safely URI-encoded.
71
91
 
72
92
  ```
73
93
  GET /Product/?rating=5|(price=gt=100&price=lt=200)
74
94
  GET /Product/?rating=5&[tag=fast|tag=scalable|tag=efficient]
75
95
  ```
76
96
 
77
- Construct grouped queries from JavaScript:
97
+ Build grouped queries in JavaScript:
78
98
 
79
99
  ```javascript
80
100
  let url = `/Product/?rating=5&[${tags.map(encodeURIComponent).join('|')}]`;
81
101
  ```
82
102
 
83
- 6. **Select specific properties with `select(`**: Use `select()` to control which fields are returned.
103
+ Nest groups for complex conditions:
104
+
105
+ ```
106
+ GET /Product/?price=lt=100|[rating=5&[tag=fast|tag=scalable|tag=efficient]&inStock=true]
107
+ ```
108
+
109
+ 8. **Select specific properties with `select(`**: Append `select(...)` as a query function separated by `&` to control which fields are returned.
84
110
 
85
111
  | Syntax | Returns |
86
112
  | -------------------------------------- | ------------------------------------------- |
@@ -95,108 +121,109 @@ Apply this rule when building or modifying code that queries Harper REST endpoin
95
121
  GET /Product/?brand.name=Microsoft&select(name,brand{name})
96
122
  ```
97
123
 
98
- 7. **Limit results with `limit(`**: Use `limit(end)` or `limit(start,end)` to paginate.
124
+ 9. **Paginate results with `limit(`**: Use `limit(end)` or `limit(start,end)` to restrict the number of records returned.
99
125
 
100
126
  ```
101
127
  GET /Product/?rating=gt=3&inStock=true&select(rating,name)&limit(20)
102
128
  GET /Product/?rating=gt=3&limit(10,30)
103
129
  ```
104
130
 
105
- 8. **Sort results with `sort(`**: Use `sort(property)` or `sort(+property,-property,...)`. Prefix `+` or no prefix = ascending; `-` = descending.
131
+ 10. **Sort results with `sort(`**: Use `sort(property)` or `sort(+property,-property,...)` to order results. Prefix `+` or no prefix = ascending; `-` = descending.
106
132
 
107
- ```
108
- GET /Product/?rating=gt=3&sort(+name)
109
- GET /Product/?sort(+rating,-price)
110
- ```
133
+ ```
134
+ GET /Product/?rating=gt=3&sort(+name)
135
+ GET /Product/?sort(+rating,-price)
136
+ ```
111
137
 
112
- 9. **Query across relationships**: Use dot-syntax to filter by related table attributes. Relationships must be defined in the schema using `@relation`.
138
+ 11. **Query across relationships using dot-syntax**: Filter on related table attributes using dot-chained property names. Relationships must be defined in the schema with `@relationship`.
113
139
 
114
- ```
115
- GET /Product/?brand.name=Microsoft
116
- GET /Brand/?products.name=Keyboard
117
- ```
140
+ ```
141
+ GET /Product/?brand.name=Microsoft
142
+ GET /Brand/?products.name=Keyboard
143
+ ```
118
144
 
119
- Use `select()` to include relationship attributes in the response (they are not included by default):
145
+ Use `select()` to include relationship attributes in the response (they are excluded by default):
120
146
 
121
- ```
122
- GET /Product/?brand.name=Microsoft&select(name,brand{name})
123
- ```
147
+ ```
148
+ GET /Product/?brand.name=Microsoft&select(name,brand)
149
+ GET /Product/?brand.name=Microsoft&select(name,brand{name})
150
+ ```
124
151
 
125
- 10. **Access a specific property by URL**: Append the property name with dot syntax to the record ID. Only works for properties declared in the schema.
152
+ 12. **Access a specific property by URL**: Append `.propertyName` to a record ID in the URL path. Only works for properties declared in the schema.
126
153
  ```
127
154
  GET /MyTable/123.propertyName
128
155
  ```
129
156
 
130
157
  ## Examples
131
158
 
132
- **Range filter with select and limit:**
159
+ **Range filter with select and limit**:
133
160
 
134
161
  ```
135
162
  GET /Product/?category=software&price=gt=100&price=lt=200&select(name,price)&limit(20)
136
163
  ```
137
164
 
138
- **Sort descending with multiple fields:**
165
+ **Sort and paginate**:
139
166
 
140
167
  ```
141
- GET /Product/?sort(+rating,-price)
168
+ GET /Product/?rating=gt=3&sort(+rating,-price)&limit(10,30)
142
169
  ```
143
170
 
144
- **OR logic with grouping:**
171
+ **OR with grouping**:
145
172
 
146
173
  ```
147
174
  GET /Product/?price=lt=100|[rating=5&[tag=fast|tag=scalable|tag=efficient]&inStock=true]
148
175
  ```
149
176
 
150
- **Relationship join with nested select:**
151
-
152
- ```
153
- GET /Product/?brand.name=Microsoft&select(name,brand{name,id})
154
- ```
155
-
156
- **Schema defining a relationship for join queries:**
177
+ **Join query with nested select** — schema first:
157
178
 
158
179
  ```graphql
159
180
  type Product @table @export {
160
181
  id: Long @primaryKey
161
182
  name: String
162
183
  brandId: Long @indexed
163
- brand: Brand @relation(from: "brandId")
184
+ brand: Brand @relationship(from: "brandId")
164
185
  }
165
186
  type Brand @table @export {
166
187
  id: Long @primaryKey
167
188
  name: String
168
- products: [Product] @relation(to: "brandId")
189
+ products: [Product] @relationship(to: "brandId")
169
190
  }
170
191
  ```
171
192
 
172
- **Many-to-many relationship query:**
193
+ Then query:
194
+
195
+ ```
196
+ GET /Product/?brand.name=Microsoft&select(name,brand{name,id})
197
+ ```
198
+
199
+ **Many-to-many relationship** — schema:
173
200
 
174
201
  ```graphql
175
202
  type Product @table @export {
176
203
  id: Long @primaryKey
177
204
  name: String
178
205
  resellerIds: [Long] @indexed
179
- resellers: [Reseller] @relation(from: "resellerId")
206
+ resellers: [Reseller] @relationship(from: "resellerIds")
180
207
  }
181
208
  ```
182
209
 
210
+ Query:
211
+
183
212
  ```
184
213
  GET /Product/?resellers.name=Cool Shop&select(id,name,resellers{name,id})
185
214
  ```
186
215
 
187
- **Type conversion with explicit prefix:**
216
+ **Date range with URL-encoded colons**:
188
217
 
189
218
  ```
190
- GET /Product/?price==number:123
191
- GET /Product/?active==boolean:true
192
- GET /Product/?listDate==date:2024-01-05T20%3A07%3A27.955Z
219
+ GET /Product/?listDate=gt=2017-03-08T09%3A30%3A00.000Z
193
220
  ```
194
221
 
195
222
  ## Notes
196
223
 
197
- - Only indexed attributes can be used as the primary filter; additional unindexed attributes can be combined with `&` once at least one indexed attribute is present.
198
- - For null value queries, use `?attribute=null`. Indexes must have been created with null indexing support; existing indexes must be removed and re-added to support null queries.
199
- - FIQL comparators (`==`, `!=`, `=gt=`, etc.) apply automatic type conversion based on value syntax or schema-declared type. Strict operators (`=`, `===`, `!==`) skip automatic type conversion.
200
- - Filtering by a related attribute produces INNER JOIN behavior (only records with a matching related record are returned). Using `select()` on a relationship without a filter produces LEFT JOIN behavior.
201
- - The array order of foreign key values in many-to-many relationships is preserved when resolving the relationship.
202
- - See [automatic-apis.md](automatic-apis.md) for how Harper tables are automatically exposed as REST endpoints.
224
+ - All filtered attributes must be indexed unless at least one other attribute in the same query is indexed.
225
+ - Null queries (`?attr=null`) require indexes created after null indexing support was added. Rebuild existing indexes (remove and re-add) to enable null queries on them.
226
+ - When selecting a related attribute without filtering on it, the join behaves as a LEFT JOIN — the relationship property is omitted if the foreign key is null or references a non-existent record.
227
+ - The array order of foreign key values (e.g., `resellerIds`) is preserved when resolving many-to-many relationships.
228
+ - Square brackets (`[`, `]`) are preferred over parentheses for grouping when constructing queries programmatically, because standard URI encoding safely encodes them.
229
+ - `directURLMapping: true` can be set on a resource to change URL path handling semantics; see your schema configuration for details.