@codyswann/lisa 2.191.13 → 2.192.0
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/harper-fabric/copy-overwrite/.github/dependabot.yml +47 -0
- package/harper-fabric/copy-overwrite/ast-grep/rules/harper/no-early-return-in-search-loop.yml +27 -0
- package/harper-fabric/copy-overwrite/ast-grep/rules/harper/no-empty-conditions-with-sort.yml +45 -0
- package/harper-fabric/copy-overwrite/ast-grep/rules/harper/no-full-table-scan.yml +33 -0
- package/harper-fabric/copy-overwrite/ast-grep/rules/harper/require-statuscode-on-thrown-error.yml +41 -0
- package/package.json +2 -2
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/skills/harper-auth/SKILL.md +48 -0
- package/plugins/lisa-harper-fabric/skills/harper-resources/SKILL.md +35 -0
- package/plugins/lisa-harper-fabric/skills/harper-rest-queries/SKILL.md +40 -3
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/skills/harper-auth/SKILL.md +48 -0
- package/plugins/lisa-harper-fabric-agy/skills/harper-resources/SKILL.md +35 -0
- package/plugins/lisa-harper-fabric-agy/skills/harper-rest-queries/SKILL.md +40 -3
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/skills/harper-auth/SKILL.md +48 -0
- package/plugins/lisa-harper-fabric-copilot/skills/harper-resources/SKILL.md +35 -0
- package/plugins/lisa-harper-fabric-copilot/skills/harper-rest-queries/SKILL.md +40 -3
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/skills/harper-auth/SKILL.md +48 -0
- package/plugins/lisa-harper-fabric-cursor/skills/harper-resources/SKILL.md +35 -0
- package/plugins/lisa-harper-fabric-cursor/skills/harper-rest-queries/SKILL.md +40 -3
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/harper-fabric/skills/harper-auth/SKILL.md +48 -0
- package/plugins/src/harper-fabric/skills/harper-resources/SKILL.md +35 -0
- package/plugins/src/harper-fabric/skills/harper-rest-queries/SKILL.md +40 -3
|
@@ -121,6 +121,38 @@ const products = await tables.Products.search({
|
|
|
121
121
|
});
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
## Sort with no conditions crashes the planner
|
|
125
|
+
|
|
126
|
+
A `search()` that carries a `sort` but an **empty `conditions: []`** array throws
|
|
127
|
+
at runtime — the planner cannot seed the btree scan from the sort attribute. This
|
|
128
|
+
is a latent `500` the first time an unfiltered, sorted collection page is
|
|
129
|
+
requested (verified on harperdb 4.7.32).
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
// ✗ crashes: sort with nothing to seed the scan
|
|
133
|
+
tables.Advisor.search({
|
|
134
|
+
conditions: [],
|
|
135
|
+
sort: { attribute: 'lastName' },
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Seed a floor/sentinel condition on the sort attribute so the planner has an index
|
|
140
|
+
range to walk, then apply the sort:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
// ✓ floor condition on the sort attribute seeds the scan
|
|
144
|
+
tables.Advisor.search({
|
|
145
|
+
conditions: [
|
|
146
|
+
{ attribute: 'lastName', comparator: 'greater_than_equal', value: '' },
|
|
147
|
+
],
|
|
148
|
+
sort: { attribute: 'lastName' },
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Centralize this in the page helper (`searchPageAndCount`) rather than duplicating
|
|
153
|
+
a sentinel at each call site. The `harper-no-empty-conditions-with-sort` ast-grep
|
|
154
|
+
rule flags the crashing shape.
|
|
155
|
+
|
|
124
156
|
## Relationship queries
|
|
125
157
|
|
|
126
158
|
Relationship attributes can be queried with dot syntax when the relationship is
|
|
@@ -200,9 +232,14 @@ Useful query keys:
|
|
|
200
232
|
| `select` | Projection list. Keep admin-only fields out of public responses. |
|
|
201
233
|
| `explain` | Debug execution order and index usage while tuning. |
|
|
202
234
|
|
|
203
|
-
`search()` can return an `AsyncIterable`.
|
|
204
|
-
|
|
205
|
-
|
|
235
|
+
`search()` can return an `AsyncIterable`. An early `return`/`break` out of a
|
|
236
|
+
`for await (... of tables.X.search(...))` loop abandons the iterator before the
|
|
237
|
+
scan completes and **leaks the open read transaction** (verified on
|
|
238
|
+
harperdb 4.7.32). When iterating manually or stopping early, drain it or call the
|
|
239
|
+
iterator's `return()` in `finally` so Harper releases the read transaction — or
|
|
240
|
+
bound the query (`search({ ..., limit: 1 })`) when you only need the first row.
|
|
241
|
+
The `harper-no-early-return-in-search-loop` ast-grep rule flags early exits from
|
|
242
|
+
these loops:
|
|
206
243
|
|
|
207
244
|
```javascript
|
|
208
245
|
const iterator = tables.Products
|
|
@@ -260,6 +260,54 @@ Guidance:
|
|
|
260
260
|
webhook must bypass Harper auth, verify its signature first and keep its table
|
|
261
261
|
writes narrowly scoped.
|
|
262
262
|
|
|
263
|
+
## Session cookies are `SameSite=None` — guard state-changing POSTs
|
|
264
|
+
|
|
265
|
+
Harper hardcodes the session cookie's `SameSite=None` attribute on HTTPS. The
|
|
266
|
+
browser therefore attaches the session cookie to **cross-site** requests, so a
|
|
267
|
+
same-origin app cannot rely on `SameSite` to block CSRF on cookie-authenticated,
|
|
268
|
+
state-changing routes (`POST`/`PUT`/`PATCH`/`DELETE`).
|
|
269
|
+
|
|
270
|
+
Add an app-side origin check in the resource before mutating. Reject requests
|
|
271
|
+
whose `Origin` (or `Referer` fallback) is not an allowed origin:
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
const ALLOWED_ORIGINS = new Set([
|
|
275
|
+
'https://app.example.com',
|
|
276
|
+
]);
|
|
277
|
+
|
|
278
|
+
function requireSameOrigin(context) {
|
|
279
|
+
const headers = context.requestContext?.headers ?? context.headers;
|
|
280
|
+
const origin =
|
|
281
|
+
headers?.get?.('origin') ??
|
|
282
|
+
headers?.get?.('referer');
|
|
283
|
+
const ok =
|
|
284
|
+
typeof origin === 'string' &&
|
|
285
|
+
[...ALLOWED_ORIGINS].some((allowed) => origin.startsWith(allowed));
|
|
286
|
+
if (!ok) {
|
|
287
|
+
const error = new Error('Cross-origin request rejected');
|
|
288
|
+
error.statusCode = 403; // Harper reads statusCode, not status
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export class Watchlists extends tables.Watchlists {
|
|
294
|
+
static async post(target, data, context) {
|
|
295
|
+
requireSameOrigin(context);
|
|
296
|
+
// ...authorization and write
|
|
297
|
+
return super.post(target, await data, context);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Guidance:
|
|
303
|
+
|
|
304
|
+
- Call the check on every cookie-authenticated state-changing route. Read-only
|
|
305
|
+
`GET` handlers and token/`Authorization: Bearer` APIs (not cookie-driven) do
|
|
306
|
+
not need it.
|
|
307
|
+
- Prefer an allowlist of exact origins over substring matching that a lookalike
|
|
308
|
+
host could satisfy.
|
|
309
|
+
- This is defense the app owns; Harper will not add it for you.
|
|
310
|
+
|
|
263
311
|
## Verification matrix
|
|
264
312
|
|
|
265
313
|
Run the local app, create the test users, and check unauthenticated, reader, and
|
|
@@ -74,6 +74,41 @@ See [[harper-config-yaml]] for the extension wiring, [[harper-schema-graphql]] f
|
|
|
74
74
|
how the schema defines the tables resources extend, and [[harper-realtime]] when
|
|
75
75
|
`subscribe`, `publish`, or WebSocket behavior is part of the feature.
|
|
76
76
|
|
|
77
|
+
## Throwing HTTP status errors
|
|
78
|
+
|
|
79
|
+
Harper's thrown-error response writer reads **`error.statusCode`** (falling back
|
|
80
|
+
to `500`). A plain `error.status` is **ignored** — throw an error with only
|
|
81
|
+
`status` set and every intended `4xx` is served as a `500`. Verified on
|
|
82
|
+
harperdb 4.7.32.
|
|
83
|
+
|
|
84
|
+
Always set `statusCode` (keep `status` too only if callers or tests read it):
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
static async post(target, data, context) {
|
|
88
|
+
if (!context.user) {
|
|
89
|
+
const error = new Error('Authentication required');
|
|
90
|
+
error.statusCode = 401; // NOT `error.status` — Harper reads statusCode
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
// ...
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
A shared helper keeps every throw site correct:
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
function throwStatus(message, status) {
|
|
101
|
+
// Set both: `statusCode` is what Harper serves; `status` is kept for
|
|
102
|
+
// returned-response symmetry and any caller/test that reads it.
|
|
103
|
+
throw Object.assign(new Error(message), { status, statusCode: status });
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The `harper-require-statuscode-on-thrown-error` ast-grep rule flags errors that
|
|
108
|
+
carry `status` without `statusCode`. Pass `context` through to `super` and nested
|
|
109
|
+
table calls so authorization and the request transaction stay aligned — see
|
|
110
|
+
[[harper-rest-queries]] for context propagation and iterator draining.
|
|
111
|
+
|
|
77
112
|
## Project conventions (TS is source)
|
|
78
113
|
|
|
79
114
|
- **Write resources in TypeScript under `src/`. `harper-app/resources.js` is a
|
|
@@ -121,6 +121,38 @@ const products = await tables.Products.search({
|
|
|
121
121
|
});
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
## Sort with no conditions crashes the planner
|
|
125
|
+
|
|
126
|
+
A `search()` that carries a `sort` but an **empty `conditions: []`** array throws
|
|
127
|
+
at runtime — the planner cannot seed the btree scan from the sort attribute. This
|
|
128
|
+
is a latent `500` the first time an unfiltered, sorted collection page is
|
|
129
|
+
requested (verified on harperdb 4.7.32).
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
// ✗ crashes: sort with nothing to seed the scan
|
|
133
|
+
tables.Advisor.search({
|
|
134
|
+
conditions: [],
|
|
135
|
+
sort: { attribute: 'lastName' },
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Seed a floor/sentinel condition on the sort attribute so the planner has an index
|
|
140
|
+
range to walk, then apply the sort:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
// ✓ floor condition on the sort attribute seeds the scan
|
|
144
|
+
tables.Advisor.search({
|
|
145
|
+
conditions: [
|
|
146
|
+
{ attribute: 'lastName', comparator: 'greater_than_equal', value: '' },
|
|
147
|
+
],
|
|
148
|
+
sort: { attribute: 'lastName' },
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Centralize this in the page helper (`searchPageAndCount`) rather than duplicating
|
|
153
|
+
a sentinel at each call site. The `harper-no-empty-conditions-with-sort` ast-grep
|
|
154
|
+
rule flags the crashing shape.
|
|
155
|
+
|
|
124
156
|
## Relationship queries
|
|
125
157
|
|
|
126
158
|
Relationship attributes can be queried with dot syntax when the relationship is
|
|
@@ -200,9 +232,14 @@ Useful query keys:
|
|
|
200
232
|
| `select` | Projection list. Keep admin-only fields out of public responses. |
|
|
201
233
|
| `explain` | Debug execution order and index usage while tuning. |
|
|
202
234
|
|
|
203
|
-
`search()` can return an `AsyncIterable`.
|
|
204
|
-
|
|
205
|
-
|
|
235
|
+
`search()` can return an `AsyncIterable`. An early `return`/`break` out of a
|
|
236
|
+
`for await (... of tables.X.search(...))` loop abandons the iterator before the
|
|
237
|
+
scan completes and **leaks the open read transaction** (verified on
|
|
238
|
+
harperdb 4.7.32). When iterating manually or stopping early, drain it or call the
|
|
239
|
+
iterator's `return()` in `finally` so Harper releases the read transaction — or
|
|
240
|
+
bound the query (`search({ ..., limit: 1 })`) when you only need the first row.
|
|
241
|
+
The `harper-no-early-return-in-search-loop` ast-grep rule flags early exits from
|
|
242
|
+
these loops:
|
|
206
243
|
|
|
207
244
|
```javascript
|
|
208
245
|
const iterator = tables.Products
|
|
@@ -260,6 +260,54 @@ Guidance:
|
|
|
260
260
|
webhook must bypass Harper auth, verify its signature first and keep its table
|
|
261
261
|
writes narrowly scoped.
|
|
262
262
|
|
|
263
|
+
## Session cookies are `SameSite=None` — guard state-changing POSTs
|
|
264
|
+
|
|
265
|
+
Harper hardcodes the session cookie's `SameSite=None` attribute on HTTPS. The
|
|
266
|
+
browser therefore attaches the session cookie to **cross-site** requests, so a
|
|
267
|
+
same-origin app cannot rely on `SameSite` to block CSRF on cookie-authenticated,
|
|
268
|
+
state-changing routes (`POST`/`PUT`/`PATCH`/`DELETE`).
|
|
269
|
+
|
|
270
|
+
Add an app-side origin check in the resource before mutating. Reject requests
|
|
271
|
+
whose `Origin` (or `Referer` fallback) is not an allowed origin:
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
const ALLOWED_ORIGINS = new Set([
|
|
275
|
+
'https://app.example.com',
|
|
276
|
+
]);
|
|
277
|
+
|
|
278
|
+
function requireSameOrigin(context) {
|
|
279
|
+
const headers = context.requestContext?.headers ?? context.headers;
|
|
280
|
+
const origin =
|
|
281
|
+
headers?.get?.('origin') ??
|
|
282
|
+
headers?.get?.('referer');
|
|
283
|
+
const ok =
|
|
284
|
+
typeof origin === 'string' &&
|
|
285
|
+
[...ALLOWED_ORIGINS].some((allowed) => origin.startsWith(allowed));
|
|
286
|
+
if (!ok) {
|
|
287
|
+
const error = new Error('Cross-origin request rejected');
|
|
288
|
+
error.statusCode = 403; // Harper reads statusCode, not status
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export class Watchlists extends tables.Watchlists {
|
|
294
|
+
static async post(target, data, context) {
|
|
295
|
+
requireSameOrigin(context);
|
|
296
|
+
// ...authorization and write
|
|
297
|
+
return super.post(target, await data, context);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Guidance:
|
|
303
|
+
|
|
304
|
+
- Call the check on every cookie-authenticated state-changing route. Read-only
|
|
305
|
+
`GET` handlers and token/`Authorization: Bearer` APIs (not cookie-driven) do
|
|
306
|
+
not need it.
|
|
307
|
+
- Prefer an allowlist of exact origins over substring matching that a lookalike
|
|
308
|
+
host could satisfy.
|
|
309
|
+
- This is defense the app owns; Harper will not add it for you.
|
|
310
|
+
|
|
263
311
|
## Verification matrix
|
|
264
312
|
|
|
265
313
|
Run the local app, create the test users, and check unauthenticated, reader, and
|
|
@@ -74,6 +74,41 @@ See [[harper-config-yaml]] for the extension wiring, [[harper-schema-graphql]] f
|
|
|
74
74
|
how the schema defines the tables resources extend, and [[harper-realtime]] when
|
|
75
75
|
`subscribe`, `publish`, or WebSocket behavior is part of the feature.
|
|
76
76
|
|
|
77
|
+
## Throwing HTTP status errors
|
|
78
|
+
|
|
79
|
+
Harper's thrown-error response writer reads **`error.statusCode`** (falling back
|
|
80
|
+
to `500`). A plain `error.status` is **ignored** — throw an error with only
|
|
81
|
+
`status` set and every intended `4xx` is served as a `500`. Verified on
|
|
82
|
+
harperdb 4.7.32.
|
|
83
|
+
|
|
84
|
+
Always set `statusCode` (keep `status` too only if callers or tests read it):
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
static async post(target, data, context) {
|
|
88
|
+
if (!context.user) {
|
|
89
|
+
const error = new Error('Authentication required');
|
|
90
|
+
error.statusCode = 401; // NOT `error.status` — Harper reads statusCode
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
// ...
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
A shared helper keeps every throw site correct:
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
function throwStatus(message, status) {
|
|
101
|
+
// Set both: `statusCode` is what Harper serves; `status` is kept for
|
|
102
|
+
// returned-response symmetry and any caller/test that reads it.
|
|
103
|
+
throw Object.assign(new Error(message), { status, statusCode: status });
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The `harper-require-statuscode-on-thrown-error` ast-grep rule flags errors that
|
|
108
|
+
carry `status` without `statusCode`. Pass `context` through to `super` and nested
|
|
109
|
+
table calls so authorization and the request transaction stay aligned — see
|
|
110
|
+
[[harper-rest-queries]] for context propagation and iterator draining.
|
|
111
|
+
|
|
77
112
|
## Project conventions (TS is source)
|
|
78
113
|
|
|
79
114
|
- **Write resources in TypeScript under `src/`. `harper-app/resources.js` is a
|
|
@@ -121,6 +121,38 @@ const products = await tables.Products.search({
|
|
|
121
121
|
});
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
## Sort with no conditions crashes the planner
|
|
125
|
+
|
|
126
|
+
A `search()` that carries a `sort` but an **empty `conditions: []`** array throws
|
|
127
|
+
at runtime — the planner cannot seed the btree scan from the sort attribute. This
|
|
128
|
+
is a latent `500` the first time an unfiltered, sorted collection page is
|
|
129
|
+
requested (verified on harperdb 4.7.32).
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
// ✗ crashes: sort with nothing to seed the scan
|
|
133
|
+
tables.Advisor.search({
|
|
134
|
+
conditions: [],
|
|
135
|
+
sort: { attribute: 'lastName' },
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Seed a floor/sentinel condition on the sort attribute so the planner has an index
|
|
140
|
+
range to walk, then apply the sort:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
// ✓ floor condition on the sort attribute seeds the scan
|
|
144
|
+
tables.Advisor.search({
|
|
145
|
+
conditions: [
|
|
146
|
+
{ attribute: 'lastName', comparator: 'greater_than_equal', value: '' },
|
|
147
|
+
],
|
|
148
|
+
sort: { attribute: 'lastName' },
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Centralize this in the page helper (`searchPageAndCount`) rather than duplicating
|
|
153
|
+
a sentinel at each call site. The `harper-no-empty-conditions-with-sort` ast-grep
|
|
154
|
+
rule flags the crashing shape.
|
|
155
|
+
|
|
124
156
|
## Relationship queries
|
|
125
157
|
|
|
126
158
|
Relationship attributes can be queried with dot syntax when the relationship is
|
|
@@ -200,9 +232,14 @@ Useful query keys:
|
|
|
200
232
|
| `select` | Projection list. Keep admin-only fields out of public responses. |
|
|
201
233
|
| `explain` | Debug execution order and index usage while tuning. |
|
|
202
234
|
|
|
203
|
-
`search()` can return an `AsyncIterable`.
|
|
204
|
-
|
|
205
|
-
|
|
235
|
+
`search()` can return an `AsyncIterable`. An early `return`/`break` out of a
|
|
236
|
+
`for await (... of tables.X.search(...))` loop abandons the iterator before the
|
|
237
|
+
scan completes and **leaks the open read transaction** (verified on
|
|
238
|
+
harperdb 4.7.32). When iterating manually or stopping early, drain it or call the
|
|
239
|
+
iterator's `return()` in `finally` so Harper releases the read transaction — or
|
|
240
|
+
bound the query (`search({ ..., limit: 1 })`) when you only need the first row.
|
|
241
|
+
The `harper-no-early-return-in-search-loop` ast-grep rule flags early exits from
|
|
242
|
+
these loops:
|
|
206
243
|
|
|
207
244
|
```javascript
|
|
208
245
|
const iterator = tables.Products
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.192.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.192.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.192.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.192.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.192.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|