@jskit-ai/agent-docs 0.1.283 → 0.1.285
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/assistant.md +14 -2
- package/guide/agent/app-extras/realtime.md +38 -1
- package/guide/agent/app-setup/authentication.md +76 -14
- package/guide/agent/app-setup/console.md +8 -0
- package/guide/agent/app-setup/database-layer.md +23 -103
- package/guide/agent/app-setup/existing-application-migration.md +134 -72
- package/guide/agent/app-setup/multi-homing.md +94 -32
- package/guide/agent/app-setup/quickstart.md +1 -1
- package/guide/agent/app-setup/run-an-accounts-hub.md +1 -1
- package/guide/agent/app-setup/upgrading-jskit.md +17 -3
- package/guide/agent/app-setup/users.md +8 -5
- package/guide/agent/framework/application-operations.md +1 -1
- package/guide/agent/framework/crud-operations.md +63 -45
- package/guide/agent/framework/data-access.md +1207 -0
- package/guide/agent/framework/sanity-checks.md +26 -27
- package/guide/agent/framework/typed-features.md +117 -248
- package/guide/agent/index.md +2 -0
- package/package.json +6 -3
- package/patterns/feature-package/PATTERN.md +25 -17
- package/patterns/feature-package/example/booking-engine/package.json +1 -1
- package/patterns/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
- package/patterns/feature-package/example/variations/invoiceResource.js +15 -0
- package/patterns/feature-package/example/variations/invoiceRollupActions.js +4 -4
- package/patterns/feature-package/example/variations/ownedTableRepository.js +27 -0
- package/patterns/feature-package/example/variations/types.js +1 -1
- package/patterns/minimal-foundation/example/AGENTS.md +4 -0
- package/patterns/minimal-foundation/example/package.json +4 -4
- package/patterns/minimal-foundation/example/server.js +4 -1
- package/patterns/shell-foundation/example/AGENTS.md +4 -0
- package/patterns/shell-foundation/example/package.json +5 -5
- package/patterns/shell-foundation/example/packages/main/package.json +1 -1
- package/patterns/shell-foundation/example/server.js +4 -1
- package/reference/autogen/KERNEL_MAP.md +3 -1
- package/reference/autogen/PATTERN_INDEX.md +70 -59
- package/reference/autogen/README.md +1 -1
- package/reference/autogen/packages/agent-docs.md +7 -3
- package/reference/autogen/packages/agent-runtime.md +48 -11
- package/reference/autogen/packages/architecture-inspector.md +6 -2
- package/reference/autogen/packages/assistant-core.md +4 -4
- package/reference/autogen/packages/assistant-runtime.md +33 -13
- package/reference/autogen/packages/auth-provider-local-core.md +3 -1
- package/reference/autogen/packages/auth-provider-local-db-core.md +69 -14
- package/reference/autogen/packages/auth-provider-oidc-core.md +5 -1
- package/reference/autogen/packages/auth-provider-supabase-core.md +1 -1
- package/reference/autogen/packages/connectors-core.md +22 -2
- package/reference/autogen/packages/console-core.md +9 -5
- package/reference/autogen/packages/crud-core.md +45 -175
- package/reference/autogen/packages/database-runtime-mysql.md +0 -26
- package/reference/autogen/packages/database-runtime.md +11 -33
- package/reference/autogen/packages/http-runtime.md +3 -3
- package/reference/autogen/packages/http-web.md +12 -0
- package/reference/autogen/packages/kernel.md +27 -2
- package/reference/autogen/packages/oidc-issuer-core.md +4 -0
- package/reference/autogen/packages/payments-core.md +28 -6
- package/reference/autogen/packages/realtime.md +15 -6
- package/reference/autogen/packages/resource-core.md +32 -5
- package/reference/autogen/packages/resource-crud-core.md +30 -34
- package/reference/autogen/packages/resource-runtime.md +220 -0
- package/reference/autogen/packages/rewarded-core.md +16 -10
- package/reference/autogen/packages/shell-web.md +1 -0
- package/reference/autogen/packages/users-core.md +33 -14
- package/reference/autogen/packages/workspaces-core.md +17 -10
- package/reference/autogen/tooling/config-eslint.md +74 -0
- package/reference/autogen/tooling/jskit-catalog.md +50 -1
- package/reference/autogen/tooling/jskit-release.md +5 -0
- package/skills/jskit/SKILL.md +4 -1
- package/skills/jskit/references/app-operations.md +1 -1
- package/skills/jskit/references/crud-operations.md +62 -44
- package/skills/jskit/references/data-access.md +1202 -0
- package/skills/jskit/references/database-layer.md +411 -0
- package/skills/jskit/references/existing-application-migration.md +134 -72
- package/skills/jskit/references/heuristics/INDEX.md +6 -4
- package/skills/jskit/references/heuristics/crud-authoring.md +26 -15
- package/skills/jskit/references/heuristics/data-access.md +19 -0
- package/skills/jskit/references/heuristics/filters.md +6 -6
- package/skills/jskit/references/heuristics/row-policies.md +40 -16
- package/skills/jskit/references/heuristics/server-search.md +30 -42
- package/skills/jskit/references/heuristics/table-resource-fields.md +78 -0
- package/skills/jskit/references/heuristics/typed-features.md +3 -3
- package/skills/jskit/references/multi-homing.md +94 -32
- package/skills/jskit/references/pattern-index.md +70 -59
- package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +4 -0
- package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +4 -4
- package/skills/jskit/references/patterns/app/minimal-foundation/example/server.js +4 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +4 -0
- package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +5 -5
- package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/server.js +4 -1
- package/skills/jskit/references/patterns/assistant/assistant-surface/PATTERN.md +5 -2
- package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +3 -2
- package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +15 -0
- 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-contract/PATTERN.md +133 -0
- package/skills/jskit/references/patterns/crud/crud-contract/example/bookResource.js +74 -0
- package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/PATTERN.md +69 -37
- package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/package.json +5 -4
- package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/server/BooksFeature.js +13 -0
- package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/bookResource.js +74 -0
- package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/index.js +1 -0
- package/skills/jskit/references/patterns/crud/crud-screen-set/PATTERN.md +26 -8
- package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookEditPage.vue +2 -2
- package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookListPage.vue +2 -2
- package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookNewPage.vue +2 -2
- package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookViewPage.vue +3 -3
- package/skills/jskit/references/patterns/database/mysql-application/PATTERN.md +9 -4
- package/skills/jskit/references/patterns/database/mysql-application/example/package.json +2 -1
- package/skills/jskit/references/patterns/database/mysql-application/example/scripts/prepare-database.js +8 -0
- package/skills/jskit/references/patterns/database/postgres-application/PATTERN.md +8 -4
- package/skills/jskit/references/patterns/database/postgres-application/example/package.json +2 -1
- package/skills/jskit/references/patterns/database/postgres-application/example/scripts/prepare-database.js +8 -0
- package/skills/jskit/references/patterns/database/table-resource/PATTERN.md +120 -0
- package/skills/jskit/references/patterns/database/table-resource/example/orderResource.js +25 -0
- package/skills/jskit/references/patterns/realtime/realtime-application/PATTERN.md +7 -0
- package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
- package/skills/jskit/references/patterns/rewards/google-rewarded/PATTERN.md +1 -1
- package/skills/jskit/references/patterns/server/feature-package/PATTERN.md +25 -17
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
- package/skills/jskit/references/patterns/server/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
- package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceResource.js +15 -0
- package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceRollupActions.js +4 -4
- package/skills/jskit/references/patterns/server/feature-package/example/variations/ownedTableRepository.js +27 -0
- package/skills/jskit/references/patterns/server/feature-package/example/variations/types.js +1 -1
- package/skills/jskit/references/patterns/users/user-administration-server/PATTERN.md +63 -10
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +5 -4
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/server/UsersFeature.js +7 -5
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/index.js +1 -1
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userCrud.js +21 -0
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +11 -5
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/UsersWorkspaceFeature.js +14 -5
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/workspaceMembersOnly.js +52 -0
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/index.js +1 -1
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userCrud.js +21 -0
- package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +9 -9
- package/skills/jskit/references/patterns/workspaces/workspace-surfaces/PATTERN.md +16 -0
- package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/pages/home/workspaces/index.vue +2 -0
- package/skills/jskit/references/sanity-checks.md +27 -28
- package/skills/jskit/references/typed-features.md +117 -248
- package/patterns/feature-package/example/variations/customKnexRepository.js +0 -40
- package/reference/autogen/packages/json-rest-api-core.md +0 -66
- package/skills/jskit/references/heuristics/crud-repository-mapping.md +0 -140
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/server/BooksFeature.js +0 -13
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/bookResource.js +0 -83
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/index.js +0 -1
- package/skills/jskit/references/patterns/crud/resource-contract/PATTERN.md +0 -155
- package/skills/jskit/references/patterns/crud/resource-contract/example/bookResource.js +0 -87
- package/skills/jskit/references/patterns/server/feature-package/example/variations/customKnexRepository.js +0 -40
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userResource.js +0 -69
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userResource.js +0 -56
- /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/migrations/20260815000000_books.cjs +0 -0
- /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/src/server/types.js +0 -0
|
@@ -8,6 +8,9 @@ what to do, why in one line, and shows good and bad code. `npm run lint`
|
|
|
8
8
|
reports the rules a machine can see ([checks](#checks)); the others are
|
|
9
9
|
checked by reading. The `server/feature-package` pattern in the
|
|
10
10
|
[bundled source pattern index](pattern-index.md) is the smallest complete feature to copy.
|
|
11
|
+
Code that reads or writes a table starts from [data access](data-access.md):
|
|
12
|
+
the table resource and its owner feature come first, and this page shapes the
|
|
13
|
+
code around them.
|
|
11
14
|
|
|
12
15
|
The [typed features heuristic](heuristics/typed-features.md)
|
|
13
16
|
maps keywords and lint findings to the section that fixes them. Client code
|
|
@@ -39,11 +42,13 @@ it reports the call as exact instead of guessing from the method name.
|
|
|
39
42
|
### Feature files
|
|
40
43
|
|
|
41
44
|
**A `defineFeature` lives in `<Name>Feature.js`, its keys in one order, one
|
|
42
|
-
per line: `id, domain,
|
|
43
|
-
shutdown`.** A `defineProvider` lives in `<Name>Provider.js`.
|
|
45
|
+
per line: `id, domain, owns, systemScope, requires, optional, provides,
|
|
46
|
+
setup, actions, boot, shutdown`.** A `defineProvider` lives in `<Name>Provider.js`.
|
|
44
47
|
|
|
45
48
|
- `setup` builds each provided value with a named `create<Name>` factory from
|
|
46
|
-
a module of its own, never inline.
|
|
49
|
+
a module of its own, never inline. It returns exactly the keys `provides`
|
|
50
|
+
names, or boot refuses it; a value only `actions` needs is built in
|
|
51
|
+
`actions`, which receives the requires.
|
|
47
52
|
- `actions` is a method that returns `create<Name>Actions({ … })`.
|
|
48
53
|
- The hooks take `capabilities`, destructured and typed.
|
|
49
54
|
|
|
@@ -53,15 +58,13 @@ Why: every feature reads the same way, top to bottom.
|
|
|
53
58
|
// good
|
|
54
59
|
const InvoicesFeature = defineFeature({
|
|
55
60
|
id: "feature.invoices",
|
|
56
|
-
|
|
57
|
-
database: "runtime.database"
|
|
58
|
-
},
|
|
61
|
+
owns: [invoiceResource],
|
|
59
62
|
provides: {
|
|
60
63
|
invoices: "feature.invoices"
|
|
61
64
|
},
|
|
62
|
-
/** @param {{
|
|
63
|
-
setup({
|
|
64
|
-
return { invoices: createInvoices({
|
|
65
|
+
/** @param {{ resources: { invoices: import("@jskit-ai/resource-runtime/server/types").ResourceHandle } }} capabilities */
|
|
66
|
+
setup({ resources }) {
|
|
67
|
+
return { invoices: createInvoices({ invoices: resources.invoices }) };
|
|
65
68
|
},
|
|
66
69
|
actions({ invoices }) {
|
|
67
70
|
return createInvoiceActions({ invoices });
|
|
@@ -69,7 +72,7 @@ const InvoicesFeature = defineFeature({
|
|
|
69
72
|
});
|
|
70
73
|
|
|
71
74
|
// bad: the value built inline, the actions an inline array
|
|
72
|
-
setup: ({
|
|
75
|
+
setup: ({ resources }) => ({ invoices: { list: (options) => resources.invoices.queryAll({}, options) } }),
|
|
73
76
|
actions: ({ invoices }) => [{ id: "invoices.list", execute: () => invoices.list() }]
|
|
74
77
|
```
|
|
75
78
|
|
|
@@ -88,7 +91,8 @@ there.**
|
|
|
88
91
|
- Hand-write a contract only for a token with several implementations (such as
|
|
89
92
|
`auth.service`), in its owning package, with optional operations marked
|
|
90
93
|
optional.
|
|
91
|
-
- JSKIT's names: `
|
|
94
|
+
- JSKIT's names: `ResourceHandle`, `ResourceCallOptions`, `SystemContext`
|
|
95
|
+
(`@jskit-ai/resource-runtime/server/types`), `RuntimeKnex`,
|
|
92
96
|
`RuntimeKnexQueryBuilder`, `RuntimeRouter`, `RuntimeLogger`, `RuntimeEnv`,
|
|
93
97
|
`CrudService`, `CrudRepository`.
|
|
94
98
|
|
|
@@ -124,13 +128,13 @@ Why: the type is what turns a call into one step.
|
|
|
124
128
|
// good
|
|
125
129
|
/**
|
|
126
130
|
* The invoices the feature provides.
|
|
127
|
-
* @param {{
|
|
131
|
+
* @param {{ invoices: import("@jskit-ai/resource-runtime/server/types").ResourceHandle }} dependencies
|
|
128
132
|
*/
|
|
129
|
-
function createInvoices({
|
|
133
|
+
function createInvoices({ invoices }) {
|
|
130
134
|
|
|
131
135
|
// bad: untyped, read from a bag in the body
|
|
132
136
|
function createInvoices(dependencies) {
|
|
133
|
-
const
|
|
137
|
+
const invoices = dependencies.invoices;
|
|
134
138
|
```
|
|
135
139
|
|
|
136
140
|
### Required collaborators
|
|
@@ -268,6 +272,14 @@ filled after construction.**
|
|
|
268
272
|
saying so.
|
|
269
273
|
- No late-bound closures between two tokens of one feature: the consumer
|
|
270
274
|
requires the other token.
|
|
275
|
+
- `boot()` and `shutdown()` receive what the feature requires, as `setup()`
|
|
276
|
+
does, and its own setup's values second, as `outputs`:
|
|
277
|
+
`boot(capabilities, { outputs })`.
|
|
278
|
+
- A periodic job is such a capability; JSKIT ships no scheduler. `setup()`
|
|
279
|
+
builds the job, `boot()` starts its timer (`setInterval(runOnce,
|
|
280
|
+
everyMs).unref()`), `shutdown()` clears it and awaits a run in progress, and
|
|
281
|
+
tests call `runOnce()` itself. Every server runs the timer, so a run takes
|
|
282
|
+
its rows with `claim()` ([data access, Layer 4](data-access.md#layer-4-locks)).
|
|
271
283
|
|
|
272
284
|
Why: module state is shared by every runtime in the process and outlives
|
|
273
285
|
`shutdown()`.
|
|
@@ -277,8 +289,8 @@ Why: module state is shared by every runtime in the process and outlives
|
|
|
277
289
|
setup() {
|
|
278
290
|
return { jobs: createJobQueue() };
|
|
279
291
|
},
|
|
280
|
-
boot({
|
|
281
|
-
jobs.start();
|
|
292
|
+
boot(_capabilities, { outputs }) {
|
|
293
|
+
outputs.jobs.start();
|
|
282
294
|
},
|
|
283
295
|
|
|
284
296
|
// bad
|
|
@@ -303,14 +315,14 @@ setup({ events }) {
|
|
|
303
315
|
if (!config.remindersEnabled) return {};
|
|
304
316
|
return { reminders: createReminders({ events }) };
|
|
305
317
|
},
|
|
306
|
-
shutdown({
|
|
318
|
+
shutdown(_capabilities, { outputs }) {
|
|
307
319
|
if (!config.remindersEnabled) return;
|
|
308
|
-
return reminders.stop();
|
|
320
|
+
return outputs.reminders.stop();
|
|
309
321
|
}
|
|
310
322
|
|
|
311
323
|
// bad
|
|
312
|
-
shutdown({
|
|
313
|
-
return reminders?.stop?.();
|
|
324
|
+
shutdown(_capabilities, { outputs }) {
|
|
325
|
+
return outputs.reminders?.stop?.();
|
|
314
326
|
}
|
|
315
327
|
```
|
|
316
328
|
|
|
@@ -532,6 +544,44 @@ const actorId = context?.actor?.id || options.context?.user?.id || null;
|
|
|
532
544
|
|
|
533
545
|
## Services and repositories
|
|
534
546
|
|
|
547
|
+
### Resource first
|
|
548
|
+
|
|
549
|
+
**Every table has one table resource, and only its owner feature reads or
|
|
550
|
+
writes it, through the handle it lists in `owns`. A repository holds the
|
|
551
|
+
owner's queries on its handles (a plain CRUD feature's repository is the
|
|
552
|
+
handle itself); a service calls repositories and other features' services or
|
|
553
|
+
capabilities, and never builds a query or reaches another feature's table.** [Data access](data-access.md)
|
|
554
|
+
gives the five layers, from one record to reports.
|
|
555
|
+
|
|
556
|
+
Why: ownership, the row policy, validation and audits run only on the
|
|
557
|
+
resource's path; Knex in a repository skips all of them.
|
|
558
|
+
|
|
559
|
+
```js
|
|
560
|
+
// good
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* The owner's repository, on its handle.
|
|
564
|
+
* @param {{ receivals: import("@jskit-ai/resource-runtime/server/types").ResourceHandle }} handles
|
|
565
|
+
*/
|
|
566
|
+
function createReceivalsRepository({ receivals }) {
|
|
567
|
+
/**
|
|
568
|
+
* @param {string} id
|
|
569
|
+
* @param {string} arrivedAt
|
|
570
|
+
* @param {import("@jskit-ai/resource-runtime/server/types").ResourceCallOptions} options
|
|
571
|
+
*/
|
|
572
|
+
function markArrived(id, arrivedAt, options) {
|
|
573
|
+
return receivals.patch(id, { status: "arrived", arrivedAt }, options);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return Object.freeze({ markArrived });
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// bad
|
|
580
|
+
function markArrived(id, arrivedAt) {
|
|
581
|
+
return knex("receivals").where({ id }).update({ status: "arrived", arrived_at: arrivedAt });
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
535
585
|
### Validated input as is
|
|
536
586
|
|
|
537
587
|
**A method that receives an action's validated input takes it as it is: no
|
|
@@ -559,8 +609,8 @@ async function rename(input = {}, options = {}) {
|
|
|
559
609
|
it, destructure and use.**
|
|
560
610
|
|
|
561
611
|
- An options bag whose fields are optional by contract keeps `options = {}`,
|
|
562
|
-
with no `?.` or second `|| {}` on it. Pass it on as `options`, or
|
|
563
|
-
`
|
|
612
|
+
with no `?.` or second `|| {}` on it. Pass it on as `options`, or as the
|
|
613
|
+
`txOptions` that `withTransaction` hands its work inside a transaction.
|
|
564
614
|
- A parameter or bag no caller passes is dropped and its values written in
|
|
565
615
|
place. A helper's argument takes no default unless a real caller omits it,
|
|
566
616
|
and a default never comes before a required parameter.
|
|
@@ -573,13 +623,13 @@ second method inside the first.
|
|
|
573
623
|
|
|
574
624
|
```js
|
|
575
625
|
// good
|
|
576
|
-
async function list(
|
|
577
|
-
return
|
|
626
|
+
async function list(filters, options = {}) {
|
|
627
|
+
return invoices.queryAll({ filters }, options);
|
|
578
628
|
}
|
|
579
629
|
|
|
580
630
|
// bad
|
|
581
|
-
async function list(
|
|
582
|
-
const
|
|
631
|
+
async function list(filters = {}, options = {}) {
|
|
632
|
+
const context = options?.context || null;
|
|
583
633
|
}
|
|
584
634
|
async function save(invoice, skipAudit = false) {
|
|
585
635
|
```
|
|
@@ -594,8 +644,8 @@ map shows.
|
|
|
594
644
|
|
|
595
645
|
```js
|
|
596
646
|
// good
|
|
597
|
-
function createInvoices({
|
|
598
|
-
async function list(
|
|
647
|
+
function createInvoices({ invoices }) {
|
|
648
|
+
async function list(filters, options) { … }
|
|
599
649
|
async function send(input, options) { … }
|
|
600
650
|
return Object.freeze({
|
|
601
651
|
list,
|
|
@@ -661,180 +711,51 @@ helper, never by hand.**
|
|
|
661
711
|
|
|
662
712
|
| Fact | Reader |
|
|
663
713
|
| --- | --- |
|
|
664
|
-
| the call's transaction, or the client to query through | `resolveTransaction(options)`, `resolveRepoClient(knex, options)` (`@jskit-ai/database-runtime/shared`) |
|
|
665
|
-
| what a service hands its repository | `repositoryCallOptions(options)` (`@jskit-ai/crud-core/server/jsonApiService`), only at the repository call |
|
|
666
714
|
| the signed-in user | `resolveActor(context)`, `resolveActorId(context)` |
|
|
667
715
|
| a tagged result's records, or its document | `simplifyJsonApiResult(result)`, `jsonApiResultValue(result)` (`@jskit-ai/http-runtime/shared`) |
|
|
668
716
|
| a collection's next-page cursor | `jsonApiNextCursor(document)` (`@jskit-ai/http-runtime/shared`) |
|
|
669
717
|
| a created record's id | `resolveCrudJsonApiEntityIdFromResult(result)` (`@jskit-ai/crud-core/server/serviceEvents`) |
|
|
670
718
|
| a CRUD record change's event | `buildCrudRecordChangedEvent({ namespace, operation, entityId, audience })` (`@jskit-ai/crud-core/server/serviceEvents`) |
|
|
671
719
|
| the workspace a request asks for, before any action context | `requestedWorkspaceScope(request)` (`@jskit-ai/workspaces-core/server/support/workspaceRouteInput`) |
|
|
672
|
-
| a context's workspace id | `resolveWorkspaceScopeValue(context)` (`@jskit-ai/
|
|
720
|
+
| a context's workspace id | `resolveWorkspaceScopeValue(context)` (`@jskit-ai/resource-runtime/server`) |
|
|
673
721
|
|
|
674
|
-
|
|
675
|
-
|
|
722
|
+
A call's transaction is not a fact to read: pass `options` on, and inside a
|
|
723
|
+
transaction the `txOptions` that `withTransaction` hands its work
|
|
724
|
+
([Transactions](data-access.md#transactions)).
|
|
725
|
+
|
|
726
|
+
Why: each reader knows every place JSKIT keeps its fact (a tagged result's
|
|
727
|
+
wrapper, the actor in a context), and a hand read misses one.
|
|
676
728
|
|
|
677
729
|
```js
|
|
678
730
|
// good
|
|
679
|
-
const
|
|
731
|
+
const actorId = resolveActorId(context);
|
|
680
732
|
|
|
681
733
|
// bad
|
|
682
|
-
const
|
|
734
|
+
const actorId = context?.actor?.id || context?.auth?.userId || null;
|
|
683
735
|
```
|
|
684
736
|
|
|
685
737
|
### Every row
|
|
686
738
|
|
|
687
|
-
**A server-side
|
|
688
|
-
|
|
689
|
-
(
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
- json-rest-api answers a query that names no page with the resource's
|
|
693
|
-
default page, 20 rows, and says nothing about the rest. The CRUD
|
|
694
|
-
repository's `queryDocuments(query)` without a `limit` is the same first
|
|
695
|
-
page, and one with a `limit` above its `queryMaxLimit()` (100) throws,
|
|
696
|
-
because json-rest-api would answer only 100. A CRUD read of every record
|
|
697
|
-
uses the repository's `queryEveryDocument(query, options)`, which answers
|
|
698
|
-
one JSON:API document `{ data, included }` with no `limit`, `cursor` or
|
|
699
|
-
`sort`.
|
|
700
|
-
- The helper reads cursor pages of 1,000 rows (json-rest-api's server-only
|
|
701
|
-
`pageSizeLimit`; a resource whose `queryMaxLimit` is larger uses that) in id
|
|
702
|
-
order until none follows, keeping the call's transaction and context, so no
|
|
703
|
-
row is skipped or repeated. Each page pays the row policy and linkage
|
|
704
|
-
queries once, so 480 rows cost 3 statements, not 15. It sets the page and
|
|
705
|
-
sort itself; sort the rows it returns.
|
|
706
|
-
- A lookup by a unique key (an id, a slug, a token hash, a unique pair such
|
|
707
|
-
as workspace and user) matches at most one row and may read `rows[0]` of a
|
|
708
|
-
plain query. A read of the newest or first few rows names its own `sort`
|
|
709
|
-
and `page`.
|
|
710
|
-
- HTTP list routes keep the caller's page: their default and maximum page
|
|
711
|
-
sizes are public behaviour, not a server-side limit to raise.
|
|
712
|
-
|
|
713
|
-
Why: a list read without a page looks complete until the 21st row, then
|
|
714
|
-
silently drops the rest from every person picker, scope and delivery built on it.
|
|
715
|
-
|
|
716
|
-
```js
|
|
717
|
-
// good
|
|
718
|
-
const rows = await queryAllJsonRestCollectionRows(api.resources.workspaceMemberships, {
|
|
719
|
-
queryParams: { filters: { workspace: workspaceId, status: "active" }, include: ["user"] },
|
|
720
|
-
transaction: resolveTransaction(options),
|
|
721
|
-
format: "plain"
|
|
722
|
-
}, options.context);
|
|
723
|
-
|
|
724
|
-
// bad: the first 20 members only
|
|
725
|
-
const rows = extractJsonRestCollectionRows(await api.resources.workspaceMemberships.query({
|
|
726
|
-
queryParams: { filters: { workspace: workspaceId, status: "active" }, include: ["user"] },
|
|
727
|
-
transaction: resolveTransaction(options),
|
|
728
|
-
format: "plain"
|
|
729
|
-
}, createJsonRestContext(options.context)));
|
|
730
|
-
```
|
|
739
|
+
**A server-side read that needs every row it matches reads them with the
|
|
740
|
+
handle's `queryAll(params, options)`. A `query()` names its page size, and
|
|
741
|
+
answers one page.** [Reads shaped for the screen](data-access.md#reads-shaped-for-the-screen)
|
|
742
|
+
gives the rules and examples.
|
|
731
743
|
|
|
732
744
|
### Lists read related rows in batches
|
|
733
745
|
|
|
734
746
|
**A read that builds a list fetches its related rows in batches, an id-list
|
|
735
747
|
query per related table, and groups them in memory, so its query count follows
|
|
736
|
-
the tables and pages it reads, not the rows.**
|
|
737
|
-
|
|
738
|
-
- Collect the ids from the first read, then read each related table with an
|
|
739
|
-
id-list filter: a `searchSchema` entry
|
|
740
|
-
`{ type: "array", actualField: "<column>", filterOperator: "in" }`, read
|
|
741
|
-
through `queryAllJsonRestCollectionRows()`, or the resource's `include`.
|
|
742
|
-
Group the rows by id with `Map.groupBy()`.
|
|
743
|
-
- A count that grows with the rows means something runs per row:
|
|
744
|
-
`getDocumentById()`, `queryDocuments()`, a service `get` or another owner's
|
|
745
|
-
single-record read inside a loop over the list, however the loop is spelled
|
|
746
|
-
(`for`, `map` with `Promise.all`, a helper called per item), or a
|
|
747
|
-
single-record helper reused for a list.
|
|
748
|
-
- Work derived from a row (hashing, preparing a form, assembling a snapshot)
|
|
749
|
-
runs once per distinct input in the call, not once per row it appears in.
|
|
750
|
-
- A JSON:API read also costs a few lookup and visibility queries per page and
|
|
751
|
-
relationship field, and pages hold the resource's `queryMaxLimit` (100 rows
|
|
752
|
-
unless it sets one). That cost follows pages, so three hundred invoices take
|
|
753
|
-
a few more queries than ten, not three hundred more ([Query counts at
|
|
754
|
-
realistic sizes](#query-counts-at-realistic-sizes)).
|
|
755
|
-
|
|
756
|
-
Why: each JSKIT single-record read runs its lookups and row policy too, so a
|
|
757
|
-
read per row repeats all of them.
|
|
758
|
-
|
|
759
|
-
```js
|
|
760
|
-
// good: a read per related table, however many invoices there are
|
|
761
|
-
const invoiceRows = await queryAllJsonRestCollectionRows(api.resources.invoices, {
|
|
762
|
-
queryParams: { filters: { workspace: workspaceId } },
|
|
763
|
-
transaction: resolveTransaction(options),
|
|
764
|
-
format: "plain"
|
|
765
|
-
}, options.context);
|
|
766
|
-
const lineRows = await queryAllJsonRestCollectionRows(api.resources.invoiceLines, {
|
|
767
|
-
queryParams: { filters: { invoiceIds: invoiceRows.map((invoice) => invoice.id) } },
|
|
768
|
-
transaction: resolveTransaction(options),
|
|
769
|
-
format: "plain"
|
|
770
|
-
}, options.context);
|
|
771
|
-
const linesByInvoice = Map.groupBy(lineRows, (line) => line.invoiceId);
|
|
772
|
-
return invoiceRows.map((invoice) => ({ ...invoice, lines: linesByInvoice.get(invoice.id) || [] }));
|
|
773
|
-
|
|
774
|
-
// bad: one more query, and its lookups and row policy, for every invoice
|
|
775
|
-
for (const invoice of invoiceRows) {
|
|
776
|
-
invoice.lines = await invoiceLines.queryDocuments({ invoiceId: invoice.id }, options);
|
|
777
|
-
}
|
|
778
|
-
```
|
|
748
|
+
the tables and pages it reads, not the rows.** [Reads shaped for the
|
|
749
|
+
screen](data-access.md#reads-shaped-for-the-screen) shows how.
|
|
779
750
|
|
|
780
751
|
### Bounded access checks
|
|
781
752
|
|
|
782
753
|
**A row policy costs what the rows its query reads cost, never what the
|
|
783
754
|
subject's stored history costs, and a write decides access once instead of
|
|
784
|
-
paying a read policy for every row it writes.**
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
loads everything the subject was ever linked to (every source, notice or
|
|
789
|
-
grant in their history) to build an `IN` list, and never calls another
|
|
790
|
-
owner's access check once per item of that history. The
|
|
791
|
-
[row policies heuristic](heuristics/row-policies.md)
|
|
792
|
-
has the policy contract and its package composition.
|
|
793
|
-
- The policy runs on every single-record read, and a CRUD write includes one:
|
|
794
|
-
`createDocument()` and `patchDocumentById()` default to `returning: "full"`,
|
|
795
|
-
which reads the record back through the policy, and `patch` and `delete`
|
|
796
|
-
first look the record up through it.
|
|
797
|
-
- A write path decides access once, inside its transaction. A write that does
|
|
798
|
-
not use its result passes `returning: "minimal"` or `"none"`, and a batch of
|
|
799
|
-
creates (a delivery, an import, a fan-out) goes through
|
|
800
|
-
`createDocuments(payloads, { trx, context })`, which reads nothing back
|
|
801
|
-
unless asked
|
|
802
|
-
([writes that return less](https://mobily-enterprises.github.io/jskit-ai/guide/app-setup/database-layer#writes-that-return-less)).
|
|
803
|
-
Any other batch whose per-row lookup would pay an expensive policy writes
|
|
804
|
-
through a repository method added with `decorateRepository`, on
|
|
805
|
-
`resolveRepoClient(database.knex, options)`, setting the same columns the
|
|
806
|
-
resource would, its ownership columns included.
|
|
807
|
-
- Its test counts the queries of one read and one write for a subject with a
|
|
808
|
-
short history and for one with a long one ([Query counts at realistic
|
|
809
|
-
sizes](#query-counts-at-realistic-sizes)).
|
|
810
|
-
|
|
811
|
-
Why: the row policy runs on every read, write read-back and lookup, so every
|
|
812
|
-
request pays what it costs.
|
|
813
|
-
|
|
814
|
-
```js
|
|
815
|
-
// good: one predicate on the row being read
|
|
816
|
-
function noticeVisibilityPolicy({ query, context, column, value, db }) {
|
|
817
|
-
const userId = resolveActorId(context);
|
|
818
|
-
if (!userId) return false;
|
|
819
|
-
query.where(column("recipientId"), value("recipientId", userId)).whereExists(
|
|
820
|
-
db("sources").select(1)
|
|
821
|
-
.whereColumn("sources.id", column("sourceId"))
|
|
822
|
-
.where("sources.status", "published")
|
|
823
|
-
);
|
|
824
|
-
return true;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// bad: every source the recipient was ever notified about, checked one by one
|
|
828
|
-
async function noticeVisibilityPolicy({ query, context, column, value }) {
|
|
829
|
-
const sourceIds = await noticeHistory.listSourceIds(resolveActorId(context));
|
|
830
|
-
const readable = [];
|
|
831
|
-
for (const sourceId of sourceIds) {
|
|
832
|
-
if (await sources.canRead(sourceId, context)) readable.push(sourceId);
|
|
833
|
-
}
|
|
834
|
-
query.whereIn(column("sourceId"), readable.map((id) => value("sourceId", id)));
|
|
835
|
-
return true;
|
|
836
|
-
}
|
|
837
|
-
```
|
|
755
|
+
paying a read policy for every row it writes.** [Layer
|
|
756
|
+
1](data-access.md#layer-1-the-table-resource) has the policy's
|
|
757
|
+
shape and [Layer 3](data-access.md#layer-3-many-rows-at-once)
|
|
758
|
+
the batch writes.
|
|
838
759
|
|
|
839
760
|
### Routes
|
|
840
761
|
|
|
@@ -846,7 +767,9 @@ for anything that reads or writes the domain.**
|
|
|
846
767
|
- One handler shape: a named async function that sends what
|
|
847
768
|
`request.executeAction({ actionId: ACTION_X, input })` returns.
|
|
848
769
|
- A route that bypasses actions (a provider's callback) gets its own named
|
|
849
|
-
registrar
|
|
770
|
+
registrar and reads with `await request.callOptions()`
|
|
771
|
+
([data access](data-access.md#layer-1-the-table-resource)).
|
|
772
|
+
Summaries are sentences.
|
|
850
773
|
- A route that repetition can abuse (sign-in, codes, sending email) declares
|
|
851
774
|
`rateLimit: { max, timeWindow }`, which the kernel enforces per client
|
|
852
775
|
address and refuses with 429, `kernel.http.rateLimited` and `Retry-After`.
|
|
@@ -864,7 +787,7 @@ router.register("POST", "/api/invoices/:invoiceId/send", { auth: "required", met
|
|
|
864
787
|
});
|
|
865
788
|
|
|
866
789
|
// bad: the route reads storage itself
|
|
867
|
-
router.register("GET", "/api/invoices", {}, async (request, reply) => reply.send(await
|
|
790
|
+
router.register("GET", "/api/invoices", {}, async (request, reply) => reply.send(await invoices.queryAll({}, { context: request })));
|
|
868
791
|
```
|
|
869
792
|
|
|
870
793
|
## Errors
|
|
@@ -974,7 +897,7 @@ change with its package.
|
|
|
974
897
|
|
|
975
898
|
```js
|
|
976
899
|
// good
|
|
977
|
-
const invoices = createInvoices({
|
|
900
|
+
const invoices = createInvoices({ invoices: resources.invoices, mailer: { send: async () => ({ accepted: true }) } });
|
|
978
901
|
|
|
979
902
|
// bad: production code that exists for the double
|
|
980
903
|
if (typeof mailer.send !== "function") return;
|
|
@@ -1025,72 +948,10 @@ export function resetInvoiceCacheForTests() {
|
|
|
1025
948
|
|
|
1026
949
|
### Query counts at realistic sizes
|
|
1027
950
|
|
|
1028
|
-
**A list, batch or write path's database test
|
|
1029
|
-
`
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
- `countQueries(knex, work)`, from
|
|
1033
|
-
`@jskit-ai/database-runtime/server/testDatabases`, runs `work` and returns
|
|
1034
|
-
its `result`, the `count` of statements the Knex instance or transaction
|
|
1035
|
-
sent meanwhile (a transaction's `BEGIN` and `COMMIT` included) and their
|
|
1036
|
-
SQL in `queries`. Pass the Knex the path runs on: the one the test built its
|
|
1037
|
-
repository or service with.
|
|
1038
|
-
- A route is counted through the server the test boots in its own process
|
|
1039
|
-
(`createServer()`, then `app.inject()`): `countRuntimeQueries(work)`, from
|
|
1040
|
-
the same module, records what the app's own `runtime.database` sends while
|
|
1041
|
-
`work` runs, and leaves out the test's own Knex. A server in another process
|
|
1042
|
-
is out of its reach, so the route's guard boots it in the test.
|
|
1043
|
-
- Pages add a few queries; rows should add none. A path that pages nothing
|
|
1044
|
-
(one read, one write) compares a short and a long history.
|
|
1045
|
-
- When the counts differ more than the pages explain, read `queries`: the
|
|
1046
|
-
statement that repeats once per row is the one to batch. Judge the
|
|
1047
|
-
comparison by what the path has to read; never tune a fixed ceiling until
|
|
1048
|
-
it passes.
|
|
1049
|
-
- When it fails, fix the reads ([Lists read related rows in
|
|
1050
|
-
batches](#lists-read-related-rows-in-batches), [Bounded access
|
|
1051
|
-
checks](#bounded-access-checks)).
|
|
1052
|
-
|
|
1053
|
-
Why: JSON:API reads add queries per page and relationship field, so comparing
|
|
1054
|
-
two sizes separates that cost from a read per row, where one fixed number
|
|
1055
|
-
cannot.
|
|
1056
|
-
|
|
1057
|
-
```js
|
|
1058
|
-
// good
|
|
1059
|
-
import { countQueries, countRuntimeQueries } from "@jskit-ai/database-runtime/server/testDatabases";
|
|
1060
|
-
|
|
1061
|
-
test("the invoice list's queries follow its pages, not its invoices", async () => {
|
|
1062
|
-
const listAt = async (count) => {
|
|
1063
|
-
const workspace = await createWorkspace(knex);
|
|
1064
|
-
await seedInvoices(knex, { workspace, count, linesEach: 5 });
|
|
1065
|
-
return countQueries(knex, () => invoices.listWithLines(workspace, { context }));
|
|
1066
|
-
};
|
|
1067
|
-
const few = await listAt(10);
|
|
1068
|
-
const many = await listAt(300);
|
|
1069
|
-
// Thirty times the invoices may add pages; a read per invoice would add about 290 queries.
|
|
1070
|
-
assert.ok(many.count <= few.count * 2,
|
|
1071
|
-
`${few.count} queries for 10 invoices, ${many.count} for 300:\n${many.queries.join("\n")}`);
|
|
1072
|
-
});
|
|
1073
|
-
|
|
1074
|
-
// good: the same comparison for the route, through the server this test booted
|
|
1075
|
-
test("GET /api/invoices reads per page, not per invoice", async () => {
|
|
1076
|
-
const app = await createServer({ runtimeEnv });
|
|
1077
|
-
const listAt = async (count) => {
|
|
1078
|
-
const workspace = await createWorkspace(knex);
|
|
1079
|
-
await seedInvoices(knex, { workspace, count, linesEach: 5 });
|
|
1080
|
-
return countRuntimeQueries(() => app.inject({ url: invoicesUrl(workspace), headers: signedIn }));
|
|
1081
|
-
};
|
|
1082
|
-
const few = await listAt(10);
|
|
1083
|
-
const many = await listAt(300);
|
|
1084
|
-
assert.ok(many.count <= few.count * 2,
|
|
1085
|
-
`${few.count} queries for 10 invoices, ${many.count} for 300:\n${many.queries.join("\n")}`);
|
|
1086
|
-
});
|
|
1087
|
-
|
|
1088
|
-
// bad: three invoices, nothing counted, so a read per invoice passes
|
|
1089
|
-
test("the invoice list has its lines", async () => {
|
|
1090
|
-
await seedInvoices(knex, { workspace, count: 3, linesEach: 1 });
|
|
1091
|
-
assert.equal((await invoices.listWithLines(workspace, { context })).length, 3);
|
|
1092
|
-
});
|
|
1093
|
-
```
|
|
951
|
+
**A list, batch or write path's database test proves with
|
|
952
|
+
`expectFlatQueries()` that its statements follow the tables and pages it
|
|
953
|
+
reads, not the rows.** [Tests](data-access.md#tests) gives the
|
|
954
|
+
one-line test.
|
|
1094
955
|
|
|
1095
956
|
### Test files run in parallel
|
|
1096
957
|
|
|
@@ -1127,7 +988,11 @@ and Vitest's `maxWorkers` come from `testConcurrency()`.**
|
|
|
1127
988
|
spec file's tests run in order in one worker. With `PLAYWRIGHT_BASE_URL`, the
|
|
1128
989
|
tests use that managed target as its host serves it. Every run is bounded by
|
|
1129
990
|
a `globalTimeout` (20 minutes, or the suite's own measured bound) and ends
|
|
1130
|
-
with its 10 slowest tests.
|
|
991
|
+
with its 10 slowest tests. Each action and navigation is bounded too
|
|
992
|
+
(`actionTimeout` 15 s, `navigationTimeout` 30 s): a stale locator should
|
|
993
|
+
fail in seconds, not minutes, so a test waiting on something that never
|
|
994
|
+
comes fails with that action's own error. An app sets a longer bound only
|
|
995
|
+
for a measured need, never 0. With a `signIn` module, each worker's server is
|
|
1131
996
|
signed in once and every test of that worker starts signed in, instead of
|
|
1132
997
|
each test signing in. `jskit check` fails a webServer that runs the development server
|
|
1133
998
|
(`npm run develop`, `vite`), one webServer shared by every worker, and a
|
|
@@ -1304,7 +1169,8 @@ Why: a comment that narrates history is wrong the day after it is written.
|
|
|
1304
1169
|
### Running the checks
|
|
1305
1170
|
|
|
1306
1171
|
`npm run lint` runs ESLint with `@jskit-ai/config-eslint`, and `npm run
|
|
1307
|
-
verify` runs `jskit check` (the package graph, navigation
|
|
1172
|
+
verify` runs `jskit check` (the package graph, navigation, [data
|
|
1173
|
+
access](data-access.md#exceptions) and [test
|
|
1308
1174
|
runs](#test-run-rules)), lint, the tests and the build. The foundations'
|
|
1309
1175
|
`eslint.config.mjs` already holds a new
|
|
1310
1176
|
application's code to every check:
|
|
@@ -1345,7 +1211,9 @@ export default [
|
|
|
1345
1211
|
- `formatterConfig(globs)` is the formatter.
|
|
1346
1212
|
- `baseConfig` reports the `jskit/*` rules as warnings on any
|
|
1347
1213
|
`**/src/server/**`, so an existing application sees them before it lists a
|
|
1348
|
-
package; it lists each package as it converts it.
|
|
1214
|
+
package; it lists each package as it converts it. It makes the
|
|
1215
|
+
[data-access rules](data-access.md#hard-rules) errors in all
|
|
1216
|
+
code but a package's `src/client`, tests, migrations and seeds.
|
|
1349
1217
|
- `clientFeaturesConfig(globs)` holds client code to the same rules; see
|
|
1350
1218
|
[client features](client-features.md#running-the-checks).
|
|
1351
1219
|
|
|
@@ -1424,6 +1292,7 @@ if (typeof buildSerializer !== "function") {
|
|
|
1424
1292
|
| `filled by <repository>; convert with its supplier` | keeps a setter that another repository's code calls |
|
|
1425
1293
|
| `cross-repository slot <token>` | keeps a probe for a token another repository supplies |
|
|
1426
1294
|
| `shared resource:` | keeps client module state that every instance on the page shares on purpose ([client features](client-features.md#module-state)) |
|
|
1295
|
+
| `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)) |
|
|
1427
1296
|
|
|
1428
1297
|
One kind of probe gets one prefix and one wording everywhere, and a bare
|
|
1429
1298
|
`typeof x` guard of that kind carries the same comment even where no rule
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { createWithTransaction } from "@jskit-ai/database-runtime/shared";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The invoice rollup's persistence: a reviewed custom Knex repository.
|
|
5
|
-
* @param {{ knex: import("@jskit-ai/database-runtime/server/types").RuntimeKnex }} dependencies
|
|
6
|
-
*/
|
|
7
|
-
function createInvoiceRollup({ knex }) {
|
|
8
|
-
const withTransaction = createWithTransaction(knex);
|
|
9
|
-
|
|
10
|
-
async function getStatus(input, options = {}) {
|
|
11
|
-
return {
|
|
12
|
-
ok: true,
|
|
13
|
-
feature: "invoice-rollup",
|
|
14
|
-
persistence: "custom-knex",
|
|
15
|
-
tableName: "invoice_rollups",
|
|
16
|
-
hasTransaction: Boolean(options.trx),
|
|
17
|
-
input
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function execute(input, options = {}) {
|
|
22
|
-
return {
|
|
23
|
-
accepted: false,
|
|
24
|
-
feature: "invoice-rollup",
|
|
25
|
-
persistence: "custom-knex",
|
|
26
|
-
tableName: "invoice_rollups",
|
|
27
|
-
hasTransaction: Boolean(options.trx),
|
|
28
|
-
input,
|
|
29
|
-
message: "Replace this example with the reviewed domain-specific query."
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return Object.freeze({
|
|
34
|
-
withTransaction,
|
|
35
|
-
getStatus,
|
|
36
|
-
execute
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export { createInvoiceRollup };
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# packages/json-rest-api-core
|
|
2
|
-
|
|
3
|
-
Generated by `npm run agent-docs:build`.
|
|
4
|
-
Do not edit manually.
|
|
5
|
-
|
|
6
|
-
Generated inventory for `packages/json-rest-api-core`.
|
|
7
|
-
Use this on demand; do not load the full index at startup.
|
|
8
|
-
|
|
9
|
-
## Scope
|
|
10
|
-
- Source: `packages/json-rest-api-core/**/*{.js,.mjs,.cjs,.vue}`
|
|
11
|
-
- Excludes: `.build/`, `.git/`, `.jscpd/`, `.jskit/`, `coverage/`, `dist/`, `docs/`, `node_modules/`, `test/`, `tests/`, `__tests__/`, `.vitepress/cache/`, `.vitepress/dist/`, `skills/jskit/references/patterns/`, `*.test.*`, `*.spec.*`, `*.vitest.*`
|
|
12
|
-
|
|
13
|
-
## Sections
|
|
14
|
-
|
|
15
|
-
### src
|
|
16
|
-
|
|
17
|
-
### `src/server/jsonRestApiHost.js`
|
|
18
|
-
Exports
|
|
19
|
-
- `addResourceIfMissing(api, scopeName, resourceConfig)`
|
|
20
|
-
- `buildJsonRestQueryParams(resourceType = "", query = {}, { include = undefined } = {})`
|
|
21
|
-
- `createJsonRestResourceScopeOptions(resource = {}, { writeSerializers = {}, normalizeId, rowPolicy, searchSchema, queryFields } = {})`
|
|
22
|
-
- `createJsonRestContext`
|
|
23
|
-
- `extractJsonRestCollectionRows`
|
|
24
|
-
- `queryAllJsonRestCollectionDocument`
|
|
25
|
-
- `queryAllJsonRestCollectionRows`
|
|
26
|
-
- `returnNullWhenJsonRestResourceMissing(run)`
|
|
27
|
-
- `returnBadRequestWhenJsonRestFieldsetInvalid(run)`
|
|
28
|
-
- `resolveWorkspaceScopeValue(context = null)`
|
|
29
|
-
- `resolveUserScopeValue(context = null)`
|
|
30
|
-
- `createJsonRestApiHost({ knex, logger })`
|
|
31
|
-
Local functions
|
|
32
|
-
- `cloneJsonRestResourceValue(value, { writeSerializers = {} } = {})`
|
|
33
|
-
- `normalizeScopeValue(value)`
|
|
34
|
-
- `normalizeJsonRestFilterValue(value)`
|
|
35
|
-
- `normalizeJsonRestList(value)`
|
|
36
|
-
- `resolveJsonRestCollectionRelationships(resource)`
|
|
37
|
-
- `normalizeJsonRestQueryField(fieldName = "", fieldDefinition = {}, projectionDefinition = null)`
|
|
38
|
-
- `isJsonRestVirtualField(fieldDefinition = null)`
|
|
39
|
-
- `applyJsonRestQueryFields(scopeOptions = {}, extraQueryFields = {})`
|
|
40
|
-
- `resolveJsonRestDefaultExcludedFields(resource)`
|
|
41
|
-
- `applyJsonRestDefaultExclusions(scopeOptions = {}, resource = {})`
|
|
42
|
-
- `workspaceScopeOwnerId(visibilityContext)`
|
|
43
|
-
- `isJsonRestResourceMissingError(error = null)`
|
|
44
|
-
- `isJsonRestSparseFieldError(error = null)`
|
|
45
|
-
|
|
46
|
-
### `src/server/JsonRestApiProvider.js`
|
|
47
|
-
Exports
|
|
48
|
-
- `JsonRestApiProvider`
|
|
49
|
-
|
|
50
|
-
### `src/server/jsonRestCollectionReads.js`
|
|
51
|
-
Exports
|
|
52
|
-
- `createJsonRestContext(context = null)`
|
|
53
|
-
- `extractJsonRestCollectionRows(payload)`
|
|
54
|
-
- `queryAllJsonRestCollectionDocument(resource, params, context)`
|
|
55
|
-
- `queryAllJsonRestCollectionRows(resource, params, context)`
|
|
56
|
-
Local functions
|
|
57
|
-
- `readEveryJsonRestCollectionPage(resource, { queryParams, ...params }, context, { caller, onPage })`
|
|
58
|
-
|
|
59
|
-
### `src/server/jsonRestValues.js`
|
|
60
|
-
Exports
|
|
61
|
-
- `isPlainJsonRestObject(value)`
|
|
62
|
-
|
|
63
|
-
### `src/server/types.js`
|
|
64
|
-
Exports
|
|
65
|
-
- `type RuntimeJsonRestApi`
|
|
66
|
-
- `type JsonRestResource`
|