@jskit-ai/agent-docs 0.1.292 → 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/authentication.md +19 -8
- package/guide/agent/app-setup/multi-homing.md +31 -3
- package/guide/agent/app-setup/upgrading-jskit.md +4 -0
- package/guide/agent/framework/client-features.md +54 -1
- 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 +45 -5
- package/package.json +1 -1
- package/patterns/feature-package/example/booking-engine/package.json +2 -2
- package/patterns/minimal-foundation/example/AGENTS.md +5 -1
- package/patterns/minimal-foundation/example/package.json +5 -5
- package/patterns/shell-foundation/example/AGENTS.md +5 -1
- package/patterns/shell-foundation/example/package.json +6 -6
- package/patterns/shell-foundation/example/packages/main/package.json +1 -1
- package/reference/autogen/PATTERN_INDEX.md +38 -38
- 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/kernel.md +7 -1
- 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 +19 -4
- package/reference/autogen/packages/workspaces-web.md +2 -1
- package/reference/autogen/tooling/config-eslint.md +44 -1
- package/reference/autogen/tooling/jskit-catalog.md +35 -13
- package/skills/jskit/references/client-features.md +54 -1
- 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 +31 -3
- package/skills/jskit/references/pattern-index.md +38 -38
- package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +5 -1
- package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +5 -5
- package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +5 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +6 -6
- package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
- package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +3 -3
- 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 +4 -4
- package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/package.json +3 -3
- package/skills/jskit/references/patterns/crud/crud-screen-set/PATTERN.md +12 -1
- package/skills/jskit/references/patterns/database/mysql-application/example/package.json +2 -2
- package/skills/jskit/references/patterns/database/postgres-application/example/package.json +2 -2
- package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +2 -2
- package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsPreferencesSection.vue +7 -0
- 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 +6 -6
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/workspaceMembersOnly.js +69 -17
- package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +9 -0
- package/skills/jskit/references/sanity-checks.md +4 -4
- package/skills/jskit/references/typed-features.md +45 -5
|
@@ -38,9 +38,13 @@ variant also names `visibility: "workspace"`: its routes are a workspace's,
|
|
|
38
38
|
so they resolve the caller's workspace and check its `crud.users.*`
|
|
39
39
|
permissions. The `users` table is public, every user's, so the variant also
|
|
40
40
|
narrows the repository to the workspace's members with `decorateRepository`
|
|
41
|
-
(`src/server/workspaceMembersOnly.js`): a list reads only the active members
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
(`src/server/workspaceMembersOnly.js`): a list reads only the active members,
|
|
42
|
+
and anyone else is a missing record, so a workspace admin never learns who else
|
|
43
|
+
has an account. Each page is one report over users.profiles' and
|
|
44
|
+
`workspaces.memberships`' join sources: the users the list's filters match that
|
|
45
|
+
hold an active membership, in the users list's order, a page at a time, so it
|
|
46
|
+
never reads the workspace's memberships whole; a view reads the one membership
|
|
47
|
+
with `findByWorkspaceIdAndUserId`. The narrowed
|
|
44
48
|
repository answers only `query`, `get` and `pageLimits`: users.profiles' other
|
|
45
49
|
methods (`findByEmail`, `countUsers`, the writes) read or write every user, so
|
|
46
50
|
they never pass through it.
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
"./shared": "./src/shared/index.js"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@jskit-ai/crud-core": "0.2.
|
|
14
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
15
|
-
"@jskit-ai/users-core": "0.2.
|
|
13
|
+
"@jskit-ai/crud-core": "0.2.24",
|
|
14
|
+
"@jskit-ai/resource-crud-core": "0.1.210",
|
|
15
|
+
"@jskit-ai/users-core": "0.2.25"
|
|
16
16
|
},
|
|
17
17
|
"jskit": {
|
|
18
18
|
"kind": "runtime",
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
"./shared": "./src/shared/index.js"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@jskit-ai/crud-core": "0.2.
|
|
14
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
15
|
-
"@jskit-ai/users-core": "0.2.
|
|
16
|
-
"@jskit-ai/workspaces-core": "0.2.
|
|
13
|
+
"@jskit-ai/crud-core": "0.2.24",
|
|
14
|
+
"@jskit-ai/resource-crud-core": "0.1.210",
|
|
15
|
+
"@jskit-ai/users-core": "0.2.25",
|
|
16
|
+
"@jskit-ai/workspaces-core": "0.2.25"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"@jskit-ai/kernel": "0.2.
|
|
20
|
-
"@jskit-ai/resource-runtime": "0.2.
|
|
19
|
+
"@jskit-ai/kernel": "0.2.18",
|
|
20
|
+
"@jskit-ai/resource-runtime": "0.2.21"
|
|
21
21
|
},
|
|
22
22
|
"jskit": {
|
|
23
23
|
"kind": "runtime",
|
|
@@ -1,43 +1,95 @@
|
|
|
1
1
|
import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
2
2
|
import { passedOn, resolveWorkspaceScopeValue } from "@jskit-ai/resource-runtime/server";
|
|
3
3
|
|
|
4
|
+
// The page size json-rest-api reads for a list that names none.
|
|
5
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
6
|
+
|
|
7
|
+
// A page's cursor: the display name, email and id of its last user, the users list's order, as base64url JSON.
|
|
8
|
+
function encodeCursor(row) {
|
|
9
|
+
return Buffer.from(JSON.stringify([row.displayName, row.email, String(row.id)])).toString("base64url");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function decodeCursor(cursor) {
|
|
13
|
+
try {
|
|
14
|
+
const [displayName, email, id] = JSON.parse(Buffer.from(String(cursor), "base64url").toString("utf8"));
|
|
15
|
+
if ([displayName, email, id].every((value) => typeof value === "string")) {
|
|
16
|
+
return { displayName, email, id };
|
|
17
|
+
}
|
|
18
|
+
} catch {
|
|
19
|
+
// Not a cursor this list answered: refused below.
|
|
20
|
+
}
|
|
21
|
+
throw new AppError(400, "This list's cursor is not one it answered.", { code: "users.list.cursorInvalid" });
|
|
22
|
+
}
|
|
23
|
+
|
|
4
24
|
/**
|
|
5
25
|
* users-core's users repository narrowed to the caller's workspace: a list reads only the workspace's active members,
|
|
6
26
|
* and anyone else is a missing record, so a workspace admin never learns who else has an account. The users table is
|
|
7
|
-
* public, so its handle alone would list every user
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
27
|
+
* public, so its handle alone would list every user. A page is one report over the users the list's filters match
|
|
28
|
+
* that hold an active membership in the workspace, in the users list's order, so it reads a page of rows however many
|
|
29
|
+
* members the workspace has; the users repository then reads that page's users as the CRUD contract answers them. It
|
|
30
|
+
* answers only the reads it narrows (query and get, with pageLimits): the rest of users.profiles, such as findByEmail
|
|
31
|
+
* or countUsers, never passes through this feature's repository, so nothing reaches users past the narrowing through it.
|
|
32
|
+
* @param {import("@jskit-ai/crud-core/server/types").CrudRepository & Pick<import("@jskit-ai/users-core/server/types").UsersProfiles, "joinSource">} repository
|
|
33
|
+
* @param {import("@jskit-ai/workspaces-core/server/types").WorkspacesMemberships} memberships
|
|
12
34
|
*/
|
|
13
35
|
function workspaceMembersOnly(repository, memberships) {
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
36
|
+
// Knex: report - one page of the users the list's filters match that are active members of the workspace, in the users list's order
|
|
37
|
+
async function memberPage(workspaceId, filters, { size, after }, options) {
|
|
38
|
+
const user = await repository.joinSource({ alias: "u", ...(filters === undefined ? {} : { filters }) }, options);
|
|
39
|
+
const member = await memberships.joinSource({ alias: "m", filters: { workspace: workspaceId, status: "active" } }, options);
|
|
40
|
+
const [shown, mail, key] = [user.column("displayName"), user.column("email"), user.column("id")];
|
|
41
|
+
const query = user.query
|
|
42
|
+
.whereExists(member.query.select(member.column("id")).whereColumn(member.column("userId"), key))
|
|
43
|
+
.select({ id: key, displayName: shown, email: mail })
|
|
44
|
+
.orderBy([shown, mail, key])
|
|
45
|
+
.limit(size + 1);
|
|
46
|
+
if (after !== undefined) {
|
|
47
|
+
const last = decodeCursor(after);
|
|
48
|
+
query.where((later) => later.where(shown, ">", last.displayName)
|
|
49
|
+
.orWhere((tie) => tie.where(shown, last.displayName).where(mail, ">", last.email))
|
|
50
|
+
.orWhere((tie) => tie.where(shown, last.displayName).where(mail, last.email).where(key, ">", last.id)));
|
|
51
|
+
}
|
|
52
|
+
const rows = await query;
|
|
53
|
+
return { ids: rows.slice(0, size).map((row) => String(row.id)), next: rows.length > size ? encodeCursor(rows[size - 1]) : null };
|
|
18
54
|
}
|
|
19
55
|
|
|
20
56
|
/**
|
|
21
|
-
* One page of the workspace's members the params select
|
|
57
|
+
* One page of the workspace's members the params select: the report picks the page's users, the users repository
|
|
58
|
+
* reads them, and the page's cursor is the report's.
|
|
22
59
|
* @param {Record<string, any>} params
|
|
23
60
|
* @param {{ context?: object }} options
|
|
24
61
|
*/
|
|
25
62
|
async function query(params, options) {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
63
|
+
const workspaceId = resolveWorkspaceScopeValue(options.context);
|
|
64
|
+
const size = Number(params.page?.size ?? DEFAULT_PAGE_SIZE);
|
|
65
|
+
const after = params.page?.after;
|
|
66
|
+
// A context with no workspace has no members: the page is empty.
|
|
67
|
+
const page = workspaceId
|
|
68
|
+
? await memberPage(workspaceId, params.filters, { size: Math.min(size, repository.pageLimits().request), after }, passedOn(options))
|
|
69
|
+
: { ids: [], next: null };
|
|
70
|
+
// An empty list matches no row. A size over the page limit throws here, as the users list's own read does.
|
|
71
|
+
const result = await repository.query({ ...params, filters: { id: page.ids }, page: { size } }, options);
|
|
72
|
+
const hasMore = page.next !== null;
|
|
73
|
+
if (options.format !== "jsonapi") {
|
|
74
|
+
return { records: result.records, page: { size, hasMore, next: page.next } };
|
|
75
|
+
}
|
|
76
|
+
// The users read's own links would name its id list; the page's cursor is the report's.
|
|
77
|
+
const { links: _links, ...document } = result;
|
|
78
|
+
return {
|
|
79
|
+
...document,
|
|
80
|
+
meta: { ...document.meta, pagination: { pageSize: size, hasMore, ...(hasMore ? { cursor: { next: page.next } } : {}) } }
|
|
81
|
+
};
|
|
31
82
|
}
|
|
32
83
|
|
|
33
84
|
/**
|
|
34
|
-
* A member of the workspace, or a missing record for anyone else.
|
|
85
|
+
* A member of the workspace, or a missing record for anyone else. It reads the one membership.
|
|
35
86
|
* @param {unknown} id
|
|
36
87
|
* @param {Record<string, any>} params
|
|
37
88
|
* @param {{ context?: object, missing?: "skip" }} options
|
|
38
89
|
*/
|
|
39
90
|
async function get(id, params, options) {
|
|
40
|
-
|
|
91
|
+
const membership = await memberships.findByWorkspaceIdAndUserId(resolveWorkspaceScopeValue(options.context), id, passedOn(options));
|
|
92
|
+
if (membership?.status === "active") {
|
|
41
93
|
return repository.get(id, params, options);
|
|
42
94
|
}
|
|
43
95
|
if (options.missing === "skip") {
|
|
@@ -74,6 +74,15 @@ external effects out of it. Without the capability, acceptance activates the
|
|
|
74
74
|
membership only. Allow ordinary invitations; reject stale, revoked, conflicting
|
|
75
75
|
or unexpectedly missing application associations.
|
|
76
76
|
|
|
77
|
+
Provide the optional `workspaces.workspace-created-participant` capability the
|
|
78
|
+
same way to write application records for every new workspace. The
|
|
79
|
+
`workspace.workspaces.create` action and personal workspace provisioning invoke
|
|
80
|
+
`fn({ workspace, owner }, txOptions)` after writing the workspace, its owner's
|
|
81
|
+
membership and its settings, in the creation's uncommitted transaction (the
|
|
82
|
+
caller's when it passes one). Pass `txOptions` on and propagate failures: a
|
|
83
|
+
failure rolls the whole creation back. An existing personal workspace does not
|
|
84
|
+
call it.
|
|
85
|
+
|
|
77
86
|
Call `members.prepareInvite(workspace, actor, payload, txOptions)` inside
|
|
78
87
|
`repositories.workspaceInvites.withTransaction(options, async (txOptions) => …)`
|
|
79
88
|
or another resource handle's `withTransaction`. It returns
|
|
@@ -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](data-access.md#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](data-access.md#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.
|
|
@@ -76,6 +76,31 @@ setup: ({ resources }) => ({ invoices: { list: (options) => resources.invoices.q
|
|
|
76
76
|
actions: ({ invoices }) => [{ id: "invoices.list", execute: () => invoices.list() }]
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
### Package manifest
|
|
80
|
+
|
|
81
|
+
**A package's manifest, `jskit.capabilities` in its `package.json`, lists
|
|
82
|
+
exactly what its features provide, and every capability they require beyond
|
|
83
|
+
the kernel's own (`runtime.actions`, `runtime.events`, `runtime.http`,
|
|
84
|
+
`runtime.bootstrap`) and those the package provides itself.** Optional
|
|
85
|
+
capabilities are not required. A requirement no feature has stays only when
|
|
86
|
+
the package needs it at install time, such as the database its migrations
|
|
87
|
+
need.
|
|
88
|
+
|
|
89
|
+
Why: start-up resolves every package's requirements from the manifests, and
|
|
90
|
+
refuses a manifest that disagrees with the package's features. Plain
|
|
91
|
+
`jskit check` loads each application package's features (the server providers
|
|
92
|
+
`jskit.runtime.server.providers` declares) and reports the drift before that
|
|
93
|
+
(`manifest/capabilities`), each capability to add or remove; it lists the
|
|
94
|
+
requirements no feature has for review.
|
|
95
|
+
|
|
96
|
+
```jsonc
|
|
97
|
+
// good: BooksFeature provides app.books and requires crud.contracts and runtime.actions
|
|
98
|
+
"capabilities": { "provides": ["app.books"], "requires": ["crud.contracts"] }
|
|
99
|
+
|
|
100
|
+
// bad: app.books missing from provides, crud.contracts from requires
|
|
101
|
+
"capabilities": { "provides": ["app.book"], "requires": [] }
|
|
102
|
+
```
|
|
103
|
+
|
|
79
104
|
### Capability types
|
|
80
105
|
|
|
81
106
|
**Each provided token has one `@typedef` in its package's `src/server/types.js`,
|
|
@@ -622,9 +647,9 @@ Why: a value with two defaults has two meanings, and a mode flag hides a
|
|
|
622
647
|
second method inside the first.
|
|
623
648
|
|
|
624
649
|
```js
|
|
625
|
-
// good
|
|
626
|
-
async function list(filters, options = {}) {
|
|
627
|
-
return invoices.
|
|
650
|
+
// good: one page, as a list shows it
|
|
651
|
+
async function list(filters, page, options = {}) {
|
|
652
|
+
return invoices.query({ filters, page }, options);
|
|
628
653
|
}
|
|
629
654
|
|
|
630
655
|
// bad
|
|
@@ -691,6 +716,11 @@ way (is this user an operator) is a member of its owner's capability.**
|
|
|
691
716
|
meaning of 0 and false before replacing a copy.
|
|
692
717
|
- A helper two packages of an application use lives in its shared package,
|
|
693
718
|
never inside one of them.
|
|
719
|
+
- A component's closure over its own state is not a helper: a function of at
|
|
720
|
+
most two statements in `<script setup>` whose every outside name is a
|
|
721
|
+
binding `<script setup>` declares (not an import or a global). Two pages
|
|
722
|
+
that reload their own resources the same way keep their own `reload()`;
|
|
723
|
+
sharing it would take a module that receives each page's state.
|
|
694
724
|
|
|
695
725
|
Why: the kernel's helpers fix one meaning of empty, and a copy brings its own.
|
|
696
726
|
|
|
@@ -741,6 +771,13 @@ handle's `queryAll(params, options)`. A `query()` names its page size, and
|
|
|
741
771
|
answers one page.** [Reads shaped for the screen](data-access.md#reads-shaped-for-the-screen)
|
|
742
772
|
gives the rules and examples.
|
|
743
773
|
|
|
774
|
+
### Narrow in the query
|
|
775
|
+
|
|
776
|
+
**A read asks for exactly the rows the code uses: its filter, sort, limit,
|
|
777
|
+
existence check and count go in the read, never in code over the rows it
|
|
778
|
+
answered.** [Narrow in the query](data-access.md#narrow-in-the-query)
|
|
779
|
+
gives the rules and examples.
|
|
780
|
+
|
|
744
781
|
### Lists read related rows in batches
|
|
745
782
|
|
|
746
783
|
**A read that builds a list fetches its related rows in batches, an id-list
|
|
@@ -1170,8 +1207,9 @@ Why: a comment that narrates history is wrong the day after it is written.
|
|
|
1170
1207
|
|
|
1171
1208
|
`npm run lint` runs ESLint with `@jskit-ai/config-eslint`, and `npm run
|
|
1172
1209
|
verify` runs `jskit check` (the package graph, navigation, [data
|
|
1173
|
-
access](data-access.md#exceptions)
|
|
1174
|
-
runs](#test-run-rules)), lint, the
|
|
1210
|
+
access](data-access.md#exceptions), [test
|
|
1211
|
+
runs](#test-run-rules) and [package manifests](#package-manifest)), lint, the
|
|
1212
|
+
tests and the build. The foundations'
|
|
1175
1213
|
`eslint.config.mjs` already holds a new
|
|
1176
1214
|
application's code to every check:
|
|
1177
1215
|
|
|
@@ -1241,6 +1279,7 @@ Every finding ends with a link to the section of this page that explains it.
|
|
|
1241
1279
|
| `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) |
|
|
1242
1280
|
| `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) |
|
|
1243
1281
|
| `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) |
|
|
1282
|
+
| `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) |
|
|
1244
1283
|
| `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) |
|
|
1245
1284
|
| `jskit/jsdoc-description-inside` | a `//` description directly above a `/** */` block | [Comments](#comments) |
|
|
1246
1285
|
| `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) |
|
|
@@ -1292,6 +1331,7 @@ if (typeof buildSerializer !== "function") {
|
|
|
1292
1331
|
| `filled by <repository>; convert with its supplier` | keeps a setter that another repository's code calls |
|
|
1293
1332
|
| `cross-repository slot <token>` | keeps a probe for a token another repository supplies |
|
|
1294
1333
|
| `shared resource:` | keeps client module state that every instance on the page shares on purpose ([client features](client-features.md#module-state)) |
|
|
1334
|
+
| `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`) |
|
|
1295
1335
|
| `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](data-access.md#exceptions)) |
|
|
1296
1336
|
|
|
1297
1337
|
One kind of probe gets one prefix and one wording everywhere, and a bare
|