Skip to content

Commit 3148944

Browse files
docs: regenerate rules from documentation@a352139
1 parent 1771eb6 commit 3148944

10 files changed

Lines changed: 1078 additions & 912 deletions

harper-best-practices/AGENTS.md

Lines changed: 530 additions & 447 deletions
Large diffs are not rendered by default.

harper-best-practices/rules/automatic-apis.md

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ metadata:
66
sources:
77
- reference/v5/rest/overview.md
88
- reference/v5/rest/websockets.md
9-
sourceCommit: b7fbddadd42eb4487190b650a9abc4bcfeef5819
10-
inputHash: 8fcf0cfe190e013e
9+
sourceCommit: a3521397567ff1e1c055b7d531431e8e6c33994c
10+
inputHash: 6c7013b1307d9f7b
1111
---
1212

1313
# Automatic APIs
@@ -16,37 +16,37 @@ Instructions for the agent to follow when enabling and using Harper's automatica
1616

1717
## When to Use
1818

19-
Apply this rule when adding REST or WebSocket API access to Harper tables or custom resources. Use it when configuring `config.yaml` to expose endpoints, mapping HTTP methods to resource operations, or implementing real-time WebSocket connections on a resource class.
19+
Apply this rule when adding REST or WebSocket API access to Harper tables or custom resources. Use it when configuring `config.yaml` to expose endpoints, mapping HTTP methods to resource operations, or implementing real-time WebSocket communication with a resource.
2020

2121
## How It Works
2222

23-
1. **Enable the REST plugin**: Add `rest: true` to your application's `config.yaml`. This activates the HTTP REST interface and enables WebSocket support by default.
23+
1. **Enable the REST plugin**: Add `rest: true` to your application's `config.yaml`. This activates the HTTP REST interface and, by default, also enables WebSocket support.
2424

2525
```yaml
2626
rest: true
2727
```
2828
29-
To configure optional behavior:
29+
To configure additional options:
3030
3131
```yaml
3232
rest:
3333
lastModified: true # enables Last-Modified response header support
3434
webSocket: false # disables automatic WebSocket support (enabled by default)
3535
```
3636
37-
2. **Export your resource in the schema**: Tables are not exposed by default. Use the `@export` directive in your schema definition to make a table available as a REST endpoint. The exported name defines the base URL path, served on the application HTTP server port (default `9926`).
37+
2. **Export your resource**: Tables are not exposed by default. Use the `@export` directive in your schema definition to make a table available as a REST endpoint. The exported name defines the base URL path, served on the application HTTP server port (default `9926`).
3838

39-
3. **Use the correct URL structure**: The REST interface follows a consistent path convention.
39+
3. **Use the correct URL structure**: The REST interface follows a consistent URL pattern.
4040

4141
| Path | Description |
4242
| -------------------------------------------- | ---------------------------------------------------------------------------------- |
43-
| `/my-resource` | Returns a description of the resource (e.g., table metadata) |
43+
| `/my-resource` | Root path — returns resource description/metadata |
4444
| `/my-resource/` | Trailing slash — represents the full collection; append query parameters to search |
45-
| `/my-resource/record-id` | A specific record identified by its primary key |
45+
| `/my-resource/record-id` | A specific record by primary key |
4646
| `/my-resource/record-id/` | Trailing slash — collection of records with the given id prefix |
4747
| `/my-resource/record-id/with/multiple/parts` | Record id with multiple path segments |
4848

49-
4. **Map HTTP methods to operations**: Each HTTP method maps to a resource method and operation.
49+
4. **Map operations to HTTP methods**: Each HTTP method maps to a resource method.
5050
- **GET** — Retrieve a record or search. Calls `get()`.
5151

5252
```
@@ -75,7 +75,7 @@ Apply this rule when adding REST or WebSocket API access to Harper tables or cus
7575
{ "name": "some data" }
7676
```
7777

78-
- **PATCH** — Partially update a record, merging only provided properties. Unspecified properties are preserved.
78+
- **PATCH** — Partially update a record, merging only provided top-level properties. Calls the resource's patch handler. Merge is **shallow** — nested objects are replaced entirely, not deep-merged.
7979

8080
```
8181
PATCH /MyTable/123
@@ -90,13 +90,7 @@ Apply this rule when adding REST or WebSocket API access to Harper tables or cus
9090
DELETE /MyTable/?status=archived
9191
```
9292

