@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
package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookViewPage.vue
CHANGED
|
@@ -3,13 +3,13 @@ import CrudDeleteAction from "@jskit-ai/http-web/client/components/CrudDeleteAct
|
|
|
3
3
|
import CrudViewScreen from "@jskit-ai/http-web/client/components/CrudViewScreen";
|
|
4
4
|
import { useCrudDeleteAction } from "@jskit-ai/http-web/client/composables/useCrudDeleteAction";
|
|
5
5
|
import { useCrudViewScreen } from "@jskit-ai/http-web/client/composables/useCrudViewScreen";
|
|
6
|
-
import {
|
|
6
|
+
import { bookCrud } from "@local/books/shared";
|
|
7
7
|
|
|
8
8
|
// Up returns to the page the book was opened from, or else to the list.
|
|
9
9
|
definePage({ meta: { jskit: { title: "Book", parent: "/books" } } });
|
|
10
10
|
|
|
11
11
|
const screen = useCrudViewScreen({
|
|
12
|
-
resource:
|
|
12
|
+
resource: bookCrud,
|
|
13
13
|
resourceNamespace: "books",
|
|
14
14
|
apiUrlTemplate: "/books/:bookId",
|
|
15
15
|
recordIdParam: "bookId",
|
|
@@ -22,7 +22,7 @@ const screen = useCrudViewScreen({
|
|
|
22
22
|
|
|
23
23
|
const deleteAction = useCrudDeleteAction({
|
|
24
24
|
screen,
|
|
25
|
-
resource:
|
|
25
|
+
resource: bookCrud,
|
|
26
26
|
resourceNamespace: "books",
|
|
27
27
|
apiUrlTemplate: "/books/:bookId"
|
|
28
28
|
});
|
|
@@ -11,8 +11,10 @@ requires: @jskit-ai/database-runtime-mysql
|
|
|
11
11
|
## Use when
|
|
12
12
|
|
|
13
13
|
Use this pattern when an application deliberately chooses MySQL or MariaDB.
|
|
14
|
-
Install `@jskit-ai/database-runtime-mysql
|
|
15
|
-
the
|
|
14
|
+
Install `@jskit-ai/database-runtime` and `@jskit-ai/database-runtime-mysql`
|
|
15
|
+
without a package-setup wizard: the runtime brings Knex and the driver brings
|
|
16
|
+
`mysql2`. The driver takes the runtime as a peer, so the application holds its
|
|
17
|
+
one copy.
|
|
16
18
|
|
|
17
19
|
## Do not use when
|
|
18
20
|
|
|
@@ -50,8 +52,11 @@ Knex discovers package-owned migrations from the installed dependency graph.
|
|
|
50
52
|
`example/package.json` contains the runtime dependency and database scripts.
|
|
51
53
|
`example/knexfile.js` loads an optional local `.env` and fixes the MySQL dialect.
|
|
52
54
|
`example/scripts/prepare-database.js` is the portable migrate-then-seed
|
|
53
|
-
entrypoint for managed sessions and deployments.
|
|
54
|
-
the
|
|
55
|
+
entrypoint for managed sessions and deployments. It loads the optional `.env`
|
|
56
|
+
as the knexfile does, and migrates the database `DB_NAME` names without
|
|
57
|
+
creating it: the hosting environment or a database administrator creates it
|
|
58
|
+
first. `example/.env.example` names the connection values and separate test
|
|
59
|
+
databases without a secret.
|
|
55
60
|
|
|
56
61
|
`example/.github/workflows/verify.yml` is a normal app-owned CI workflow with
|
|
57
62
|
exactly one database service: `mariadb` (`mariadb:11.4`, `MARIADB_*`
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import { prepareDatabaseFromApp } from "@jskit-ai/database-runtime/server/databaseSetup";
|
|
2
2
|
|
|
3
|
+
try {
|
|
4
|
+
process.loadEnvFile(".env");
|
|
5
|
+
} catch (error) {
|
|
6
|
+
if (error?.code !== "ENOENT") {
|
|
7
|
+
throw error;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
await prepareDatabaseFromApp({ client: "mysql2", environment: process.env });
|
|
@@ -11,8 +11,9 @@ requires: @jskit-ai/database-runtime-postgres
|
|
|
11
11
|
## Use when
|
|
12
12
|
|
|
13
13
|
Use this pattern when an application deliberately chooses PostgreSQL. Install
|
|
14
|
-
`@jskit-ai/database-runtime-postgres
|
|
15
|
-
|
|
14
|
+
`@jskit-ai/database-runtime` and `@jskit-ai/database-runtime-postgres` without
|
|
15
|
+
a package-setup wizard: the runtime brings Knex and the driver brings `pg`. The
|
|
16
|
+
driver takes the runtime as a peer, so the application holds its one copy.
|
|
16
17
|
|
|
17
18
|
## Do not use when
|
|
18
19
|
|
|
@@ -50,8 +51,11 @@ Knex discovers package-owned migrations from the installed dependency graph.
|
|
|
50
51
|
`example/package.json` contains the runtime dependency and database scripts.
|
|
51
52
|
`example/knexfile.js` loads an optional local `.env` and fixes the PostgreSQL
|
|
52
53
|
dialect. `example/scripts/prepare-database.js` is the portable
|
|
53
|
-
migrate-then-seed entrypoint for managed sessions and deployments.
|
|
54
|
-
|
|
54
|
+
migrate-then-seed entrypoint for managed sessions and deployments. It loads
|
|
55
|
+
the optional `.env` as the knexfile does, and migrates the database `DB_NAME`
|
|
56
|
+
names without creating it: the hosting environment or a database
|
|
57
|
+
administrator creates it first. `example/.env.example` names the five
|
|
58
|
+
connection values without a secret.
|
|
55
59
|
|
|
56
60
|
Tests get their own databases from
|
|
57
61
|
`@jskit-ai/database-runtime/server/testDatabases`. The `test` script runs
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import { prepareDatabaseFromApp } from "@jskit-ai/database-runtime/server/databaseSetup";
|
|
2
2
|
|
|
3
|
+
try {
|
|
4
|
+
process.loadEnvFile(".env");
|
|
5
|
+
} catch (error) {
|
|
6
|
+
if (error?.code !== "ENOENT") {
|
|
7
|
+
throw error;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
await prepareDatabaseFromApp({ client: "pg", environment: process.env });
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: database/table-resource
|
|
3
|
+
title: Table resource
|
|
4
|
+
summary: Describe one database table once (its fields, columns, ownership, search and sort) so its owner feature reads and writes it through a scoped handle.
|
|
5
|
+
keywords: database, defaultTo, defineTableResource, lockVersionField, lookup, owns, ownership, resource, system, table, workspace_or_shared
|
|
6
|
+
requires: @jskit-ai/resource-core
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Table resource
|
|
10
|
+
|
|
11
|
+
## Use when
|
|
12
|
+
|
|
13
|
+
Use this pattern for every table an application owns, whether or not it is
|
|
14
|
+
served over HTTP. The table resource is the table's one description; the
|
|
15
|
+
feature that owns the table lists it in `owns`, and every read and write goes
|
|
16
|
+
through the handle setup receives
|
|
17
|
+
([data access](https://mobily-enterprises.github.io/jskit-ai/guide/framework/data-access)).
|
|
18
|
+
|
|
19
|
+
## Do not use when
|
|
20
|
+
|
|
21
|
+
Do not describe a table twice, and do not describe another feature's table:
|
|
22
|
+
ask its owner. A table with standard screens over HTTP adds a CRUD contract on
|
|
23
|
+
this resource (`crud/crud-contract`), not a second resource.
|
|
24
|
+
|
|
25
|
+
## Product decisions
|
|
26
|
+
|
|
27
|
+
- who owns a row, always stated: nobody (`public`), a workspace, a user, a
|
|
28
|
+
user inside a workspace, a workspace with rows shared with every
|
|
29
|
+
workspace, or no one any request may reach (`system`)
|
|
30
|
+
- the fields, which are required, searchable or hidden, and their defaults
|
|
31
|
+
- whether people edit records from forms read earlier (a version number)
|
|
32
|
+
- the default order of lists
|
|
33
|
+
|
|
34
|
+
## Invariants
|
|
35
|
+
|
|
36
|
+
- One table, one table resource, one owner feature.
|
|
37
|
+
- `ownership` matches the owner columns: `workspace` filters on the hidden
|
|
38
|
+
`workspaceId` field, `user` on `userId`, `workspace_user` on both, and
|
|
39
|
+
`workspace_or_shared` reads a workspace's rows plus the rows whose
|
|
40
|
+
`workspaceId` is null. The handle adds that predicate to every statement and
|
|
41
|
+
stamps new rows from the caller's context. A `system` table has no owner
|
|
42
|
+
columns, and its handle refuses every call whose context is not a system
|
|
43
|
+
context, before any statement: it is for rows that only the owner's
|
|
44
|
+
background work keeps (queues, leases, run state), never served to a request.
|
|
45
|
+
- A field's default value is `defaultTo`; `default` is refused, since
|
|
46
|
+
json-rest-api never read it.
|
|
47
|
+
- A field with `defaultTo` is not `required`: a create is checked for
|
|
48
|
+
required fields before defaults apply, so `defineTableResource` refuses the
|
|
49
|
+
two together.
|
|
50
|
+
- `lockVersionField` names an integer field over a non-null integer column
|
|
51
|
+
with a starting value; `patch(id, changes, { ...options, lockVersion })` then refuses
|
|
52
|
+
stale edits with a 409.
|
|
53
|
+
- The definition is shared with the client, so it holds no server-only keys:
|
|
54
|
+
the row policy, hooks, server-only search entries and computed query fields
|
|
55
|
+
go in the owner's `owns` entry.
|
|
56
|
+
- The source-controlled migration and the resource describe the same table.
|
|
57
|
+
|
|
58
|
+
## Framework APIs
|
|
59
|
+
|
|
60
|
+
`defineTableResource()` from `@jskit-ai/resource-core/shared/tableResource`
|
|
61
|
+
freezes the description and refuses the CRUD contract's keys (`namespace`,
|
|
62
|
+
`apiAccess`, `operations`, `messages`), `autofilter` and the server-only keys,
|
|
63
|
+
each with an error naming where it belongs.
|
|
64
|
+
|
|
65
|
+
A field that looks up a record of another resource names that resource's
|
|
66
|
+
namespace with `lookup`:
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
customerId: { type: "id", required: true, search: true, lookup: "customers" }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`defineTableResource()` expands it, where `lookup` stood, to:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
relation: { kind: "lookup", namespace: "customers", valueKey: "id" },
|
|
76
|
+
belongsTo: "customers", // the table resource named by the namespace, camel-cased
|
|
77
|
+
as: "customer", // the field key without its Id suffix
|
|
78
|
+
ui: { formControl: "autocomplete" }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Keys you write beside `lookup` win: `belongsTo` and `as` replace the
|
|
82
|
+
filled-in values, and `relation` and `ui` are merged over them. A field whose
|
|
83
|
+
key has no `Id` suffix must write `as`.
|
|
84
|
+
|
|
85
|
+
The owner, on the server:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
const OrdersFeature = defineFeature({
|
|
89
|
+
id: "orders",
|
|
90
|
+
owns: [{ resource: orderResource, rowPolicy: ordersTheActorMayHandle }],
|
|
91
|
+
provides: { orders: "orders" },
|
|
92
|
+
setup: ({ resources }) => ({ orders: createOrdersRepository({ orders: resources.orders }) })
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Example files
|
|
97
|
+
|
|
98
|
+
- `example/orderResource.js` is a workspace-owned orders table with a lookup,
|
|
99
|
+
defaults, a version number, a text search and a default sort.
|
|
100
|
+
|
|
101
|
+
## Variation points
|
|
102
|
+
|
|
103
|
+
Rename the table and fields, pick the ownership kind the product's owner
|
|
104
|
+
columns support, add search entries and sorts. A computed output field is
|
|
105
|
+
`storage: { virtual: true }` here and a `queryFields` entry in `owns`.
|
|
106
|
+
|
|
107
|
+
## Verification
|
|
108
|
+
|
|
109
|
+
- Import the resource and check its ownership, fields and lookups.
|
|
110
|
+
- Rebuild the table from source-controlled migrations in a disposable database.
|
|
111
|
+
- Give each repository path a scope test (`expectScoped`) and a flat-query
|
|
112
|
+
test (`expectFlatQueries`) from `@jskit-ai/resource-runtime/test`.
|
|
113
|
+
|
|
114
|
+
## Avoid
|
|
115
|
+
|
|
116
|
+
- a second table resource for the same table
|
|
117
|
+
- `where workspace_id = ?` written by hand: ownership adds it
|
|
118
|
+
- a row policy, hooks or SQL in the shared definition
|
|
119
|
+
- `default:` instead of `defaultTo:`
|
|
120
|
+
- `required: true` beside `defaultTo`
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineTableResource } from "@jskit-ai/resource-core/shared/tableResource";
|
|
2
|
+
|
|
3
|
+
// The orders table: one workspace owns each row. Shared with the client; the
|
|
4
|
+
// owner feature lists it in `owns` on the server.
|
|
5
|
+
const orderResource = defineTableResource({
|
|
6
|
+
name: "orders",
|
|
7
|
+
tableName: "orders",
|
|
8
|
+
ownership: "workspace",
|
|
9
|
+
lockVersionField: "lockVersion",
|
|
10
|
+
schema: {
|
|
11
|
+
workspaceId: { type: "id", required: true, hidden: true },
|
|
12
|
+
customerId: { type: "id", required: true, search: true, lookup: "customers" },
|
|
13
|
+
reference: { type: "string", maxLength: 64, required: true, search: true },
|
|
14
|
+
status: { type: "string", maxLength: 32, search: true, defaultTo: "open" },
|
|
15
|
+
total: { type: "number", defaultTo: 0 },
|
|
16
|
+
lockVersion: { type: "integer", min: 0, readOnly: true, defaultTo: 0 },
|
|
17
|
+
createdAt: { type: "dateTime", temporalPrecision: 0, readOnly: true }
|
|
18
|
+
},
|
|
19
|
+
searchSchema: {
|
|
20
|
+
q: { type: "string", oneOf: ["reference"], filterOperator: "like" }
|
|
21
|
+
},
|
|
22
|
+
defaultSort: ["-createdAt"]
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export { orderResource };
|
|
@@ -43,6 +43,13 @@ name and audience. Client features use `RealtimeClientProvider` and the public
|
|
|
43
43
|
listener registration helpers from `@jskit-ai/realtime`. Use the normal shell
|
|
44
44
|
placement registry for the optional status component.
|
|
45
45
|
|
|
46
|
+
An audience's `audienceUsers({ event, options })` reads in a context scoped to
|
|
47
|
+
the event's workspace, user or workspace user. An application whose action
|
|
48
|
+
events declare such an audience registers `RealtimeAudienceScopeFeature` from
|
|
49
|
+
`@jskit-ai/realtime/server/RealtimeAudienceScopeFeature`, which provides those
|
|
50
|
+
contexts, or realtime refuses to boot. An audience that reads only public
|
|
51
|
+
tables marks itself `publicTablesOnly: true` and reads with no context.
|
|
52
|
+
|
|
46
53
|
## Example files
|
|
47
54
|
|
|
48
55
|
`example/package.json` declares realtime and shell runtime packages.
|
|
@@ -38,7 +38,7 @@ capability composition beside `RewardedClientProvider` from
|
|
|
38
38
|
`client.rewarded-delivery`; the generic UI supplies `client.rewarded`.
|
|
39
39
|
Use `useRewardedRuntime().requireUnlock({ gateKey, workspaceSlug })` for gates.
|
|
40
40
|
Separately supply server capability `rewarded.grant-policy` with
|
|
41
|
-
`authorizeGrant({ session, context,
|
|
41
|
+
`authorizeGrant({ session, context, options })`; only `true` permits a grant.
|
|
42
42
|
This delivery example does not provide or choose the server policy.
|
|
43
43
|
|
|
44
44
|
## Example files
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
id: server/feature-package
|
|
3
3
|
title: App-local server feature package
|
|
4
4
|
summary: Define a server feature through explicit capabilities and first-class actions, adding services or repositories only when its domain earns them.
|
|
5
|
-
keywords: actions,
|
|
5
|
+
keywords: actions, aggregate, feature, handle, orchestration, owns, package, provider, repository, routes, server, table resource
|
|
6
6
|
requires: @jskit-ai/kernel
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -17,16 +17,18 @@ actions whose implementations capture those exact dependencies.
|
|
|
17
17
|
|
|
18
18
|
The concrete `booking-engine` example is intentionally repository-free. The
|
|
19
19
|
`variations/` directory preserves the two important escalations: a meaningful
|
|
20
|
-
orchestration service and
|
|
21
|
-
|
|
20
|
+
orchestration service and a repository over a table the feature owns, for a
|
|
21
|
+
domain whose operations are not CRUD screens.
|
|
22
22
|
|
|
23
23
|
## Do not use when
|
|
24
24
|
|
|
25
25
|
Do not create a package for a tiny composition callback or one route with no
|
|
26
|
-
domain behavior. Use the CRUD
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
domain behavior. Use the CRUD feature pattern when the table's operations are
|
|
27
|
+
screens over HTTP. Do not reach for Knex because SQL is familiar: a table's
|
|
28
|
+
reads and writes go through its handle, and a report the handle cannot express
|
|
29
|
+
is one `scopedQuery()` method, as the
|
|
30
|
+
[data access guide](https://mobily-enterprises.github.io/jskit-ai/guide/framework/data-access)
|
|
31
|
+
says.
|
|
30
32
|
|
|
31
33
|
## Product decisions
|
|
32
34
|
|
|
@@ -50,7 +52,7 @@ questionnaire.
|
|
|
50
52
|
- Every factory, action creator and hook that receives a collaborator names its
|
|
51
53
|
type in a JSDoc `@param`: `import("./types.js").<Name>` or
|
|
52
54
|
`ReturnType<typeof createX>` inside the package, the owner's `./server/types`
|
|
53
|
-
across packages; a
|
|
55
|
+
across packages; a table's handle is resource-runtime's `ResourceHandle`.
|
|
54
56
|
- Collaborators are required. There is no `= null`, `= {}` or no-op default,
|
|
55
57
|
no `typeof` probe, no optional call and no "requires x" guard: the capability
|
|
56
58
|
runtime refuses to start without a required token.
|
|
@@ -80,9 +82,11 @@ questionnaire.
|
|
|
80
82
|
- One copy of each pure helper per package, imported directly. Files stay under
|
|
81
83
|
750 lines and functions under 600, one concern per factory.
|
|
82
84
|
- The service orchestrates the feature and does not issue SQL directly.
|
|
83
|
-
- A
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
- A feature that owns a table lists its table resource in `owns`; setup
|
|
86
|
+
receives the table's handle, and only its repository uses it. One table has
|
|
87
|
+
one owner.
|
|
88
|
+
- A repository holds the owner's queries on its handles and passes each call's
|
|
89
|
+
options (context and transaction) on.
|
|
86
90
|
- Ordinary HTTP and assistant exposure is projected from actions. Explicit
|
|
87
91
|
route code exists only for a genuinely custom transport.
|
|
88
92
|
- Register routes, actions, contributors, and other catalogue entries during
|
|
@@ -97,8 +101,8 @@ questionnaire.
|
|
|
97
101
|
The provider uses `defineFeature()` from
|
|
98
102
|
`@jskit-ai/kernel/server/features`. The runtime supplies named capabilities
|
|
99
103
|
and the feature publishes its actions through the dedicated action catalogue.
|
|
100
|
-
The
|
|
101
|
-
|
|
104
|
+
The persistence variation owns its table with `defineFeature({ owns })` and
|
|
105
|
+
reads totals with the handle's `aggregate()`.
|
|
102
106
|
|
|
103
107
|
## Example files
|
|
104
108
|
|
|
@@ -147,9 +151,11 @@ const SeasonalAvailabilityFeature = defineFeature({
|
|
|
147
151
|
`example/variations/availabilityEngineActions.js` and
|
|
148
152
|
`example/variations/orchestratorService.js` show the repository-free lane.
|
|
149
153
|
`example/variations/InvoiceRollupFeature.js`,
|
|
150
|
-
`example/variations/invoiceRollupActions.js
|
|
151
|
-
`example/variations/
|
|
152
|
-
|
|
154
|
+
`example/variations/invoiceRollupActions.js`,
|
|
155
|
+
`example/variations/invoiceResource.js` and
|
|
156
|
+
`example/variations/ownedTableRepository.js` show the persistence lane: the
|
|
157
|
+
feature owns the invoices table, and its repository reads monthly totals with
|
|
158
|
+
`aggregate()`, scoped to the caller's workspace by the handle. `example/variations/types.js` declares the
|
|
153
159
|
`AvailabilityEngine` and `InvoiceRollup` types.
|
|
154
160
|
|
|
155
161
|
The [typed features guide](https://mobily-enterprises.github.io/jskit-ai/guide/framework/typed-features)
|
|
@@ -172,7 +178,9 @@ illustrative status query with narrow product language before shipping.
|
|
|
172
178
|
denied access.
|
|
173
179
|
- Exercise HTTP routes only when the package owns them.
|
|
174
180
|
- Test service orchestration independently from persistence.
|
|
175
|
-
- Test repository
|
|
181
|
+
- Test each repository path against a second workspace's rows
|
|
182
|
+
(`expectScoped`) and at two sizes (`expectFlatQueries`), from
|
|
183
|
+
`@jskit-ai/resource-runtime/test`.
|
|
176
184
|
- Rebuild any schema from immutable migrations in a disposable database.
|
|
177
185
|
- Run the app's package, server, and integration verification.
|
|
178
186
|
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { defineFeature } from "@jskit-ai/kernel/server/features";
|
|
2
|
-
import {
|
|
2
|
+
import { invoiceResource } from "./invoiceResource.js";
|
|
3
|
+
import { createInvoiceRollup } from "./ownedTableRepository.js";
|
|
3
4
|
import { createInvoiceRollupActions } from "./invoiceRollupActions.js";
|
|
4
5
|
|
|
5
6
|
const InvoiceRollupFeature = defineFeature({
|
|
6
7
|
id: "feature.invoice-rollup",
|
|
7
8
|
domain: "invoices",
|
|
8
|
-
|
|
9
|
-
database: "runtime.database"
|
|
10
|
-
},
|
|
9
|
+
owns: [invoiceResource],
|
|
11
10
|
provides: {
|
|
12
11
|
invoiceRollup: "feature.invoice-rollup"
|
|
13
12
|
},
|
|
14
|
-
/** @param {{
|
|
15
|
-
setup({
|
|
16
|
-
return { invoiceRollup: createInvoiceRollup({
|
|
13
|
+
/** @param {{ resources: { invoices: import("@jskit-ai/resource-runtime/server/types").ResourceHandle } }} capabilities */
|
|
14
|
+
setup({ resources }) {
|
|
15
|
+
return { invoiceRollup: createInvoiceRollup({ invoices: resources.invoices }) };
|
|
17
16
|
},
|
|
18
17
|
actions({ invoiceRollup }) {
|
|
19
18
|
return createInvoiceRollupActions({ invoiceRollup });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineTableResource } from "@jskit-ai/resource-core/shared/tableResource";
|
|
2
|
+
|
|
3
|
+
/** The invoices table: its fields, and the workspace that owns each row. Shared with the client. */
|
|
4
|
+
const invoiceResource = defineTableResource({
|
|
5
|
+
name: "invoices",
|
|
6
|
+
tableName: "invoices",
|
|
7
|
+
ownership: "workspace",
|
|
8
|
+
schema: {
|
|
9
|
+
status: { type: "string", required: true, search: true },
|
|
10
|
+
amount: { type: "number", required: true },
|
|
11
|
+
issuedAt: { type: "dateTime", required: true }
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export { invoiceResource };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { emptyInputValidator } from "@jskit-ai/kernel/server/actions/actionContributorHelpers";
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const ACTION_READ_MONTHLY_TOTALS = "invoices.rollup.monthly-totals.read";
|
|
4
4
|
|
|
5
5
|
/** The fields every invoice rollup query shares, spread first into each definition: they run on the admin surface. */
|
|
6
6
|
const ROLLUP_QUERY = Object.freeze({
|
|
@@ -15,12 +15,12 @@ function createInvoiceRollupActions({ invoiceRollup }) {
|
|
|
15
15
|
return Object.freeze([
|
|
16
16
|
Object.freeze({
|
|
17
17
|
...ROLLUP_QUERY,
|
|
18
|
-
id:
|
|
18
|
+
id: ACTION_READ_MONTHLY_TOTALS,
|
|
19
19
|
input: emptyInputValidator,
|
|
20
20
|
output: null,
|
|
21
|
-
execute: (input, context) => invoiceRollup.
|
|
21
|
+
execute: (input, context) => invoiceRollup.totalsByMonth(input, { context })
|
|
22
22
|
})
|
|
23
23
|
]);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export {
|
|
26
|
+
export { ACTION_READ_MONTHLY_TOTALS, createInvoiceRollupActions };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The invoice rollup's persistence: the queries of the invoices table its
|
|
3
|
+
* feature owns, on that table's handle. The handle adds the workspace to every
|
|
4
|
+
* statement, so no query names it.
|
|
5
|
+
* @param {{ invoices: import("@jskit-ai/resource-runtime/server/types").ResourceHandle }} dependencies
|
|
6
|
+
*/
|
|
7
|
+
function createInvoiceRollup({ invoices }) {
|
|
8
|
+
/**
|
|
9
|
+
* How many invoices of each status the workspace issued each month, and their total.
|
|
10
|
+
* @param {Record<string, never>} input
|
|
11
|
+
* @param {import("@jskit-ai/resource-runtime/server/types").ResourceCallOptions} options
|
|
12
|
+
*/
|
|
13
|
+
function totalsByMonth(input, options) {
|
|
14
|
+
return invoices.aggregate({
|
|
15
|
+
groupBy: ["status", { field: "issuedAt", bucket: "month", as: "month" }],
|
|
16
|
+
measures: { invoices: { count: "*" }, amount: { sum: "amount" } },
|
|
17
|
+
sort: ["month"]
|
|
18
|
+
}, options);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return Object.freeze({
|
|
22
|
+
withTransaction: invoices.withTransaction,
|
|
23
|
+
totalsByMonth
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { createInvoiceRollup };
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
// capabilities, for features that require them.
|
|
3
3
|
|
|
4
4
|
/** @typedef {ReturnType<typeof import("./orchestratorService.js").createAvailabilityEngine>} AvailabilityEngine */
|
|
5
|
-
/** @typedef {ReturnType<typeof import("./
|
|
5
|
+
/** @typedef {ReturnType<typeof import("./ownedTableRepository.js").createInvoiceRollup>} InvoiceRollup */
|
|
6
6
|
|
|
7
7
|
export {};
|
|
@@ -22,21 +22,70 @@ credentials, or an application that needs no administrative user listing.
|
|
|
22
22
|
|
|
23
23
|
Choose global versus workspace scope, visible and editable fields, permission
|
|
24
24
|
policy, route surface, filters, and whether account deletion is part of the
|
|
25
|
-
product. Do not expose password
|
|
25
|
+
product. Do not expose password, session or sign-in identity data through the
|
|
26
|
+
CRUD contract.
|
|
26
27
|
|
|
27
28
|
## Framework APIs
|
|
28
29
|
|
|
29
30
|
Use `@jskit-ai/users-core` for profile persistence and policies,
|
|
30
|
-
`
|
|
31
|
-
and
|
|
31
|
+
`defineCrudFeature()` from `@jskit-ai/crud-core` for standard mechanics,
|
|
32
|
+
and `defineCrudContract()` from `@jskit-ai/resource-crud-core`.
|
|
33
|
+
|
|
34
|
+
users-core is the `users` table's one owner, so an administration feature does
|
|
35
|
+
not own it: `defineCrudFeature({ crud: userCrud, ownerRepository: "users.profiles", ... })`
|
|
36
|
+
serves the CRUD contract through users-core's repository. The workspace
|
|
37
|
+
variant also names `visibility: "workspace"`: its routes are a workspace's,
|
|
38
|
+
so they resolve the caller's workspace and check its `crud.users.*`
|
|
39
|
+
permissions. The `users` table is public, every user's, so the variant also
|
|
40
|
+
narrows the repository to the workspace's members with `decorateRepository`
|
|
41
|
+
(`src/server/workspaceMembersOnly.js`): a list reads only the active members
|
|
42
|
+
from `workspaces.memberships`' `activeMemberIds`, and anyone else is a missing
|
|
43
|
+
record, so a workspace admin never learns who else has an account. The narrowed
|
|
44
|
+
repository answers only `query`, `get` and `pageLimits`: users.profiles' other
|
|
45
|
+
methods (`findByEmail`, `countUsers`, the writes) read or write every user, so
|
|
46
|
+
they never pass through it.
|
|
47
|
+
|
|
48
|
+
The `users` table has one table resource, users-core's `userProfileTableResource`
|
|
49
|
+
(`@jskit-ai/users-core/shared/resources/userProfileResource`). An
|
|
50
|
+
administration package never defines a second one; `jskit check` fails a table
|
|
51
|
+
with two. It builds its CRUD contract on that resource,
|
|
52
|
+
`defineCrudContract(userProfileTableResource, { namespace, operations, fields })`,
|
|
53
|
+
which chooses the operations, their namespace and the fields each returns; a
|
|
54
|
+
field it does not name, such as the sign-in identity, is never shown. The
|
|
55
|
+
list searches with the resource's `q` filter: every word in the display name,
|
|
56
|
+
email or username.
|
|
57
|
+
|
|
58
|
+
Other code that reads or writes the users' records (a decorated service, a
|
|
59
|
+
hook, a job) requires their owners' capabilities, never the `users` or
|
|
60
|
+
`user_settings` tables:
|
|
61
|
+
|
|
62
|
+
- `users.profiles`: `findById`, `findByEmail`, `findByIdentity`,
|
|
63
|
+
`listByEmails(emails, options)` (one read),
|
|
64
|
+
`listByIds(ids, options)` (every profile of the ids, sorted by id, one read
|
|
65
|
+
per 1,000 ids), `upsert`,
|
|
66
|
+
`createWithSettings({ profile, settings }, options)` (the profile and its
|
|
67
|
+
settings in one transaction; settings start from the defaults),
|
|
68
|
+
`createWithId(profile, options)` (a profile keeping an earlier table's id),
|
|
69
|
+
`createManyIgnoringExisting(profiles, options)` (a re-runnable bulk import,
|
|
70
|
+
system context only), `changeUsername(userId, username, options)` and
|
|
71
|
+
`countUsers({ excludeIds, createdSince, authProvider }, options)`.
|
|
72
|
+
- `users.settings`: `findByUserId`, `listByUserIds(userIds, options)` (the
|
|
73
|
+
settings of every listed user who has them, sorted by user id, one read per
|
|
74
|
+
1,000 ids), `ensureForUserId` and the settings updates.
|
|
75
|
+
|
|
76
|
+
Their types are `UsersProfiles` and `UsersSettings` in
|
|
77
|
+
`@jskit-ai/users-core/server/types`. Every method takes `options` last and
|
|
78
|
+
joins the caller's transaction: inside `withTransaction(options, async
|
|
79
|
+
(txOptions) => …)`, pass `txOptions` to every call.
|
|
32
80
|
|
|
33
81
|
## Invariants
|
|
34
82
|
|
|
35
83
|
- The public user id is the resource identity.
|
|
36
84
|
- Workspace operations are scoped before repository access.
|
|
37
85
|
- Permission checks happen before records or record existence are disclosed.
|
|
38
|
-
- Shared profile persistence remains owned by `@jskit-ai/users-core
|
|
39
|
-
|
|
86
|
+
- Shared profile persistence remains owned by `@jskit-ai/users-core`, and so
|
|
87
|
+
does the `users` table's one table resource.
|
|
88
|
+
- The CRUD contract is the field contract; the feature projects its repository,
|
|
40
89
|
service, actions, and routes without app-local ceremony.
|
|
41
90
|
- Each package exports the type of the capability it provides:
|
|
42
91
|
`src/server/types.js` declares `AppUsers` (for `app.users`) or
|
|
@@ -54,9 +103,12 @@ and the resource contracts from `@jskit-ai/resource-crud-core`.
|
|
|
54
103
|
|
|
55
104
|
`example/packages/users/` is a global administration package.
|
|
56
105
|
`example/packages/users-workspace/` demonstrates the workspace route/action
|
|
57
|
-
boundary, with workspaces-core's `WORKSPACE_CRUD_SCOPE`,
|
|
58
|
-
service contract. Each package's
|
|
59
|
-
|
|
106
|
+
boundary, with workspaces-core's `WORKSPACE_CRUD_SCOPE`, and lists only the
|
|
107
|
+
workspace's members through the same service contract. Each package's `src/shared/userCrud.js` builds its CRUD
|
|
108
|
+
contract on users-core's `userProfileTableResource`, its `package.json` requires
|
|
109
|
+
`users.profiles` (users-core, which owns the table, must be installed), and its
|
|
110
|
+
`src/server/types.js` declares the type of the capability it provides; the
|
|
111
|
+
workspace variant also requires `workspaces.memberships`. Copy only the composition
|
|
60
112
|
that matches the product and adapt it as ordinary application source.
|
|
61
113
|
|
|
62
114
|
## Variation points
|
|
@@ -71,9 +123,10 @@ isolation, missing records, null/cleared fields, filtering, and pagination.
|
|
|
71
123
|
|
|
72
124
|
## Avoid
|
|
73
125
|
|
|
74
|
-
-
|
|
126
|
+
- a second table resource on `users` or `user_settings`
|
|
127
|
+
- copying authentication credential tables into the CRUD contract
|
|
75
128
|
- trusting route scope without repository scope
|
|
76
|
-
- adding app-local mechanics already provided by `
|
|
129
|
+
- adding app-local mechanics already provided by `defineCrudFeature()`
|
|
77
130
|
- generator metadata or field questionnaires
|
|
78
131
|
- probing a collaborator, defaulting one to `null`, `{}` or a no-op, setters
|
|
79
132
|
that fill a slot after construction, and module-level registries
|