@jskit-ai/agent-docs 0.1.293 → 0.1.294
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/guide/agent/app-extras/realtime.md +14 -4
- package/guide/agent/app-setup/multi-homing.md +12 -3
- package/guide/agent/framework/client-features.md +28 -0
- package/guide/agent/framework/crud-operations.md +7 -0
- package/guide/agent/framework/data-access.md +75 -9
- package/guide/agent/framework/sanity-checks.md +4 -4
- package/guide/agent/framework/typed-features.md +12 -3
- package/package.json +1 -1
- package/patterns/minimal-foundation/example/package.json +2 -2
- package/patterns/shell-foundation/example/package.json +3 -3
- package/reference/autogen/PATTERN_INDEX.md +35 -35
- package/reference/autogen/packages/agent.md +4 -2
- package/reference/autogen/packages/assistant-core.md +1 -1
- package/reference/autogen/packages/assistant-runtime.md +1 -0
- package/reference/autogen/packages/crud-core.md +2 -0
- package/reference/autogen/packages/http-web.md +9 -6
- package/reference/autogen/packages/realtime.md +1 -0
- package/reference/autogen/packages/resource-runtime.md +1 -0
- package/reference/autogen/packages/users-core.md +3 -0
- package/reference/autogen/packages/workspaces-core.md +11 -2
- package/reference/autogen/packages/workspaces-web.md +1 -1
- package/reference/autogen/tooling/config-eslint.md +26 -0
- package/reference/autogen/tooling/jskit-catalog.md +27 -13
- package/skills/jskit/references/client-features.md +28 -0
- package/skills/jskit/references/crud-operations.md +7 -0
- package/skills/jskit/references/data-access.md +75 -9
- package/skills/jskit/references/heuristics/INDEX.md +1 -1
- package/skills/jskit/references/heuristics/data-access.md +1 -0
- package/skills/jskit/references/heuristics/filters.md +14 -0
- package/skills/jskit/references/heuristics/server-search.md +2 -0
- package/skills/jskit/references/multi-homing.md +12 -3
- package/skills/jskit/references/pattern-index.md +35 -35
- package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +2 -2
- package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +3 -3
- package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +1 -1
- package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
- package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +2 -2
- package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/package.json +2 -2
- package/skills/jskit/references/patterns/crud/crud-screen-set/PATTERN.md +9 -0
- package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
- package/skills/jskit/references/patterns/users/user-administration-server/PATTERN.md +7 -3
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +3 -3
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +5 -5
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/workspaceMembersOnly.js +69 -17
- package/skills/jskit/references/sanity-checks.md +4 -4
- package/skills/jskit/references/typed-features.md +12 -3
|
@@ -103,10 +103,13 @@ audience contributor is registered and a target room is a user or workspace
|
|
|
103
103
|
room, when its actor's contributed rooms include a target room or it has not
|
|
104
104
|
signed in yet. A socket joins only its own actor's contributed rooms, so a
|
|
105
105
|
publish to `workspace:7` checks that workspace's members' sockets, not every
|
|
106
|
-
connected one. To decide, the publish
|
|
107
|
-
signed-in socket
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
connected one. To decide, the publish asks each contributor once about the
|
|
107
|
+
actors of every signed-in socket outside the target rooms, whatever their
|
|
108
|
+
number, naming only the target rooms' scopes, so a contributor reads only
|
|
109
|
+
those (a publish to `workspace:7` reads workspace 7's memberships among them,
|
|
110
|
+
and a publish to user rooms reads no memberships); meanwhile it looks up the
|
|
111
|
+
contributed rooms of the actors already inside, for their checks. Other
|
|
112
|
+
sockets are not checked for that publish; the 30-second check reaches them. Sockets whose authentication requests are identical, such as tabs of
|
|
110
113
|
one browser, share one `authenticateRequest()` call within a publish or a
|
|
111
114
|
30-second pass, and the sockets a publish or pass checks reuse that one lookup
|
|
112
115
|
of their contributed rooms; nothing is reused from one publish or pass to the
|
|
@@ -140,12 +143,19 @@ them in one query rather than one per actor: it returns, or resolves to, a `Map`
|
|
|
140
143
|
from actor id to that actor's event scopes (`{ kind: "user", id }`,
|
|
141
144
|
`{ kind: "workspace", id }` or `{ kind: "workspace_user", workspaceId, userId }`);
|
|
142
145
|
an actor it leaves out has none. A single actor is asked as a list of one.
|
|
146
|
+
A publish asking whether actors outside its rooms belong to them calls
|
|
147
|
+
`resolveActorsScopes(actorIds, { scopes })` with those rooms' scopes: the
|
|
148
|
+
contributor may read only those scopes, and realtime keeps only them of its
|
|
149
|
+
answer, so a contributor that ignores `scopes` still answers correctly.
|
|
143
150
|
Realtime turns each scope into the same room an `event_scope` audience targets. Sockets
|
|
144
151
|
join those rooms on connection and, on every revalidation, leave the contributed
|
|
145
152
|
rooms the actor no longer belongs to; rooms joined by anything else are left
|
|
146
153
|
alone. `@jskit-ai/workspaces-core` contributes the actors' active workspace
|
|
147
154
|
memberships this way when realtime is installed, with
|
|
148
155
|
`listActiveWorkspaceIdsByUserIds()`: one memberships query per 1,000 actors.
|
|
156
|
+
Asked about some scopes, it reads only the memberships of the workspaces they
|
|
157
|
+
name, with `listActiveWorkspaceIdsByUserIdsAmong()`, and none when they name
|
|
158
|
+
no workspace.
|
|
149
159
|
|
|
150
160
|
The authentication request includes parsed cookies, the `Host`, `Origin` and
|
|
151
161
|
`X-Forwarded-Proto` headers when present, and the socket address and TLS state.
|
|
@@ -196,12 +196,21 @@ method takes the call's `options` last and joins the transaction of the
|
|
|
196
196
|
| Capability | Methods |
|
|
197
197
|
| --- | --- |
|
|
198
198
|
| `workspaces` | `findById(id, options)`, `findBySlug(slug, options)` (a deleted workspace is not found, though its slug stays taken), `listSlugsIn`, `findPersonalByOwnerUserId`, `listForUserId(userId, options)`, `insert`, `updateById`, `lock(workspaceId, options)` |
|
|
199
|
-
| `workspaces.memberships` | `findByWorkspaceIdAndUserId`, `ensureOwnerMembership`, `upsertMembership(workspaceId, userId, patch, options)`, `addMember({ workspaceId, userId, roleSid }, options)`, `activeMemberIds(workspaceId, options)`, `listActiveByWorkspaceId`, `findActiveByWorkspaceIdAndUserId`, `listActiveWorkspaceIdsByUserId`, `listActiveWorkspaceIdsByUserIds` |
|
|
199
|
+
| `workspaces.memberships` | `findByWorkspaceIdAndUserId`, `ensureOwnerMembership`, `upsertMembership(workspaceId, userId, patch, options)`, `addMember({ workspaceId, userId, roleSid }, options)`, `activeMemberIds(workspaceId, options)`, `listActiveByWorkspaceId`, `findActiveByWorkspaceIdAndUserId`, `listActiveWorkspaceIdsByUserId`, `listActiveWorkspaceIdsByUserIds`, `listActiveWorkspaceIdsByUserIdsAmong(userIds, workspaceIds, options)`, `joinSource({ alias, filters }, options)` |
|
|
200
200
|
|
|
201
201
|
`addMember` makes the user an active member with the role (`member` when none
|
|
202
202
|
is given), adding the membership or reactivating it. `activeMemberIds` answers
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
every active member's user id, without their profiles: it reads all of the
|
|
204
|
+
workspace's active memberships, so its cost grows with the workspace, and so
|
|
205
|
+
do `listActiveByWorkspaceId`'s and
|
|
206
|
+
`workspaces.services.members.listMembers(workspace, options)`'s. Use them where
|
|
207
|
+
the work needs every member, such as a job, a delivery's recipients or a
|
|
208
|
+
realtime audience. A screen or list reads a page instead:
|
|
209
|
+
`workspaces.services.members.listMembersPage(workspace, { cursor, limit }, options)`
|
|
210
|
+
answers one page of active members by display name (`limit`, 20 when none)
|
|
211
|
+
with the `nextCursor` that asks for the next, null on the last, and an app's
|
|
212
|
+
own list of members pages them in SQL with one report over the memberships'
|
|
213
|
+
`joinSource`. `lock` takes
|
|
205
214
|
the workspace's row lock until the caller's transaction ends, so work that must
|
|
206
215
|
not interleave for one workspace takes turns; it runs only inside a
|
|
207
216
|
transaction, given its `txOptions`, and answers `{ id }`, or `null` when there
|
|
@@ -519,6 +519,33 @@ let attachmentClientSequence = 0;
|
|
|
519
519
|
let pendingDraft = "";
|
|
520
520
|
```
|
|
521
521
|
|
|
522
|
+
### Search on the server
|
|
523
|
+
|
|
524
|
+
**A search over a list's records runs on the server: the list's search
|
|
525
|
+
(`useList({ search: { enabled: true } })`) sends what the person types as `q`,
|
|
526
|
+
and the table resource defines `searchSchema.q`. An autocomplete over a list's
|
|
527
|
+
items or a lookup's options takes `no-filter` and sends the typed text with
|
|
528
|
+
`@update:search` (a lookup's `setLookupSearch`).**
|
|
529
|
+
|
|
530
|
+
Why: a list holds the page it loaded, so a search over its items misses every
|
|
531
|
+
row past that page.
|
|
532
|
+
|
|
533
|
+
```vue
|
|
534
|
+
<!-- good: the server searches every record -->
|
|
535
|
+
<SelectField v-model="formState.serviceId" searchable no-filter
|
|
536
|
+
:search="resolveLookupSearch('serviceId')"
|
|
537
|
+
@update:search="setLookupSearch('serviceId', $event)"
|
|
538
|
+
:items="resolveLookupItems('serviceId', { selectedValue: formState.serviceId })" />
|
|
539
|
+
|
|
540
|
+
<!-- bad: Vuetify searches only the options already loaded -->
|
|
541
|
+
<v-autocomplete v-model="serviceId" :items="services.items" />
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
```js
|
|
545
|
+
// bad: a search over the page the list loaded
|
|
546
|
+
const visible = computed(() => list.items.filter((service) => service.name.includes(search.value)));
|
|
547
|
+
```
|
|
548
|
+
|
|
522
549
|
## Helpers and sizes
|
|
523
550
|
|
|
524
551
|
### One helper per package
|
|
@@ -587,6 +614,7 @@ client-only rules below. The server's action and error rules do not apply.
|
|
|
587
614
|
| `jskit/released-listeners` | a listener, interval, observer or subscription the file starts and never releases | [Released listeners](#released-listeners) |
|
|
588
615
|
| `jskit/no-module-state` | a module-level `let`, `ref()` or `reactive()` | [Module state](#module-state) |
|
|
589
616
|
| `jskit/selection-field` | a `v-select`, `v-autocomplete` or `v-combobox` whose items are not fixed, and a `SelectField` whose items are not fixed that binds no `loading` | [Selection fields](#selection-fields) |
|
|
617
|
+
| `jskit/search-on-server` | a `computed()` that filters a list composable's items by a search ref; an autocomplete over a list's items or a lookup's options without `no-filter` or an `update:search` handler | [Search on the server](#search-on-the-server) |
|
|
590
618
|
| `vue/require-prop-types`, `vue/require-default-prop`, `vue/require-valid-default-prop`, `vue/no-required-prop-with-default` | a prop without a type, optional without a default, or both | [Collaborator props](#collaborator-props) |
|
|
591
619
|
| `vue/require-explicit-emits` | an emitted event the component does not declare | [Declared emits](#declared-emits) |
|
|
592
620
|
|
|
@@ -256,6 +256,13 @@ mapping in the resource; do not add page-local filtering or duplicate request
|
|
|
256
256
|
code. The full Books source example maps `q` to title, author and notes; narrow
|
|
257
257
|
it when adapting the example to fewer fields.
|
|
258
258
|
|
|
259
|
+
Every CRUD list also takes `id`, one record id or a list of them
|
|
260
|
+
(`filter[id]` on the JSON:API transport), and answers only those of its rows,
|
|
261
|
+
in the caller's scope. A picker whose selected values are not on the page it
|
|
262
|
+
loaded reads them this way, all in one request, instead of showing their raw
|
|
263
|
+
ids or reading pages until it finds them; `useCrudListFilterLookups()` does it
|
|
264
|
+
for a lookup filter's chips and options.
|
|
265
|
+
|
|
259
266
|
The browser page's route query and internal CRUD query use plain `q`. The
|
|
260
267
|
JSON:API HTTP transport uses `filter[q]`. Shared CRUD clients encode and decode
|
|
261
268
|
this automatically. Raw HTTP/API tests must send the transport key themselves:
|
|
@@ -65,7 +65,9 @@ A grouped `aggregate` answers its groups from one statement, at most
|
|
|
65
65
|
`pageLimits().request` of them (the resource's `queryMaxLimit`). When there
|
|
66
66
|
are more it throws a 400 coded `RESOURCE_GROUP_LIMIT` instead of answering
|
|
67
67
|
part of them. Narrow the filters, or read the groups in pages, `page: { size,
|
|
68
|
-
number }` with `number` from 1
|
|
68
|
+
number }` with `number` from 1. A page answers as a `query` page does, `{ rows,
|
|
69
|
+
page: { size, number, hasMore, next } }`, never a bare array that could pass
|
|
70
|
+
for every group: read the next while `page.hasMore`.
|
|
69
71
|
Page numbers are offsets, so read every page before work that changes what
|
|
70
72
|
the filters match.
|
|
71
73
|
|
|
@@ -715,12 +717,12 @@ takes first.
|
|
|
715
717
|
// good: the lock is the transaction's first read, so the bookings read after it are current
|
|
716
718
|
const reserve = (seatId, options) => seats.withTransaction(options, async (txOptions) => {
|
|
717
719
|
await seats.lock(seatId, { ...txOptions, fields: ["status"] });
|
|
718
|
-
const taken = await bookings.queryAll({ filters: { seatIds: [seatId] } }, txOptions);
|
|
719
|
-
return taken
|
|
720
|
+
const [taken] = await bookings.queryAll({ filters: { seatIds: [seatId] }, take: 1 }, txOptions);
|
|
721
|
+
return taken ? null : bookings.create({ seatId }, { ...txOptions, returning: "id" });
|
|
720
722
|
});
|
|
721
723
|
|
|
722
724
|
// bad: the read before the lock fixed the snapshot, so a booking committed while the lock waited is missed
|
|
723
|
-
const
|
|
725
|
+
const [booked] = await bookings.queryAll({ filters: { seatIds: [seatId] }, take: 1 }, txOptions);
|
|
724
726
|
await seats.lock(seatId, txOptions);
|
|
725
727
|
```
|
|
726
728
|
|
|
@@ -931,7 +933,11 @@ async function activeDays(memberIds, options) {
|
|
|
931
933
|
`page.number` reads with OFFSET, which slows as the rows grow, so anything
|
|
932
934
|
that grows pages with `page.after`. Each page is one statement, with no
|
|
933
935
|
count: never walk pages to reach one.
|
|
934
|
-
- A screen list never uses `queryAll
|
|
936
|
+
- A screen list never uses `queryAll`, and neither does a picker: it reads
|
|
937
|
+
one page searched on the server as the person types (`q`), narrowed in the
|
|
938
|
+
query by what it depends on (the parent record, `active`), and reads its
|
|
939
|
+
selected values past that page by id. Reading every row for a dropdown, or
|
|
940
|
+
narrowing a page in code, either grows with the table or misses rows.
|
|
935
941
|
- Related rows come by id list (`filters: { id: ids }`, or an `in` filter on
|
|
936
942
|
another field) or `include`, grouped in memory. Never a query per row, however
|
|
937
943
|
the loop is spelled (`for`, `map` with `Promise.all`, a helper called per
|
|
@@ -987,6 +993,66 @@ const { records: firstPage } = await subscriptions.query({ filters: { newsletter
|
|
|
987
993
|
|
|
988
994
|
Bad: a list read 100-row pages in a loop for each of 40 related sets.
|
|
989
995
|
|
|
996
|
+
## Narrow in the query
|
|
997
|
+
|
|
998
|
+
A read asks the store for exactly the rows the code uses. The filter, sort,
|
|
999
|
+
limit, existence check and count go in the read, never in code over the rows
|
|
1000
|
+
it answered: a read of every row then narrowed is slow, and a page or a `take`
|
|
1001
|
+
then narrowed is also wrong, since the row wanted can sit past it.
|
|
1002
|
+
|
|
1003
|
+
- Filter with the resource's searchSchema: a field
|
|
1004
|
+
(`filters: { status: "open" }`), an id list (`filters: { id: ids }`, or an
|
|
1005
|
+
`in` filter on another field), or `q` for typed text, whose `oneOf` names the
|
|
1006
|
+
fields it searches.
|
|
1007
|
+
- One row: a unique filter with `take: 1` or `page: { size: 1 }`, which also
|
|
1008
|
+
answers whether one exists. A count: `aggregate()`.
|
|
1009
|
+
- Never filter, find, slice or count the rows a read answered, nor skip some
|
|
1010
|
+
in a loop. Mapping them and grouping them by a key (`Map.groupBy`) narrow
|
|
1011
|
+
nothing, and neither does code that needs every row it read: rows handed on
|
|
1012
|
+
whole (returned, passed, stored, mapped or walked in full) with a subset
|
|
1013
|
+
derived beside them, and rows matched against other items in a loop or a
|
|
1014
|
+
callback (an in-memory join, one read instead of one per item).
|
|
1015
|
+
- A lookup field's picker and a list's Search box send what the person types
|
|
1016
|
+
as `q` to the server, which searches every row; `useList` does with
|
|
1017
|
+
`search: { enabled: true }`. The table resource defines `searchSchema.q`.
|
|
1018
|
+
Never search the page a list loaded in the browser: it misses every row past
|
|
1019
|
+
it.
|
|
1020
|
+
- A CRUD list refuses a query key it does not take with a 400
|
|
1021
|
+
(`crud.query.undeclaredKeys`). A list filter is declared in the feature's
|
|
1022
|
+
`listFilterQueryValidator` and the resource's searchSchema.
|
|
1023
|
+
- Prove a capped read with a test that seeds past the cap and makes the row
|
|
1024
|
+
the cap leaves out (the oldest, for a newest-first read) the one that
|
|
1025
|
+
matches: the read that asks for it finds it; a read narrowed in code does
|
|
1026
|
+
not.
|
|
1027
|
+
|
|
1028
|
+
```js
|
|
1029
|
+
// good: the store answers one module's sections
|
|
1030
|
+
const { records } = await sections.query({ filters: { moduleId }, sort: ["-createdAt"], page: { size: 50 } }, options);
|
|
1031
|
+
|
|
1032
|
+
// bad: the newest 200 sections of every module, then one module's kept in code
|
|
1033
|
+
const { records: recent } = await sections.query({ sort: ["-createdAt"], page: { size: 200 } }, options);
|
|
1034
|
+
const moduleSections = recent.filter((section) => section.moduleId === moduleId);
|
|
1035
|
+
|
|
1036
|
+
// good: one row says who holds a unique username
|
|
1037
|
+
const [holder] = await profiles.queryAll({ filters: { usernames: [username] }, take: 1 }, options);
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
`jskit/narrow-in-query` reports `.filter()`, `.find()`, `.some()`,
|
|
1041
|
+
`.every()`, `.slice()`, a lone `.length` and a loop that skips rows with
|
|
1042
|
+
`continue` over the rows a read answered in the same function, unless the
|
|
1043
|
+
code also uses those rows whole, runs the narrowing once per item of
|
|
1044
|
+
something else, read at most one row, or slices a read that asked for one row
|
|
1045
|
+
more than it keeps (`limit(size + 1)`) to learn whether more follow.
|
|
1046
|
+
`jskit/search-on-server` reports a client search over a list's loaded items.
|
|
1047
|
+
`jskit check` fails a lookup field or a CRUD list screen whose app table
|
|
1048
|
+
resource has no `searchSchema.q`, on the resource or its owns entry
|
|
1049
|
+
(`data/search-q`). Other code that needs every row a read answers says why on
|
|
1050
|
+
the line above the read or the narrowing:
|
|
1051
|
+
|
|
1052
|
+
```js
|
|
1053
|
+
// readable-check: allow narrow-in-query - every row: a conversation holds at most keep + 1 summaries, since each save deletes the older ones
|
|
1054
|
+
```
|
|
1055
|
+
|
|
990
1056
|
## What changed since
|
|
991
1057
|
|
|
992
1058
|
A page that catches up on a chat, a watch that acts on changes and a sync to
|
|
@@ -1082,10 +1148,10 @@ const SettlementFeature = defineFeature({
|
|
|
1082
1148
|
async function settleDue(options) {
|
|
1083
1149
|
const size = cohorts.pageLimits().request;
|
|
1084
1150
|
const due = [];
|
|
1085
|
-
for (let number = 1; ;
|
|
1086
|
-
const page = await cohorts.aggregate({ measures: { cohorts: { count: "id" } }, groupBy: ["workspaceId"], filters: { status: "finished" }, page: { size, number } }, options);
|
|
1087
|
-
due.push(...
|
|
1088
|
-
|
|
1151
|
+
for (let number = 1; number; ) {
|
|
1152
|
+
const { rows, page } = await cohorts.aggregate({ measures: { cohorts: { count: "id" } }, groupBy: ["workspaceId"], filters: { status: "finished" }, page: { size, number } }, options);
|
|
1153
|
+
due.push(...rows);
|
|
1154
|
+
number = page.next;
|
|
1089
1155
|
}
|
|
1090
1156
|
for (const { workspaceId } of due) {
|
|
1091
1157
|
await settleWorkspace({ context: systemContext("settle finished weekly leagues for every user").forWorkspace(workspaceId) });
|
|
@@ -67,13 +67,13 @@ expects the relationship id or an unselected field never gets it.
|
|
|
67
67
|
### Every row
|
|
68
68
|
|
|
69
69
|
- A handle's `query()` answers one page and refuses a missing or oversized
|
|
70
|
-
`page.size`, where json-rest-api would cut the list silently. A
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
`page.size`, where json-rest-api would cut the list silently. A delivery
|
|
71
|
+
reaching every row uses `queryAll()`; a picker reads a page searched with
|
|
72
|
+
`q` and its selected values by `id`
|
|
73
73
|
([Reads](/guide/framework/data-access#reads-shaped-for-the-screen)).
|
|
74
74
|
- A grouped `aggregate()` answers every group or throws
|
|
75
75
|
`RESOURCE_GROUP_LIMIT`; past `pageLimits().request` groups, page it with
|
|
76
|
-
`page: { size, number }`
|
|
76
|
+
`page: { size, number }` (answers `{ rows, page }`)
|
|
77
77
|
([The handle's calls](/guide/framework/data-access#the-handle-s-calls)).
|
|
78
78
|
- A lookup by a unique key matches at most one row: `get(id)`, or a `query()`
|
|
79
79
|
of page size 1.
|
|
@@ -652,9 +652,9 @@ Why: a value with two defaults has two meanings, and a mode flag hides a
|
|
|
652
652
|
second method inside the first.
|
|
653
653
|
|
|
654
654
|
```js
|
|
655
|
-
// good
|
|
656
|
-
async function list(filters, options = {}) {
|
|
657
|
-
return invoices.
|
|
655
|
+
// good: one page, as a list shows it
|
|
656
|
+
async function list(filters, page, options = {}) {
|
|
657
|
+
return invoices.query({ filters, page }, options);
|
|
658
658
|
}
|
|
659
659
|
|
|
660
660
|
// bad
|
|
@@ -776,6 +776,13 @@ handle's `queryAll(params, options)`. A `query()` names its page size, and
|
|
|
776
776
|
answers one page.** [Reads shaped for the screen](/guide/framework/data-access#reads-shaped-for-the-screen)
|
|
777
777
|
gives the rules and examples.
|
|
778
778
|
|
|
779
|
+
### Narrow in the query
|
|
780
|
+
|
|
781
|
+
**A read asks for exactly the rows the code uses: its filter, sort, limit,
|
|
782
|
+
existence check and count go in the read, never in code over the rows it
|
|
783
|
+
answered.** [Narrow in the query](/guide/framework/data-access#narrow-in-the-query)
|
|
784
|
+
gives the rules and examples.
|
|
785
|
+
|
|
779
786
|
### Lists read related rows in batches
|
|
780
787
|
|
|
781
788
|
**A read that builds a list fetches its related rows in batches, an id-list
|
|
@@ -1277,6 +1284,7 @@ Every finding ends with a link to the section of this page that explains it.
|
|
|
1277
1284
|
| `jskit/app-error-code` | an `AppError`, `DomainError`, `ConflictError` or `NotFoundError` (or a subclass the module declares) without its own code, and one code naming two sentences in a module | [Refusals carry their own code](#refusals-carry-their-own-code) |
|
|
1278
1285
|
| `jskit/no-patched-error` | `Object.assign(new Error(…), { statusCode })`, or an error builder's result given its status afterwards | [One construction per error](#one-construction-per-error) |
|
|
1279
1286
|
| `jskit/no-test-only-export` | an export whose name says only tests use it (`…ForTests`, `__reset…`; `__testables` aside) | [No test-only exports](#no-test-only-exports) |
|
|
1287
|
+
| `jskit/narrow-in-query` | `.filter()`, `.find()`, `.some()`, `.every()`, `.slice()`, a lone `.length` or a loop that skips with `continue`, over the rows a read in the same function answered and uses no other way | [Narrow in the query](#narrow-in-the-query) |
|
|
1280
1288
|
| `jskit/one-property-per-line` | two properties on one line in a returned `Object.freeze({ … })` or a `requires` / `optional` / `provides` map (`eslint --fix` splits them) | [Formatter](#formatter) |
|
|
1281
1289
|
| `jskit/jsdoc-description-inside` | a `//` description directly above a `/** */` block | [Comments](#comments) |
|
|
1282
1290
|
| `jskit/allow-comment-form` | a `readable-check: allow` reason without a prefix from the table below, or one that cites tests or the state of a project | [Exceptions](#exceptions) |
|
|
@@ -1328,6 +1336,7 @@ if (typeof buildSerializer !== "function") {
|
|
|
1328
1336
|
| `filled by <repository>; convert with its supplier` | keeps a setter that another repository's code calls |
|
|
1329
1337
|
| `cross-repository slot <token>` | keeps a probe for a token another repository supplies |
|
|
1330
1338
|
| `shared resource:` | keeps client module state that every instance on the page shares on purpose ([client features](/guide/framework/client-features#module-state)) |
|
|
1339
|
+
| `every row: <why>` | code needs every row a read answers and narrows them for its own use, such as a read the data itself keeps small (a conversation's few summaries) whose oldest ones it deletes (`jskit/narrow-in-query`) |
|
|
1331
1340
|
| `infrastructure: <what the module is>` | excuses a whole file whose job is data access itself from `jskit/no-direct-knex` or `jskit/no-transaction-reader`, one line per rule before its first line of code ([data access](/guide/framework/data-access#exceptions)) |
|
|
1332
1341
|
|
|
1333
1342
|
One kind of probe gets one prefix and one wording everywhere, and a bare
|
package/package.json
CHANGED
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"@vue/devtools-api": "^8.2.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@jskit-ai/config-eslint": "0.1.
|
|
53
|
-
"@jskit-ai/jskit-catalog": "0.2.
|
|
52
|
+
"@jskit-ai/config-eslint": "0.1.250",
|
|
53
|
+
"@jskit-ai/jskit-catalog": "0.2.46",
|
|
54
54
|
"@playwright/test": "1.61.1",
|
|
55
55
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
56
56
|
"eslint": "^10.8.0",
|
|
@@ -45,13 +45,13 @@
|
|
|
45
45
|
"vue-router": "^5.3.1",
|
|
46
46
|
"vuetify": "^4.3.0",
|
|
47
47
|
"@jskit-ai/http-runtime": "0.2.18",
|
|
48
|
-
"@jskit-ai/shell-web": "0.2.
|
|
48
|
+
"@jskit-ai/shell-web": "0.2.23",
|
|
49
49
|
"@fastify/fast-json-stringify-compiler": "^5.1.0",
|
|
50
50
|
"@vue/devtools-api": "^8.2.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@jskit-ai/config-eslint": "0.1.
|
|
54
|
-
"@jskit-ai/jskit-catalog": "0.2.
|
|
53
|
+
"@jskit-ai/config-eslint": "0.1.250",
|
|
54
|
+
"@jskit-ai/jskit-catalog": "0.2.46",
|
|
55
55
|
"@playwright/test": "1.61.1",
|
|
56
56
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
57
57
|
"eslint": "^10.8.0",
|