93-
5. **Access the auto-generated OpenAPI spec**: Harper generates an OpenAPI specification for all exported resources. Retrieve it at:
94-
95-
```
96-
GET /openapi
97-
```
98-
99-
6. **Connect via WebSocket**: When `rest` is enabled, WebSocket support is on by default. Connect to a resource URL to subscribe to change events for that resource.
93+
5. **Connect via WebSocket**: When `rest` is enabled, WebSocket support is on by default. Connect to a resource URL to subscribe to change events for that resource.
10094

10195
```javascript
10296
let ws = new WebSocket('wss://server/my-resource/341');
@@ -105,13 +99,18 @@ Apply this rule when adding REST or WebSocket API access to Harper tables or cus
10599
};
106100
```
107101

108-
Connecting to `wss://server/my-resource/341` accesses the `my-resource` resource with record id `341` and subscribes to it. When the record changes or a message is published to it, the WebSocket connection receives the update.
102+
When the record at `my-resource/341` changes or a message is published to it, the WebSocket connection receives the update. See [real-time-apps.md](real-time-apps.md) for building real-time features on top of this.
109103

110-
7. **Implement a custom `connect()` handler**: Override `connect(incomingMessages)` on a resource class to control WebSocket behavior. The method must return an async iterable or generator that produces messages to send to the client.
104+
6. **Implement a custom `connect()` handler**: Override `connect(incomingMessages)` on a resource class to control WebSocket behavior. The method must return an async iterable or generator that produces messages to send to the client.
105+
106+
7. **Access the OpenAPI spec**: Harper automatically generates an OpenAPI specification for all exported resources, available at:
107+
```
108+
GET /openapi
109+
```
111110

112111
## Examples
113112

114-
**Simple echo server using an async generator**:
113+
**Simple echo WebSocket server**:
115114

116115
```javascript
117116
export class Echo extends Resource {
@@ -123,7 +122,7 @@ export class Echo extends Resource {
123122
}
124123
```
125124

126-
**Using the default `connect()` with event-style access and a timer**:
125+
**Custom `connect()` using the default handler with event-style access**:
127126

128127
```javascript
129128
export class Example extends Resource {
@@ -147,19 +146,16 @@ export class Example extends Resource {
147146
}
148147
```
149148

150-
**Minimal `config.yaml` enabling REST with WebSocket disabled**:
149+
**MQTT over WebSocket**: Set the sub-protocol header as required by the MQTT specification:
151150

152-
```yaml
153-
rest:
154-
webSocket: false
151+
```
152+
Sec-WebSocket-Protocol: mqtt
155153
```
156154
157155
## Notes
158156
159-
- Tables must be explicitly exported using `@export` in the schema — they are not exposed by default.
160-
- `rest: true` is the minimal configuration to enable both REST and WebSocket support. See [real-time-apps.md](real-time-apps.md) for patterns around real-time WebSocket usage.
161-
- For full query syntax on `GET` and `DELETE` with query parameters, see [querying-rest-apis.md](querying-rest-apis.md).
162-
- The default `connect()` returns an iterable with a `send(message)` method and a `close` event for cleanup on disconnect.
163-
- For MQTT over WebSockets, set the sub-protocol header `Sec-WebSocket-Protocol: mqtt`.
164-
- In distributed environments, non-retained messages are delivered in the order received per node; retained messages (PUT/updated records) keep only the latest-timestamp version as the winning record across the cluster.
165-
- Use the `Content-Type` request header to specify body format and the `Accept` header to request a specific response format.
157+
- `rest: true` is the minimal config to activate both REST and WebSocket support. Set `webSocket: false` under the `rest` key to disable WebSocket only.
158+
- PATCH merges are shallow (top-level only). Nested objects in the body replace the entire existing sub-object. To update a single nested field, either read-modify-write the parent or send the full nested object with updated values. Dot-path keys (e.g., `"settings.theme"`) are stored literally and are not interpreted as paths.
159+
- In distributed (multi-node) environments, non-retained messages are delivered in the order received per node. Retained messages (PUT/updated records) use the latest timestamp as the winning record for eventual consistency.
160+
- Use the `Content-Type` header for request bodies and the `Accept` header to request a specific response format.
161+
- See [querying-rest-apis.md](querying-rest-apis.md) for the full URL query syntax, operators, and examples.

harper-best-practices/rules/defining-relationships.md

Lines changed: 43 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ metadata:
66
sources:
77
- reference/v5/database/schema.md#Relationships
88
- reference/v5/rest/querying.md#Relationships and Joins
9-
sourceCommit: 4fe4c9c95e0974eaa77032f6f10e36fbd8ec64ac
10-
inputHash: 6953f507f0cde0f7
9+
sourceCommit: a3521397567ff1e1c055b7d531431e8e6c33994c
10+
inputHash: fd399fd81a88f13e
1111
---
1212

1313
# Defining Relationships Between Tables in Harper
@@ -16,7 +16,7 @@ Instructions for the agent to follow when defining and querying relationships be
1616

1717
## When to Use
1818

19-
Apply this rule whenever a schema requires linking two tables via a foreign key — for example, modeling shows and networks, products and brands, or orders and items. Use it when queries need to filter or select nested related records using dot-syntax.
19+
Apply this rule whenever you need to link two tables via a foreign key and enable join queries or nested property selection in results. Use it when modeling one-to-many, many-to-one, many-to-many, or self-referential table relationships in a Harper GraphQL schema.
2020

2121
## How It Works
2222

@@ -26,7 +26,7 @@ Apply this rule whenever a schema requires linking two tables via a foreign key
2626
type RealityShow @table @export {
2727
id: Long @primaryKey
2828
networkId: Long @indexed
29-
network: Network @relationship(from: networkId)
29+
network: Network @relationship(from: networkId) # many-to-one
3030
title: String @indexed
3131
}
3232

@@ -36,7 +36,7 @@ Apply this rule whenever a schema requires linking two tables via a foreign key
3636
}
3737
```
3838

