@harperfast/template-react-ts-studio 1.12.3 → 1.12.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 +362 -322
- package/.agents/skills/harper-best-practices/rules/adding-tables-with-schemas.md +7 -7
- package/.agents/skills/harper-best-practices/rules/checking-authentication.md +253 -209
- package/.agents/skills/harper-best-practices/rules/custom-resources.md +28 -45
- package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +103 -89
- package/.agents/skills/harper-best-practices/rules.manifest.yaml +62 -3
- package/.agents/skills/harper-mcp/AGENTS.md +77 -31
- package/.agents/skills/harper-mcp/rules/custom-mcp-tools.md +79 -33
- package/.agents/skills/harper-mcp/rules.manifest.yaml +1 -1
- package/agent/skills/harper-best-practices/AGENTS.md +362 -322
- package/agent/skills/harper-best-practices/SKILL.md +9 -3
- package/agent/skills/harper-best-practices/rules/adding-tables-with-schemas.md +7 -7
- package/agent/skills/harper-best-practices/rules/checking-authentication.md +253 -209
- package/agent/skills/harper-best-practices/rules/custom-resources.md +28 -45
- package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +103 -89
- package/agent/skills/harper-best-practices/rules.manifest.yaml +62 -3
- package/agent/skills/harper-mcp/AGENTS.md +77 -31
- package/agent/skills/harper-mcp/SKILL.md +10 -3
- package/agent/skills/harper-mcp/rules/custom-mcp-tools.md +79 -33
- package/agent/skills/harper-mcp/rules.manifest.yaml +1 -1
- package/package.json +1 -1
- package/skills-lock.json +2 -2
|
@@ -6,9 +6,9 @@ metadata:
|
|
|
6
6
|
sources:
|
|
7
7
|
- reference/v5/resources/overview.md#Custom External Data Source
|
|
8
8
|
- reference/v5/resources/overview.md#Exporting Resources as Endpoints
|
|
9
|
-
- reference/v5/components/javascript-environment.md#Module
|
|
10
|
-
sourceCommit:
|
|
11
|
-
inputHash:
|
|
9
|
+
- reference/v5/components/javascript-environment.md#Module Formats
|
|
10
|
+
sourceCommit: 9e6ecf87dd25bb488ad44dc2876ca6439ccd682e
|
|
11
|
+
inputHash: 86858206e33925f0
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# Custom Resources
|
|
@@ -17,11 +17,11 @@ Instructions for the agent to follow when defining custom REST endpoints with Ja
|
|
|
17
17
|
|
|
18
18
|
## When to Use
|
|
19
19
|
|
|
20
|
-
Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or registering routes programmatically in a Harper application. Use it any time business logic must live outside a table-backed
|
|
20
|
+
Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or registering routes programmatically in a Harper application. Use it any time business logic must live outside a table-backed resource or when a specific URL shape is required.
|
|
21
21
|
|
|
22
22
|
## How It Works
|
|
23
23
|
|
|
24
|
-
1. **Import `Resource` from `harper
|
|
24
|
+
1. **Import `Resource` from the `harper` package**: Always import explicitly rather than relying on globals.
|
|
25
25
|
|
|
26
26
|
```javascript
|
|
27
27
|
import { tables, Resource } from 'harper';
|
|
@@ -39,7 +39,7 @@ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or
|
|
|
39
39
|
}
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
3. **Use `async` static methods
|
|
42
|
+
3. **Use `async` static methods to call external services**: Return or forward the response directly.
|
|
43
43
|
|
|
44
44
|
```javascript
|
|
45
45
|
export class MyExternalData extends Resource {
|
|
@@ -57,7 +57,7 @@ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or
|
|
|
57
57
|
}
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
4. **Export the class to
|
|
60
|
+
4. **Export the class to expose it as an endpoint**: The export form controls the resulting URL. Choose the form that matches the URL shape you need.
|
|
61
61
|
|
|
62
62
|
| Export form | URL | Notes |
|
|
63
63
|
| ------------------------------------------- | --------------- | --------------------------------------------------------------- |
|
|
@@ -68,12 +68,17 @@ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or
|
|
|
68
68
|
| `static path = '/widget/:id'` (class field) | `/widget/:id` | Declare path on the class; overrides the export name. |
|
|
69
69
|
| `server.resources.set('my-path', Foo);` | `/my-path/` | Programmatic registration for dynamic paths. |
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
5. **Register programmatically when the path is dynamic**: Use `server.resources.set(` with a path string and the resource class.
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
```javascript
|
|
74
|
+
server.resources.set('my-path', Foo);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
6. **Declare dynamic path segments with `static path`**: Use `:name` for a single segment and `*name` as a catch-all. Matched values are bound onto `target.<name>`.
|
|
74
78
|
|
|
75
79
|
```javascript
|
|
76
80
|
export class Widget extends Resource {
|
|
81
|
+
// GET /widget/10/action/jump -> target.id === '10', target.action === 'jump'
|
|
77
82
|
static path = '/widget/:id/action/:action';
|
|
78
83
|
static get(target) {
|
|
79
84
|
return { id: target.id, action: target.action };
|
|
@@ -81,22 +86,11 @@ Apply this rule when creating custom HTTP endpoints, wrapping external APIs, or
|
|
|
81
86
|
}
|
|
82
87
|
```
|
|
83
88
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
6. **Register programmatically when the path is dynamic**: Use `server.resources.set(` when the path cannot be known at export time.
|
|
87
|
-
|
|
88
|
-
```javascript
|
|
89
|
-
server.resources.set('my-path', Foo);
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
7. **Optionally source a table from a custom resource**: Use the resource as a caching layer for a local table.
|
|
93
|
-
```javascript
|
|
94
|
-
tables.MyCache.sourcedFrom(MyExternalData);
|
|
95
|
-
```
|
|
89
|
+
7. **Resolve path precedence correctly**: Exact and static paths always win over parameterized ones. Among parameterized routes, more specific paths win: a literal segment beats a `:param`, which beats a `*` wildcard, compared left to right.
|
|
96
90
|
|
|
97
91
|
## Examples
|
|
98
92
|
|
|
99
|
-
|
|
93
|
+
**Wrapping an external API and using it as a cache source:**
|
|
100
94
|
|
|
101
95
|
```javascript
|
|
102
96
|
import { tables, Resource } from 'harper';
|
|
@@ -119,19 +113,9 @@ export class MyExternalData extends Resource {
|
|
|
119
113
|
tables.MyCache.sourcedFrom(MyExternalData);
|
|
120
114
|
```
|
|
121
115
|
|
|
122
|
-
|
|
116
|
+
**Catch-all wildcard path:**
|
|
123
117
|
|
|
124
118
|
```javascript
|
|
125
|
-
import { Resource } from 'harper';
|
|
126
|
-
|
|
127
|
-
export class Widget extends Resource {
|
|
128
|
-
// GET /widget/10/action/jump -> target.id === '10', target.action === 'jump'
|
|
129
|
-
static path = '/widget/:id/action/:action';
|
|
130
|
-
static get(target) {
|
|
131
|
-
return { id: target.id, action: target.action };
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
119
|
export class Files extends Resource {
|
|
136
120
|
// GET /files/a/b/c.txt -> target.rest === 'a/b/c.txt'
|
|
137
121
|
static path = '/files/*rest';
|
|
@@ -141,25 +125,24 @@ export class Files extends Resource {
|
|
|
141
125
|
}
|
|
142
126
|
```
|
|
143
127
|
|
|
144
|
-
|
|
128
|
+
**Root-relative fixed route:**
|
|
145
129
|
|
|
146
130
|
```javascript
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
export class Foo extends Resource {
|
|
131
|
+
export class AcmeChallenge extends Resource {
|
|
132
|
+
static path = '/.well-known/acme-challenge/:token';
|
|
150
133
|
static get(target) {
|
|
151
|
-
return {
|
|
134
|
+
return { token: target.token };
|
|
152
135
|
}
|
|
153
136
|
}
|
|
154
|
-
|
|
155
|
-
server.resources.set('my-path', Foo);
|
|
156
137
|
```
|
|
157
138
|
|
|
158
139
|
## Notes
|
|
159
140
|
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
141
|
+
- URL path matching is case-sensitive — `/Foo/` and `/foo/` are different endpoints.
|
|
142
|
+
- A leading `/` in `static path` makes the path root-relative (top-level), independent of the file's location.
|
|
143
|
+
- A leading `./` or a bare name in `static path` resolves relative to the component directory.
|
|
144
|
+
- A bare `*` (no name) binds under `target.wildcard`. A wildcard must be the final segment of the path.
|
|
145
|
+
- `static path` takes precedence over the export name when both are present.
|
|
146
|
+
- Parameterized routes appear in the generated OpenAPI document as templated paths (e.g. `/widget/{id}/action/{action}`) and in MCP `resources/templates/list` as URI templates.
|
|
147
|
+
- When a resource `extends` an existing table, avoid conflicting exports between the schema and the JavaScript implementation.
|
|
164
148
|
- Link the `harper` package in your component directory to ensure correct typings: `npm link harper`. All installed components have `harper` automatically linked.
|
|
165
|
-
- Harper runs as a single process — `tables`, `databases`, and other APIs are the same live, process-wide objects regardless of which component accesses them.
|
|
@@ -13,30 +13,32 @@ metadata:
|
|
|
13
13
|
- >-
|
|
14
14
|
reference/v5/resources/resource-api.md#`addTo(property: string, value:
|
|
15
15
|
number)`
|
|
16
|
-
- reference/v5/components/javascript-environment.md#Module
|
|
17
|
-
sourceCommit:
|
|
18
|
-
inputHash:
|
|
16
|
+
- reference/v5/components/javascript-environment.md#Module Formats
|
|
17
|
+
sourceCommit: 9e6ecf87dd25bb488ad44dc2876ca6439ccd682e
|
|
18
|
+
inputHash: f5629f73cde74764
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
# Programmatic Table Requests
|
|
22
22
|
|
|
23
|
-
Instructions for the agent to interact with Harper tables programmatically using the `tables` object,
|
|
23
|
+
Instructions for the agent to interact with Harper tables programmatically using the `tables` object, the Query API, and transactions.
|
|
24
24
|
|
|
25
25
|
## When to Use
|
|
26
26
|
|
|
27
|
-
Apply this rule when writing server-side Harper component code that reads from or writes to tables directly —
|
|
27
|
+
Apply this rule when writing server-side Harper component code that reads from or writes to tables directly — for example, in HTTP handlers, background jobs, timers, or SSR render functions. Use it whenever you need to construct queries with `conditions`, `sort`, `select`, `limit`, or `offset`, or when you need explicit transaction control via `transaction()`.
|
|
28
28
|
|
|
29
29
|
## How It Works
|
|
30
30
|
|
|
31
|
-
1. **Import `tables` from `harper`**: Access
|
|
31
|
+
1. **Import `tables` (and other APIs) from `harper`**: Access every table defined in `schema.graphql` as a named property of `tables`. Each property is the table class implementing the Resource API.
|
|
32
32
|
|
|
33
33
|
```javascript
|
|
34
|
-
import { tables } from 'harper';
|
|
34
|
+
import { tables, transaction } from 'harper';
|
|
35
35
|
const { Product } = tables;
|
|
36
|
-
//
|
|
36
|
+
// equivalent to: databases.data.Product
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
For standalone components, run `npm link harper` so imports resolve to the live runtime.
|
|
40
|
+
|
|
41
|
+
2. **Define your schema with `@table`**: Tables must be declared in `schema.graphql`. Mark attributes you intend to sort or filter on with `@indexed`.
|
|
40
42
|
|
|
41
43
|
```graphql
|
|
42
44
|
type Product @table {
|
|
@@ -46,7 +48,7 @@ Apply this rule when writing server-side Harper component code that reads from o
|
|
|
46
48
|
}
|
|
47
49
|
```
|
|
48
50
|
|
|
49
|
-
3. **
|
|
51
|
+
3. **Query records with `search(`**: Pass a Query object to `search(`. Iterate results with `for await`.
|
|
50
52
|
|
|
51
53
|
```javascript
|
|
52
54
|
const query = {
|
|
@@ -57,7 +59,7 @@ Apply this rule when writing server-side Harper component code that reads from o
|
|
|
57
59
|
}
|
|
58
60
|
```
|
|
59
61
|
|
|
60
|
-
4. **Build `conditions
|
|
62
|
+
4. **Build `conditions`**: Each condition object supports these properties:
|
|
61
63
|
|
|
62
64
|
| Property | Description |
|
|
63
65
|
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -67,87 +69,79 @@ Apply this rule when writing server-side Harper component code that reads from o
|
|
|
67
69
|
| `conditions` | Nested conditions array |
|
|
68
70
|
| `operator` | `and` (default) or `or` for the nested `conditions` |
|
|
69
71
|
|
|
70
|
-
5. **
|
|
72
|
+
5. **Control result shape with `select`**: Pass an array of property names, a single string, or nested objects for relationships.
|
|
71
73
|
|
|
72
74
|
```javascript
|
|
73
|
-
//
|
|
75
|
+
// Scalar fields only
|
|
74
76
|
Product.search({ select: ['name', 'price'] });
|
|
75
77
|
|
|
76
|
-
//
|
|
78
|
+
// Partial related record
|
|
77
79
|
Book.get({ id: 42, select: ['id', 'title', { name: 'author', select: ['name'] }] });
|
|
78
80
|
```
|
|
79
81
|
|
|
80
82
|
Special `select` values: `$id`, `$updatedtime`, `$distance`.
|
|
81
83
|
|
|
82
|
-
6. **
|
|
84
|
+
6. **Paginate with `limit` and `offset`**:
|
|
83
85
|
|
|
84
86
|
```javascript
|
|
85
|
-
|
|
86
|
-
Product.search({
|
|
87
|
-
conditions: [{ attribute: 'id', comparator: 'greater_than', value: '' }],
|
|
88
|
-
sort: { attribute: 'id' },
|
|
89
|
-
});
|
|
87
|
+
Product.search({ conditions: [...], limit: 20, offset: 40 });
|
|
90
88
|
```
|
|
91
89
|
|
|
92
|
-
|
|
90
|
+
7. **Sort with `sort`**: The `sort` object accepts `attribute`, `descending` (default `false`), and `next` for tie-breaking. Harper uses an index for sort order — the sort `attribute` must be `@indexed`, **or** at least one entry in `conditions` must be present.
|
|
91
|
+
|
|
92
|
+
Sorting by a non-indexed attribute with zero conditions raises:
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
| ------------ | -------------------------------------------------------- |
|
|
96
|
-
| `attribute` | Property name or array for chained relationship property |
|
|
97
|
-
| `descending` | Sort descending if `true` (default: `false`) |
|
|
98
|
-
| `next` | Secondary sort to resolve ties (same structure) |
|
|
94
|
+
> `HdbError: <attribute> is not indexed and not combined with any other conditions`
|
|
99
95
|
|
|
100
|
-
|
|
96
|
+
Note: `@primaryKey` alone is treated as not indexed for sort purposes. To scan the whole table in primary-key order, add an open-ended condition:
|
|
101
97
|
|
|
102
98
|
```javascript
|
|
103
|
-
Product.search({
|
|
99
|
+
Product.search({
|
|
100
|
+
conditions: [{ attribute: 'id', comparator: 'greater_than', value: '' }],
|
|
101
|
+
sort: { attribute: 'id' },
|
|
102
|
+
});
|
|
104
103
|
```
|
|
105
104
|
|
|
106
|
-
|
|
105
|
+
Alternatively, pass `allowFullScan: true` to permit an unconditional ordered scan, or omit `sort` entirely to iterate without an index requirement.
|
|
106
|
+
|
|
107
|
+
8. **Debug query planning with `explain` and `enforceExecutionOrder`**:
|
|
107
108
|
- `explain: true` — returns conditions reordered as Harper will execute them.
|
|
108
109
|
- `enforceExecutionOrder: true` — forces conditions to execute in the order supplied, disabling automatic re-ordering.
|
|
109
110
|
|
|
110
111
|
9. **Use `addTo` for concurrent-safe numeric updates**: `addTo` uses CRDT incrementation, safe across threads and nodes.
|
|
111
112
|
|
|
112
113
|
```javascript
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
record.addTo('quantity', -1); // decrement safely across nodes
|
|
116
|
-
}
|
|
114
|
+
const record = await Product.update(32);
|
|
115
|
+
record.addTo('quantity', -1);
|
|
117
116
|
```
|
|
118
117
|
|
|
119
|
-
10. **Wrap background work in `transaction()`**: HTTP handlers get a transaction automatically. Use `transaction()` explicitly for timers, background jobs, or any code outside a
|
|
118
|
+
10. **Wrap background work in `transaction()`**: HTTP handlers get a transaction automatically. Use `transaction()` explicitly for timers, background jobs, or any code outside a request context. Always `await` the call and `catch` errors.
|
|
120
119
|
|
|
121
120
|
```javascript
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
let data = await (await fetch('https://example.com/data')).json();
|
|
128
|
-
transaction(async (txn) => {
|
|
129
|
-
for (let item of data) {
|
|
130
|
-
await MyTable.put(item, txn);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
}, 3600000); // every hour
|
|
134
|
-
}
|
|
121
|
+
await transaction(async (txn) => {
|
|
122
|
+
for (let item of data) {
|
|
123
|
+
await MyTable.put(item, txn);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
135
126
|
```
|
|
136
127
|
|
|
137
|
-
The `txn` object
|
|
128
|
+
The `txn` object exposes:
|
|
129
|
+
|
|
130
|
+
| Member | Description |
|
|
131
|
+
| --------------------- | ------------------------------------------------------ |
|
|
132
|
+
| `commit()` | Commits the current transaction |
|
|
133
|
+
| `abort()` | Aborts and resets the transaction |
|
|
134
|
+
| `resetReadSnapshot()` | Resets the read snapshot to the latest committed state |
|
|
135
|
+
| `timestamp` | Timestamp associated with the current transaction |
|
|
138
136
|
|
|
139
|
-
|
|
140
|
-
| --------------------- | --------------- | ------------------------------------------------------ |
|
|
141
|
-
| `commit()` | `() => Promise` | Commits the current transaction |
|
|
142
|
-
| `abort()` | `() => void` | Aborts the transaction and resets it |
|
|
143
|
-
| `resetReadSnapshot()` | `() => void` | Resets the read snapshot to the latest committed state |
|
|
144
|
-
| `timestamp` | `number` | Timestamp associated with the current transaction |
|
|
137
|
+
**Atomicity**: all tables in the same database share one transactional context — writes across multiple tables commit atomically. Tables in different databases each get their own transaction with no cross-database atomicity guarantee.
|
|
145
138
|
|
|
146
|
-
|
|
139
|
+
If `transaction()` is called with a context that already has an active transaction, it reuses that transaction — safe to call defensively.
|
|
147
140
|
|
|
148
|
-
|
|
141
|
+
11. **Keep `harper` external when bundling for SSR**: In `vite.config`, mark `harper` as external so it resolves to the runtime rather than being bundled.
|
|
149
142
|
|
|
150
143
|
```javascript
|
|
144
|
+
// vite.config
|
|
151
145
|
ssr: {
|
|
152
146
|
external: ['harper'];
|
|
153
147
|
}
|
|
@@ -155,7 +149,31 @@ Apply this rule when writing server-side Harper component code that reads from o
|
|
|
155
149
|
|
|
156
150
|
## Examples
|
|
157
151
|
|
|
158
|
-
###
|
|
152
|
+
### Full CRUD sequence
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
import { tables } from 'harper';
|
|
156
|
+
const { Product } = tables;
|
|
157
|
+
|
|
158
|
+
// Create
|
|
159
|
+
const created = await Product.create({ name: 'Shirt', price: 9.5 });
|
|
160
|
+
|
|
161
|
+
// Patch
|
|
162
|
+
await Product.patch(created.id, { price: Math.round(created.price * 0.8 * 100) / 100 });
|
|
163
|
+
|
|
164
|
+
// Retrieve by primary key
|
|
165
|
+
const record = await Product.get(created.id);
|
|
166
|
+
|
|
167
|
+
// Query with conditions
|
|
168
|
+
const query = {
|
|
169
|
+
conditions: [{ attribute: 'price', comparator: 'less_than', value: 8.0 }],
|
|
170
|
+
};
|
|
171
|
+
for await (const record of Product.search(query)) {
|
|
172
|
+
// process record
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Nested conditions with `or`
|
|
159
177
|
|
|
160
178
|
```javascript
|
|
161
179
|
Product.search({
|
|
@@ -178,28 +196,35 @@ Product.search({
|
|
|
178
196
|
Product.search({ conditions: [{ attribute: ['brand', 'name'], value: 'Harper' }] });
|
|
179
197
|
```
|
|
180
198
|
|
|
181
|
-
###
|
|
199
|
+
### Background job with `transaction()`
|
|
182
200
|
|
|
183
201
|
```javascript
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
import { isMainThread } from 'node:worker_threads';
|
|
203
|
+
import { tables, transaction } from 'harper';
|
|
204
|
+
const { MyTable } = tables;
|
|
205
|
+
|
|
206
|
+
if (isMainThread) {
|
|
207
|
+
let running = false;
|
|
208
|
+
setInterval(async () => {
|
|
209
|
+
if (running) return;
|
|
210
|
+
running = true;
|
|
211
|
+
try {
|
|
212
|
+
let data = await (await fetch('https://example.com/data')).json();
|
|
213
|
+
await transaction(async (txn) => {
|
|
214
|
+
for (let item of data) {
|
|
215
|
+
await MyTable.put(item, txn);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
} catch (error) {
|
|
219
|
+
logger.error('hourly import failed', error);
|
|
220
|
+
} finally {
|
|
221
|
+
running = false;
|
|
222
|
+
}
|
|
223
|
+
}, 3600000);
|
|
199
224
|
}
|
|
200
225
|
```
|
|
201
226
|
|
|
202
|
-
### SSR
|
|
227
|
+
### SSR render with `tables`
|
|
203
228
|
|
|
204
229
|
```typescript
|
|
205
230
|
import { tables } from 'harper';
|
|
@@ -210,20 +235,9 @@ export async function render(url: string): Promise<string> {
|
|
|
210
235
|
}
|
|
211
236
|
```
|
|
212
237
|
|
|
213
|
-
### Mutable update with `addTo`
|
|
214
|
-
|
|
215
|
-
```javascript
|
|
216
|
-
const product = await Product.update(32);
|
|
217
|
-
product.status = 'active';
|
|
218
|
-
product.subtractFrom('quantity', 1);
|
|
219
|
-
product.save();
|
|
220
|
-
```
|
|
221
|
-
|
|
222
238
|
## Notes
|
|
223
239
|
|
|
224
|
-
- `tables`
|
|
225
|
-
- Destructive operations (`update`, `patch`, `delete`) act on live data and are not easily reversible.
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
- `transaction()` is safe to call defensively — if a transaction is already active on the context, it reuses it and executes the callback immediately.
|
|
229
|
-
- Link the `harper` package for correct typings in standalone component directories: `npm link harper`.
|
|
240
|
+
- `tables` and `databases` do **not** automatically apply role permissions — calls run in a trusted server-side context. Apply your own authorization controls before exposing results.
|
|
241
|
+
- Destructive operations (`update`, `patch`, `delete`) act on live data and are not easily reversible. Scope them with specific `conditions` and validate the affected set before writing.
|
|
242
|
+
- `tables` is the same live, process-wide object whether accessed as a global or via `import { tables } from 'harper'`. A record written through one component is immediately visible to every other.
|
|
243
|
+
- Run `npm link harper` for components in their own directory to ensure typings match the running installation.
|
|
@@ -251,6 +251,18 @@ rules:
|
|
|
251
251
|
- path: reference/v5/resources/resource-api.md
|
|
252
252
|
section: '`getCurrentUser(): User | undefined`'
|
|
253
253
|
role: primary
|
|
254
|
+
# Sourced for the `getContext()` anchor below, which was unsatisfiable
|
|
255
|
+
# without it: `getContext()` does not occur in any other sourced section
|
|
256
|
+
# (`Session and Login from a Resource` uses the `(target, data, context)`
|
|
257
|
+
# trailing-argument form throughout), so the anchor demanded a string the
|
|
258
|
+
# model could only invent — and the system prompt forbids inventing, so
|
|
259
|
+
# every run failed validation instead. This section also carries the
|
|
260
|
+
# instance-vs-static contract the rule needs: `getContext()` is an
|
|
261
|
+
# instance method, and a static verb must read the context from its
|
|
262
|
+
# trailing argument or use the `harper` module export.
|
|
263
|
+
- path: reference/v5/resources/resource-api.md
|
|
264
|
+
section: '`getContext(): Context`'
|
|
265
|
+
role: supplemental
|
|
254
266
|
- path: reference/v5/resources/resource-api.md
|
|
255
267
|
section: 'Session and Login from a Resource'
|
|
256
268
|
role: primary
|
|
@@ -371,6 +383,13 @@ rules:
|
|
|
371
383
|
- 'Basic Authentication'
|
|
372
384
|
- 'server-to-server'
|
|
373
385
|
- 'preferred over Basic Auth'
|
|
386
|
+
# Notation, not content. The old body wrote the dotted config path as one
|
|
387
|
+
# code span; the new one states the same requirement as a YAML block under
|
|
388
|
+
# `authentication:` plus prose, and the `enableSessions` anchor above still
|
|
389
|
+
# holds it. The fact-retention check compares whole code spans, so it
|
|
390
|
+
# cannot see that the two notations say the same thing.
|
|
391
|
+
allow_dropped:
|
|
392
|
+
- 'authentication.enableSessions: true'
|
|
374
393
|
cross_links:
|
|
375
394
|
- custom-resources
|
|
376
395
|
|
|
@@ -392,13 +411,29 @@ rules:
|
|
|
392
411
|
section: 'Exporting Resources as Endpoints'
|
|
393
412
|
role: primary
|
|
394
413
|
- path: reference/v5/components/javascript-environment.md
|
|
395
|
-
section: 'Module
|
|
414
|
+
section: 'Module Formats'
|
|
396
415
|
role: supplemental
|
|
397
416
|
must_cover:
|
|
398
417
|
- 'extends Resource'
|
|
399
418
|
- 'export class'
|
|
400
419
|
- 'server.resources.set('
|
|
401
420
|
- 'static'
|
|
421
|
+
# Both surfaced by the fact-retention check and both judged notation
|
|
422
|
+
# rather than content:
|
|
423
|
+
#
|
|
424
|
+
# `{param}` — the old body illustrated MCP URI templates as "`{param}`
|
|
425
|
+
# URI templates"; the new one keeps the same sentence with a concrete
|
|
426
|
+
# `/widget/{id}/action/{action}` example, which is strictly clearer.
|
|
427
|
+
#
|
|
428
|
+
# `databases` — came from a Notes bullet asserting that `tables` and
|
|
429
|
+
# `databases` are the same live objects "regardless of which component
|
|
430
|
+
# accesses them". `programmatic-table-requests` owns that topic, and the
|
|
431
|
+
# globals-vs-import framing of that bullet was flagged as contentious
|
|
432
|
+
# during the #84 review (the docs assert it in one place and caution
|
|
433
|
+
# against globals in another), so it is not re-pinned here.
|
|
434
|
+
allow_dropped:
|
|
435
|
+
- '{param}'
|
|
436
|
+
- 'databases'
|
|
402
437
|
|
|
403
438
|
- rule: extending-tables
|
|
404
439
|
description: How to add custom logic to automatically generated table resources in Harper.
|
|
@@ -442,7 +477,7 @@ rules:
|
|
|
442
477
|
section: '`addTo(property: string, value: number)`'
|
|
443
478
|
role: supplemental
|
|
444
479
|
- path: reference/v5/components/javascript-environment.md
|
|
445
|
-
section: 'Module
|
|
480
|
+
section: 'Module Formats'
|
|
446
481
|
role: supplemental
|
|
447
482
|
must_cover:
|
|
448
483
|
- 'tables'
|
|
@@ -476,10 +511,34 @@ rules:
|
|
|
476
511
|
- 'atomicity'
|
|
477
512
|
- 'limit'
|
|
478
513
|
- 'offset'
|
|
479
|
-
# § Module
|
|
514
|
+
# § Module Formats' one piece of actionable build guidance: keep `harper`
|
|
480
515
|
# external when bundling for SSR, or it is bundled instead of resolving
|
|
481
516
|
# to the runtime. Only occurrence of `vite.config` in the sources.
|
|
482
517
|
- 'vite.config'
|
|
518
|
+
# Both anchored after the fact-retention check caught this regeneration
|
|
519
|
+
# dropping them. The literal error string is how an agent matches the
|
|
520
|
+
# runtime failure to the fix; a body that only says "throws an error"
|
|
521
|
+
# (which is what regeneration produced) leaves it guessing. Anchored
|
|
522
|
+
# verbatim rather than as a `HdbError:` prefix, because retention
|
|
523
|
+
# compares whole code spans — a truncated variant would satisfy a short
|
|
524
|
+
# anchor and still register as a loss.
|
|
525
|
+
- 'HdbError: <attribute> is not indexed and not combined with any other conditions'
|
|
526
|
+
- 'npm link harper'
|
|
527
|
+
# The `txn` member table lost its Type column on regeneration. All four
|
|
528
|
+
# members and their behavior survived (and `resetReadSnapshot()` is
|
|
529
|
+
# anchored above), so what went is the type annotation, not the fact.
|
|
530
|
+
# Waived rather than anchored for two reasons: `number` is generic enough
|
|
531
|
+
# that requiring it pins nothing in particular, and the old `() => Promise`
|
|
532
|
+
# was already a degraded value — `commit()` returns `Promise<void>`, and
|
|
533
|
+
# the `<void>` had been stripped somewhere upstream of the rule.
|
|
534
|
+
#
|
|
535
|
+
# This is the fact-retention check's noise floor: it compares code spans,
|
|
536
|
+
# so a dropped table column reads the same as a dropped fact. Waiving is
|
|
537
|
+
# the intended response, and it stays visible here in review.
|
|
538
|
+
allow_dropped:
|
|
539
|
+
- '() => Promise'
|
|
540
|
+
- '() => void'
|
|
541
|
+
- 'number'
|
|
483
542
|
|
|
484
543
|
- rule: typescript-type-stripping
|
|
485
544
|
description: How to run TypeScript files directly in Harper without a build step.
|