@harperfast/template-react-ts-studio 1.12.3 → 1.12.4

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.
@@ -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 Loading
10
- sourceCommit: f37a8c4021e20d5c74c1d339a6b6c8c196b5603e
11
- inputHash: df69870433c0b3e5
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 schema, or when a specific URL shape is required.
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`**: Always import from the `harper` package rather than relying on globals.
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 for external calls**: Await fetch or other async operations inside `static` handlers.
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 create an endpoint**: The export form controls the resulting URL. Choose the form that matches the URL shape you need.
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
- URL path matching is case-sensitive — `/Foo/` and `/foo/` are different endpoints.
71
+ 5. **Register programmatically when the path is dynamic**: Use `server.resources.set(` with a path string and the resource class.
72
72
 
73
- 5. **Declare path parameters with `static path`**: Use `:name` for a single segment and `*name` as a catch-all. Matched values are bound onto `target.<name>`.
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
- A `static path` takes precedence over the export name. A leading `/` makes the path root-relative (top-level). A leading `./` or bare name resolves relative to the component directory.
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
- ### External API wrapper with GET and PUT
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
- ### Path parameters with `static path`
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
- ### Programmatic registration
128
+ **Root-relative fixed route:**
145
129
 
146
130
  ```javascript
147
- import { Resource } from 'harper';
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 { data: doSomething() };
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
- - A bare `*` wildcard (no name) binds under `target.wildcard`. A wildcard must be the final segment of the path.
161
- - Resolution order: exact/static paths always win over parameterized ones. Among parameterized routes, more specific paths win — a literal segment beats `:param`, which beats `*`, compared left to right.
162
- - Parameterized routes appear in the generated OpenAPI document as templated paths (e.g. `/widget/{id}/action/{action}`) and in MCP `resources/templates/list` as `{param}` URI templates.
163
- - If a resource `extends` an existing table, avoid conflicting exports between the schema and the JavaScript implementation.
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 Loading
17
- sourceCommit: 677ad213d67822e109c83619e181ca23a59823db
18
- inputHash: ce2181ecade6522f
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, including querying, transactions, and module integration.
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 — bypassing REST endpoints — such as in request handlers, background jobs, timers, or SSR rendering. Use it whenever you need to construct queries with `conditions`, manage transactions explicitly, or perform CRDT-safe mutations.
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 all tables in the default `data` database via the `tables` object. Each table defined with `@table` in `schema.graphql` is a property.
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
- // same as: databases.data.Product
36
+ // equivalent to: databases.data.Product
37
37
  ```
38
38
 
39
- 2. **Define your schema with `@table`**: Tables must be declared in `schema.graphql`. Use `@indexed` on attributes you intend to sort or filter efficiently.
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. **Use `search(` to query records**: Pass a Query object to `search(`. Iterate results with `for await`.
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` arrays to filter**: Each condition object supports these properties:
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. **Apply `select` to shape results**: Return only the fields you need. Supports arrays, nested relationship selects, and special properties.
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
- // Array of fields
75
+ // Scalar fields only
74
76
  Product.search({ select: ['name', 'price'] });
75
77
 
76
- // Nested relationship select
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. **Apply `sort` with an `@indexed` attribute**: Harper uses an index to provide sort order. Sort by an `@indexed` attribute without requiring a condition, or provide at least one condition when sorting by a non-indexed attribute.
84
+ 6. **Paginate with `limit` and `offset`**:
83
85
 
84
86
  ```javascript
85
- // Sort by primary key with an open-ended condition to avoid scan error
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
- Sort object properties:
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
- | Property | Description |
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
- 7. **Use `limit` and `offset` for pagination**:
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({ conditions: [...], limit: 20, offset: 40 });
99
+ Product.search({
100
+ conditions: [{ attribute: 'id', comparator: 'greater_than', value: '' }],
101
+ sort: { attribute: 'id' },
102
+ });
104
103
  ```
105
104
 
106
- 8. **Use `explain` and `enforceExecutionOrder` for debugging**:
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
- static async post(target, data) {
114
- const record = await this.update(target.id);
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 natural transaction context.
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
- import { tables } from 'harper';
123
- const { MyTable } = tables;
124
-
125
- if (isMainThread) {
126
- setInterval(async () => {
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 members:
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
- | Member | Type | Description |
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
- 11. **Understand atomicity boundaries**: All tables within 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.
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
- 12. **Keep `harper` external in bundlers**: When using SSR bundlers, mark `harper` as external so it resolves to the live runtime. In `vite.config`:
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
- ### Nested conditions query
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
- ### Full CRUD sequence
199
+ ### Background job with `transaction()`
182
200
 
183
201
  ```javascript
184
- // Create a new record (id auto-generated)
185
- const created = await Product.create({ name: 'Shirt', price: 9.5 });
186
-
187
- // Modify the record
188
- await Product.patch(created.id, { price: Math.round(created.price * 0.8 * 100) / 100 });
189
-
190
- // Retrieve by primary key
191
- const record = await Product.get(created.id);
192
-
193
- // Query with conditions
194
- const query = {
195
- conditions: [{ attribute: 'price', comparator: 'less_than', value: 8.0 }],
196
- };
197
- for await (const record of Product.search(query)) {
198
- // process record
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 rendering with `tables`
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` calls run in a trusted server-side context and do **not** automatically apply the target table's role permissions. Enforce authorization in your own application logic.
225
- - Destructive operations (`update`, `patch`, `delete`) act on live data and are not easily reversible. Always scope with specific `conditions`, validate the affected set before writing, and gate behind authorization controls.
226
- - Sorting by the bare `@primaryKey` alone with no conditions triggers `HdbError: <attribute> is not indexed and not combined with any other conditions`. Add an open-ended range condition or pass `allowFullScan: true` to permit an unconditional scan.
227
- - Selecting a relationship field without filtering on it behaves as a **LEFT JOIN**; adding a condition on a related attribute behaves as an **INNER JOIN**.
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 Loading'
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 Loading'
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 Loading's one piece of actionable build guidance: keep `harper`
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.