39-
For a many-to-many relationship, make the foreign key an array:
39+
If the foreign key is an array, this establishes a many-to-many relationship:
4040

4141
```graphql
4242
type RealityShow @table @export {
@@ -46,24 +46,24 @@ Apply this rule whenever a schema requires linking two tables via a foreign key
4646
}
4747
```
4848

49-
2. **Use `@relationship(to: attribute)` for one-to-many or many-to-many**: Place this on a field in the current table when the foreign key lives in the target table and references the primary key of this table. The result type must be an array.
49+
2. **Use `@relationship(to: attribute)` for one-to-many or many-to-many**: Place this on a field in the current table when the foreign key lives in the **target** table and references the primary key of this table. The result type must be an array.
5050

5151
```graphql
5252
type Network @table @export {
5353
id: Long @primaryKey
5454
name: String @indexed
55-
shows: [RealityShow] @relationship(to: networkId)
55+
shows: [RealityShow] @relationship(to: networkId) # one-to-many
5656
}
5757
```
5858

59-
3. **Use `@relationship(from: attribute, to: attribute)` for foreign key to foreign key joins**: Specify both `from` and `to` when neither side uses the primary key. This is useful for joining on non-primary-key attributes.
59+
3. **Use `@relationship(from: attribute, to: attribute)` for foreign key to foreign key**: Specify both `from` and `to` when neither side uses the primary key. Harper resolves the relationship by searching the target table's `to` attribute for matches using this record's `from` attribute as the search value. The result type must be an array.
6060

6161
```graphql
6262
type OrderItem @table @export {
6363
id: Long @primaryKey
6464
orderId: Long @indexed
6565
productSku: Long @indexed
66-
product: Product @relationship(from: productSku, to: sku)
66+
products: [Product] @relationship(from: productSku, to: sku)
6767
}
6868

6969
type Product @table @export {
@@ -73,93 +73,77 @@ Apply this rule whenever a schema requires linking two tables via a foreign key
7373
}
7474
```
7575

76-
4. **Query across relationships using dot-syntax**: Filter by related table attributes using chained dot notation. This behaves as an INNER JOIN.
76+
4. **Query across relationships using dot-syntax**: Filter records by attributes on related tables. This behaves as an INNER JOIN.
7777

