@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.
Files changed (48) hide show
  1. package/.agents/skills/harper-best-practices/AGENTS.md +687 -453
  2. package/.agents/skills/harper-best-practices/SKILL.md +24 -21
  3. package/.agents/skills/harper-best-practices/rules/adding-tables-with-schemas.md +2 -0
  4. package/.agents/skills/harper-best-practices/rules/automatic-apis.md +2 -0
  5. package/.agents/skills/harper-best-practices/rules/caching.md +2 -0
  6. package/.agents/skills/harper-best-practices/rules/checking-authentication.md +2 -0
  7. package/.agents/skills/harper-best-practices/rules/custom-resources.md +2 -0
  8. package/.agents/skills/harper-best-practices/rules/defining-relationships.md +102 -40
  9. package/.agents/skills/harper-best-practices/rules/deploying-from-ci.md +121 -93
  10. package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +149 -103
  11. package/.agents/skills/harper-best-practices/rules/extending-tables.md +2 -0
  12. package/.agents/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
  13. package/.agents/skills/harper-best-practices/rules/load-env.md +2 -0
  14. package/.agents/skills/harper-best-practices/rules/logging.md +2 -0
  15. package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +124 -91
  16. package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +86 -82
  17. package/.agents/skills/harper-best-practices/rules/real-time-apps.md +2 -0
  18. package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +92 -59
  19. package/.agents/skills/harper-best-practices/rules/serving-web-content.md +2 -0
  20. package/.agents/skills/harper-best-practices/rules/typescript-type-stripping.md +2 -0
  21. package/.agents/skills/harper-best-practices/rules/using-blob-datatype.md +2 -0
  22. package/.agents/skills/harper-best-practices/rules/vector-indexing.md +2 -0
  23. package/.agents/skills/harper-best-practices/rules.manifest.yaml +44 -0
  24. package/agent/skills/harper-best-practices/AGENTS.md +687 -453
  25. package/agent/skills/harper-best-practices/SKILL.md +26 -21
  26. package/agent/skills/harper-best-practices/rules/adding-tables-with-schemas.md +2 -0
  27. package/agent/skills/harper-best-practices/rules/automatic-apis.md +2 -0
  28. package/agent/skills/harper-best-practices/rules/caching.md +2 -0
  29. package/agent/skills/harper-best-practices/rules/checking-authentication.md +2 -0
  30. package/agent/skills/harper-best-practices/rules/custom-resources.md +2 -0
  31. package/agent/skills/harper-best-practices/rules/defining-relationships.md +102 -40
  32. package/agent/skills/harper-best-practices/rules/deploying-from-ci.md +121 -93
  33. package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +149 -103
  34. package/agent/skills/harper-best-practices/rules/extending-tables.md +2 -0
  35. package/agent/skills/harper-best-practices/rules/handling-binary-data.md +2 -0
  36. package/agent/skills/harper-best-practices/rules/load-env.md +2 -0
  37. package/agent/skills/harper-best-practices/rules/logging.md +2 -0
  38. package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +124 -91
  39. package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +86 -82
  40. package/agent/skills/harper-best-practices/rules/real-time-apps.md +2 -0
  41. package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +92 -59
  42. package/agent/skills/harper-best-practices/rules/serving-web-content.md +2 -0
  43. package/agent/skills/harper-best-practices/rules/typescript-type-stripping.md +2 -0
  44. package/agent/skills/harper-best-practices/rules/using-blob-datatype.md +2 -0
  45. package/agent/skills/harper-best-practices/rules/vector-indexing.md +2 -0
  46. package/agent/skills/harper-best-practices/rules.manifest.yaml +44 -0
  47. package/package.json +1 -1
  48. 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: e73e5efb2823cc52caf7a6458d67fbcff3bbff2c
18
- inputHash: bb360b0d6c0a7bd4
17
+ sourceCommit: 6c3e29dd9149201364b0d74642725c30f0305e3e
18
+ inputHash: b42fe5775d69e9a2
19
19
  ---
20
20
 
21
21
  # Programmatic Table Requests
22
22
 
