@harperfast/template-react-ts-ssr-studio 1.13.4 → 1.13.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/harper-best-practices/AGENTS.md +687 -453
- package/.agents/skills/harper-best-practices/SKILL.md +24 -21
- package/.agents/skills/harper-best-practices/rules/adding-tables-with-schemas.md +2 -0
- package/.agents/skills/harper-best-practices/rules/automatic-apis.md +2 -0
- package/.agents/skills/harper-best-practices/rules/caching.md +2 -0
- package/.agents/skills/harper-best-practices/rules/checking-authentication.md +2 -0
- package/.agents/skills/harper-best-practices/rules/custom-resources.md +2 -0
- package/.agents/skills/harper-best-practices/rules/defining-relationships.md +102 -40
- package/.agents/skills/harper-best-practices/rules/deploying-from-ci.md +121 -93
- package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +149 -103
- package/.agents/skills/harper-best-practices/rules/extending-tables.md +2 -0
- package/.agents/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
- package/.agents/skills/harper-best-practices/rules/load-env.md +2 -0
- package/.agents/skills/harper-best-practices/rules/logging.md +2 -0
- package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +124 -91
- package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +86 -82
- package/.agents/skills/harper-best-practices/rules/real-time-apps.md +2 -0
- package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +92 -59
- package/.agents/skills/harper-best-practices/rules/serving-web-content.md +2 -0
- package/.agents/skills/harper-best-practices/rules/typescript-type-stripping.md +2 -0
- package/.agents/skills/harper-best-practices/rules/using-blob-datatype.md +2 -0
- package/.agents/skills/harper-best-practices/rules/vector-indexing.md +2 -0
- package/.agents/skills/harper-best-practices/rules.manifest.yaml +44 -0
- package/agent/skills/harper-best-practices/AGENTS.md +687 -453
- package/agent/skills/harper-best-practices/SKILL.md +26 -21
- package/agent/skills/harper-best-practices/rules/adding-tables-with-schemas.md +2 -0
- package/agent/skills/harper-best-practices/rules/automatic-apis.md +2 -0
- package/agent/skills/harper-best-practices/rules/caching.md +2 -0
- package/agent/skills/harper-best-practices/rules/checking-authentication.md +2 -0
- package/agent/skills/harper-best-practices/rules/custom-resources.md +2 -0
- package/agent/skills/harper-best-practices/rules/defining-relationships.md +102 -40
- package/agent/skills/harper-best-practices/rules/deploying-from-ci.md +121 -93
- package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +149 -103
- package/agent/skills/harper-best-practices/rules/extending-tables.md +2 -0
- package/agent/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
- package/agent/skills/harper-best-practices/rules/load-env.md +2 -0
- package/agent/skills/harper-best-practices/rules/logging.md +2 -0
- package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +124 -91
- package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +86 -82
- package/agent/skills/harper-best-practices/rules/real-time-apps.md +2 -0
- package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +92 -59
- package/agent/skills/harper-best-practices/rules/serving-web-content.md +2 -0
- package/agent/skills/harper-best-practices/rules/typescript-type-stripping.md +2 -0
- package/agent/skills/harper-best-practices/rules/using-blob-datatype.md +2 -0
- package/agent/skills/harper-best-practices/rules/vector-indexing.md +2 -0
- package/agent/skills/harper-best-practices/rules.manifest.yaml +44 -0
- package/package.json +1 -1
- package/skills-lock.json +1 -1
|
@@ -14,155 +14,161 @@ metadata:
|
|
|
14
14
|
reference/v5/resources/resource-api.md#`addTo(property: string, value:
|
|
15
15
|
number)`
|
|
16
16
|
- reference/v5/components/javascript-environment.md#Module Formats
|
|
17
|
-
sourceCommit:
|
|
18
|
-
inputHash:
|
|
17
|
+
sourceCommit: 6c3e29dd9149201364b0d74642725c30f0305e3e
|
|
18
|
+
inputHash: b42fe5775d69e9a2
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
# Programmatic Table Requests
|
|
22
22
|
|
|
23
|
-
Instructions for the agent to
|
|
23
|
+
Instructions for the agent to interact with Harper tables programmatically using the `tables` object, the Query API, and transactions.
|
|
24
|
+
|
|
25
|
+
**Instead of:** ORM and query-builder clients (Prisma, Drizzle, Knex). Use this rule rather than adding that service. See `harper-architecture`.
|
|
24
26
|
|
|
25
27
|
## When to Use
|
|
26
28
|
|
|
27
|
-
Apply this rule when writing server-side Harper
|
|
29
|
+
Apply this rule when writing server-side Harper code that reads, writes, searches, or transactionally modifies table data through the `tables` object. Use it when building background jobs, SSR renderers, or any code that calls `search(`, `addTo`, `select`, `sort`, or `transaction()` directly rather than through REST endpoints.
|
|
28
30
|
|
|
29
31
|
## How It Works
|
|
30
32
|
|
|
31
|
-
1. **Import `tables` (and other APIs) from `harper`**: Access every table defined in `schema.graphql` as a property of `tables`.
|
|
33
|
+
1. **Import `tables` (and other APIs) from `harper`**: Access every table defined in `schema.graphql` as a named property of `tables`. This is equivalent to `databases.data.<TableName>`.
|
|
32
34
|
|
|
33
35
|
```javascript
|
|
34
|
-
import { tables,
|
|
36
|
+
import { tables, Resource } from 'harper';
|
|
35
37
|
const { Product } = tables;
|
|
36
38
|
// same as: databases.data.Product
|
|
37
39
|
```
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
CommonJS is also supported: `const { tables, Resource } = require('harper');`
|
|
42
|
+
|
|
43
|
+
For components in their own directory, link the package first:
|
|
40
44
|
|
|
41
45
|
```bash
|
|
42
46
|
npm link harper
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
When bundling for SSR with Vite, keep `harper` external so it resolves to the runtime:
|
|
46
50
|
|
|
47
|
-
|
|
51
|
+
```javascript
|
|
52
|
+
// vite.config
|
|
53
|
+
ssr: {
|
|
54
|
+
external: ['harper'];
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
2. **Define your schema with `@table` (and `@indexed` where needed)**: Tables must be declared in `schema.graphql`. Mark attributes that will be used as sort keys or standalone condition attributes with `@indexed`.
|
|
48
59
|
|
|
49
60
|
```graphql
|
|
50
61
|
type Product @table {
|
|
51
62
|
id: Long @primaryKey
|
|
52
63
|
name: String
|
|
53
|
-
price: Float
|
|
64
|
+
price: Float @indexed
|
|
54
65
|
}
|
|
55
66
|
```
|
|
56
67
|
|
|
57
|
-
3. **Query records using `search(`**: Pass a Query object to `search()`. The query is an async iterable.
|
|
68
|
+
3. **Query records using `search(`**: Pass a Query object to `search()`. The query is an async iterable; iterate the result with `for await`. Use a `conditions` array to filter records. Each condition specifies `attribute`, `value`, and an optional `comparator`.
|
|
69
|
+
|
|
70
|
+
| Property | Description |
|
|
71
|
+
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
72
|
+
| `attribute` | Property name, or array for chained/joined properties |
|
|
73
|
+
| `value` | The value to match |
|
|
74
|
+
| `comparator` | `equals` (default), `greater_than`, `greater_than_equal`, `less_than`, `less_than_equal`, `starts_with`, `contains`, `ends_with`, `between`, `not_equal`, or a full-text comparator |
|
|
75
|
+
| `conditions` | Nested conditions array |
|
|
76
|
+
| `operator` | `and` (default) or `or` for the nested `conditions` |
|
|
58
77
|
|
|
59
78
|
```javascript
|
|
60
|
-
const
|
|
79
|
+
for await (const record of Product.search({
|
|
61
80
|
conditions: [{ attribute: 'price', comparator: 'less_than', value: 8.0 }],
|
|
62
|
-
}
|
|
63
|
-
for await (const record of Product.search(query)) {
|
|
81
|
+
})) {
|
|
64
82
|
// process record
|
|
65
83
|
}
|
|
66
84
|
```
|
|
67
85
|
|
|
68
|
-
4. **
|
|
86
|
+
4. **Use `select` to control which properties are returned**: Pass an array of property names, a string for a single property, or nested objects for relationships.
|
|
69
87
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
| `operator` | `and` (default) or `or` for the nested `conditions` |
|
|
88
|
+
```javascript
|
|
89
|
+
const book = await Book.get({
|
|
90
|
+
id: 42,
|
|
91
|
+
select: ['id', 'title', { name: 'author', select: ['name'] }],
|
|
92
|
+
});
|
|
93
|
+
```
|
|
77
94
|
|
|
78
|
-
|
|
95
|
+
Special `select` properties:
|
|
96
|
+
- `$id` — returns the primary key regardless of its name
|
|
97
|
+
- `$updatedtime` — returns the last-updated timestamp
|
|
98
|
+
- `$distance` — returns computed vector distance when using a vector index
|
|
99
|
+
- `$score` — returns BM25 relevance score for full-text queries
|
|
100
|
+
- `$highlights` — returns full-text fragments and matching spans
|
|
79
101
|
|
|
80
|
-
|
|
81
|
-
// Flat select
|
|
82
|
-
Product.search({ select: ['name', 'price'] });
|
|
102
|
+
5. **Use `sort` with an `@indexed` attribute or combine with `conditions`**: Harper requires either an `@indexed` sort attribute or at least one `conditions` entry when sorting. Sorting by a non-indexed attribute with zero conditions raises:
|
|
83
103
|
|
|
84
|
-
|
|
85
|
-
Book.get({ id: 42, select: ['id', 'title', { name: 'author', select: ['name'] }] });
|
|
86
|
-
```
|
|
104
|
+
> `HdbError: <attribute> is not indexed and not combined with any other conditions`
|
|
87
105
|
|
|
88
|
-
|
|
106
|
+
Note: `@primaryKey` alone is treated as not indexed for sort purposes. To iterate a whole table in primary-key order, add an open-ended condition:
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
Product.search({
|
|
110
|
+
conditions: [{ attribute: 'id', comparator: 'greater_than', value: '' }],
|
|
111
|
+
sort: { attribute: 'id' },
|
|
112
|
+
});
|
|
113
|
+
```
|
|
89
114
|
|
|
90
|
-
|
|
115
|
+
`sort` object properties:
|
|
91
116
|
|
|
92
117
|
| Property | Description |
|
|
93
118
|
| ------------ | ---------------------------------------------------------- |
|
|
94
119
|
| `attribute` | Property name (or array for chained relationship property) |
|
|
95
120
|
| `descending` | Sort descending if `true` (default: `false`) |
|
|
96
121
|
| `next` | Secondary sort to resolve ties (same structure) |
|
|
97
|
-
- If the sort `attribute` is `@indexed`, no condition is required.
|
|
98
|
-
- If the sort `attribute` is not indexed, at least one entry in `conditions` (on **any** attribute) is required.
|
|
99
|
-
- Sorting by a non-indexed attribute with zero conditions raises:
|
|
100
|
-
> `HdbError: <attribute> is not indexed and not combined with any other conditions`
|
|
101
|
-
- The bare `@primaryKey` is treated as not indexed for sort purposes. To iterate in primary-key order, add an open-ended condition:
|
|
102
|
-
|
|
103
|
-
```javascript
|
|
104
|
-
Product.search({
|
|
105
|
-
conditions: [{ attribute: 'id', comparator: 'greater_than', value: '' }],
|
|
106
|
-
sort: { attribute: 'id' },
|
|
107
|
-
});
|
|
108
|
-
```
|
|
109
122
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
7. **Paginate with `limit` and `offset`**:
|
|
123
|
+
6. **Apply `limit` and `offset` for pagination**: Add these to the Query object to bound result sets.
|
|
113
124
|
|
|
114
125
|
```javascript
|
|
115
|
-
Product.search({
|
|
126
|
+
Product.search({
|
|
127
|
+
conditions: [{ attribute: 'price', comparator: 'less_than', value: 100 }],
|
|
128
|
+
limit: 20,
|
|
129
|
+
offset: 40,
|
|
130
|
+
});
|
|
116
131
|
```
|
|
117
132
|
|
|
118
|
-
|
|
133
|
+
7. **Use `explain` and `enforceExecutionOrder` for debugging and optimization**:
|
|
119
134
|
- `explain: true` — returns conditions reordered as Harper will execute them.
|
|
120
135
|
- `enforceExecutionOrder: true` — forces conditions to execute in the order supplied, disabling Harper's automatic re-ordering.
|
|
121
136
|
|
|
122
|
-
|
|
137
|
+
8. **Use `addTo` for concurrent-safe numeric updates**: Increments or decrements a property using CRDT incrementation, safe across threads and nodes.
|
|
123
138
|
|
|
124
139
|
```javascript
|
|
125
140
|
static async post(target, data) {
|
|
126
141
|
const record = await this.update(target.id);
|
|
127
|
-
record.addTo('quantity', -1);
|
|
142
|
+
record.addTo('quantity', -1); // decrement safely across nodes
|
|
128
143
|
}
|
|
129
144
|
```
|
|
130
145
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
```javascript
|
|
134
|
-
await transaction(async (txn) => {
|
|
135
|
-
for (let item of data) {
|
|
136
|
-
await MyTable.put(item, txn);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
```
|
|
146
|
+
9. **Wrap background or timer work in `transaction()`**: HTTP handlers get a transaction automatically. Use `transaction()` explicitly for timers, background jobs, or any code outside a natural request context. Always `await` the call and `catch` errors.
|
|
140
147
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
| `timestamp` | `number` | Timestamp associated with the current transaction |
|
|
148
|
+
```javascript
|
|
149
|
+
await transaction(async (txn) => {
|
|
150
|
+
for (let item of data) {
|
|
151
|
+
await MyTable.put(item, txn);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
```
|
|
149
155
|
|
|
150
|
-
|
|
156
|
+
The `txn` object exposes:
|
|
151
157
|
|
|
152
|
-
|
|
158
|
+
| Member | Type | Description |
|
|
159
|
+
| --------------------- | --------------- | ------------------------------------------------------ |
|
|
160
|
+
| `commit()` | `() => Promise` | Commits the current transaction |
|
|
161
|
+
| `abort()` | `() => void` | Aborts the transaction and resets it |
|
|
162
|
+
| `resetReadSnapshot()` | `() => void` | Resets the read snapshot to the latest committed state |
|
|
163
|
+
| `timestamp` | `number` | Timestamp associated with the current transaction |
|
|
153
164
|
|
|
154
|
-
|
|
165
|
+
On normal callback completion the transaction commits automatically. If the callback throws, the transaction is aborted.
|
|
155
166
|
|
|
156
|
-
|
|
157
|
-
// vite.config
|
|
158
|
-
ssr: {
|
|
159
|
-
external: ['harper'];
|
|
160
|
-
}
|
|
161
|
-
```
|
|
167
|
+
10. **Understand atomicity boundaries**: All tables within the same database share a single transactional context — reads return a consistent snapshot and writes are committed atomically. Tables in different databases each get their own transaction with no cross-database atomicity guarantee.
|
|
162
168
|
|
|
163
169
|
## Examples
|
|
164
170
|
|
|
165
|
-
|
|
171
|
+
### Basic CRUD and search
|
|
166
172
|
|
|
167
173
|
```javascript
|
|
168
174
|
import { tables } from 'harper';
|
|
@@ -178,14 +184,15 @@ await Product.patch(created.id, { price: Math.round(created.price * 0.8 * 100) /
|
|
|
178
184
|
const record = await Product.get(created.id);
|
|
179
185
|
|
|
180
186
|
// Search with conditions
|
|
181
|
-
|
|
187
|
+
const query = {
|
|
182
188
|
conditions: [{ attribute: 'price', comparator: 'less_than', value: 8.0 }],
|
|
183
|
-
}
|
|
189
|
+
};
|
|
190
|
+
for await (const record of Product.search(query)) {
|
|
184
191
|
// process record
|
|
185
192
|
}
|
|
186
193
|
```
|
|
187
194
|
|
|
188
|
-
|
|
195
|
+
### Nested conditions with `or`
|
|
189
196
|
|
|
190
197
|
```javascript
|
|
191
198
|
Product.search({
|
|
@@ -202,13 +209,32 @@ Product.search({
|
|
|
202
209
|
});
|
|
203
210
|
```
|
|
204
211
|
|
|
205
|
-
|
|
212
|
+
### Chained attribute reference (join/relationship)
|
|
206
213
|
|
|
207
214
|
```javascript
|
|
208
215
|
Product.search({ conditions: [{ attribute: ['brand', 'name'], value: 'Harper' }] });
|
|
209
216
|
```
|
|
210
217
|
|
|
211
|
-
|
|
218
|
+
### Selecting and constraining related records
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
const brand = await Brand.get({
|
|
222
|
+
id: 42,
|
|
223
|
+
select: [
|
|
224
|
+
'id',
|
|
225
|
+
{
|
|
226
|
+
name: 'products',
|
|
227
|
+
conditions: [{ attribute: 'price', comparator: 'greater_than_equal', value: 30 }],
|
|
228
|
+
sort: { attribute: 'price' },
|
|
229
|
+
offset: 0,
|
|
230
|
+
limit: 1,
|
|
231
|
+
select: ['id', 'price'],
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Background job with `transaction()`
|
|
212
238
|
|
|
213
239
|
```javascript
|
|
214
240
|
import { isMainThread } from 'node:worker_threads';
|
|
@@ -218,7 +244,7 @@ const { MyTable } = tables;
|
|
|
218
244
|
if (isMainThread) {
|
|
219
245
|
let running = false;
|
|
220
246
|
setInterval(async () => {
|
|
221
|
-
if (running) return;
|
|
247
|
+
if (running) return; // the previous run has not committed yet
|
|
222
248
|
running = true;
|
|
223
249
|
try {
|
|
224
250
|
let data = await (await fetch('https://example.com/data')).json();
|
|
@@ -232,11 +258,11 @@ if (isMainThread) {
|
|
|
232
258
|
} finally {
|
|
233
259
|
running = false;
|
|
234
260
|
}
|
|
235
|
-
}, 3600000);
|
|
261
|
+
}, 3600000); // every hour
|
|
236
262
|
}
|
|
237
263
|
```
|
|
238
264
|
|
|
239
|
-
|
|
265
|
+
### SSR with Vite
|
|
240
266
|
|
|
241
267
|
```typescript
|
|
242
268
|
import { tables } from 'harper';
|
|
@@ -247,11 +273,18 @@ export async function render(url: string): Promise<string> {
|
|
|
247
273
|
}
|
|
248
274
|
```
|
|
249
275
|
|
|
276
|
+
Keep `harper` external in `vite.config` so it resolves to the runtime:
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
ssr: {
|
|
280
|
+
external: ['harper'];
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
250
284
|
## Notes
|
|
251
285
|
|
|
252
|
-
-
|
|
253
|
-
-
|
|
254
|
-
-
|
|
255
|
-
-
|
|
256
|
-
- Guard against
|
|
257
|
-
- CommonJS is also supported: `const { tables, Resource } = require('harper');`
|
|
286
|
+
- Calls through `tables` run in a trusted server-side context and do **not** automatically apply the target table's role permissions. Set `checkPermission: true` on the query and pass the authenticated `context` as the second argument when authorization is required.
|
|
287
|
+
- `update`, `patch`, and `delete` operate directly on stored data. Scope destructive operations with specific `conditions`, validate the affected set before writing, and gate them behind authorization controls.
|
|
288
|
+
- Pass `allowFullScan: true` to permit an unconditional ordered scan without conditions. If order doesn't matter, omit `sort` entirely — `search({})` iterates without an index requirement.
|
|
289
|
+
- If `transaction()` is called with a context that already has an active transaction, it reuses that transaction and executes the callback immediately — safe to call defensively.
|
|
290
|
+
- Guard timer-based jobs against overlap: a timer fires on schedule regardless of whether the previous run has committed. Use a `running` flag to skip a tick when the previous run is still active.
|