7878
```
79-
GET /RealityShow?network.name=Bravo
8079
GET /Product/?brand.name=Microsoft
8180
GET /Brand/?products.name=Keyboard
8281
```
8382
84-
5. **Select nested relationship fields with `select()`**: Relationship attributes are not included by default. Use `select()` to include them in results. When selecting without a filter on the related table, this acts as a LEFT JOIN — the relationship property is omitted if the foreign key is null or references a non-existent record.
83+
5. **Select relationship attributes explicitly with `select()`**: Relationship fields are not included in results by default. Use `select()` to include them, optionally with nested field selection using `{}`.
8584
8685
```
8786
GET /Product/?brand.name=Microsoft&select(name,brand)
8887
GET /Product/?brand.name=Microsoft&select(name,brand{name})
8988
GET /Product/?name=Keyboard&select(name,brand{name,id})
9089
```
9190
91+
When selecting a relationship without filtering on it, Harper performs a LEFT JOIN — the relationship property is omitted if the foreign key is null or references a non-existent record.
92+
93+
6. **Model many-to-many without a junction table**: Use an array of foreign key values on the owning side. The array order of the foreign key values is preserved when resolving the relationship.
94+
95+
```graphql
96+
type Product @table @export {
97+
id: Long @primaryKey
98+
name: String
99+
resellerIds: [Long] @indexed
100+
resellers: [Reseller] @relationship(from: "resellerIds")
101+
}
102+
```
103+
92104
## Examples
93105

94-
**Many-to-one relationship** — a show belongs to a network:
106+
**Full schema with bidirectional relationships:**
95107

96108
```graphql
97-
type RealityShow @table @export {
109+
type Product @table @export {
98110
id: Long @primaryKey
99-
networkId: Long @indexed
100-
network: Network @relationship(from: networkId)
101-
title: String @indexed
111+
name: String
112+
brandId: Long @indexed
113+
brand: Brand @relationship(from: "brandId")
102114
}
103115

104-
type Network @table @export {
116+
type Brand @table @export {
105117
id: Long @primaryKey
106-
name: String @indexed
118+
name: String
119+
products: [Product] @relationship(to: "brandId")
107120
}
108121
```
109122

110-
Query:
123+
**Query by related attribute:**
111124

112125
```
113-
GET /RealityShow?network.name=Bravo
126+
GET /Product/?brand.name=Microsoft
127+
GET /Brand/?products.name=Keyboard
114128
```
115129
116-
**One-to-many relationship** — a network has many shows:
130+
**Nested select with join:**
117131
118-
```graphql
119-
type Network @table @export {
120-
id: Long @primaryKey
121-
name: String @indexed
122-
shows: [RealityShow] @relationship(to: networkId)
123-
}
124132
```
125-
126-
**Many-to-many with array foreign keys** — a product has multiple resellers:
127-
128-
```graphql
129-
type Product @table @export {
130-
id: Long @primaryKey
131-
name: String
132-
resellerIds: [Long] @indexed
133-
resellers: [Reseller] @relationship(from: resellerIds)
134-
}
133+
GET /Product/?brand.name=Microsoft&select(name,brand{name,id})
135134
```
136135
137-
Query with nested select:
136+
**Many-to-many query with nested select:**
138137
139138
```
140139
GET /Product/?resellers.name=Cool Shop&select(id,name,resellers{name,id})
141140
```
142141
143-
**Foreign key to foreign key join** — order item joined on SKU:
144-
145-
```graphql
146-
type OrderItem @table @export {
147-
id: Long @primaryKey
148-
orderId: Long @indexed
149-
productSku: Long @indexed
150-
product: Product @relationship(from: productSku, to: sku)
151-
}
152-
153-
type Product @table @export {
154-
id: Long @primaryKey
155-
sku: Long @indexed
156-
name: String
157-
}
158-
```
142+
**Self-referential relationships** are also supported, enabling parent-child hierarchies within a single table using the same `@relationship` directive patterns above.
159143
160144
## Notes
161145
162-
- The `@relationship` directive requires the referenced attribute to be `@indexed` on the foreign key side.
163-
- Self-referential relationships are supported, enabling parent-child hierarchies within a single table.
164-
- The array order of foreign key values (e.g., `resellerIds`) is preserved when resolving many-to-many relationships.
165-
- When using `select()` without a filter on the related table, the join behaves as a LEFT JOIN — missing or null foreign keys result in the relationship property being omitted rather than causing an error.
146+
- The `from` parameter refers to an attribute on the **current** table; the `to` parameter refers to an attribute on the **target** table.
147+
- `@relationship(to: attribute)` and `@relationship(from: attribute, to: attribute)` both require the result type to be an array (`[TargetType]`).
148+
- Schemas can define self-referential relationships for parent-child hierarchies within a single table.
149+
- Foreign key attributes used in `@relationship` should be marked `@indexed` for efficient join resolution.

0 commit comments

Comments
 (0)