23
- Instructions for the agent to follow when interacting 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
+
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 component code that reads from or writes to tables using the programmatic API — for example, in HTTP handlers, background jobs, timers, or SSR entry points. Use it whenever you need to construct queries with `conditions`, `sort`, `select`, pagination, or transactions outside of the REST layer.
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`. Each property is the table class implementing the Resource API.
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, transaction } from 'harper';
36
+ import { tables, Resource } from 'harper';
35
37
  const { Product } = tables;
36
38
  // same as: databases.data.Product
37
39
  ```
38
40
 
39
- For components in their own directory, run:
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
- All installed components have `harper` automatically linked.
49
+ When bundling for SSR with Vite, keep `harper` external so it resolves to the runtime:
46
50
 
47
- 2. **Define your schema with `@table`**: Every type you want to access via `tables` must carry the `@table` directive. Mark queryable fields with `@indexed`.
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 query = {
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. **Build `conditions`**: Each condition object supports the following properties:
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
- | Property | Description |
71
- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
72
- | `attribute` | Property name, or array for chained/joined properties (e.g. `['brand', 'name']`) |
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` |
75
- | `conditions` | Nested conditions array |
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
- 5. **Apply `select` to shape results**: Pass an array of property names, a string for a single property, or nested objects for relationships.
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
- ```javascript
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
- // Nested relationship select
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
- Special `select` values: `$id`, `$updatedtime`, `$distance`.
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
- 6. **Apply `sort`**: Harper uses an index to provide sort order.
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
- - Pass `allowFullScan: true` to permit an unconditional ordered scan, or omit `sort` entirely to iterate without an index requirement.
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({ conditions: [...], limit: 20, offset: 40 });
126
+ Product.search({
127
+ conditions: [{ attribute: 'price', comparator: 'less_than', value: 100 }],
128
+ limit: 20,
129
+ offset: 40,
130
+ });
116
131
  ```
117
132
 
118
- 8. **Debug with `explain` and `enforceExecutionOrder`**:
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
- 9. **Use `addTo` for concurrent-safe increments**: `addTo(property, value)` uses CRDT incrementation, safe across threads and nodes.
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
- 10. **Wrap background work in `transaction()`**: Harper auto-starts transactions for HTTP handlers. Use `transaction()` explicitly for timers, background jobs, or any code outside a natural transaction context.
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
- The `txn` object exposes:
142
-
143
- | Member | Type | Description |
144
- | --------------------- | --------------- | ------------------------------------------------------ |
145
- | `commit()` | `() => Promise` | Commits the current transaction |
146
- | `abort()` | `() => void` | Aborts the transaction and resets it |
147
- | `resetReadSnapshot()` | `() => void` | Resets the read snapshot to the latest committed state |
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
- On normal callback completion the transaction commits automatically. If the callback throws, the transaction is aborted.
156
+ The `txn` object exposes:
151
157
 
152
- 11. **Understand atomicity**: Transactions span a single database. All tables within the same database share one transactional context — reads return a consistent snapshot and writes across multiple tables commit atomically. Cross-database operations get separate transactions with no cross-database atomicity guarantee.
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
- 12. **Keep `harper` external when bundling for SSR**: In `vite.config`, mark `harper` as external so it resolves to the runtime rather than being bundled.
165
+ On normal callback completion the transaction commits automatically. If the callback throws, the transaction is aborted.
155
166
 
156
- ```javascript
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
- **Full CRUD flow:**
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
- for await (const record of Product.search({
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
- **Nested conditions with `or`:**
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
- **Chained attribute reference (join):**
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
- **Background job with `transaction()`:**
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
- **SSR with Harper data:**
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
- - `tables` and `databases` calls run in a trusted server-side context and do **not** automatically apply the target table's role permissions.
253
- - Programmatic `update`, `patch`, and `delete` calls operate directly on stored data. Scope destructive operations with specific `conditions`, validate the affected set before writing, and gate them behind authorization controls.
254
- - If `transaction()` is called with a context that already has an active transaction, it reuses that transaction — making it safe to call defensively.
255
- - Always `await` the `transaction()` call and `catch` errors when outside a request context; an unawaited call means a failed write is never observed.
256
- - Guard against timer overlap: if a job can outlast its interval, use a `running` flag to skip a tick rather than opening two transactions over the same rows.
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.