@harperfast/template-react-ts-studio 1.11.3 → 1.11.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/harper-best-practices/AGENTS.md +567 -490
- package/.agents/skills/harper-best-practices/rules/automatic-apis.md +46 -78
- package/.agents/skills/harper-best-practices/rules/defining-relationships.md +41 -63
- package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +59 -45
- package/.agents/skills/harper-best-practices/rules/load-env.md +24 -23
- package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +62 -78
- package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +92 -65
- package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +53 -55
- package/.agents/skills/harper-best-practices/rules/v5-upgrade.md +98 -62
- package/.agents/skills/harper-best-practices/rules/vector-indexing.md +113 -42
- package/.agents/skills/harper-mcp/AGENTS.md +501 -0
- package/.agents/skills/harper-mcp/SKILL.md +91 -0
- package/.agents/skills/harper-mcp/rules/automatic-verb-tools.md +50 -0
- package/.agents/skills/harper-mcp/rules/connecting-clients.md +59 -0
- package/.agents/skills/harper-mcp/rules/custom-mcp-prompts.md +52 -0
- package/.agents/skills/harper-mcp/rules/custom-mcp-resources.md +64 -0
- package/.agents/skills/harper-mcp/rules/custom-mcp-tools.md +72 -0
- package/.agents/skills/harper-mcp/rules/durable-quotas.md +73 -0
- package/.agents/skills/harper-mcp/rules/enabling-mcp.md +63 -0
- package/.agents/skills/harper-mcp/rules/rate-limiting.md +62 -0
- package/.agents/skills/harper-mcp/rules/resources-surface.md +44 -0
- package/.agents/skills/harper-mcp/rules/security-posture.md +36 -0
- package/.agents/skills/harper-mcp/rules.manifest.yaml +203 -0
- package/agent/skills/harper-best-practices/AGENTS.md +567 -490
- package/agent/skills/harper-best-practices/rules/automatic-apis.md +46 -78
- package/agent/skills/harper-best-practices/rules/defining-relationships.md +41 -63
- package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +59 -45
- package/agent/skills/harper-best-practices/rules/load-env.md +24 -23
- package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +62 -78
- package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +92 -65
- package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +53 -55
- package/agent/skills/harper-best-practices/rules/v5-upgrade.md +98 -62
- package/agent/skills/harper-best-practices/rules/vector-indexing.md +113 -42
- package/agent/skills/harper-mcp/AGENTS.md +501 -0
- package/agent/skills/harper-mcp/SKILL.md +83 -0
- package/agent/skills/harper-mcp/rules/automatic-verb-tools.md +50 -0
- package/agent/skills/harper-mcp/rules/connecting-clients.md +59 -0
- package/agent/skills/harper-mcp/rules/custom-mcp-prompts.md +52 -0
- package/agent/skills/harper-mcp/rules/custom-mcp-resources.md +64 -0
- package/agent/skills/harper-mcp/rules/custom-mcp-tools.md +72 -0
- package/agent/skills/harper-mcp/rules/durable-quotas.md +73 -0
- package/agent/skills/harper-mcp/rules/enabling-mcp.md +63 -0
- package/agent/skills/harper-mcp/rules/rate-limiting.md +62 -0
- package/agent/skills/harper-mcp/rules/resources-surface.md +44 -0
- package/agent/skills/harper-mcp/rules/security-posture.md +36 -0
- package/agent/skills/harper-mcp/rules.manifest.yaml +203 -0
- package/package.json +2 -2
- package/skills-lock.json +7 -1
|
@@ -46,13 +46,11 @@ Instructions for the agent to follow when designing Harper schemas, applying cor
|
|
|
46
46
|
|
|
47
47
|
#### When to Use
|
|
48
48
|
|
|
49
|
-
Apply this rule when creating or modifying Harper schema files, configuring `graphqlSchema` in `config.yaml`, or deciding which directives to apply to
|
|
49
|
+
Apply this rule when creating or modifying Harper schema files (`.graphql`), configuring `graphqlSchema` in `config.yaml`, or deciding which directives to apply to types and fields. Use it any time you need to define tables, primary keys, indexes, or exported endpoints.
|
|
50
50
|
|
|
51
51
|
#### How It Works
|
|
52
52
|
|
|
53
|
-
1. **
|
|
54
|
-
|
|
55
|
-
2. **Register the schema** in the component's `config.yaml` using the `graphqlSchema` plugin:
|
|
53
|
+
1. **Declare the schema file** in the component's `config.yaml` using the `graphqlSchema` plugin:
|
|
56
54
|
|
|
57
55
|
```yaml
|
|
58
56
|
graphqlSchema:
|
|
@@ -61,7 +59,7 @@ Apply this rule when creating or modifying Harper schema files, configuring `gra
|
|
|
61
59
|
|
|
62
60
|
Both plugins and applications can specify schemas.
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
2. **Mark types as tables** with `@table`. The type name becomes the table name by default:
|
|
65
63
|
|
|
66
64
|
```graphql
|
|
67
65
|
type Dog @table {
|
|
@@ -72,13 +70,13 @@ Apply this rule when creating or modifying Harper schema files, configuring `gra
|
|
|
72
70
|
}
|
|
73
71
|
```
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
3. **Set a primary key** on every table using `@primaryKey`. Primary keys must be unique; duplicate-key inserts are rejected. If no primary key is provided on insert, Harper auto-generates one based on the field type:
|
|
76
74
|
- `String` or `ID` → UUID string
|
|
77
75
|
- `Int`, `Long`, or `Any` → auto-incrementing integer
|
|
78
76
|
|
|
79
77
|
Use `Long` or `Any` for auto-generated numeric keys; `Int` is 32-bit and may be insufficient for large tables.
|
|
80
78
|
|
|
81
|
-
|
|
79
|
+
4. **Index fields for querying** with `@indexed`. Required for filtering by an attribute in REST queries, SQL, or NoSQL operations:
|
|
82
80
|
|
|
83
81
|
```graphql
|
|
84
82
|
type Breed @table {
|
|
@@ -89,7 +87,7 @@ Apply this rule when creating or modifying Harper schema files, configuring `gra
|
|
|
89
87
|
|
|
90
88
|
If the field value is an array, each element is individually indexed. Null values are indexed by default.
|
|
91
89
|
|
|
92
|
-
|
|
90
|
+
5. **Expose a table as an external endpoint** with `@export`. Available via REST, MQTT, and other interfaces. The optional `name` parameter sets the URL path segment:
|
|
93
91
|
|
|
94
92
|
```graphql
|
|
95
93
|
type MyTable @table @export(name: "my-table") {
|
|
@@ -97,9 +95,9 @@ Apply this rule when creating or modifying Harper schema files, configuring `gra
|
|
|
97
95
|
}
|
|
98
96
|
```
|
|
99
97
|
|
|
100
|
-
Without `name`, the type name is used
|
|
98
|
+
Without `name`, the type name is used.
|
|
101
99
|
|
|
102
|
-
|
|
100
|
+
6. **Configure `@table` arguments** as needed. All arguments are optional:
|
|
103
101
|
|
|
104
102
|
| Argument | Type | Default | Description |
|
|
105
103
|
| -------------- | --------- | ----------------------------- | ------------------------------------------------------------- |
|
|
@@ -110,13 +108,15 @@ Apply this rule when creating or modifying Harper schema files, configuring `gra
|
|
|
110
108
|
| `scanInterval` | `Int` | `(expiration + eviction) / 4` | Seconds between eviction scans |
|
|
111
109
|
| `replicate` | `Boolean` | `true` | Enable replication of this table |
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
- `@createdTime` — auto-assigns Unix epoch
|
|
115
|
-
- `@updatedTime` — auto-assigns Unix epoch
|
|
116
|
-
- `@
|
|
117
|
-
- `@
|
|
111
|
+
7. **Apply additional field directives** where needed:
|
|
112
|
+
- `@createdTime` — auto-assigns creation timestamp (Unix epoch ms)
|
|
113
|
+
- `@updatedTime` — auto-assigns update timestamp (Unix epoch ms)
|
|
114
|
+
- `@expiresAt` — marks a field as the record's absolute expiration time (Unix epoch ms)
|
|
115
|
+
- `@embed(source:, model:)` — computes an embedding vector when the source field is written; field type must be `[Float]`
|
|
116
|
+
- `@hidden` — suppresses the field from MCP tool descriptors and OpenAPI document (not an access-control mechanism)
|
|
117
|
+
|
|
118
|
+
8. **Use `@sealed`** on a type to prevent records from including properties beyond those declared in the schema:
|
|
118
119
|
|
|
119
|
-
9. **Restrict extra properties** with `@sealed` if records must not include properties beyond those declared:
|
|
120
120
|
```graphql
|
|
121
121
|
type StrictRecord @table @sealed {
|
|
122
122
|
id: Long @primaryKey
|
|
@@ -124,9 +124,11 @@ Apply this rule when creating or modifying Harper schema files, configuring `gra
|
|
|
124
124
|
}
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
9. **Use unique database names** in plugins or applications to avoid table naming collisions, since all tables default to the `data` database.
|
|
128
|
+
|
|
127
129
|
#### Examples
|
|
128
130
|
|
|
129
|
-
**Minimal two
|
|
131
|
+
**Minimal schema with two tables:**
|
|
130
132
|
|
|
131
133
|
```graphql
|
|
132
134
|
type Dog @table {
|
|
@@ -142,7 +144,7 @@ type Breed @table {
|
|
|
142
144
|
}
|
|
143
145
|
```
|
|
144
146
|
|
|
145
|
-
**Table with expiration, eviction, and
|
|
147
|
+
**Table with expiration, eviction, and scan tuning:**
|
|
146
148
|
|
|
147
149
|
```graphql
|
|
148
150
|
# Expire after 5 minutes, evict after 1 hour, scan every 10 minutes
|
|
@@ -152,63 +154,59 @@ type WeatherCache @table(expiration: 300, eviction: 3300, scanInterval: 600) {
|
|
|
152
154
|
}
|
|
153
155
|
```
|
|
154
156
|
|
|
155
|
-
**
|
|
157
|
+
**Table with multiple `@table` arguments combined:**
|
|
156
158
|
|
|
157
159
|
```graphql
|
|
158
|
-
type
|
|
160
|
+
type Event @table(database: "analytics", expiration: 86400) {
|
|
159
161
|
id: Long @primaryKey
|
|
160
162
|
name: String @indexed
|
|
161
|
-
createdAt: Long @createdTime
|
|
162
|
-
updatedAt: Long @updatedTime
|
|
163
|
-
|
|
164
|
-
"""
|
|
165
|
-
Internal — do not surface to external consumers.
|
|
166
|
-
"""
|
|
167
|
-
creditScore: Int @hidden
|
|
168
163
|
}
|
|
169
164
|
```
|
|
170
165
|
|
|
171
|
-
**
|
|
166
|
+
**Exported table with overridden table name:**
|
|
172
167
|
|
|
173
168
|
```graphql
|
|
174
|
-
|
|
175
|
-
type Product @table(table: "products") {
|
|
169
|
+
type Product @table(table: "products") @export(name: "products") {
|
|
176
170
|
id: Long @primaryKey
|
|
171
|
+
category: String @indexed
|
|
172
|
+
price: Float @indexed
|
|
177
173
|
}
|
|
174
|
+
```
|
|
178
175
|
|
|
179
|
-
|
|
180
|
-
type Order @table(database: "commerce") {
|
|
181
|
-
id: Long @primaryKey
|
|
182
|
-
}
|
|
176
|
+
**Table with timestamps and per-record expiration:**
|
|
183
177
|
|
|
184
|
-
|
|
185
|
-
type Session @table
|
|
186
|
-
id:
|
|
187
|
-
|
|
178
|
+
```graphql
|
|
179
|
+
type Session @table {
|
|
180
|
+
id: ID @primaryKey
|
|
181
|
+
token: String
|
|
182
|
+
createdAt: Long @createdTime
|
|
183
|
+
updatedAt: Long @updatedTime
|
|
184
|
+
expiresAt: Long @expiresAt
|
|
188
185
|
}
|
|
186
|
+
```
|
|
189
187
|
|
|
190
|
-
|
|
191
|
-
type LocalRecord @table(replicate: false) {
|
|
192
|
-
id: Long @primaryKey
|
|
193
|
-
value: String
|
|
194
|
-
}
|
|
188
|
+
**Table with a hidden internal field:**
|
|
195
189
|
|
|
196
|
-
|
|
197
|
-
type
|
|
190
|
+
```graphql
|
|
191
|
+
type Customer @table {
|
|
198
192
|
id: Long @primaryKey
|
|
199
|
-
name: String
|
|
193
|
+
name: String
|
|
194
|
+
|
|
195
|
+
"""
|
|
196
|
+
Internal — do not surface to external consumers.
|
|
197
|
+
"""
|
|
198
|
+
creditScore: Int @hidden
|
|
200
199
|
}
|
|
201
200
|
```
|
|
202
201
|
|
|
203
202
|
#### Notes
|
|
204
203
|
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
- `
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
-
|
|
211
|
-
- Disabling replication (`replicate: false`) and re-enabling it later will not catch up on writes made while replication was disabled.
|
|
204
|
+
- `@table`, `@export`, `@sealed`, and `@hidden` are type-level directives; `@primaryKey`, `@indexed`, `@embed`, `@createdTime`, `@updatedTime`, `@expiresAt`, and `@hidden` are field-level directives.
|
|
205
|
+
- `eviction` removes non-indexed record data but does **not** remove a record from its secondary indexes. Indexes remain functional for evicted records; Harper fetches the full record on demand when a query matches an evicted entry.
|
|
206
|
+
- `scanInterval` is clock-aligned to the server's local timezone, not startup-aligned. The server's startup time does not affect when eviction runs.
|
|
207
|
+
- Replication is enabled by default. If you disable replication on a table and re-enable it later, it will not catch up on writes made while replication was disabled.
|
|
208
|
+
- `@hidden` is a metadata-visibility directive only. Use `attribute_permissions` on roles to enforce data access control.
|
|
209
|
+
- A full-record `put` that omits an `@expiresAt` field clears it; a `patch` of other fields preserves it.
|
|
212
210
|
|
|
213
211
|
### 1.3 Defining Relationships Between Tables in Harper
|
|
214
212
|
|
|
@@ -216,17 +214,17 @@ Instructions for the agent to follow when defining and querying relationships be
|
|
|
216
214
|
|
|
217
215
|
#### When to Use
|
|
218
216
|
|
|
219
|
-
Apply this rule
|
|
217
|
+
Apply this rule when adding foreign key relationships between schema tables, enabling join queries, or returning nested related records in query results. Use it any time a schema type needs to reference records in another table via a foreign key attribute.
|
|
220
218
|
|
|
221
219
|
#### How It Works
|
|
222
220
|
|
|
223
|
-
1. **Use `@relationship(from: attribute)` for many-to-one or many-to-many**: Place this on
|
|
221
|
+
1. **Use `@relationship(from: attribute)` for many-to-one or many-to-many**: Place this on the field in the table that holds the foreign key. The `from` parameter names the attribute on this table that stores the foreign key referencing the target table's primary key.
|
|
224
222
|
|
|
225
223
|
```graphql
|
|
226
224
|
type RealityShow @table @export {
|
|
227
225
|
id: Long @primaryKey
|
|
228
226
|
networkId: Long @indexed
|
|
229
|
-
network: Network @relationship(from: networkId)
|
|
227
|
+
network: Network @relationship(from: networkId) # many-to-one
|
|
230
228
|
title: String @indexed
|
|
231
229
|
}
|
|
232
230
|
|
|
@@ -236,7 +234,7 @@ Apply this rule whenever a schema requires linking two tables via a foreign key
|
|
|
236
234
|
}
|
|
237
235
|
```
|
|
238
236
|
|
|
239
|
-
|
|
237
|
+
If the foreign key attribute is an array, the relationship becomes many-to-many:
|
|
240
238
|
|
|
241
239
|
```graphql
|
|
242
240
|
type RealityShow @table @export {
|
|
@@ -246,24 +244,24 @@ Apply this rule whenever a schema requires linking two tables via a foreign key
|
|
|
246
244
|
}
|
|
247
245
|
```
|
|
248
246
|
|
|
249
|
-
2. **Use `@relationship(to: attribute)` for one-to-many or many-to-many**: Place this on
|
|
247
|
+
2. **Use `@relationship(to: attribute)` for one-to-many or many-to-many**: Place this on the table whose primary key is referenced by the foreign key in the target table. The `to` parameter names the attribute on the target table that holds the foreign key. The result type **must** be an array.
|
|
250
248
|
|
|
251
249
|
```graphql
|
|
252
250
|
type Network @table @export {
|
|
253
251
|
id: Long @primaryKey
|
|
254
252
|
name: String @indexed
|
|
255
|
-
shows: [RealityShow] @relationship(to: networkId)
|
|
253
|
+
shows: [RealityShow] @relationship(to: networkId) # one-to-many
|
|
256
254
|
}
|
|
257
255
|
```
|
|
258
256
|
|
|
259
|
-
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.
|
|
257
|
+
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. Harper resolves the relationship by searching the target table's `to` attribute for matches using this record's `from` attribute value. The result type must be an array.
|
|
260
258
|
|
|
261
259
|
```graphql
|
|
262
260
|
type OrderItem @table @export {
|
|
263
261
|
id: Long @primaryKey
|
|
264
262
|
orderId: Long @indexed
|
|
265
263
|
productSku: Long @indexed
|
|
266
|
-
|
|
264
|
+
products: [Product] @relationship(from: productSku, to: sku)
|
|
267
265
|
}
|
|
268
266
|
|
|
269
267
|
type Product @table @export {
|
|
@@ -273,15 +271,14 @@ Apply this rule whenever a schema requires linking two tables via a foreign key
|
|
|
273
271
|
}
|
|
274
272
|
```
|
|
275
273
|
|
|
276
|
-
4. **Query across relationships using dot-syntax**: Filter by related table attributes using chained dot notation. This behaves as an INNER JOIN.
|
|
274
|
+
4. **Query across relationships using dot-syntax**: Filter records by related table attributes using chained dot notation. This behaves as an INNER JOIN — only records with a matching related record are returned.
|
|
277
275
|
|
|
278
276
|
```
|
|
279
|
-
GET /RealityShow?network.name=Bravo
|
|
280
277
|
GET /Product/?brand.name=Microsoft
|
|
281
278
|
GET /Brand/?products.name=Keyboard
|
|
282
279
|
```
|
|
283
280
|
|
|
284
|
-
5. **
|
|
281
|
+
5. **Include relationship fields in results using `select()`**: Relationship attributes are not returned by default. Use `select()` to include them, optionally specifying nested fields with `{}`.
|
|
285
282
|
|
|
286
283
|
```
|
|
287
284
|
GET /Product/?brand.name=Microsoft&select(name,brand)
|
|
@@ -289,80 +286,59 @@ Apply this rule whenever a schema requires linking two tables via a foreign key
|
|
|
289
286
|
GET /Product/?name=Keyboard&select(name,brand{name,id})
|
|
290
287
|
```
|
|
291
288
|
|
|
292
|
-
|
|
289
|
+
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.
|
|
293
290
|
|
|
294
|
-
**
|
|
295
|
-
|
|
296
|
-
```graphql
|
|
297
|
-
type RealityShow @table @export {
|
|
298
|
-
id: Long @primaryKey
|
|
299
|
-
networkId: Long @indexed
|
|
300
|
-
network: Network @relationship(from: networkId)
|
|
301
|
-
title: String @indexed
|
|
302
|
-
}
|
|
291
|
+
6. **Model many-to-many without a junction table**: Store an array of foreign key values and use `@relationship(from: ...)` pointing to that array attribute. The array order of the foreign key values is preserved when resolving the relationship.
|
|
303
292
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
293
|
+
```graphql
|
|
294
|
+
type Product @table @export {
|
|
295
|
+
id: Long @primaryKey
|
|
296
|
+
name: String
|
|
297
|
+
resellerIds: [Long] @indexed
|
|
298
|
+
resellers: [Reseller] @relationship(from: "resellerIds")
|
|
299
|
+
}
|
|
300
|
+
```
|
|
309
301
|
|
|
310
|
-
|
|
302
|
+
7. **Define self-referential relationships** for parent-child hierarchies by pointing `@relationship` back at the same table type.
|
|
311
303
|
|
|
312
|
-
|
|
313
|
-
GET /RealityShow?network.name=Bravo
|
|
314
|
-
```
|
|
304
|
+
#### Examples
|
|
315
305
|
|
|
316
|
-
**
|
|
306
|
+
**Full schema with bidirectional relationships:**
|
|
317
307
|
|
|
318
308
|
```graphql
|
|
319
|
-
type
|
|
309
|
+
type Product @table @export {
|
|
320
310
|
id: Long @primaryKey
|
|
321
|
-
name: String
|
|
322
|
-
|
|
311
|
+
name: String
|
|
312
|
+
brandId: Long @indexed
|
|
313
|
+
brand: Brand @relationship(from: "brandId")
|
|
323
314
|
}
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
**Many-to-many with array foreign keys** — a product has multiple resellers:
|
|
327
315
|
|
|
328
|
-
|
|
329
|
-
type Product @table @export {
|
|
316
|
+
type Brand @table @export {
|
|
330
317
|
id: Long @primaryKey
|
|
331
318
|
name: String
|
|
332
|
-
|
|
333
|
-
resellers: [Reseller] @relationship(from: resellerIds)
|
|
319
|
+
products: [Product] @relationship(to: "brandId")
|
|
334
320
|
}
|
|
335
321
|
```
|
|
336
322
|
|
|
337
|
-
|
|
323
|
+
**Querying with joins and nested select:**
|
|
338
324
|
|
|
339
325
|
```
|
|
340
|
-
GET /Product/?
|
|
326
|
+
GET /Product/?brand.name=Microsoft&select(name,brand{name,id})
|
|
327
|
+
GET /Brand/?products.name=Keyboard
|
|
341
328
|
```
|
|
342
329
|
|
|
343
|
-
**
|
|
344
|
-
|
|
345
|
-
```graphql
|
|
346
|
-
type OrderItem @table @export {
|
|
347
|
-
id: Long @primaryKey
|
|
348
|
-
orderId: Long @indexed
|
|
349
|
-
productSku: Long @indexed
|
|
350
|
-
product: Product @relationship(from: productSku, to: sku)
|
|
351
|
-
}
|
|
330
|
+
**Many-to-many query with nested select:**
|
|
352
331
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
sku: Long @indexed
|
|
356
|
-
name: String
|
|
357
|
-
}
|
|
332
|
+
```
|
|
333
|
+
GET /Product/?resellers.name=Cool Shop&select(id,name,resellers{name,id})
|
|
358
334
|
```
|
|
359
335
|
|
|
360
336
|
#### Notes
|
|
361
337
|
|
|
362
|
-
-
|
|
363
|
-
-
|
|
364
|
-
- The array
|
|
365
|
-
-
|
|
338
|
+
- Every attribute named in `from` or `to` must exist on the respective table and be annotated with `@indexed` to support join queries.
|
|
339
|
+
- The `to`-only and `from`+`to` forms both require the result field type to be an array (e.g., `[RealityShow]`).
|
|
340
|
+
- The `from`-only form on a non-array attribute produces a many-to-one relationship; on an array attribute it produces many-to-many.
|
|
341
|
+
- Self-referential relationships are supported for hierarchical data within a single table.
|
|
366
342
|
|
|
367
343
|
### 1.4 Vector Indexing
|
|
368
344
|
|
|
@@ -370,11 +346,11 @@ Instructions for the agent to enable HNSW vector indexes on table fields and que
|
|
|
370
346
|
|
|
371
347
|
#### When to Use
|
|
372
348
|
|
|
373
|
-
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
|
|
349
|
+
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.
|
|
374
350
|
|
|
375
351
|
#### How It Works
|
|
376
352
|
|
|
377
|
-
1. **Declare the vector index on a
|
|
353
|
+
1. **Declare the vector index** on a `[Float]` field using `@indexed(type: "HNSW")`:
|
|
378
354
|
|
|
379
355
|
```graphql
|
|
380
356
|
type Document @table {
|
|
@@ -383,7 +359,7 @@ Apply this rule when adding a vector similarity search capability to a Harper ta
|
|
|
383
359
|
}
|
|
384
360
|
```
|
|
385
361
|
|
|
386
|
-
2. **Query
|
|
362
|
+
2. **Query nearest neighbors** using the `sort` parameter with `attribute` and `target`:
|
|
387
363
|
|
|
388
364
|
```javascript
|
|
389
365
|
let results = Document.search({
|
|
@@ -392,7 +368,7 @@ Apply this rule when adding a vector similarity search capability to a Harper ta
|
|
|
392
368
|
});
|
|
393
369
|
```
|
|
394
370
|
|
|
395
|
-
3. **Combine with filter conditions
|
|
371
|
+
3. **Combine with filter conditions** to narrow results before or during traversal:
|
|
396
372
|
|
|
397
373
|
```javascript
|
|
398
374
|
let results = Document.search({
|
|
@@ -402,7 +378,25 @@ Apply this rule when adding a vector similarity search capability to a Harper ta
|
|
|
402
378
|
});
|
|
403
379
|
```
|
|
404
380
|
|
|
405
|
-
|
|
381
|
+
Conditions are evaluated _during_ graph traversal (predicate-aware search), not after. Very selective conditions are automatically diverted to an exact-scan strategy.
|
|
382
|
+
|
|
383
|
+
4. **Use a `vectorFilter` function** for predicates not expressible as conditions (JavaScript API only):
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
let results = Document.search(
|
|
387
|
+
{
|
|
388
|
+
sort: { attribute: 'textEmbeddings', target: searchVector },
|
|
389
|
+
vectorFilter: (record) =>
|
|
390
|
+
record.tenantId === context.user.tenantId && record.status === 'published',
|
|
391
|
+
limit: 10,
|
|
392
|
+
},
|
|
393
|
+
context,
|
|
394
|
+
);
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
The function receives a frozen candidate record and must return a boolean synchronously. It must be side-effect free and fast — it can run once per candidate visited during traversal (verdicts are memoized per query).
|
|
398
|
+
|
|
399
|
+
5. **Filter by distance threshold** using `target` on a condition instead of `sort`:
|
|
406
400
|
|
|
407
401
|
```javascript
|
|
408
402
|
let results = Document.search({
|
|
@@ -415,7 +409,7 @@ Apply this rule when adding a vector similarity search capability to a Harper ta
|
|
|
415
409
|
});
|
|
416
410
|
```
|
|
417
411
|
|
|
418
|
-
|
|
412
|
+
6. **Include computed distance in results** using the `$distance` field in `select`:
|
|
419
413
|
|
|
420
414
|
```javascript
|
|
421
415
|
let results = Document.search({
|
|
@@ -425,7 +419,9 @@ Apply this rule when adding a vector similarity search capability to a Harper ta
|
|
|
425
419
|
});
|
|
426
420
|
```
|
|
427
421
|
|
|
428
|
-
|
|
422
|
+
`$distance` works in both `sort`-based and threshold-based queries.
|
|
423
|
+
|
|
424
|
+
7. **Override the distance function or exploration budget per query** via options on the `sort` descriptor:
|
|
429
425
|
|
|
430
426
|
```javascript
|
|
431
427
|
let results = Document.search({
|
|
@@ -434,10 +430,25 @@ Apply this rule when adding a vector similarity search capability to a Harper ta
|
|
|
434
430
|
});
|
|
435
431
|
```
|
|
436
432
|
|
|
437
|
-
- `distance` —
|
|
438
|
-
- `ef` — overrides the search exploration budget. Higher values improve recall at the cost of latency.
|
|
433
|
+
- `distance` — `"cosine"`, `"euclidean"`, or `"dotProduct"`.
|
|
434
|
+
- `ef` — overrides the search exploration budget for this query. Higher values improve recall at the cost of latency.
|
|
439
435
|
|
|
440
|
-
|
|
436
|
+
8. **Tune filtered traversal** when a `vectorFilter` is very selective by raising `ef` or `filterExpansion`:
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
let results = Document.search(
|
|
440
|
+
{
|
|
441
|
+
sort: { attribute: 'textEmbeddings', target: searchVector, ef: 200, filterExpansion: 40 },
|
|
442
|
+
vectorFilter: (record) => record.category === 'rare',
|
|
443
|
+
limit: 10,
|
|
444
|
+
},
|
|
445
|
+
context,
|
|
446
|
+
);
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Filtered traversal visits at most `ef * filterExpansion` nodes (`filterExpansion` defaults to `24`). If the budget is exhausted before results fill, the search returns what was found rather than erroring.
|
|
450
|
+
|
|
451
|
+
9. **Configure HNSW index parameters** directly on the directive:
|
|
441
452
|
|
|
442
453
|
```graphql
|
|
443
454
|
type Document @table {
|
|
@@ -447,18 +458,48 @@ Apply this rule when adding a vector similarity search capability to a Harper ta
|
|
|
447
458
|
}
|
|
448
459
|
```
|
|
449
460
|
|
|
450
|
-
|
|
461
|
+
| Parameter | Default | Description |
|
|
462
|
+
| ---------------------- | ----------------- | ------------------------------------------------------------------------------------------------ |
|
|
463
|
+
| `distance` | `"cosine"` | Distance function: `"cosine"`, `"euclidean"`, or `"dotProduct"` |
|
|
464
|
+
| `efConstruction` | `100` | Max nodes explored during index construction. Higher = better recall, lower = better performance |
|
|
465
|
+
| `M` | `16` | Preferred connections per graph layer |
|
|
466
|
+
| `optimizeRouting` | `0.5` | Heuristic aggressiveness for omitting redundant connections (0 = off, 1 = most aggressive) |
|
|
467
|
+
| `mL` | computed from `M` | Normalization factor for level generation |
|
|
468
|
+
| `efConstructionSearch` | auto-scaled | Max nodes explored during search. When unset, auto-scales with index size |
|
|
469
|
+
| `quantization` | — | `"int8"` stores vectors quantized to int8 |
|
|
470
|
+
| `filterExpansion` | `24` | Visit-budget multiplier for filtered search |
|
|
451
471
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
472
|
+
Changing `efConstructionSearch` on an existing index does not trigger a rebuild. Changing structural parameters (`distance`, `M`, `efConstruction`, `quantization`) does rebuild the index.
|
|
473
|
+
|
|
474
|
+
10. **Enable int8 quantization** to reduce index size and memory usage:
|
|
475
|
+
|
|
476
|
+
```graphql
|
|
477
|
+
type Document @table {
|
|
478
|
+
id: Long @primaryKey
|
|
479
|
+
textEmbeddings: [Float] @indexed(type: "HNSW", quantization: "int8")
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Graph navigation uses quantized distances. For `sort` queries, Harper re-ranks results against full-precision vectors, restoring exact ordering and exact `$distance` values. Distance-threshold queries filter on the approximate distance.
|
|
484
|
+
|
|
485
|
+
11. **Implement record-level access control** by overriding `allowRead(user, target, context)` on the table resource. During vector queries the check participates in graph traversal, so a restricted user receives the k nearest records they are allowed to see:
|
|
486
|
+
|
|
487
|
+
```javascript
|
|
488
|
+
export class Reports extends tables.Reports {
|
|
489
|
+
allowRead(user, target, context) {
|
|
490
|
+
if (!super.allowRead(user, target, context)) return false;
|
|
491
|
+
if (user.role.permission.super_user) return true;
|
|
492
|
+
if (this.ownerId == null) return true;
|
|
493
|
+
return this.ownerId === user.id;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
The check must be synchronous, side-effect free, and fast. `this` is the frozen record during per-record evaluation. A thrown exception denies that record (fail closed).
|
|
458
499
|
|
|
459
500
|
#### Examples
|
|
460
501
|
|
|
461
|
-
Full schema with custom HNSW parameters
|
|
502
|
+
**Full schema with custom HNSW parameters:**
|
|
462
503
|
|
|
463
504
|
```graphql
|
|
464
505
|
type Document @table {
|
|
@@ -468,16 +509,33 @@ type Document @table {
|
|
|
468
509
|
}
|
|
469
510
|
```
|
|
470
511
|
|
|
512
|
+
**Nearest-neighbor search with distance output:**
|
|
513
|
+
|
|
471
514
|
```javascript
|
|
472
|
-
// Nearest-neighbor search with distance scores
|
|
473
515
|
let results = Document.search({
|
|
474
516
|
select: ['name', '$distance'],
|
|
475
517
|
sort: { attribute: 'textEmbeddings', target: searchVector },
|
|
476
518
|
limit: 5,
|
|
477
519
|
});
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**Filtered vector search with tuned traversal budget:**
|
|
523
|
+
|
|
524
|
+
```javascript
|
|
525
|
+
let results = Document.search(
|
|
526
|
+
{
|
|
527
|
+
sort: { attribute: 'textEmbeddings', target: searchVector, ef: 200, filterExpansion: 40 },
|
|
528
|
+
vectorFilter: (record) => record.category === 'rare',
|
|
529
|
+
limit: 10,
|
|
530
|
+
},
|
|
531
|
+
context,
|
|
532
|
+
);
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
**Distance threshold query (no ranking, cutoff only):**
|
|
478
536
|
|
|
479
|
-
|
|
480
|
-
let
|
|
537
|
+
```javascript
|
|
538
|
+
let results = Document.search({
|
|
481
539
|
conditions: {
|
|
482
540
|
attribute: 'textEmbeddings',
|
|
483
541
|
comparator: 'lt',
|
|
@@ -489,24 +547,13 @@ let closeMatches = Document.search({
|
|
|
489
547
|
|
|
490
548
|
#### Notes
|
|
491
549
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
| `optimizeRouting` | `0.5` | Heuristic aggressiveness for omitting redundant connections (0 = off, 1 = most aggressive) |
|
|
500
|
-
| `mL` | computed from `M` | Normalization factor for level generation |
|
|
501
|
-
| `efConstructionSearch` | auto-scaled | Max nodes explored during search. When unset, auto-scales with index size; setting it fixes the budget |
|
|
502
|
-
| `quantization` | — | `"int8"` stores vectors quantized to int8 |
|
|
503
|
-
|
|
504
|
-
- The `distance` option on a per-query `sort` descriptor accepts `"cosine"`, `"euclidean"`, or `"dotProduct"`.
|
|
505
|
-
- When no `ef` is passed and `efConstructionSearch` (or `efConstruction`) is not explicitly set on the index, the search budget auto-scales with index size.
|
|
506
|
-
- `efConstruction` seeds the initial value of `efConstructionSearch`; setting either one fixes the search budget.
|
|
507
|
-
- The correct parameter name is `efConstructionSearch` (not `efSearchConstruction`).
|
|
508
|
-
- `$distance` is available in both `sort`-based ranking and `conditions`-based threshold queries.
|
|
509
|
-
- For `quantization: "int8"`, distance-threshold (`lt`/`le`) queries filter on approximate distance; `sort` queries re-rank against full-precision vectors.
|
|
550
|
+
- Use `@indexed(type: "HNSW")` on a `[Float]` field — not on scalar fields.
|
|
551
|
+
- The default `distance` is `"cosine"`. Override it per-index via the directive or per-query via the `sort` descriptor.
|
|
552
|
+
- `efConstruction` seeds the initial value of `efConstructionSearch`. When neither is set, the search budget auto-scales with index size.
|
|
553
|
+
- `vectorFilter` is available from the JavaScript API only; it cannot be expressed in a REST query string.
|
|
554
|
+
- The parameter name Harper reads is `efConstructionSearch` (not `efSearchConstruction`).
|
|
555
|
+
- `$distance` must be listed explicitly in `select` to appear in results.
|
|
556
|
+
- For record-level `allowRead` overrides: collection-scope calls (where `this.ownerId` is `null`) should return `true` to open the connection; per-record filtering happens during query execution and event delivery.
|
|
510
557
|
|
|
511
558
|
### 1.5 Using the Blob Data Type
|
|
512
559
|
|
|
@@ -739,13 +786,13 @@ Apply this rule when adding REST or WebSocket API access to Harper tables or cus
|
|
|
739
786
|
|
|
740
787
|
#### How It Works
|
|
741
788
|
|
|
742
|
-
1. **Enable the REST plugin**: Add `rest: true` to your application's `config.yaml`. This activates the HTTP REST interface and enables WebSocket support
|
|
789
|
+
1. **Enable the REST plugin**: Add `rest: true` to your application's `config.yaml`. This activates the HTTP REST interface on the application server port (default `9926`) and enables WebSocket support automatically.
|
|
743
790
|
|
|
744
791
|
```yaml
|
|
745
792
|
rest: true
|
|
746
793
|
```
|
|
747
794
|
|
|
748
|
-
To configure
|
|
795
|
+
To configure options explicitly:
|
|
749
796
|
|
|
750
797
|
```yaml
|
|
751
798
|
rest:
|
|
@@ -753,69 +800,26 @@ Apply this rule when adding REST or WebSocket API access to Harper tables or cus
|
|
|
753
800
|
webSocket: false # disables automatic WebSocket support (enabled by default)
|
|
754
801
|
```
|
|
755
802
|
|
|
756
|
-
2. **Export your resource in the schema**: Tables are not exposed by default. Use the `@export` directive in your schema definition to
|
|
757
|
-
|
|
758
|
-
3. **Use the correct URL structure**: The REST interface follows a consistent path convention.
|
|
759
|
-
|
|
760
|
-
| Path | Description |
|
|
761
|
-
| -------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
762
|
-
| `/my-resource` | Returns a description of the resource (e.g., table metadata) |
|
|
763
|
-
| `/my-resource/` | Trailing slash — represents the full collection; append query parameters to search |
|
|
764
|
-
| `/my-resource/record-id` | A specific record identified by its primary key |
|
|
765
|
-
| `/my-resource/record-id/` | Trailing slash — collection of records with the given id prefix |
|
|
766
|
-
| `/my-resource/record-id/with/multiple/parts` | Record id with multiple path segments |
|
|
767
|
-
|
|
768
|
-
4. **Map HTTP methods to operations**: Each HTTP method maps to a resource method and operation.
|
|
769
|
-
- **GET** — Retrieve a record or search. Calls `get()`.
|
|
770
|
-
|
|
771
|
-
```
|
|
772
|
-
GET /MyTable/123
|
|
773
|
-
GET /MyTable/?name=Harper
|
|
774
|
-
GET /MyTable/123.propertyName
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
Responses include an `ETag` header. Clients may send `If-None-Match` to receive `304 Not Modified` when the record is unchanged.
|
|
803
|
+
2. **Export your resource in the schema**: Tables are not exposed by default. Use the `@export` directive in your schema definition to expose a table as a REST endpoint. The exported name defines the base URL path.
|
|
778
804
|
|
|
779
|
-
|
|
805
|
+
3. **Use the correct URL structure**: Target resources using these path conventions:
|
|
780
806
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
807
|
+
| Path | Description |
|
|
808
|
+
| -------------------------------------------- | ----------------------------------------------------------- |
|
|
809
|
+
| `/my-resource` | Returns resource metadata |
|
|
810
|
+
| `/my-resource/` | Collection — all records; append query parameters to search |
|
|
811
|
+
| `/my-resource/record-id` | Specific record by primary key |
|
|
812
|
+
| `/my-resource/record-id/` | Collection of records with the given id prefix |
|
|
813
|
+
| `/my-resource/record-id/with/multiple/parts` | Record id with multiple path segments |
|
|
787
814
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
815
|
+
4. **Map operations to HTTP methods**: Each HTTP method maps to a resource method:
|
|
816
|
+
- **GET** — Retrieve a record or search. Calls `get()`. Responses include an `ETag` header; send `If-None-Match` on subsequent requests to receive `304 Not Modified` when unchanged.
|
|
817
|
+
- **PUT** — Create or replace a record (upsert). Calls `put(record)`. The stored record exactly matches the submitted body; omitted properties are removed.
|
|
818
|
+
- **POST** — Create a record without specifying a primary key. Calls `post(data)`. The assigned key is returned in the `Location` response header.
|
|
819
|
+
- **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.
|
|
820
|
+
- **DELETE** — Delete a record by id or all records matching a query.
|
|
793
821
|
|
|
794
|
-
|
|
795
|
-
```
|
|
796
|
-
|
|
797
|
-
- **PATCH** — Partially update a record, merging only provided properties. Unspecified properties are preserved.
|
|
798
|
-
|
|
799
|
-
```
|
|
800
|
-
PATCH /MyTable/123
|
|
801
|
-
Content-Type: application/json
|
|
802
|
-
|
|
803
|
-
{ "status": "active" }
|
|
804
|
-
```
|
|
805
|
-
|
|
806
|
-
- **DELETE** — Delete a record or all records matching a query.
|
|
807
|
-
```
|
|
808
|
-
DELETE /MyTable/123
|
|
809
|
-
DELETE /MyTable/?status=archived
|
|
810
|
-
```
|
|
811
|
-
|
|
812
|
-
5. **Access the auto-generated OpenAPI spec**: Harper generates an OpenAPI specification for all exported resources. Retrieve it at:
|
|
813
|
-
|
|
814
|
-
```
|
|
815
|
-
GET /openapi
|
|
816
|
-
```
|
|
817
|
-
|
|
818
|
-
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.
|
|
822
|
+
5. **Connect via WebSocket**: A WebSocket connection to a resource URL subscribes to that resource and streams change events. See [real-time-apps.md](real-time-apps.md) for full real-time patterns.
|
|
819
823
|
|
|
820
824
|
```javascript
|
|
821
825
|
let ws = new WebSocket('wss://server/my-resource/341');
|
|
@@ -824,13 +828,17 @@ Apply this rule when adding REST or WebSocket API access to Harper tables or cus
|
|
|
824
828
|
};
|
|
825
829
|
```
|
|
826
830
|
|
|
827
|
-
|
|
831
|
+
6. **Implement a custom `connect()` handler** on a resource class to control WebSocket behavior. The method receives `incomingMessages` and must return an async iterable producing messages to send to the client.
|
|
828
832
|
|
|
829
|
-
7. **
|
|
833
|
+
7. **Retrieve the OpenAPI spec**: Harper auto-generates an OpenAPI specification for all exported resources, available at:
|
|
834
|
+
|
|
835
|
+
```
|
|
836
|
+
GET /openapi
|
|
837
|
+
```
|
|
830
838
|
|
|
831
839
|
#### Examples
|
|
832
840
|
|
|
833
|
-
**Simple echo server
|
|
841
|
+
**Simple echo WebSocket server**:
|
|
834
842
|
|
|
835
843
|
```javascript
|
|
836
844
|
export class Echo extends Resource {
|
|
@@ -842,7 +850,7 @@ export class Echo extends Resource {
|
|
|
842
850
|
}
|
|
843
851
|
```
|
|
844
852
|
|
|
845
|
-
**
|
|
853
|
+
**Custom `connect()` using the default iterable with `send()` and `close` event**:
|
|
846
854
|
|
|
847
855
|
```javascript
|
|
848
856
|
export class Example extends Resource {
|
|
@@ -866,30 +874,37 @@ export class Example extends Resource {
|
|
|
866
874
|
}
|
|
867
875
|
```
|
|
868
876
|
|
|
869
|
-
**
|
|
877
|
+
**Common REST operations**:
|
|
870
878
|
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
|
|
879
|
+
```
|
|
880
|
+
GET /MyTable/123
|
|
881
|
+
GET /MyTable/?name=Harper
|
|
882
|
+
PUT /MyTable/123
|
|
883
|
+
PATCH /MyTable/123
|
|
884
|
+
DELETE /MyTable/?status=archived
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
```json
|
|
888
|
+
{ "name": "some data" }
|
|
874
889
|
```
|
|
875
890
|
|
|
876
891
|
#### Notes
|
|
877
892
|
|
|
878
|
-
-
|
|
879
|
-
-
|
|
880
|
-
-
|
|
881
|
-
-
|
|
882
|
-
-
|
|
883
|
-
-
|
|
884
|
-
-
|
|
893
|
+
- `rest: true` is the minimal config to enable both REST and WebSocket support. Set `webSocket: false` under the `rest` key to disable WebSocket only.
|
|
894
|
+
- The `@export` directive in the schema is required for any table to appear as a REST endpoint — tables are not exported by default.
|
|
895
|
+
- PATCH merges are shallow (top-level only). Nested objects in the request body replace the entire existing sub-object. Dot-path keys (e.g., `"settings.theme"`) are stored as literal keys, not interpreted as paths.
|
|
896
|
+
- For MQTT over WebSocket, set the sub-protocol header `Sec-WebSocket-Protocol: mqtt`.
|
|
897
|
+
- In distributed environments, non-retained messages are delivered in arrival order; retained messages (PUT/updated records) keep only the latest timestamp as the winning record.
|
|
898
|
+
- For full query syntax on GET and DELETE, see [querying-rest-apis.md](querying-rest-apis.md).
|
|
899
|
+
- For building real-time features with WebSocket subscriptions, see [real-time-apps.md](real-time-apps.md).
|
|
885
900
|
|
|
886
|
-
### 2.2 Querying REST APIs
|
|
901
|
+
### 2.2 Querying Harper REST APIs
|
|
887
902
|
|
|
888
|
-
Instructions for the agent to filter, sort, select, and paginate Harper
|
|
903
|
+
Instructions for the agent to filter, sort, select, and paginate records using Harper's URL-based query language on REST collection endpoints.
|
|
889
904
|
|
|
890
905
|
#### When to Use
|
|
891
906
|
|
|
892
|
-
Apply this rule when building or
|
|
907
|
+
Apply this rule when building or debugging REST API calls against Harper tables that require filtering by attribute values, comparison ranges, sorting, field selection, or result pagination. This rule also covers OR logic, grouping, and querying across related tables via dot-syntax joins. See [automatic-apis.md](automatic-apis.md) for how Harper exposes tables as REST endpoints.
|
|
893
908
|
|
|
894
909
|
#### How It Works
|
|
895
910
|
|
|
@@ -900,21 +915,27 @@ Apply this rule when building or modifying code that queries Harper REST endpoin
|
|
|
900
915
|
GET /Product/?category=software&inStock=true
|
|
901
916
|
```
|
|
902
917
|
|
|
903
|
-
2. **
|
|
918
|
+
2. **Filter for null values**: Use `=null` as the value to match null or non-null records.
|
|
904
919
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
|
912
|
-
|
|
|
913
|
-
|
|
|
914
|
-
| `=
|
|
915
|
-
| `=
|
|
916
|
-
|
|
|
917
|
-
|
|
|
920
|
+
```
|
|
921
|
+
GET /Product/?discount=null
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
3. **Apply comparison operators (FIQL syntax)**: Use FIQL operators in query parameters for range and string matching.
|
|
925
|
+
|
|
926
|
+
| Operator | Meaning |
|
|
927
|
+
| -------------------- | -------------------------------------- |
|
|
928
|
+
| `==` | Equal |
|
|
929
|
+
| `=lt=` | Less than |
|
|
930
|
+
| `=le=` | Less than or equal |
|
|
931
|
+
| `=gt=` | Greater than |
|
|
932
|
+
| `=ge=` | Greater than or equal |
|
|
933
|
+
| `=ne=`, `!=` | Not equal |
|
|
934
|
+
| `=ct=` | Contains (strings) |
|
|
935
|
+
| `=sw=`, `==<value>*` | Starts with (strings) |
|
|
936
|
+
| `=ew=` | Ends with (strings) |
|
|
937
|
+
| `=`, `===` | Strict equality (no type conversion) |
|
|
938
|
+
| `!==` | Strict inequality (no type conversion) |
|
|
918
939
|
|
|
919
940
|
```
|
|
920
941
|
GET /Product/?price=gt=100
|
|
@@ -929,32 +950,52 @@ Apply this rule when building or modifying code that queries Harper REST endpoin
|
|
|
929
950
|
GET /Product/?listDate=gt=2017-03-08T09%3A30%3A00.000Z
|
|
930
951
|
```
|
|
931
952
|
|
|
932
|
-
|
|
953
|
+
4. **Chain conditions for range queries**: Omit the attribute name on the second condition to apply it to the same attribute. Only `gt`/`ge` combined with `lt`/`le` is supported for chaining.
|
|
933
954
|
|
|
934
955
|
```
|
|
935
956
|
GET /Product/?price=gt=100<=200
|
|
936
957
|
```
|
|
937
958
|
|
|
938
|
-
|
|
959
|
+
5. **Apply type conversion**: For FIQL comparators, Harper converts values automatically. Use explicit prefixes to force a type.
|
|
960
|
+
|
|
961
|
+
| Syntax | Behavior |
|
|
962
|
+
| ----------------------------------------- | ------------------------------------------- |
|
|
963
|
+
| `name==null` | Converts to `null` |
|
|
964
|
+
| `name==123` | Converts to number if attribute is untyped |
|
|
965
|
+
| `name==true` | Converts to boolean if attribute is untyped |
|
|
966
|
+
| `name==number:123` | Explicit number conversion |
|
|
967
|
+
| `name==boolean:true` | Explicit boolean conversion |
|
|
968
|
+
| `name==string:some%20text` | Keep as string with URL decode |
|
|
969
|
+
| `name==date:2024-01-05T20%3A07%3A27.955Z` | Explicit Date conversion |
|
|
970
|
+
|
|
971
|
+
For strict operators (`=`, `===`, `!==`), no automatic type conversion is applied.
|
|
972
|
+
|
|
973
|
+
6. **Combine conditions with OR logic**: Use `|` instead of `&` to express OR between conditions.
|
|
939
974
|
|
|
940
975
|
```
|
|
941
976
|
GET /Product/?rating=5|featured=true
|
|
942
977
|
```
|
|
943
978
|
|
|
944
|
-
|
|
979
|
+
7. **Group conditions**: Use parentheses or square brackets to control evaluation order. Prefer square brackets when building queries from user input, since `[` and `]` are safely URI-encoded.
|
|
945
980
|
|
|
946
981
|
```
|
|
947
982
|
GET /Product/?rating=5|(price=gt=100&price=lt=200)
|
|
948
983
|
GET /Product/?rating=5&[tag=fast|tag=scalable|tag=efficient]
|
|
949
984
|
```
|
|
950
985
|
|
|
951
|
-
|
|
986
|
+
Build grouped queries in JavaScript:
|
|
952
987
|
|
|
953
988
|
```javascript
|
|
954
989
|
let url = `/Product/?rating=5&[${tags.map(encodeURIComponent).join('|')}]`;
|
|
955
990
|
```
|
|
956
991
|
|
|
957
|
-
|
|
992
|
+
Nest groups for complex conditions:
|
|
993
|
+
|
|
994
|
+
```
|
|
995
|
+
GET /Product/?price=lt=100|[rating=5&[tag=fast|tag=scalable|tag=efficient]&inStock=true]
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
8. **Select specific properties with `select(`**: Append `select(...)` as a query function separated by `&` to control which fields are returned.
|
|
958
999
|
|
|
959
1000
|
| Syntax | Returns |
|
|
960
1001
|
| -------------------------------------- | ------------------------------------------- |
|
|
@@ -969,111 +1010,112 @@ Apply this rule when building or modifying code that queries Harper REST endpoin
|
|
|
969
1010
|
GET /Product/?brand.name=Microsoft&select(name,brand{name})
|
|
970
1011
|
```
|
|
971
1012
|
|
|
972
|
-
|
|
1013
|
+
9. **Paginate results with `limit(`**: Use `limit(end)` or `limit(start,end)` to restrict the number of records returned.
|
|
973
1014
|
|
|
974
1015
|
```
|
|
975
1016
|
GET /Product/?rating=gt=3&inStock=true&select(rating,name)&limit(20)
|
|
976
1017
|
GET /Product/?rating=gt=3&limit(10,30)
|
|
977
1018
|
```
|
|
978
1019
|
|
|
979
|
-
|
|
1020
|
+
10. **Sort results with `sort(`**: Use `sort(property)` or `sort(+property,-property,...)` to order results. Prefix `+` or no prefix = ascending; `-` = descending.
|
|
980
1021
|
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1022
|
+
```
|
|
1023
|
+
GET /Product/?rating=gt=3&sort(+name)
|
|
1024
|
+
GET /Product/?sort(+rating,-price)
|
|
1025
|
+
```
|
|
985
1026
|
|
|
986
|
-
|
|
1027
|
+
11. **Query across relationships using dot-syntax**: Filter on related table attributes using dot-chained property names. Relationships must be defined in the schema with `@relationship`.
|
|
987
1028
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1029
|
+
```
|
|
1030
|
+
GET /Product/?brand.name=Microsoft
|
|
1031
|
+
GET /Brand/?products.name=Keyboard
|
|
1032
|
+
```
|
|
992
1033
|
|
|
993
|
-
|
|
1034
|
+
Use `select()` to include relationship attributes in the response (they are excluded by default):
|
|
994
1035
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1036
|
+
```
|
|
1037
|
+
GET /Product/?brand.name=Microsoft&select(name,brand)
|
|
1038
|
+
GET /Product/?brand.name=Microsoft&select(name,brand{name})
|
|
1039
|
+
```
|
|
998
1040
|
|
|
999
|
-
|
|
1041
|
+
12. **Access a specific property by URL**: Append `.propertyName` to a record ID in the URL path. Only works for properties declared in the schema.
|
|
1000
1042
|
```
|
|
1001
1043
|
GET /MyTable/123.propertyName
|
|
1002
1044
|
```
|
|
1003
1045
|
|
|
1004
1046
|
#### Examples
|
|
1005
1047
|
|
|
1006
|
-
**Range filter with select and limit
|
|
1048
|
+
**Range filter with select and limit**:
|
|
1007
1049
|
|
|
1008
1050
|
```
|
|
1009
1051
|
GET /Product/?category=software&price=gt=100&price=lt=200&select(name,price)&limit(20)
|
|
1010
1052
|
```
|
|
1011
1053
|
|
|
1012
|
-
**Sort
|
|
1054
|
+
**Sort and paginate**:
|
|
1013
1055
|
|
|
1014
1056
|
```
|
|
1015
|
-
GET /Product/?sort(+rating,-price)
|
|
1057
|
+
GET /Product/?rating=gt=3&sort(+rating,-price)&limit(10,30)
|
|
1016
1058
|
```
|
|
1017
1059
|
|
|
1018
|
-
**OR
|
|
1060
|
+
**OR with grouping**:
|
|
1019
1061
|
|
|
1020
1062
|
```
|
|
1021
1063
|
GET /Product/?price=lt=100|[rating=5&[tag=fast|tag=scalable|tag=efficient]&inStock=true]
|
|
1022
1064
|
```
|
|
1023
1065
|
|
|
1024
|
-
**
|
|
1025
|
-
|
|
1026
|
-
```
|
|
1027
|
-
GET /Product/?brand.name=Microsoft&select(name,brand{name,id})
|
|
1028
|
-
```
|
|
1029
|
-
|
|
1030
|
-
**Schema defining a relationship for join queries:**
|
|
1066
|
+
**Join query with nested select** — schema first:
|
|
1031
1067
|
|
|
1032
1068
|
```graphql
|
|
1033
1069
|
type Product @table @export {
|
|
1034
1070
|
id: Long @primaryKey
|
|
1035
1071
|
name: String
|
|
1036
1072
|
brandId: Long @indexed
|
|
1037
|
-
brand: Brand @
|
|
1073
|
+
brand: Brand @relationship(from: "brandId")
|
|
1038
1074
|
}
|
|
1039
1075
|
type Brand @table @export {
|
|
1040
1076
|
id: Long @primaryKey
|
|
1041
1077
|
name: String
|
|
1042
|
-
products: [Product] @
|
|
1078
|
+
products: [Product] @relationship(to: "brandId")
|
|
1043
1079
|
}
|
|
1044
1080
|
```
|
|
1045
1081
|
|
|
1046
|
-
|
|
1082
|
+
Then query:
|
|
1083
|
+
|
|
1084
|
+
```
|
|
1085
|
+
GET /Product/?brand.name=Microsoft&select(name,brand{name,id})
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
**Many-to-many relationship** — schema:
|
|
1047
1089
|
|
|
1048
1090
|
```graphql
|
|
1049
1091
|
type Product @table @export {
|
|
1050
1092
|
id: Long @primaryKey
|
|
1051
1093
|
name: String
|
|
1052
1094
|
resellerIds: [Long] @indexed
|
|
1053
|
-
resellers: [Reseller] @
|
|
1095
|
+
resellers: [Reseller] @relationship(from: "resellerIds")
|
|
1054
1096
|
}
|
|
1055
1097
|
```
|
|
1056
1098
|
|
|
1099
|
+
Query:
|
|
1100
|
+
|
|
1057
1101
|
```
|
|
1058
1102
|
GET /Product/?resellers.name=Cool Shop&select(id,name,resellers{name,id})
|
|
1059
1103
|
```
|
|
1060
1104
|
|
|
1061
|
-
**
|
|
1105
|
+
**Date range with URL-encoded colons**:
|
|
1062
1106
|
|
|
1063
1107
|
```
|
|
1064
|
-
GET /Product/?
|
|
1065
|
-
GET /Product/?active==boolean:true
|
|
1066
|
-
GET /Product/?listDate==date:2024-01-05T20%3A07%3A27.955Z
|
|
1108
|
+
GET /Product/?listDate=gt=2017-03-08T09%3A30%3A00.000Z
|
|
1067
1109
|
```
|
|
1068
1110
|
|
|
1069
1111
|
#### Notes
|
|
1070
1112
|
|
|
1071
|
-
-
|
|
1072
|
-
-
|
|
1073
|
-
-
|
|
1074
|
-
-
|
|
1075
|
-
-
|
|
1076
|
-
-
|
|
1113
|
+
- All filtered attributes must be indexed unless at least one other attribute in the same query is indexed.
|
|
1114
|
+
- Null queries (`?attr=null`) require indexes created after null indexing support was added. Rebuild existing indexes (remove and re-add) to enable null queries on them.
|
|
1115
|
+
- When selecting a related attribute without filtering on it, the join behaves as a LEFT JOIN — the relationship property is omitted if the foreign key is null or references a non-existent record.
|
|
1116
|
+
- The array order of foreign key values (e.g., `resellerIds`) is preserved when resolving many-to-many relationships.
|
|
1117
|
+
- Square brackets (`[`, `]`) are preferred over parentheses for grouping when constructing queries programmatically, because standard URI encoding safely encodes them.
|
|
1118
|
+
- `directURLMapping: true` can be set on a resource to change URL path handling semantics; see your schema configuration for details.
|
|
1077
1119
|
|
|
1078
1120
|
### 2.3 Real-Time Apps with WebSockets and Pub/Sub
|
|
1079
1121
|
|
|
@@ -1608,11 +1650,11 @@ Instructions for the agent to interact with Harper tables programmatically using
|
|
|
1608
1650
|
|
|
1609
1651
|
#### When to Use
|
|
1610
1652
|
|
|
1611
|
-
Apply this rule when writing server-side
|
|
1653
|
+
Apply this rule when writing server-side code that reads from or writes to Harper tables directly — for example, in request handlers, background jobs, or SSR rendering — without going through the REST API. Use it whenever you need to construct queries with `conditions`, `select`, `sort`, or `search(`.
|
|
1612
1654
|
|
|
1613
1655
|
#### How It Works
|
|
1614
1656
|
|
|
1615
|
-
1. **Import `tables`**:
|
|
1657
|
+
1. **Import `tables`**: Pull `tables` from the `harper` package. Each property on `tables` corresponds to a table defined in `schema.graphql`.
|
|
1616
1658
|
|
|
1617
1659
|
```javascript
|
|
1618
1660
|
import { tables } from 'harper';
|
|
@@ -1620,10 +1662,9 @@ Apply this rule when writing server-side Harper code that reads from or writes t
|
|
|
1620
1662
|
// same as: databases.data.Product
|
|
1621
1663
|
```
|
|
1622
1664
|
|
|
1623
|
-
2. **Define your schema**: Declare tables in `schema.graphql
|
|
1665
|
+
2. **Define your schema**: Declare tables with `@table` in `schema.graphql`. Each type becomes a property on `tables`.
|
|
1624
1666
|
|
|
1625
1667
|
```graphql
|
|
1626
|
-
# schema.graphql
|
|
1627
1668
|
type Product @table {
|
|
1628
1669
|
id: Long @primaryKey
|
|
1629
1670
|
name: String
|
|
@@ -1631,57 +1672,75 @@ Apply this rule when writing server-side Harper code that reads from or writes t
|
|
|
1631
1672
|
}
|
|
1632
1673
|
```
|
|
1633
1674
|
|
|
1634
|
-
3. **Create and
|
|
1675
|
+
3. **Create and modify records**: Use `create`, `patch`, and `get` for basic CRUD.
|
|
1635
1676
|
|
|
1636
1677
|
```javascript
|
|
1637
|
-
// Create a new record (id auto-generated)
|
|
1638
1678
|
const created = await Product.create({ name: 'Shirt', price: 9.5 });
|
|
1639
|
-
|
|
1640
|
-
// Modify the record
|
|
1641
1679
|
await Product.patch(created.id, { price: Math.round(created.price * 0.8 * 100) / 100 });
|
|
1642
|
-
|
|
1643
|
-
// Retrieve by primary key
|
|
1644
1680
|
const record = await Product.get(created.id);
|
|
1645
1681
|
```
|
|
1646
1682
|
|
|
1647
|
-
4. **Query with `search(
|
|
1683
|
+
4. **Query with `search(`**: Pass a query object to `Product.search(query)`. It returns an async iterable.
|
|
1648
1684
|
|
|
1649
1685
|
```javascript
|
|
1650
1686
|
const query = {
|
|
1651
1687
|
conditions: [{ attribute: 'price', comparator: 'less_than', value: 8.0 }],
|
|
1652
1688
|
};
|
|
1653
1689
|
for await (const record of Product.search(query)) {
|
|
1654
|
-
//
|
|
1690
|
+
// process record
|
|
1655
1691
|
}
|
|
1656
1692
|
```
|
|
1657
1693
|
|
|
1658
|
-
5. **
|
|
1694
|
+
5. **Build `conditions`**: Each condition object filters records. Nest conditions with `operator` for boolean logic.
|
|
1695
|
+
|
|
1696
|
+
| Property | Description |
|
|
1697
|
+
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1698
|
+
| `attribute` | Property name, or array for chained/joined properties (e.g. `['brand', 'name']`) |
|
|
1699
|
+
| `value` | The value to match |
|
|
1700
|
+
| `comparator` | `equals` (default), `greater_than`, `greater_than_equal`, `less_than`, `less_than_equal`, `starts_with`, `contains`, `ends_with`, `between`, `not_equal` |
|
|
1701
|
+
| `conditions` | Nested conditions array |
|
|
1702
|
+
| `operator` | `and` (default) or `or` for the nested `conditions` |
|
|
1703
|
+
|
|
1704
|
+
6. **Use `select`** to control which properties are returned. Accepts an array of property names, a string for a single property, or nested objects for relationships.
|
|
1659
1705
|
|
|
1660
1706
|
```javascript
|
|
1661
|
-
|
|
1662
|
-
|
|
1707
|
+
// Array of names
|
|
1708
|
+
Product.search({ select: ['name', 'price'] });
|
|
1663
1709
|
|
|
1664
|
-
//
|
|
1665
|
-
|
|
1666
|
-
id: 42,
|
|
1667
|
-
select: ['id', 'title', { name: 'author', select: ['name'] }],
|
|
1668
|
-
});
|
|
1710
|
+
// Nested relationship select
|
|
1711
|
+
Book.get({ id: 42, select: ['id', 'title', { name: 'author', select: ['name'] }] });
|
|
1669
1712
|
```
|
|
1670
1713
|
|
|
1671
|
-
|
|
1714
|
+
Special `select` values:
|
|
1715
|
+
- `$id` — returns the primary key regardless of its name
|
|
1716
|
+
- `$updatedtime` — returns the last-updated timestamp
|
|
1717
|
+
- `$distance` — returns the computed distance when querying a vector index
|
|
1718
|
+
|
|
1719
|
+
7. **Use `addTo`** for concurrent-safe numeric increments (CRDT incrementation, safe across threads and nodes).
|
|
1672
1720
|
|
|
1673
1721
|
```javascript
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1722
|
+
const record = await Product.update(target.id);
|
|
1723
|
+
record.addTo('quantity', -1);
|
|
1724
|
+
```
|
|
1725
|
+
|
|
1726
|
+
8. **Apply `sort`, `limit`, and `offset`** for ordering and pagination. A `sort` attribute must be `@indexed`, or at least one `conditions` entry must be present. Pass `allowFullScan: true` to permit an unconditional ordered scan.
|
|
1727
|
+
|
|
1728
|
+
```javascript
|
|
1729
|
+
Product.search({
|
|
1730
|
+
conditions: [{ attribute: 'id', comparator: 'greater_than', value: '' }],
|
|
1731
|
+
sort: { attribute: 'id' },
|
|
1732
|
+
});
|
|
1678
1733
|
```
|
|
1679
1734
|
|
|
1680
|
-
|
|
1735
|
+
| Sort property | Description |
|
|
1736
|
+
| ------------- | ---------------------------------------------------------- |
|
|
1737
|
+
| `attribute` | Property name (or array for chained relationship property) |
|
|
1738
|
+
| `descending` | Sort descending if `true` (default: `false`) |
|
|
1739
|
+
| `next` | Secondary sort to resolve ties (same structure) |
|
|
1681
1740
|
|
|
1682
1741
|
#### Examples
|
|
1683
1742
|
|
|
1684
|
-
|
|
1743
|
+
**Nested conditions with `or`:**
|
|
1685
1744
|
|
|
1686
1745
|
```javascript
|
|
1687
1746
|
Product.search({
|
|
@@ -1698,23 +1757,25 @@ Product.search({
|
|
|
1698
1757
|
});
|
|
1699
1758
|
```
|
|
1700
1759
|
|
|
1701
|
-
|
|
1760
|
+
**Chained attribute reference (relationship join):**
|
|
1702
1761
|
|
|
1703
1762
|
```javascript
|
|
1704
1763
|
Product.search({ conditions: [{ attribute: ['brand', 'name'], value: 'Harper' }] });
|
|
1705
1764
|
```
|
|
1706
1765
|
|
|
1707
|
-
|
|
1766
|
+
**Deep nested `select` across multiple relationships:**
|
|
1708
1767
|
|
|
1709
1768
|
```javascript
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
]
|
|
1769
|
+
Product.search({
|
|
1770
|
+
select: [
|
|
1771
|
+
'id',
|
|
1772
|
+
'name',
|
|
1773
|
+
{ name: 'segments', select: ['id', 'name', { name: 'client', select: ['id', 'name'] }] },
|
|
1774
|
+
],
|
|
1775
|
+
});
|
|
1715
1776
|
```
|
|
1716
1777
|
|
|
1717
|
-
|
|
1778
|
+
**SSR usage — read directly from `tables` in a render function:**
|
|
1718
1779
|
|
|
1719
1780
|
```typescript
|
|
1720
1781
|
import { tables } from 'harper';
|
|
@@ -1727,47 +1788,12 @@ export async function render(url: string): Promise<string> {
|
|
|
1727
1788
|
|
|
1728
1789
|
#### Notes
|
|
1729
1790
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
| `greater_than_equal` | Greater than or equal |
|
|
1737
|
-
| `less_than` | Strictly less |
|
|
1738
|
-
| `less_than_equal` | Less than or equal |
|
|
1739
|
-
| `starts_with` | String prefix match |
|
|
1740
|
-
| `contains` | String contains |
|
|
1741
|
-
| `ends_with` | String suffix match |
|
|
1742
|
-
| `between` | Range match |
|
|
1743
|
-
| `not_equal` | Inequality match |
|
|
1744
|
-
|
|
1745
|
-
##### Query object options
|
|
1746
|
-
|
|
1747
|
-
| Property | Description |
|
|
1748
|
-
| ----------------------- | ----------------------------------------------------------------------- |
|
|
1749
|
-
| `conditions` | Array of condition objects to filter records |
|
|
1750
|
-
| `operator` | Top-level `and` (default) or `or` for the `conditions` array |
|
|
1751
|
-
| `limit` | Maximum number of records to return |
|
|
1752
|
-
| `offset` | Number of records to skip (for pagination) |
|
|
1753
|
-
| `select` | Properties to include in each returned record |
|
|
1754
|
-
| `sort` | Sort order object with `attribute`, `descending`, and `next` properties |
|
|
1755
|
-
| `explain` | If `true`, returns conditions reordered as Harper will execute them |
|
|
1756
|
-
| `enforceExecutionOrder` | If `true`, forces conditions to execute in the order supplied |
|
|
1757
|
-
|
|
1758
|
-
##### `select` special properties
|
|
1759
|
-
|
|
1760
|
-
- `$id` — Returns the primary key regardless of its name
|
|
1761
|
-
- `$updatedtime` — Returns the last-updated timestamp
|
|
1762
|
-
- `$distance` — Returns the computed distance from the target vector when the query uses a vector index
|
|
1763
|
-
|
|
1764
|
-
##### Relationship join behavior
|
|
1765
|
-
|
|
1766
|
-
- Selecting a relationship **without** filtering on it behaves as a **LEFT JOIN** — records with no related row are still returned.
|
|
1767
|
-
- Adding a `conditions` entry on a related attribute (e.g. `attribute: ['author', 'name']`) behaves as an **INNER JOIN** — only records with a matching related row are returned.
|
|
1768
|
-
|
|
1769
|
-
- Keep `harper` external when bundling for SSR (e.g. `ssr: { external: ['harper'] }` in `vite.config`) so it resolves to the runtime instead of being bundled.
|
|
1770
|
-
- `tables`, `databases`, and other Harper APIs are the same live, process-wide objects regardless of whether accessed as globals or via `import { tables } from 'harper'`.
|
|
1791
|
+
- Scope destructive operations (`update`, `patch`, `delete`) with specific `conditions` and validate the affected set before writing. These operate on live data and are not easily reversible.
|
|
1792
|
+
- Sorting by a bare `@primaryKey` with no conditions raises `HdbError: <attribute> is not indexed and not combined with any other conditions`. Add an open-ended condition or pass `allowFullScan: true`.
|
|
1793
|
+
- Selecting a relationship field without filtering on it behaves as a **LEFT JOIN**. Adding a condition on a related attribute (e.g. `attribute: ['author', 'name']`) behaves as an **INNER JOIN**.
|
|
1794
|
+
- A to-many relationship resolves to an array; `await` the property before iterating when needed.
|
|
1795
|
+
- `tables` and `databases.data` reference the same live objects — a record written through one component is immediately visible to all others.
|
|
1796
|
+
- Keep `harper` external when bundling for SSR (e.g. `ssr: { external: ['harper'] }` in `vite.config`) so it resolves to the runtime.
|
|
1771
1797
|
|
|
1772
1798
|
### 3.4 TypeScript Type Stripping in Harper
|
|
1773
1799
|
|
|
@@ -1986,22 +2012,26 @@ await fetch('http://localhost:9926/JokeCache/1', {
|
|
|
1986
2012
|
|
|
1987
2013
|
### 4.1 Deploying to Harper Fabric
|
|
1988
2014
|
|
|
1989
|
-
Instructions for the agent to follow when deploying a Harper application to
|
|
2015
|
+
Instructions for the agent to follow when deploying a Harper application to a remote Harper Fabric cloud instance.
|
|
1990
2016
|
|
|
1991
2017
|
#### When to Use
|
|
1992
2018
|
|
|
1993
|
-
Apply this rule when deploying a Harper application to a remote Harper
|
|
2019
|
+
Apply this rule when deploying a Harper application to a remote Harper Fabric cluster or any remote Harper instance. Use it when setting up CI/CD pipelines that push application packages to a target environment, or when deploying from a local directory or external package source to a remote cluster.
|
|
1994
2020
|
|
|
1995
2021
|
#### How It Works
|
|
1996
2022
|
|
|
1997
|
-
1. **
|
|
2023
|
+
1. **Obtain the target URL**: Get the cluster's **Application URL** from the cluster's **Config → Overview** page. This is the hostname passed to all CLI commands as `target`.
|
|
2024
|
+
|
|
2025
|
+
2. **Authenticate with `harper login`**: Run `harper login` once to store an authentication token locally. The CLI also writes `HARPER_CLI_TARGET` to a local `.env` for subsequent commands.
|
|
1998
2026
|
|
|
1999
2027
|
```bash
|
|
2000
2028
|
harper login <Application URL>
|
|
2001
2029
|
# Provide cluster username and password when prompted
|
|
2002
2030
|
```
|
|
2003
2031
|
|
|
2004
|
-
|
|
2032
|
+
See [creating-a-fabric-account-and-cluster.md](creating-a-fabric-account-and-cluster.md) for setting up a cluster before this step.
|
|
2033
|
+
|
|
2034
|
+
3. **Deploy with `harper deploy`**: After logging in, deploy without repeating credentials.
|
|
2005
2035
|
|
|
2006
2036
|
```bash
|
|
2007
2037
|
harper deploy \
|
|
@@ -2012,86 +2042,96 @@ Apply this rule when deploying a Harper application to a remote Harper instance
|
|
|
2012
2042
|
replicated=true
|
|
2013
2043
|
```
|
|
2014
2044
|
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
| Value | Effect |
|
|
2018
|
-
| ---------------------------------------------------- | ------------------------------------------------ |
|
|
2019
|
-
| _(omitted)_ | Packages and deploys the current local directory |
|
|
2020
|
-
| `"@harperdb/status-check"` | npm package |
|
|
2021
|
-
| `"HarperDB/status-check"` | GitHub repo (short form) |
|
|
2022
|
-
| `"https://github.com/HarperDB/status-check"` | GitHub repo (full URL) |
|
|
2023
|
-
| `"git+ssh://git@github.com:HarperDB/secret-app.git"` | Private repo via SSH |
|
|
2024
|
-
| `"https://example.com/application.tar.gz"` | Remote tarball |
|
|
2045
|
+
4. **Use environment variables for CI/CD**: Instead of `harper login`, export credentials as environment variables before running `harper deploy`.
|
|
2025
2046
|
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2047
|
+
```bash
|
|
2048
|
+
export HARPER_CLI_USERNAME=<username>
|
|
2049
|
+
export HARPER_CLI_PASSWORD=<password>
|
|
2050
|
+
harper deploy \
|
|
2051
|
+
project=<name> \
|
|
2052
|
+
package=<package> \
|
|
2053
|
+
target=<remote> \
|
|
2054
|
+
restart=true \
|
|
2055
|
+
replicated=true
|
|
2030
2056
|
```
|
|
2031
2057
|
|
|
2032
|
-
|
|
2058
|
+
5. **Use inline auth parameters for one-off commands**: Pass `auth_username` and `auth_password` directly. These take precedence over environment variables and saved login tokens. Not recommended for production.
|
|
2033
2059
|
|
|
2034
2060
|
```bash
|
|
2035
|
-
export HARPER_CLI_USERNAME=<username>
|
|
2036
|
-
export HARPER_CLI_PASSWORD=<password>
|
|
2037
2061
|
harper deploy \
|
|
2038
2062
|
project=<name> \
|
|
2039
2063
|
package=<package> \
|
|
2064
|
+
auth_username=<username> \
|
|
2065
|
+
auth_password=<password> \
|
|
2040
2066
|
target=<remote> \
|
|
2041
2067
|
restart=true \
|
|
2042
2068
|
replicated=true
|
|
2043
2069
|
```
|
|
2044
2070
|
|
|
2045
|
-
|
|
2071
|
+
6. **Choose a package source**: Set the `package` parameter to any valid npm dependency value, or omit it to package and deploy the current local directory.
|
|
2072
|
+
|
|
2073
|
+
| Value | Meaning |
|
|
2074
|
+
| ---------------------------------------------------- | ---------------------------------------------- |
|
|
2075
|
+
| _(omitted)_ | Package and deploy the current local directory |
|
|
2076
|
+
| `"@harperdb/status-check"` | npm package |
|
|
2077
|
+
| `"HarperDB/status-check"` | GitHub shorthand |
|
|
2078
|
+
| `"https://github.com/HarperDB/status-check"` | GitHub full URL |
|
|
2079
|
+
| `"git+ssh://git@github.com:HarperDB/secret-app.git"` | Private repo via SSH |
|
|
2080
|
+
| `"https://example.com/application.tar.gz"` | Remote tarball |
|
|
2081
|
+
|
|
2082
|
+
For pinned git tags, use the `semver` directive:
|
|
2083
|
+
|
|
2084
|
+
```
|
|
2085
|
+
HarperDB/application-template#semver:v1.0.0
|
|
2086
|
+
```
|
|
2087
|
+
|
|
2088
|
+
For SSH-based private repos, register keys with the `Add SSH Key` operation before deploying.
|
|
2046
2089
|
|
|
2047
2090
|
#### Examples
|
|
2048
2091
|
|
|
2049
|
-
**Interactive login then deploy
|
|
2092
|
+
**Interactive login then deploy:**
|
|
2050
2093
|
|
|
2051
2094
|
```bash
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
# Provide your username and password when prompted
|
|
2095
|
+
harper login https://my-cluster.harperdbcloud.com
|
|
2096
|
+
# Enter username and password when prompted
|
|
2055
2097
|
|
|
2056
|
-
# Subsequently deploy without credentials
|
|
2057
2098
|
harper deploy \
|
|
2058
|
-
project
|
|
2059
|
-
package
|
|
2060
|
-
target
|
|
2099
|
+
project=my-app \
|
|
2100
|
+
package="HarperDB/application-template#semver:v1.0.0" \
|
|
2101
|
+
target=https://my-cluster.harperdbcloud.com \
|
|
2061
2102
|
restart=true \
|
|
2062
2103
|
replicated=true
|
|
2063
2104
|
```
|
|
2064
2105
|
|
|
2065
|
-
**
|
|
2106
|
+
**CI/CD pipeline deploy using environment variables:**
|
|
2066
2107
|
|
|
2067
2108
|
```bash
|
|
2109
|
+
export HARPER_CLI_USERNAME=admin
|
|
2110
|
+
export HARPER_CLI_PASSWORD=secret
|
|
2068
2111
|
harper deploy \
|
|
2069
|
-
project
|
|
2070
|
-
package
|
|
2071
|
-
|
|
2072
|
-
password=<password> \
|
|
2073
|
-
target=<remote> \
|
|
2112
|
+
project=my-app \
|
|
2113
|
+
package="@harperdb/status-check" \
|
|
2114
|
+
target=https://my-cluster.harperdbcloud.com \
|
|
2074
2115
|
restart=true \
|
|
2075
2116
|
replicated=true
|
|
2076
2117
|
```
|
|
2077
2118
|
|
|
2078
|
-
**Deploy
|
|
2119
|
+
**Deploy current local directory:**
|
|
2079
2120
|
|
|
2080
2121
|
```bash
|
|
2081
2122
|
harper deploy \
|
|
2082
2123
|
project=my-app \
|
|
2083
|
-
|
|
2084
|
-
target=<remote> \
|
|
2124
|
+
target=https://my-cluster.harperdbcloud.com \
|
|
2085
2125
|
restart=true \
|
|
2086
2126
|
replicated=true
|
|
2087
2127
|
```
|
|
2088
2128
|
|
|
2089
2129
|
#### Notes
|
|
2090
2130
|
|
|
2091
|
-
-
|
|
2092
|
-
-
|
|
2093
|
-
- Harper generates a `package.json` from component configurations and resolves
|
|
2094
|
-
- For SSH-based private repos, register
|
|
2131
|
+
- Authentication precedence (highest to lowest): inline `auth_username`/`auth_password` parameters → environment variables (`HARPER_CLI_USERNAME`/`HARPER_CLI_PASSWORD`) → saved login token from `harper login`.
|
|
2132
|
+
- `harper login` writes `HARPER_CLI_TARGET` to a local `.env`, so subsequent commands do not need `target` repeated if that file is present.
|
|
2133
|
+
- Harper generates a `package.json` from component configurations and resolves packages via `npm install`. A local file path creates a symlink, so changes are picked up between restarts without redeploying.
|
|
2134
|
+
- For SSH-based private repos, register the SSH key with the `Add SSH Key` operation before running `harper deploy`.
|
|
2095
2135
|
|
|
2096
2136
|
### 4.2 Creating a Harper Fabric Account and Cluster
|
|
2097
2137
|
|
|
@@ -2434,18 +2474,18 @@ Instructions for the agent to follow when loading environment variables from `.e
|
|
|
2434
2474
|
|
|
2435
2475
|
#### When to Use
|
|
2436
2476
|
|
|
2437
|
-
Apply this rule when a Harper application needs to
|
|
2477
|
+
Apply this rule when a Harper application needs to supply secrets, API endpoints, or other configuration values to component code via `process.env` without hardcoding them. Use `loadEnv` any time you need to load one or more `.env` files at application startup.
|
|
2438
2478
|
|
|
2439
2479
|
#### How It Works
|
|
2440
2480
|
|
|
2441
|
-
1. **Declare `loadEnv` in `config.yaml`**: Add `loadEnv` as
|
|
2481
|
+
1. **Declare `loadEnv` in `config.yaml`**: Add `loadEnv` as the first entry in `config.yaml`. It is built into Harper and requires no installation.
|
|
2442
2482
|
|
|
2443
2483
|
```yaml
|
|
2444
2484
|
loadEnv:
|
|
2445
2485
|
files: '.env'
|
|
2446
2486
|
```
|
|
2447
2487
|
|
|
2448
|
-
2. **Place `loadEnv` first**:
|
|
2488
|
+
2. **Place `loadEnv` first**: Harper is a single-process application. List `loadEnv` before all other components so that environment variables are available on `process.env` before dependent components start.
|
|
2449
2489
|
|
|
2450
2490
|
```yaml
|
|
2451
2491
|
# config.yaml — loadEnv must come first
|
|
@@ -2458,9 +2498,9 @@ Apply this rule when a Harper application needs to load secrets or configuration
|
|
|
2458
2498
|
files: './src/*.js'
|
|
2459
2499
|
```
|
|
2460
2500
|
|
|
2461
|
-
3. **
|
|
2501
|
+
3. **Access loaded values in component code**: After `loadEnv` runs, all loaded values are available on `process.env` and shared across all components.
|
|
2462
2502
|
|
|
2463
|
-
4. **
|
|
2503
|
+
4. **Control override behavior**: By default, existing environment variables take precedence over values in `.env` files. Set `override: true` to make loaded values win instead.
|
|
2464
2504
|
|
|
2465
2505
|
```yaml
|
|
2466
2506
|
loadEnv:
|
|
@@ -2468,14 +2508,17 @@ Apply this rule when a Harper application needs to load secrets or configuration
|
|
|
2468
2508
|
override: true
|
|
2469
2509
|
```
|
|
2470
2510
|
|
|
2471
|
-
5. **Load multiple files
|
|
2511
|
+
5. **Load multiple files**: Pass an array of paths or a glob pattern to `files`. Files are loaded in the order specified.
|
|
2512
|
+
|
|
2472
2513
|
```yaml
|
|
2473
2514
|
loadEnv:
|
|
2474
2515
|
files:
|
|
2475
2516
|
- '.env'
|
|
2476
2517
|
- '.env.local'
|
|
2477
2518
|
```
|
|
2478
|
-
|
|
2519
|
+
|
|
2520
|
+
or with a glob:
|
|
2521
|
+
|
|
2479
2522
|
```yaml
|
|
2480
2523
|
loadEnv:
|
|
2481
2524
|
files: 'env-vars/*'
|
|
@@ -2490,17 +2533,23 @@ Apply this rule when a Harper application needs to load secrets or configuration
|
|
|
2490
2533
|
|
|
2491
2534
|
#### Examples
|
|
2492
2535
|
|
|
2493
|
-
**
|
|
2536
|
+
**Single file, default behavior:**
|
|
2494
2537
|
|
|
2495
2538
|
```yaml
|
|
2539
|
+
# config.yaml
|
|
2496
2540
|
loadEnv:
|
|
2497
2541
|
files: '.env'
|
|
2542
|
+
|
|
2543
|
+
rest: true
|
|
2544
|
+
|
|
2545
|
+
myApp:
|
|
2546
|
+
files: './src/*.js'
|
|
2498
2547
|
```
|
|
2499
2548
|
|
|
2500
|
-
**
|
|
2549
|
+
**Multiple files with override:**
|
|
2501
2550
|
|
|
2502
2551
|
```yaml
|
|
2503
|
-
# config.yaml
|
|
2552
|
+
# config.yaml
|
|
2504
2553
|
loadEnv:
|
|
2505
2554
|
files:
|
|
2506
2555
|
- '.env'
|
|
@@ -2513,114 +2562,142 @@ myApp:
|
|
|
2513
2562
|
files: './src/*.js'
|
|
2514
2563
|
```
|
|
2515
2564
|
|
|
2516
|
-
**Glob pattern:**
|
|
2517
|
-
|
|
2518
|
-
```yaml
|
|
2519
|
-
loadEnv:
|
|
2520
|
-
files: 'env-vars/*'
|
|
2521
|
-
```
|
|
2522
|
-
|
|
2523
2565
|
#### Notes
|
|
2524
2566
|
|
|
2525
|
-
- `loadEnv`
|
|
2526
|
-
-
|
|
2527
|
-
-
|
|
2528
|
-
- `files` is the only required option; omitting it will produce an invalid configuration.
|
|
2567
|
+
- `loadEnv` loads values into `process.env` for **application** code only — it does not configure Harper itself.
|
|
2568
|
+
- Harper's own instance-wide configuration is composed at startup **before** any component's `loadEnv` runs. Variables such as `HARPER_CONFIG`, `HARPER_SET_CONFIG`, and `HARPER_DEFAULT_CONFIG` delivered through a `.env` file are read too late and are ignored. Set Harper configuration directly in the configuration file or export variables in the real process/container environment before Harper starts.
|
|
2569
|
+
- For production credentials, prefer the encrypted secrets store over a committed `.env` file. Secrets are also delivered to components via `process.env`.
|
|
2529
2570
|
|
|
2530
|
-
### 4.7
|
|
2571
|
+
### 4.7 v5 Upgrade: Breaking Changes and Migration Guide
|
|
2531
2572
|
|
|
2532
|
-
Instructions for the agent to
|
|
2573
|
+
Instructions for the agent to apply when migrating a Harper application to v5, covering all breaking changes and required code updates.
|
|
2533
2574
|
|
|
2534
2575
|
#### When to Use
|
|
2535
2576
|
|
|
2536
|
-
Apply this rule when upgrading
|
|
2577
|
+
Apply this rule when upgrading an existing Harper application to v5, when encountering runtime errors related to renamed packages, changed APIs, or security restrictions introduced in v5, or when reviewing application code for v5 compatibility before deployment.
|
|
2537
2578
|
|
|
2538
2579
|
#### How It Works
|
|
2539
2580
|
|
|
2540
|
-
1. **
|
|
2581
|
+
1. **Update the package import from `harperdb` to `harper`**: All application code must import from `harper` instead of `harperdb`.
|
|
2541
2582
|
|
|
2542
2583
|
```javascript
|
|
2543
2584
|
import { tables } from 'harper';
|
|
2544
2585
|
```
|
|
2545
2586
|
|
|
2546
|
-
2. **
|
|
2587
|
+
2. **Enable `allowInstallScripts` if packages require install scripts**: Harper v5 uses `--ignore-scripts` by default when installing packages. If your application requires installation scripts (e.g., to install additional binaries), set the `allowInstallScripts` option when deploying.
|
|
2547
2588
|
|
|
2548
|
-
3. **Update `Table.get` return
|
|
2589
|
+
3. **Update `Table.get` usage — return value is now a frozen record object**: `Table.get` now returns a plain record object, not a table class instance. The record is frozen; you cannot mutate it directly.
|
|
2590
|
+
- Replace `wasLoadedFromSource()` with `target.loadedFromSource`:
|
|
2549
2591
|
|
|
2550
|
-
|
|
2592
|
+
```javascript
|
|
2593
|
+
// Old — remove this pattern:
|
|
2594
|
+
const record = await Table.get(id);
|
|
2595
|
+
if (record.wasLoadedFromSource()) { ... }
|
|
2551
2596
|
|
|
2552
|
-
|
|
2597
|
+
// New — use loadedFromSource on the target:
|
|
2598
|
+
const target = new RequestTarget();
|
|
2599
|
+
target.id = id;
|
|
2600
|
+
const record = await Table.get(target);
|
|
2601
|
+
if (target.loadedFromSource) {
|
|
2602
|
+
// record was loaded from origin (not cache)
|
|
2603
|
+
}
|
|
2604
|
+
```
|
|
2553
2605
|
|
|
2554
|
-
|
|
2606
|
+
- Replace in-place mutation with object spread, since records are frozen:
|
|
2555
2607
|
|
|
2556
|
-
|
|
2608
|
+
```javascript
|
|
2609
|
+
// Old — throws in v5:
|
|
2610
|
+
const record = await Table.get(id);
|
|
2611
|
+
record.property = 'changed';
|
|
2557
2612
|
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
- `allowedBuiltinModules` — an optional allowlist restricting which Node.js built-ins the application may import (all are allowed if omitted).
|
|
2563
|
-
- `dependencyLoader` — `auto` (default), `app`, or `native`, controlling how npm dependencies are loaded through or around the VM loader.
|
|
2613
|
+
// New — copy instead of mutate:
|
|
2614
|
+
let record = await Table.get(id);
|
|
2615
|
+
record = { ...record, property: 'changed' };
|
|
2616
|
+
```
|
|
2564
2617
|
|
|
2565
|
-
|
|
2618
|
+
- `getUpdatedTime` and `getExpiresAt` methods remain available on the record object.
|
|
2566
2619
|
|
|
2567
|
-
**
|
|
2620
|
+
4. **Update transaction and context handling**: Harper v5 uses asynchronous context tracking. `Table.get` and other table calls now automatically inherit the current transaction context. Code that previously omitted context to bypass a transaction will no longer work as expected. Use `getContext` (imported from `harper`) to access and commit the current transaction explicitly when you need to see updated data.
|
|
2568
2621
|
|
|
2569
|
-
```javascript
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
if (record.wasLoadedFromSource()) {
|
|
2573
|
-
// loaded from origin, not cache
|
|
2574
|
-
}
|
|
2622
|
+
```javascript
|
|
2623
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
2624
|
+
import { getContext, transaction } from 'harper';
|
|
2575
2625
|
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2626
|
+
class MyResource {
|
|
2627
|
+
static async get(target) {
|
|
2628
|
+
// Commit the current transaction to read latest data:
|
|
2629
|
+
await getContext().transaction.commit();
|
|
2630
|
+
// Optionally wrap each poll in a new transaction:
|
|
2631
|
+
while ((await transaction(() => Table.get(target))).status !== 'ready') {
|
|
2632
|
+
await delay(100);
|
|
2633
|
+
}
|
|
2634
|
+
return Table.get(target);
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
```
|
|
2584
2638
|
|
|
2585
|
-
**
|
|
2639
|
+
5. **Register allowed spawn commands via `allowedSpawnCommands`**: Any use of `spawn`, `exec`, or `execFile` from `node:child_process` must reference executables listed in `applications.allowedSpawnCommands` in `harperdb-config.yaml`. Provide a `name` property in the options argument to ensure only a single named process is started across threads.
|
|
2586
2640
|
|
|
2587
|
-
|
|
2588
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
2589
|
-
import { getContext, transaction } from 'harper';
|
|
2641
|
+
6. **Replace `blob.save()` with `saveBeforeCommit`**: The `blob.save()` method has been removed. Use the `saveBeforeCommit` flag in the options passed to the `Blob` constructor instead.
|
|
2590
2642
|
|
|
2591
|
-
|
|
2592
|
-
static async get(target) {
|
|
2593
|
-
// The current transaction is a consistent snapshot; commit it to see updates.
|
|
2594
|
-
await getContext().transaction.commit();
|
|
2595
|
-
while ((await transaction(() => Table.get(target))).status !== 'ready') {
|
|
2596
|
-
delay(100);
|
|
2597
|
-
}
|
|
2598
|
-
return Table.get(target);
|
|
2599
|
-
}
|
|
2600
|
-
}
|
|
2601
|
-
```
|
|
2643
|
+
7. **Configure the `moduleLoader` and `lockdown` settings**: Harper v5 loads application modules through Node.js's VM module API. Control this behavior in `harperdb-config.yaml` under the `applications` key.
|
|
2602
2644
|
|
|
2603
|
-
|
|
2645
|
+
| Setting | Default | Options |
|
|
2646
|
+
| ------------------ | -------------------- | --------------------------------------------------- |
|
|
2647
|
+
| `moduleLoader` | `vm-current-context` | `vm-current-context`, `vm`, `native`, `compartment` |
|
|
2648
|
+
| `lockdown` | `freeze-after-load` | `freeze-after-load`, `freeze`, `ses`, `none` |
|
|
2649
|
+
| `dependencyLoader` | `auto` | `auto`, `app`, `native` |
|
|
2650
|
+
| `allowedDirectory` | `app` | `app`, `any` |
|
|
2651
|
+
- Use `moduleLoader: native` to disable the VM loader entirely and restore pre-v5 behavior (application-specific context such as tagged logging and per-app `config` will not be available).
|
|
2652
|
+
- Use `lockdown: none` as a temporary workaround if a dependency modifies intrinsic prototypes at runtime and throws a `TypeError`.
|
|
2653
|
+
|
|
2654
|
+
#### Examples
|
|
2655
|
+
|
|
2656
|
+
##### Full `harperdb-config.yaml` `applications` block
|
|
2604
2657
|
|
|
2605
2658
|
```yaml
|
|
2606
2659
|
applications:
|
|
2607
|
-
lockdown: freeze-after-load
|
|
2608
|
-
moduleLoader: vm-current-context
|
|
2609
|
-
dependencyLoader: auto
|
|
2610
|
-
allowedDirectory: app
|
|
2660
|
+
lockdown: freeze-after-load
|
|
2661
|
+
moduleLoader: vm-current-context
|
|
2662
|
+
dependencyLoader: auto
|
|
2663
|
+
allowedDirectory: app
|
|
2611
2664
|
allowedSpawnCommands:
|
|
2612
2665
|
- npm
|
|
2613
2666
|
- node
|
|
2614
|
-
# allowedBuiltinModules: [] # if omitted, all Node.js built-ins are allowed
|
|
2615
2667
|
```
|
|
2616
2668
|
|
|
2617
|
-
|
|
2669
|
+
##### Restricting allowed built-in modules
|
|
2618
2670
|
|
|
2619
|
-
|
|
2671
|
+
```yaml
|
|
2672
|
+
applications:
|
|
2673
|
+
allowedBuiltinModules:
|
|
2674
|
+
- fs
|
|
2675
|
+
- path
|
|
2676
|
+
- http
|
|
2677
|
+
```
|
|
2678
|
+
|
|
2679
|
+
##### Disabling the VM loader for package compatibility
|
|
2680
|
+
|
|
2681
|
+
```yaml
|
|
2682
|
+
applications:
|
|
2683
|
+
moduleLoader: native
|
|
2684
|
+
```
|
|
2685
|
+
|
|
2686
|
+
##### Accessing context and committing a transaction
|
|
2687
|
+
|
|
2688
|
+
```javascript
|
|
2689
|
+
import { getContext } from 'harper';
|
|
2690
|
+
|
|
2691
|
+
await getContext().transaction.commit();
|
|
2692
|
+
```
|
|
2693
|
+
|
|
2694
|
+
#### Notes
|
|
2620
2695
|
|
|
2621
|
-
-
|
|
2622
|
-
-
|
|
2623
|
-
-
|
|
2696
|
+
- The `lockdown: freeze-after-load` default freezes `Object`, `Array`, `Promise`, `Map`, `Set`, and other intrinsics after all application code loads. Any code or dependency that mutates intrinsic prototypes after startup will throw a `TypeError`.
|
|
2697
|
+
- Under `lockdown: ses`, the constrained `fetch` (https-only) is applied only in `vm` mode. In `vm-current-context` and `native` modes, application code uses the standard global `fetch`.
|
|
2698
|
+
- In production, `allowedDirectory: app` prevents loading modules from outside the application's own directory tree. Set `allowedDirectory: any` only if your application legitimately requires it.
|
|
2699
|
+
- `dependencyLoader: native` is a narrower alternative to `moduleLoader: native` — it uses native loading only for npm packages while keeping the VM loader for first-party application source files.
|
|
2700
|
+
- Access Harper functions and APIs through the `harper` package rather than through global variables. Use `getContext` from `harper` to access request context without passing it explicitly through every call.
|
|
2624
2701
|
|
|
2625
2702
|
### 4.8 Delegating to the Built-in Agent
|
|
2626
2703
|
|