@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.
Files changed (48) hide show
  1. package/.agents/skills/harper-best-practices/AGENTS.md +687 -453
  2. package/.agents/skills/harper-best-practices/SKILL.md +24 -21
  3. package/.agents/skills/harper-best-practices/rules/adding-tables-with-schemas.md +2 -0
  4. package/.agents/skills/harper-best-practices/rules/automatic-apis.md +2 -0
  5. package/.agents/skills/harper-best-practices/rules/caching.md +2 -0
  6. package/.agents/skills/harper-best-practices/rules/checking-authentication.md +2 -0
  7. package/.agents/skills/harper-best-practices/rules/custom-resources.md +2 -0
  8. package/.agents/skills/harper-best-practices/rules/defining-relationships.md +102 -40
  9. package/.agents/skills/harper-best-practices/rules/deploying-from-ci.md +121 -93
  10. package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +149 -103
  11. package/.agents/skills/harper-best-practices/rules/extending-tables.md +2 -0
  12. package/.agents/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
  13. package/.agents/skills/harper-best-practices/rules/load-env.md +2 -0
  14. package/.agents/skills/harper-best-practices/rules/logging.md +2 -0
  15. package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +124 -91
  16. package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +86 -82
  17. package/.agents/skills/harper-best-practices/rules/real-time-apps.md +2 -0
  18. package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +92 -59
  19. package/.agents/skills/harper-best-practices/rules/serving-web-content.md +2 -0
  20. package/.agents/skills/harper-best-practices/rules/typescript-type-stripping.md +2 -0
  21. package/.agents/skills/harper-best-practices/rules/using-blob-datatype.md +2 -0
  22. package/.agents/skills/harper-best-practices/rules/vector-indexing.md +2 -0
  23. package/.agents/skills/harper-best-practices/rules.manifest.yaml +44 -0
  24. package/agent/skills/harper-best-practices/AGENTS.md +687 -453
  25. package/agent/skills/harper-best-practices/SKILL.md +26 -21
  26. package/agent/skills/harper-best-practices/rules/adding-tables-with-schemas.md +2 -0
  27. package/agent/skills/harper-best-practices/rules/automatic-apis.md +2 -0
  28. package/agent/skills/harper-best-practices/rules/caching.md +2 -0
  29. package/agent/skills/harper-best-practices/rules/checking-authentication.md +2 -0
  30. package/agent/skills/harper-best-practices/rules/custom-resources.md +2 -0
  31. package/agent/skills/harper-best-practices/rules/defining-relationships.md +102 -40
  32. package/agent/skills/harper-best-practices/rules/deploying-from-ci.md +121 -93
  33. package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +149 -103
  34. package/agent/skills/harper-best-practices/rules/extending-tables.md +2 -0
  35. package/agent/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
  36. package/agent/skills/harper-best-practices/rules/load-env.md +2 -0
  37. package/agent/skills/harper-best-practices/rules/logging.md +2 -0
  38. package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +124 -91
  39. package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +86 -82
  40. package/agent/skills/harper-best-practices/rules/real-time-apps.md +2 -0
  41. package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +92 -59
  42. package/agent/skills/harper-best-practices/rules/serving-web-content.md +2 -0
  43. package/agent/skills/harper-best-practices/rules/typescript-type-stripping.md +2 -0
  44. package/agent/skills/harper-best-practices/rules/using-blob-datatype.md +2 -0
  45. package/agent/skills/harper-best-practices/rules/vector-indexing.md +2 -0
  46. package/agent/skills/harper-best-practices/rules.manifest.yaml +44 -0
  47. package/package.json +1 -1
  48. 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: 2cfb81318f17e0aca2d600109e6ad813d2d0443e
9
- inputHash: 25b9daabc0a28fd5
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 through Harper's URL-based REST query language.
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 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.
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 names and values. The queried attribute must be indexed.
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 pattern matching.
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. Only `gt`/`ge` combined with `lt`/`le` is supported.
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&lt=200
68
58
  ```
69
59
 
70
- 4. **Apply type conversion**: For FIQL comparators (`==`, `!=`, `=gt=`, etc.), Harper converts values automatically. Use explicit type prefixes to force a specific type.
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 evaluation order. Prefer square brackets when building queries from user input, since standard URI encoding safely encodes `[` and `]`.
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
- Build grouped queries in JavaScript:
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()`**: Append `select()` as a query function separated by `&`.
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)`**: Append as a query function.
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. List multiple properties to break ties in order.
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. **Paginate with a total count**: Use `limit(start,end)` for paging and send a `Prefer` request header to receive a total match count.
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 count of matching records (opt-in) |
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
- The server responds with:
137
+ Response headers:
145
138
 
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/*`).
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 the application's REST configuration:
146
+ Enable exact counts in REST configuration:
155
147
 
156
148
  ```yaml
157
149
  rest:
158
150
  exactCount: true
159
151
  ```
160
152
 
