@harperfast/template-react-ts-ssr-studio 1.13.4 → 1.13.6
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 +687 -453
- package/.agents/skills/harper-best-practices/SKILL.md +24 -21
- package/.agents/skills/harper-best-practices/rules/adding-tables-with-schemas.md +2 -0
- package/.agents/skills/harper-best-practices/rules/automatic-apis.md +2 -0
- package/.agents/skills/harper-best-practices/rules/caching.md +2 -0
- package/.agents/skills/harper-best-practices/rules/checking-authentication.md +2 -0
- package/.agents/skills/harper-best-practices/rules/custom-resources.md +2 -0
- package/.agents/skills/harper-best-practices/rules/defining-relationships.md +102 -40
- package/.agents/skills/harper-best-practices/rules/deploying-from-ci.md +121 -93
- package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +149 -103
- package/.agents/skills/harper-best-practices/rules/extending-tables.md +2 -0
- package/.agents/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
- package/.agents/skills/harper-best-practices/rules/load-env.md +2 -0
- package/.agents/skills/harper-best-practices/rules/logging.md +2 -0
- package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +124 -91
- package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +86 -82
- package/.agents/skills/harper-best-practices/rules/real-time-apps.md +2 -0
- package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +92 -59
- package/.agents/skills/harper-best-practices/rules/serving-web-content.md +2 -0
- package/.agents/skills/harper-best-practices/rules/typescript-type-stripping.md +2 -0
- package/.agents/skills/harper-best-practices/rules/using-blob-datatype.md +2 -0
- package/.agents/skills/harper-best-practices/rules/vector-indexing.md +2 -0
- package/.agents/skills/harper-best-practices/rules.manifest.yaml +44 -0
- package/agent/skills/harper-best-practices/AGENTS.md +687 -453
- package/agent/skills/harper-best-practices/SKILL.md +26 -21
- package/agent/skills/harper-best-practices/rules/adding-tables-with-schemas.md +2 -0
- package/agent/skills/harper-best-practices/rules/automatic-apis.md +2 -0
- package/agent/skills/harper-best-practices/rules/caching.md +2 -0
- package/agent/skills/harper-best-practices/rules/checking-authentication.md +2 -0
- package/agent/skills/harper-best-practices/rules/custom-resources.md +2 -0
- package/agent/skills/harper-best-practices/rules/defining-relationships.md +102 -40
- package/agent/skills/harper-best-practices/rules/deploying-from-ci.md +121 -93
- package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +149 -103
- package/agent/skills/harper-best-practices/rules/extending-tables.md +2 -0
- package/agent/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
- package/agent/skills/harper-best-practices/rules/load-env.md +2 -0
- package/agent/skills/harper-best-practices/rules/logging.md +2 -0
- package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +124 -91
- package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +86 -82
- package/agent/skills/harper-best-practices/rules/real-time-apps.md +2 -0
- package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +92 -59
- package/agent/skills/harper-best-practices/rules/serving-web-content.md +2 -0
- package/agent/skills/harper-best-practices/rules/typescript-type-stripping.md +2 -0
- package/agent/skills/harper-best-practices/rules/using-blob-datatype.md +2 -0
- package/agent/skills/harper-best-practices/rules/vector-indexing.md +2 -0
- package/agent/skills/harper-best-practices/rules.manifest.yaml +44 -0
- package/package.json +1 -1
- package/skills-lock.json +1 -1
|
@@ -5,34 +5,29 @@ metadata:
|
|
|
5
5
|
mode: generate
|
|
6
6
|
sources:
|
|
7
7
|
- reference/v5/rest/querying.md
|
|
8
|
-
sourceCommit:
|
|
9
|
-
inputHash:
|
|
8
|
+
sourceCommit: 6c3e29dd9149201364b0d74642725c30f0305e3e
|
|
9
|
+
inputHash: e034fdc191f2cdc0
|
|
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 records
|
|
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 whenever
|
|
18
|
+
Apply this rule whenever building or modifying REST requests against Harper collection paths that need attribute filtering, comparison operators, sorting, field selection, pagination, or relationship joins. This rule covers all query parameter construction for Harper's automatic REST APIs (see [automatic-apis.md](automatic-apis.md)).
|
|
19
19
|
|
|
20
20
|
## How It Works
|
|
21
21
|
|
|
22
|
-
1. **Filter by attribute**: Add query parameters matching attribute
|
|
22
|
+
1. **Filter by attribute**: Add query parameters matching attribute name to value. The queried attribute must be indexed.
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
GET /Product/?category=software
|
|
26
26
|
GET /Product/?category=software&inStock=true
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
To filter for null values:
|
|
30
|
-
|
|
31
|
-
```
|
|
32
27
|
GET /Product/?discount=null
|
|
33
28
|
```
|
|
34
29
|
|
|
35
|
-
2. **Apply comparison operators (FIQL syntax)**: Use FIQL operators in the query string for range and
|
|
30
|
+
2. **Apply comparison operators (FIQL syntax)**: Use FIQL operators in the query string for range and string comparisons.
|
|
36
31
|
|
|
37
32
|
| Operator | Meaning |
|
|
38
33
|
| -------------------- | -------------------------------------- |
|
|
@@ -53,21 +48,18 @@ Apply this rule whenever you need to construct or handle GET requests against Ha
|
|
|
53
48
|
GET /Product/?price=le=20
|
|
54
49
|
GET /Product/?name==Keyboard*
|
|
55
50
|
GET /Product/?category=software&price=gt=100&price=lt=200
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
For date fields, URL-encode colons as `%3A`:
|
|
59
|
-
|
|
60
|
-
```
|
|
61
51
|
GET /Product/?listDate=gt=2017-03-08T09%3A30%3A00.000Z
|
|
62
52
|
```
|
|
63
53
|
|
|
64
|
-
3. **Chain conditions for range queries**: Omit the attribute name on the second condition to apply it to the same attribute.
|
|
54
|
+
3. **Chain conditions for range queries**: Omit the attribute name on the second condition to apply it to the same attribute.
|
|
65
55
|
|
|
66
56
|
```
|
|
67
57
|
GET /Product/?price=gt=100<=200
|
|
68
58
|
```
|
|
69
59
|
|
|
70
|
-
|
|
60
|
+
Chaining supports `gt`/`ge` combined with `lt`/`le` only.
|
|
61
|
+
|
|
62
|
+
4. **Apply type conversion**: For FIQL comparators, Harper converts values automatically. Use explicit type prefixes when needed.
|
|
71
63
|
|
|
72
64
|
| Syntax | Behavior |
|
|
73
65
|
| ----------------------------------------- | ------------------------------------------- |
|
|
@@ -87,80 +79,83 @@ Apply this rule whenever you need to construct or handle GET requests against Ha
|
|
|
87
79
|
GET /Product/?rating=5|featured=true
|
|
88
80
|
```
|
|
89
81
|
|
|
90
|
-
6. **Group conditions**: Use parentheses or square brackets to control
|
|
82
|
+
6. **Group conditions**: Use parentheses or square brackets to control order of operations. Prefer square brackets when constructing queries from user input.
|
|
91
83
|
|
|
92
84
|
```
|
|
93
85
|
GET /Product/?rating=5|(price=gt=100&price=lt=200)
|
|
94
86
|
GET /Product/?rating=5&[tag=fast|tag=scalable|tag=efficient]
|
|
95
87
|
```
|
|
96
88
|
|
|
97
|
-
|
|
89
|
+
Construct from JavaScript:
|
|
98
90
|
|
|
99
91
|
```javascript
|
|
100
92
|
let url = `/Product/?rating=5&[${tags.map(encodeURIComponent).join('|')}]`;
|
|
101
93
|
```
|
|
102
94
|
|
|
103
|
-
7. **Select specific properties with `select()`**:
|
|
95
|
+
7. **Select specific properties with `select()`**: Specify which properties to include in the response.
|
|
104
96
|
|
|
105
|
-
| Syntax | Returns
|
|
106
|
-
| -------------------------------------- |
|
|
107
|
-
| `?select(property)` | Values of a single property directly
|
|
108
|
-
| `?select(property1,property2)` | Objects with only the specified properties
|
|
109
|
-
| `?select([property1,property2])` | Arrays of property values
|
|
110
|
-
| `?select(property1,)` | Objects with a single specified property
|
|
111
|
-
| `?select(property{subProp1,subProp2})` | Nested objects with specific sub-properties
|
|
97
|
+
| Syntax | Returns |
|
|
98
|
+
| -------------------------------------- | ------------------------------------------------- |
|
|
99
|
+
| `?select(property)` | Values of a single property directly |
|
|
100
|
+
| `?select(property1,property2)` | Objects with only the specified properties |
|
|
101
|
+
| `?select([property1,property2])` | Arrays of property values |
|
|
102
|
+
| `?select(property1,)` | Objects with a single specified property |
|
|
103
|
+
| `?select(property{subProp1,subProp2})` | Nested objects with specific sub-properties |
|
|
104
|
+
| `?select(id,products[limit(1)])` | Parents with a constrained relationship selection |
|
|
112
105
|
|
|
113
106
|
```
|
|
114
107
|
GET /Product/?category=software&select(name)
|
|
115
108
|
GET /Product/?brand.name=Microsoft&select(name,brand{name})
|
|
116
109
|
```
|
|
117
110
|
|
|
118
|
-
8. **Limit results with `limit(end)` or `limit(start,end)`**:
|
|
111
|
+
8. **Limit results with `limit(end)` or `limit(start,end)`**: Return records in a zero-based window from `start` (inclusive) to `end` (exclusive). `limit(end)` starts at zero.
|
|
119
112
|
|
|
120
113
|
```
|
|
121
114
|
GET /Product/?rating=gt=3&inStock=true&select(rating,name)&limit(20)
|
|
122
115
|
GET /Product/?rating=gt=3&limit(10,30)
|
|
123
116
|
```
|
|
124
117
|
|
|
125
|
-
9. **Sort results with `sort(property)` or `sort(+property,-property,...)`**: Prefix `+` or no prefix = ascending; `-` = descending.
|
|
118
|
+
9. **Sort results with `sort(property)` or `sort(+property,-property,...)`**: Prefix `+` or no prefix = ascending; `-` = descending. Multiple properties break ties in order.
|
|
126
119
|
|
|
127
120
|
```
|
|
128
121
|
GET /Product/?rating=gt=3&sort(+name)
|
|
129
122
|
GET /Product/?sort(+rating,-price)
|
|
130
123
|
```
|
|
131
124
|
|
|
132
|
-
10. **
|
|
125
|
+
10. **Request a total count for pagination**: Send a `Prefer` header on `GET` or `HEAD` requests. Counting requires a bounded `limit()` with page size no larger than 10,000 and window end no larger than 1,000,000.
|
|
133
126
|
|
|
134
|
-
| `Prefer` value | Meaning
|
|
135
|
-
| ----------------- |
|
|
136
|
-
| `count=exact` | Exact
|
|
137
|
-
| `count=estimated` | Fast approximate count
|
|
127
|
+
| `Prefer` value | Meaning |
|
|
128
|
+
| ----------------- | --------------------------------------------------------------------------------------------- |
|
|
129
|
+
| `count=exact` | Exact number of matching records. Off by default; requires `exactCount: true` in REST config. |
|
|
130
|
+
| `count=estimated` | Fast approximate count. Does not require `exactCount`. |
|
|
138
131
|
|
|
139
132
|
```
|
|
140
133
|
GET /Product/?category=software&limit(0,25)
|
|
141
134
|
Prefer: count=exact
|
|
142
135
|
```
|
|
143
136
|
|
|
144
|
-
|
|
137
|
+
Response headers:
|
|
145
138
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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/*`).
|
|
139
|
+
```
|
|
140
|
+
HTTP/1.1 200 OK
|
|
141
|
+
Content-Range: items 0-24/1234
|
|
142
|
+
Range-Unit: items
|
|
143
|
+
Preference-Applied: count=exact
|
|
144
|
+
```
|
|
153
145
|
|
|
154
|
-
Enable exact counts in
|
|
146
|
+
Enable exact counts in REST configuration:
|
|
155
147
|
|
|
156
148
|
```yaml
|
|
157
149
|
rest:
|
|
158
150
|
exactCount: true
|
|
159
151
|
```
|
|
160
152
|
|
|
161
|
-
|
|
153
|
+
- The response status for paginated requests is always `200`; Harper does not use `206 Partial Content`.
|
|
154
|
+
- A `HEAD` request with a `count` preference returns the count headers with no body.
|
|
155
|
+
- When CORS is enabled, `Content-Range`, `Range-Unit`, and `Preference-Applied` are added to `Access-Control-Expose-Headers`.
|
|
156
|
+
- Without `exactCount: true`, a `count=exact` request is served as an estimate. When the total cannot be produced, it is reported as `*` (e.g., `Content-Range: items 0-24/*`). `Preference-Applied` still echoes the requested mode, so an unavailable total (`.../*`) is distinct from a request that asked for no count.
|
|
162
157
|
|
|
163
|
-
11. **Query across relationships
|
|
158
|
+
11. **Query across relationships**: Use dot-syntax on attributes defined with `@relationship` in the schema.
|
|
164
159
|
|
|
165
160
|
```
|
|
166
161
|
GET /Product/?brand.name=Microsoft
|
|
@@ -168,32 +163,47 @@ Apply this rule whenever you need to construct or handle GET requests against Ha
|
|
|
168
163
|
GET /Product/?brand.name=Microsoft&select(name,brand{name})
|
|
169
164
|
```
|
|
170
165
|
|
|
171
|
-
|
|
166
|
+
Selecting a relationship without an outer dot-syntax filter behaves as a LEFT JOIN. An outer filter such as `products.price=ge=number:30` removes parents without a qualifying product and also narrows the selected products.
|
|
172
167
|
|
|
173
|
-
12. **
|
|
168
|
+
12. **Filter and page nested relationships**: Use square brackets inside `select()` to apply conditions, sort, and pagination to each parent's related records.
|
|
174
169
|
|
|
175
170
|
```
|
|
176
|
-
GET /
|
|
171
|
+
GET /Brand/?select(id,name,products[price=ge=number:30&sort(price)&limit(1)&select(id,name,price)])
|
|
177
172
|
```
|
|
178
173
|
|
|
179
|
-
|
|
174
|
+
An outer relationship filter also narrows that relationship's nested results — do not repeat the outer filter inside `[...]` unless you want to narrow further.
|
|
175
|
+
|
|
176
|
+
| Nested option | Behavior |
|
|
177
|
+
| ------------------ | --------------------------------------------------------------- |
|
|
178
|
+
| Conditions | Qualify children; combine with `&` (AND), `\|` (OR), and groups |
|
|
179
|
+
| `sort(price)` | Order eligible children before pagination |
|
|
180
|
+
| `limit(end)` | Return at most `end` children per parent |
|
|
181
|
+
| `limit(start,end)` | Skip `start` eligible children and return at most `end - start` |
|
|
182
|
+
| `select(id,price)` | Project fields from returned children |
|
|
183
|
+
- Invalid nested bounds (negative or fractional values) return HTTP 400. `limit(0)` returns no children without reading them.
|
|
180
184
|
|
|
181
185
|
## Examples
|
|
182
186
|
|
|
183
|
-
|
|
187
|
+
### Range filter with sort and limit
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
GET /Product/?category=software&price=gt=100&price=lt=200&sort(+price)&limit(0,25)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Relationship join with nested selection
|
|
184
194
|
|
|
185
195
|
```
|
|
186
|
-
GET /Product/?
|
|
196
|
+
GET /Product/?brand.name=Microsoft&select(name,brand{name,id})
|
|
187
197
|
```
|
|
188
198
|
|
|
189
|
-
|
|
199
|
+
### Paginated request with exact count
|
|
190
200
|
|
|
191
201
|
```
|
|
192
202
|
GET /Product/?category=software&limit(0,25)
|
|
193
203
|
Prefer: count=exact
|
|
194
204
|
```
|
|
195
205
|
|
|
196
|
-
Response
|
|
206
|
+
Response:
|
|
197
207
|
|
|
198
208
|
```
|
|
199
209
|
HTTP/1.1 200 OK
|
|
@@ -202,27 +212,21 @@ Range-Unit: items
|
|
|
202
212
|
Preference-Applied: count=exact
|
|
203
213
|
```
|
|
204
214
|
|
|
205
|
-
|
|
215
|
+
### Nested relationship filter with OR, sort, and pagination
|
|
206
216
|
|
|
207
|
-
```graphql
|
|
208
|
-
type Product @table @export {
|
|
209
|
-
id: Long @primaryKey
|
|
210
|
-
name: String
|
|
211
|
-
brandId: Long @indexed
|
|
212
|
-
brand: Brand @relationship(from: "brandId")
|
|
213
|
-
}
|
|
214
|
-
type Brand @table @export {
|
|
215
|
-
id: Long @primaryKey
|
|
216
|
-
name: String
|
|
217
|
-
products: [Product] @relationship(to: "brandId")
|
|
218
|
-
}
|
|
219
217
|
```
|
|
218
|
+
GET /Brand/?sort(name,id)&limit(0,20)&select(id,name,products[[price=lt=number:30|price=ge=number:60]&sort(-price,id)&limit(1,2)&select(id,name,price)])
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Outer filter inheriting into nested selection
|
|
220
222
|
|
|
221
223
|
```
|
|
222
|
-
GET /
|
|
224
|
+
GET /Brand/?products.price=ge=number:30&select(id,name,products[sort(price)&limit(1)&select(id,name,price)])
|
|
223
225
|
```
|
|
224
226
|
|
|
225
|
-
|
|
227
|
+
### Many-to-many relationship query
|
|
228
|
+
|
|
229
|
+
Schema:
|
|
226
230
|
|
|
227
231
|
```graphql
|
|
228
232
|
type Product @table @export {
|
|
@@ -233,21 +237,21 @@ type Product @table @export {
|
|
|
233
237
|
}
|
|
234
238
|
```
|
|
235
239
|
|
|
240
|
+
Query:
|
|
241
|
+
|
|
236
242
|
```
|
|
237
243
|
GET /Product/?resellers.name=Cool Shop&select(id,name,resellers{name,id})
|
|
238
244
|
```
|
|
239
245
|
|
|
240
|
-
**OR grouping with user-supplied tags:**
|
|
241
|
-
|
|
242
|
-
```javascript
|
|
243
|
-
let url = `/Product/?rating=5&[${tags.map(encodeURIComponent).join('|')}]`;
|
|
244
|
-
```
|
|
245
|
-
|
|
246
246
|
## Notes
|
|
247
247
|
|
|
248
|
-
-
|
|
249
|
-
-
|
|
250
|
-
-
|
|
251
|
-
-
|
|
252
|
-
-
|
|
253
|
-
-
|
|
248
|
+
- Queried attributes must be indexed for filtering to execute; only one attribute in a multi-attribute query needs to be indexed.
|
|
249
|
+
- URL-encode colons in date values as `%3A` (e.g., `listDate=gt=2017-03-08T09%3A30%3A00.000Z`).
|
|
250
|
+
- Square brackets are preferred over parentheses when constructing queries programmatically because standard URI encoding safely encodes `[` and `]`.
|
|
251
|
+
- Brackets inside `select()` after a relationship name (e.g., `select(id,products[limit(1)])`) form a nested relationship query, not a grouping expression.
|
|
252
|
+
- For full-text fields marked `@fullText`, use operators such as `=matches=`, `=matches_phrase=`, `=matches_prefix=`, `=matches_fuzzy=`, and their `=not_*=` negated forms. Full-text queries cannot provide an exact total count.
|
|
253
|
+
- `limit(end)` and `limit(start,end)` inside a nested relationship selection apply per parent, not to the total result set.
|
|
254
|
+
- Always add a unique field as the final sort key (e.g., `sort(name,id)`) to ensure deterministic ordering across pages.
|
|
255
|
+
- Nested filters require a table-backed relationship; filters on plain JSON projections or full-text predicates inside a relationship selection return HTTP 400.
|
|
256
|
+
- For streaming JSON-array responses, check for a terminal object shaped like `{ "error": "ErrorName: message" }` and treat the response as failed.
|
|
257
|
+
- Access a specific property of a record using dot syntax on the record ID: `GET /MyTable/123.propertyName`. This only works for declared schema properties. The suffixes `.json`, `.cbor`, `.msgpack`, and `.csv` are reserved as content-type selectors and take precedence over a property of the same name.
|
|
@@ -13,6 +13,8 @@ metadata:
|
|
|
13
13
|
|
|
14
14
|
Instructions for the agent to follow when building real-time features in Harper using WebSockets and Pub/Sub.
|
|
15
15
|
|
|
16
|
+
**Instead of:** Socket.io, `ws`, Pusher, Ably, Redis pub/sub, a separate MQTT broker. Use this rule rather than adding that service. See `harper-architecture`.
|
|
17
|
+
|
|
16
18
|
## When to Use
|
|
17
19
|
|
|
18
20
|
Apply this rule when implementing any feature that requires real-time bidirectional communication, live data streaming, or push-based updates in a Harper application. This includes chat, live dashboards, sensor feeds, and any scenario where clients must receive resource changes as they happen.
|
|
@@ -9,30 +9,30 @@ metadata:
|
|
|
9
9
|
- reference/v5/database/schema.md#Overview
|
|
10
10
|
- reference/v5/database/schema.md#Type Directives
|
|
11
11
|
- reference/v5/database/schema.md#Field Directives
|
|
12
|
-
sourceCommit:
|
|
13
|
-
inputHash:
|
|
12
|
+
sourceCommit: 4aea8b1fffc53ee1dcf4841676350fef58f24056
|
|
13
|
+
inputHash: af9e326b0cf010d8
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
# Schema Design Tooling
|
|
16
|
+
# Schema Design and GraphQL Tooling
|
|
17
17
|
|
|
18
18
|
Instructions for the agent to follow when designing Harper schemas, applying core directives, and configuring GraphQL tooling.
|
|
19
19
|
|
|
20
20
|
## When to Use
|
|
21
21
|
|
|
22
|
-
Apply this rule when creating or modifying Harper schema files
|
|
22
|
+
Apply this rule when creating or modifying Harper schema files, configuring `graphqlSchema` in `config.yaml`, or deciding which directives to apply to tables and fields. Use it whenever a task involves defining tables, primary keys, indexes, or export behavior.
|
|
23
23
|
|
|
24
24
|
## How It Works
|
|
25
25
|
|
|
26
|
-
1. **
|
|
26
|
+
1. **Load the schema via `graphqlSchema`**: In the component's `config.yaml`, point to the schema file:
|
|
27
27
|
|
|
28
28
|
```yaml
|
|
29
29
|
graphqlSchema:
|
|
30
30
|
files: 'schema.graphql'
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Both plugins and applications can specify schemas.
|
|
33
|
+
Both plugins and applications can specify schemas. The schema ensures required tables exist on deployment, enforces types, controls indexing, and defines relationships.
|
|
34
34
|
|
|
35
|
-
2. **Mark
|
|
35
|
+
2. **Mark tables with `@table`**: Apply `@table` to every GraphQL type that should become a Harper database table. The type name becomes the table name by default.
|
|
36
36
|
|
|
37
37
|
```graphql
|
|
38
38
|
type Dog @table {
|
|
@@ -43,47 +43,60 @@ Apply this rule when creating or modifying Harper schema files (`.graphql`), con
|
|
|
43
43
|
}
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
- `String` or `ID` → UUID string
|
|
48
|
-
- `Int`, `Long`, or `Any` → auto-incrementing integer
|
|
46
|
+
Use `@table` arguments to override defaults:
|
|
49
47
|
|
|
50
|
-
|
|
48
|
+
| Argument | Type | Default | Description |
|
|
49
|
+
| ------------------------ | --------- | ----------------------------- | ------------------------------------------------------------------------------ |
|
|
50
|
+
| `table` | `String` | type name | Override the table name |
|
|
51
|
+
| `database` | `String` | `"data"` | Database to place the table in |
|
|
52
|
+
| `expiration` | `Int` | — | Seconds until a record goes stale |
|
|
53
|
+
| `eviction` | `Int` | `0` | Additional seconds after `expiration` before physical removal |
|
|
54
|
+
| `scanInterval` | `Int` | `(expiration + eviction) / 4` | Seconds between eviction scans |
|
|
55
|
+
| `replicate` | `Boolean` | `true` | Enable replication of this table |
|
|
56
|
+
| `replicaExpiration` | `Float` | `3600` | Seconds a record fetched from another node is kept as a local replica |
|
|
57
|
+
| `replicaRefreshFraction` | `Float` | `0.5` | Fraction of `replicaExpiration` after which a read renews the replica lifetime |
|
|
58
|
+
| `audit` | `Boolean` | `logging.auditLog` | Enable the table's transaction log |
|
|
59
|
+
| `cacheControl` | `String` | — | `Cache-Control` header for anonymous GET/HEAD 200/304 responses |
|
|
60
|
+
| `randomAccessFields` | `Boolean` | `storage.randomAccessFields` | Pin this table's record encoding |
|
|
51
61
|
|
|
52
|
-
|
|
62
|
+
3. **Designate a primary key with `@primaryKey`**: Apply `@primaryKey` to exactly one field per table. Primary keys must be unique; duplicate inserts are rejected.
|
|
53
63
|
|
|
54
64
|
```graphql
|
|
55
|
-
type
|
|
65
|
+
type Product @table {
|
|
56
66
|
id: Long @primaryKey
|
|
57
|
-
name: String
|
|
67
|
+
name: String
|
|
58
68
|
}
|
|
59
69
|
```
|
|
60
70
|
|
|
61
|
-
If
|
|
71
|
+
If no primary key is provided on insert, Harper auto-generates one:
|
|
72
|
+
- **UUID string** — when type is `String` or `ID`
|
|
73
|
+
- **Auto-incrementing integer** — when type is `Int`, `Long`, or `Any`
|
|
74
|
+
|
|
75
|
+
Prefer `Long` or `Any` for auto-generated numeric keys; `Int` is 32-bit and may be insufficient for large tables.
|
|
62
76
|
|
|
63
|
-
|
|
77
|
+
4. **Index queryable fields with `@indexed`**: Apply `@indexed` to any field that must be filterable via REST queries, SQL, or NoSQL operations.
|
|
64
78
|
|
|
65
79
|
```graphql
|
|
66
|
-
type
|
|
80
|
+
type Product @table {
|
|
67
81
|
id: Long @primaryKey
|
|
82
|
+
category: String @indexed
|
|
83
|
+
price: Float @indexed
|
|
68
84
|
}
|
|
69
85
|
```
|
|
70
86
|
|
|
71
|
-
|
|
87
|
+
If the field value is an array, each element is individually indexed. Null values are indexed by default.
|
|
88
|
+
|
|
89
|
+
5. **Expose tables externally with `@export`**: Apply `@export` to make a table accessible via REST, MQTT, and other interfaces. The optional `name` parameter sets the URL path segment.
|
|
72
90
|
|
|
73
|
-
|
|
91
|
+
```graphql
|
|
92
|
+
type MyTable @table @export(name: "my-table") {
|
|
93
|
+
id: Long @primaryKey
|
|
94
|
+
}
|
|
95
|
+
```
|
|
74
96
|
|
|
75
|
-
|
|
76
|
-
| -------------------- | --------- | ----------------------------- | -------------------------------------------------------------- |
|
|
77
|
-
| `table` | `String` | type name | Override the table name |
|
|
78
|
-
| `database` | `String` | `"data"` | Database to place the table in |
|
|
79
|
-
| `expiration` | `Int` | — | Seconds until a record goes stale |
|
|
80
|
-
| `eviction` | `Int` | `0` | Additional seconds after `expiration` before physical removal |
|
|
81
|
-
| `scanInterval` | `Int` | `(expiration + eviction) / 4` | Seconds between eviction scans |
|
|
82
|
-
| `replicate` | `Boolean` | `true` | Enable replication of this table |
|
|
83
|
-
| `cacheControl` | `String` | — | `Cache-Control` header on anonymous GET/HEAD 200/304 responses |
|
|
84
|
-
| `randomAccessFields` | `Boolean` | `storage.randomAccessFields` | Pin this table's record encoding |
|
|
97
|
+
`@export` alone does not serve HTTP traffic — REST must also be enabled for the application via `rest: true` in `config.yaml` or Harper's built-in default. `@export` is a routing directive, not access control: omitting it returns 404 to callers but the table remains accessible through the Operations API and SQL.
|
|
85
98
|
|
|
86
|
-
|
|
99
|
+
6. **Prevent extra properties with `@sealed`** (when needed): Apply `@sealed` to reject records that include properties beyond those declared in the schema. By default, Harper allows additional properties.
|
|
87
100
|
|
|
88
101
|
```graphql
|
|
89
102
|
type StrictRecord @table @sealed {
|
|
@@ -92,13 +105,33 @@ Apply this rule when creating or modifying Harper schema files (`.graphql`), con
|
|
|
92
105
|
}
|
|
93
106
|
```
|
|
94
107
|
|
|
95
|
-
|
|
96
|
-
- `@createdTime` — assigns Unix epoch milliseconds on record creation
|
|
97
|
-
- `@updatedTime` — assigns Unix epoch milliseconds on each update
|
|
98
|
-
- `@expiresAt` — marks a field as the record's absolute expiration time (Unix epoch milliseconds); authoritative over the table-level `expiration` default
|
|
108
|
+
7. **Use unique database names for plugins and applications**: All tables default to the `data` database. Specify a distinct `database` argument on `@table` to avoid naming collisions between components.
|
|
99
109
|
|
|
100
|
-
|
|
110
|
+
8. **Apply timestamp and expiration field directives** where needed:
|
|
111
|
+
- `@createdTime` — auto-assigns a Unix epoch millisecond timestamp on record creation.
|
|
112
|
+
- `@updatedTime` — auto-assigns a Unix epoch millisecond timestamp on each update.
|
|
113
|
+
- `@expiresAt` — marks a field as the record's absolute expiration time (Unix epoch milliseconds); overrides the table-level `expiration` default.
|
|
114
|
+
- `@hidden` — suppresses the type or field from MCP tool descriptors and the OpenAPI document. Does not restrict data access.
|
|
101
115
|
|
|
116
|
+
```graphql
|
|
117
|
+
type Event @table {
|
|
118
|
+
id: Long @primaryKey
|
|
119
|
+
createdAt: Long @createdTime
|
|
120
|
+
updatedAt: Long @updatedTime
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type Session @table {
|
|
124
|
+
id: ID @primaryKey
|
|
125
|
+
token: String
|
|
126
|
+
expiresAt: Long @expiresAt
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Key `@expiresAt` behaviors:
|
|
131
|
+
- Takes an absolute Unix epoch millisecond timestamp, not a duration. A full-record `put` that omits the field clears it; a `patch` preserves it.
|
|
132
|
+
- A per-record value may extend or shorten a record's expiration relative to the table-level `expiration` default. When a record omits the field, the table default (if any) applies.
|
|
133
|
+
|
|
134
|
+
9. **Use `@embed`** to automatically compute an embedding vector for an attribute whenever the source field is written (requires `source` and `model` arguments; field type must be `[Float]`):
|
|
102
135
|
```graphql
|
|
103
136
|
type Document @table {
|
|
104
137
|
id: Long @primaryKey
|
|
@@ -106,12 +139,11 @@ Apply this rule when creating or modifying Harper schema files (`.graphql`), con
|
|
|
106
139
|
embedding: [Float] @embed(source: "text", model: "default")
|
|
107
140
|
}
|
|
108
141
|
```
|
|
109
|
-
|
|
110
|
-
10. **Use `@hidden`** on types or fields to suppress them from MCP tool descriptors and the OpenAPI document. This is a metadata-visibility directive only — it does not restrict data access. Use `attribute_permissions` on roles for field-level access control.
|
|
142
|
+
The attribute is automatically indexed with an HNSW vector index. A failure to compute the embedding fails the write. An update that does not touch the `source` field leaves the vector unchanged. Setting the `source` field to `null` sets the vector to `null`.
|
|
111
143
|
|
|
112
144
|
## Examples
|
|
113
145
|
|
|
114
|
-
**Minimal two
|
|
146
|
+
**Minimal schema with two tables:**
|
|
115
147
|
|
|
116
148
|
```graphql
|
|
117
149
|
type Dog @table {
|
|
@@ -127,7 +159,7 @@ type Breed @table {
|
|
|
127
159
|
}
|
|
128
160
|
```
|
|
129
161
|
|
|
130
|
-
**
|
|
162
|
+
**Caching table with expiration, eviction, and scan tuning:**
|
|
131
163
|
|
|
132
164
|
```graphql
|
|
133
165
|
# Expire after 5 minutes, evict after 1 hour, scan every 10 minutes
|
|
@@ -147,7 +179,7 @@ type Product @table(cacheControl: "public, max-age=60") @export {
|
|
|
147
179
|
}
|
|
148
180
|
```
|
|
149
181
|
|
|
150
|
-
**Table
|
|
182
|
+
**Table in a named database with expiration:**
|
|
151
183
|
|
|
152
184
|
```graphql
|
|
153
185
|
type Event @table(database: "analytics", expiration: 86400) {
|
|
@@ -156,29 +188,28 @@ type Event @table(database: "analytics", expiration: 86400) {
|
|
|
156
188
|
}
|
|
157
189
|
```
|
|
158
190
|
|
|
159
|
-
**
|
|
191
|
+
**Replica lifetime tuning:**
|
|
160
192
|
|
|
161
193
|
```graphql
|
|
162
|
-
|
|
194
|
+
# Keep fetched records for a day; a read after six hours renews them
|
|
195
|
+
type Order @table(replicaExpiration: 86400, replicaRefreshFraction: 0.25) {
|
|
163
196
|
id: ID @primaryKey
|
|
164
|
-
|
|
165
|
-
expiresAt: Long @expiresAt
|
|
197
|
+
region: String @indexed
|
|
166
198
|
}
|
|
167
199
|
```
|
|
168
200
|
|
|
169
|
-
**
|
|
201
|
+
**Random-access field encoding pinned on the table:**
|
|
170
202
|
|
|
171
203
|
```graphql
|
|
172
|
-
type
|
|
173
|
-
id:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
updatedAt: Long @updatedTime
|
|
204
|
+
type Reading @table(randomAccessFields: true) {
|
|
205
|
+
id: ID @primaryKey
|
|
206
|
+
sensorId: String @indexed
|
|
207
|
+
celsius: Float
|
|
208
|
+
recordedAt: Int
|
|
178
209
|
}
|
|
179
210
|
```
|
|
180
211
|
|
|
181
|
-
**`config.yaml` schema
|
|
212
|
+
**`config.yaml` loading the schema:**
|
|
182
213
|
|
|
183
214
|
```yaml
|
|
184
215
|
graphqlSchema:
|
|
@@ -187,10 +218,12 @@ graphqlSchema:
|
|
|
187
218
|
|
|
188
219
|
## Notes
|
|
189
220
|
|
|
190
|
-
-
|
|
191
|
-
-
|
|
192
|
-
- `cacheControl` emits the header
|
|
193
|
-
-
|
|
194
|
-
- Eviction removes non-indexed record data but does not remove a record from its secondary indexes; indexes remain functional for evicted records
|
|
195
|
-
- `
|
|
196
|
-
-
|
|
221
|
+
- Schemas are flexible by default — records may include additional properties. Use `@sealed` to prevent this.
|
|
222
|
+
- `@export` is a routing directive, not an access control mechanism. Use RBAC and `attribute_permissions` to restrict data access.
|
|
223
|
+
- `cacheControl` on `@table` only emits the header for anonymous (unauthenticated) GET/HEAD 200/304 responses. Authenticated responses always receive `Cache-Control: private, no-cache`.
|
|
224
|
+
- `expiration`, `eviction`, and `scanInterval` work together: `expiration` marks a record stale, `eviction` controls physical removal, and `scanInterval` sets how often the sweep runs. Eviction scans are clock-aligned to the server's local timezone, not startup-aligned.
|
|
225
|
+
- Eviction removes non-indexed record data but does not remove a record from its secondary indexes; indexes remain functional for evicted records.
|
|
226
|
+
- `replicaExpiration` and `replicaRefreshFraction` apply only on the RocksDB engine. On LMDB, remote reads remain proxy reads regardless of these settings.
|
|
227
|
+
- Disabling replication on a table (`replicate: false`) and re-enabling it later will not catch up on writes made while replication was disabled.
|
|
228
|
+
- `randomAccessFields` on `@table` pins the encoding when the table is created. Editing the argument later does not repin an existing table.
|
|
229
|
+
- `@hidden` is a metadata-visibility directive only — it does not restrict data access. Use `attribute_permissions` on roles for field-level access control.
|
|
@@ -9,6 +9,8 @@ metadata:
|
|
|
9
9
|
|
|
10
10
|
Instructions for the agent to follow when serving web content from Harper.
|
|
11
11
|
|
|
12
|
+
**Instead of:** Nginx, Vercel, or Netlify for the frontend; a separate Vite dev server. Use this rule rather than adding that service. See `harper-architecture`.
|
|
13
|
+
|
|
12
14
|
## When to Use
|
|
13
15
|
|
|
14
16
|
Use this skill when you need to serve a frontend (HTML, CSS, JS, or a React/Vue app) directly from your Harper instance — either plain static files or an integrated Vite app with hot module replacement (HMR) in development and a real production build when deployed.
|
|
@@ -13,6 +13,8 @@ metadata:
|
|
|
13
13
|
|
|
14
14
|
Instructions for the agent to run `.ts` files directly in Harper without a build step using Node.js's built-in type stripping.
|
|
15
15
|
|
|
16
|
+
**Instead of:** `tsc` or `ts-node` build steps for server code. Use this rule rather than adding that service. See `harper-architecture`.
|
|
17
|
+
|
|
16
18
|
## When to Use
|
|
17
19
|
|
|
18
20
|
Apply this rule when writing Harper resource files in TypeScript. Use it any time you need to reference `.ts` source files from `config.yaml` or import between local TypeScript modules in a Harper project.
|
|
@@ -16,6 +16,8 @@ metadata:
|
|
|
16
16
|
|
|
17
17
|
Instructions for the agent to follow when storing and retrieving large binary content using the `Blob` data type in Harper.
|
|
18
18
|
|
|
19
|
+
**Instead of:** S3 or GCS buckets for app-managed files. Use this rule rather than adding that service. See `harper-architecture`.
|
|
20
|
+
|
|
19
21
|
## When to Use
|
|
20
22
|
|
|
21
23
|
Apply this rule when a schema field needs to store large binary content such as images, video, audio, or large HTML — typically content larger than 20KB. Use `Blob` instead of `Bytes` when streaming support and out-of-record storage are required. See [handling-binary-data](handling-binary-data.md) for broader binary data guidance.
|
|
@@ -13,6 +13,8 @@ metadata:
|
|
|
13
13
|
|
|
14
14
|
Instructions for the agent to enable HNSW vector indexes on table fields and query them for similarity search in Harper.
|
|
15
15
|
|
|
16
|
+
**Instead of:** Pinecone, Weaviate, pgvector. Use this rule rather than adding that service. See `harper-architecture`.
|
|
17
|
+
|
|
16
18
|
## When to Use
|
|
17
19
|
|
|
18
20
|
Apply this rule when adding a vector similarity search capability to a Harper table — for example, storing text embeddings and querying for nearest neighbors, filtering by distance threshold, or combining vector search with record-level access control. See [adding-tables-with-schemas.md](adding-tables-with-schemas.md) for how to define the surrounding table schema.
|