161
- Without this, a `count=exact` request is served as an estimate.
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 using dot syntax**: Define relationships in the schema with `@relationship`, then filter and select across them.
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
- 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.
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. **Access a specific property by URL**: Append the property name with dot syntax to the record ID.
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 /MyTable/123.propertyName
171
+ GET /Brand/?select(id,name,products[price=ge=number:30&sort(price)&limit(1)&select(id,name,price)])
177
172
  ```
178
173
 
179
- This only works for declared schema properties. The suffixes `.json`, `.cbor`, `.msgpack`, and `.csv` are reserved as content-type selectors.
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
- **Range filter with select and sort:**
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/?category=software&price=gt=100&price=lt=200&select(name,price)&sort(+price)
196
+ GET /Product/?brand.name=Microsoft&select(name,brand{name,id})
187
197
  ```
188
198
 
189
- **Paginated request with exact count:**
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 headers:
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
- **Relationship schema and join query:**
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 /Product/?brand.name=Microsoft&select(name,brand{name,id})
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
- **Many-to-many relationship:**
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
- - 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`.
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: 2cfb81318f17e0aca2d600109e6ad813d2d0443e
13
- inputHash: a644f4ea2f289f77
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 (`.graphql`), 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.
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. **Register the schema file** in the component's `config.yaml` using the `graphqlSchema` plugin key:
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 types as tables** using `@table`. The type name becomes the table name by default:
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
- 3. **Designate a primary key** on every table using `@primaryKey`. Primary keys must be unique; duplicate inserts are rejected. If no primary key is provided on insert, Harper auto-generates one:
47
- - `String` or `ID` → UUID string
48
- - `Int`, `Long`, or `Any` → auto-incrementing integer
46
+ Use `@table` arguments to override defaults:
49
47
 
50
- Use `Long` or `Any` for auto-generated numeric keys; `Int` is 32-bit and may be insufficient for large tables.
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
- 4. **Index fields for querying** using `@indexed`. Required for filtering by an attribute in REST queries, SQL, or NoSQL operations:
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 Breed @table {
65
+ type Product @table {
56
66
  id: Long @primaryKey
57
- name: String @indexed
67
+ name: String
58
68
  }
59
69
  ```
60
70
 
61
- If the field value is an array, each element is individually indexed. Null values are indexed by default.
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
- 5. **Expose tables as REST/MQTT endpoints** using `@export`. The optional `name` parameter sets the URL path segment; without it, the type name is used:
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 MyTable @table @export(name: "my-table") {
80
+ type Product @table {
67
81
  id: Long @primaryKey
82
+ category: String @indexed
83
+ price: Float @indexed
68
84
  }
69
85
  ```
70
86
 
71
- `@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 but does not protect the data.
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
- 6. **Configure `@table` arguments** to control database placement, expiration, replication, and caching behavior. Key arguments:
91
+ ```graphql
92
+ type MyTable @table @export(name: "my-table") {
93
+ id: Long @primaryKey
94
+ }
95
+ ```
74
96
 
75
- | Argument | Type | Default | Description |
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
- 7. **Seal a type** with `@sealed` to prevent records from including properties beyond those declared:
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
- 8. **Use timestamp directives** for automatic record lifecycle tracking:
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
- 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]`):
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-table schema:**
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
- **Table with expiration, eviction, and scan tuning:**
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 with multiple `@table` arguments combined:**
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
- **Session table with per-record expiration:**
191
+ **Replica lifetime tuning:**
160
192
 
161
193
  ```graphql
162
- type Session @table {
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
- token: String
165
- expiresAt: Long @expiresAt
197
+ region: String @indexed
166
198
  }
167
199
  ```
168
200
 
169
- **Table with timestamp tracking and indexed fields:**
201
+ **Random-access field encoding pinned on the table:**
170
202
 
171
203
  ```graphql
172
- type Order @table(database: "commerce") @export {
173
- id: Long @primaryKey
174
- userId: String @indexed
175
- status: String @indexed
176
- createdAt: Long @createdTime
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 registration:**
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
- - Use unique `database` names in plugins and applications to avoid table naming collisions, since all tables default to the `"data"` database.
191
- - Disabling replication (`replicate: false`) and re-enabling it later will not catch up on writes made while replication was disabled.
192
- - `cacheControl` emits the header only on anonymous (unauthenticated) GET/HEAD `200`/`304` responses. Authenticated responses receive `Cache-Control: private, no-cache` regardless of the declaration. The header is never emitted on `401` responses.
193
- - `@expiresAt` requires an absolute Unix epoch millisecond timestamp, not a duration. Negative values are ignored and fall back to the table default. A full-record `put` that omits the field clears it; a `patch` preserves it.
194
- - Eviction removes non-indexed record data but does not remove a record from its secondary indexes; indexes remain functional for evicted records, with full records fetched on demand.
195
- - `scanInterval` is clock-aligned to the server's local timezone, not startup-aligned — the server's startup time does not affect when eviction runs.
196
- - `randomAccessFields` pins the table's encoding at creation time. Editing the argument later does not repin an existing table. Omit it to follow the global `storage.randomAccessFields` setting.
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.