@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.
Files changed (152) hide show
  1. package/guide/agent/app-extras/assistant.md +14 -2
  2. package/guide/agent/app-extras/realtime.md +38 -1
  3. package/guide/agent/app-setup/authentication.md +76 -14
  4. package/guide/agent/app-setup/console.md +8 -0
  5. package/guide/agent/app-setup/database-layer.md +23 -103
  6. package/guide/agent/app-setup/existing-application-migration.md +134 -72
  7. package/guide/agent/app-setup/multi-homing.md +94 -32
  8. package/guide/agent/app-setup/quickstart.md +1 -1
  9. package/guide/agent/app-setup/run-an-accounts-hub.md +1 -1
  10. package/guide/agent/app-setup/upgrading-jskit.md +17 -3
  11. package/guide/agent/app-setup/users.md +8 -5
  12. package/guide/agent/framework/application-operations.md +1 -1
  13. package/guide/agent/framework/crud-operations.md +63 -45
  14. package/guide/agent/framework/data-access.md +1207 -0
  15. package/guide/agent/framework/sanity-checks.md +26 -27
  16. package/guide/agent/framework/typed-features.md +117 -248
  17. package/guide/agent/index.md +2 -0
  18. package/package.json +6 -3
  19. package/patterns/feature-package/PATTERN.md +25 -17
  20. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  21. package/patterns/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
  22. package/patterns/feature-package/example/variations/invoiceResource.js +15 -0
  23. package/patterns/feature-package/example/variations/invoiceRollupActions.js +4 -4
  24. package/patterns/feature-package/example/variations/ownedTableRepository.js +27 -0
  25. package/patterns/feature-package/example/variations/types.js +1 -1
  26. package/patterns/minimal-foundation/example/AGENTS.md +4 -0
  27. package/patterns/minimal-foundation/example/package.json +4 -4
  28. package/patterns/minimal-foundation/example/server.js +4 -1
  29. package/patterns/shell-foundation/example/AGENTS.md +4 -0
  30. package/patterns/shell-foundation/example/package.json +5 -5
  31. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  32. package/patterns/shell-foundation/example/server.js +4 -1
  33. package/reference/autogen/KERNEL_MAP.md +3 -1
  34. package/reference/autogen/PATTERN_INDEX.md +70 -59
  35. package/reference/autogen/README.md +1 -1
  36. package/reference/autogen/packages/agent-docs.md +7 -3
  37. package/reference/autogen/packages/agent-runtime.md +48 -11
  38. package/reference/autogen/packages/architecture-inspector.md +6 -2
  39. package/reference/autogen/packages/assistant-core.md +4 -4
  40. package/reference/autogen/packages/assistant-runtime.md +33 -13
  41. package/reference/autogen/packages/auth-provider-local-core.md +3 -1
  42. package/reference/autogen/packages/auth-provider-local-db-core.md +69 -14
  43. package/reference/autogen/packages/auth-provider-oidc-core.md +5 -1
  44. package/reference/autogen/packages/auth-provider-supabase-core.md +1 -1
  45. package/reference/autogen/packages/connectors-core.md +22 -2
  46. package/reference/autogen/packages/console-core.md +9 -5
  47. package/reference/autogen/packages/crud-core.md +45 -175
  48. package/reference/autogen/packages/database-runtime-mysql.md +0 -26
  49. package/reference/autogen/packages/database-runtime.md +11 -33
  50. package/reference/autogen/packages/http-runtime.md +3 -3
  51. package/reference/autogen/packages/http-web.md +12 -0
  52. package/reference/autogen/packages/kernel.md +27 -2
  53. package/reference/autogen/packages/oidc-issuer-core.md +4 -0
  54. package/reference/autogen/packages/payments-core.md +28 -6
  55. package/reference/autogen/packages/realtime.md +15 -6
  56. package/reference/autogen/packages/resource-core.md +32 -5
  57. package/reference/autogen/packages/resource-crud-core.md +30 -34
  58. package/reference/autogen/packages/resource-runtime.md +220 -0
  59. package/reference/autogen/packages/rewarded-core.md +16 -10
  60. package/reference/autogen/packages/shell-web.md +1 -0
  61. package/reference/autogen/packages/users-core.md +33 -14
  62. package/reference/autogen/packages/workspaces-core.md +17 -10
  63. package/reference/autogen/tooling/config-eslint.md +74 -0
  64. package/reference/autogen/tooling/jskit-catalog.md +50 -1
  65. package/reference/autogen/tooling/jskit-release.md +5 -0
  66. package/skills/jskit/SKILL.md +4 -1
  67. package/skills/jskit/references/app-operations.md +1 -1
  68. package/skills/jskit/references/crud-operations.md +62 -44
  69. package/skills/jskit/references/data-access.md +1202 -0
  70. package/skills/jskit/references/database-layer.md +411 -0
  71. package/skills/jskit/references/existing-application-migration.md +134 -72
  72. package/skills/jskit/references/heuristics/INDEX.md +6 -4
  73. package/skills/jskit/references/heuristics/crud-authoring.md +26 -15
  74. package/skills/jskit/references/heuristics/data-access.md +19 -0
  75. package/skills/jskit/references/heuristics/filters.md +6 -6
  76. package/skills/jskit/references/heuristics/row-policies.md +40 -16
  77. package/skills/jskit/references/heuristics/server-search.md +30 -42
  78. package/skills/jskit/references/heuristics/table-resource-fields.md +78 -0
  79. package/skills/jskit/references/heuristics/typed-features.md +3 -3
  80. package/skills/jskit/references/multi-homing.md +94 -32
  81. package/skills/jskit/references/pattern-index.md +70 -59
  82. package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +4 -0
  83. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +4 -4
  84. package/skills/jskit/references/patterns/app/minimal-foundation/example/server.js +4 -1
  85. package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +4 -0
  86. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +5 -5
  87. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  88. package/skills/jskit/references/patterns/app/shell-foundation/example/server.js +4 -1
  89. package/skills/jskit/references/patterns/assistant/assistant-surface/PATTERN.md +5 -2
  90. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +3 -2
  91. package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +15 -0
  92. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
  93. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  94. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  95. package/skills/jskit/references/patterns/crud/crud-contract/PATTERN.md +133 -0
  96. package/skills/jskit/references/patterns/crud/crud-contract/example/bookResource.js +74 -0
  97. package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/PATTERN.md +69 -37
  98. package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/package.json +5 -4
  99. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/server/BooksFeature.js +13 -0
  100. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/bookResource.js +74 -0
  101. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/index.js +1 -0
  102. package/skills/jskit/references/patterns/crud/crud-screen-set/PATTERN.md +26 -8
  103. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookEditPage.vue +2 -2
  104. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookListPage.vue +2 -2
  105. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookNewPage.vue +2 -2
  106. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookViewPage.vue +3 -3
  107. package/skills/jskit/references/patterns/database/mysql-application/PATTERN.md +9 -4
  108. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +2 -1
  109. package/skills/jskit/references/patterns/database/mysql-application/example/scripts/prepare-database.js +8 -0
  110. package/skills/jskit/references/patterns/database/postgres-application/PATTERN.md +8 -4
  111. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +2 -1
  112. package/skills/jskit/references/patterns/database/postgres-application/example/scripts/prepare-database.js +8 -0
  113. package/skills/jskit/references/patterns/database/table-resource/PATTERN.md +120 -0
  114. package/skills/jskit/references/patterns/database/table-resource/example/orderResource.js +25 -0
  115. package/skills/jskit/references/patterns/realtime/realtime-application/PATTERN.md +7 -0
  116. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  117. package/skills/jskit/references/patterns/rewards/google-rewarded/PATTERN.md +1 -1
  118. package/skills/jskit/references/patterns/server/feature-package/PATTERN.md +25 -17
  119. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  120. package/skills/jskit/references/patterns/server/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
  121. package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceResource.js +15 -0
  122. package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceRollupActions.js +4 -4
  123. package/skills/jskit/references/patterns/server/feature-package/example/variations/ownedTableRepository.js +27 -0
  124. package/skills/jskit/references/patterns/server/feature-package/example/variations/types.js +1 -1
  125. package/skills/jskit/references/patterns/users/user-administration-server/PATTERN.md +63 -10
  126. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +5 -4
  127. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/server/UsersFeature.js +7 -5
  128. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/index.js +1 -1
  129. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userCrud.js +21 -0
  130. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +11 -5
  131. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/UsersWorkspaceFeature.js +14 -5
  132. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/workspaceMembersOnly.js +52 -0
  133. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/index.js +1 -1
  134. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userCrud.js +21 -0
  135. package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +9 -9
  136. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/PATTERN.md +16 -0
  137. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/pages/home/workspaces/index.vue +2 -0
  138. package/skills/jskit/references/sanity-checks.md +27 -28
  139. package/skills/jskit/references/typed-features.md +117 -248
  140. package/patterns/feature-package/example/variations/customKnexRepository.js +0 -40
  141. package/reference/autogen/packages/json-rest-api-core.md +0 -66
  142. package/skills/jskit/references/heuristics/crud-repository-mapping.md +0 -140
  143. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/server/BooksFeature.js +0 -13
  144. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/bookResource.js +0 -83
  145. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/index.js +0 -1
  146. package/skills/jskit/references/patterns/crud/resource-contract/PATTERN.md +0 -155
  147. package/skills/jskit/references/patterns/crud/resource-contract/example/bookResource.js +0 -87
  148. package/skills/jskit/references/patterns/server/feature-package/example/variations/customKnexRepository.js +0 -40
  149. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userResource.js +0 -69
  150. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userResource.js +0 -56
  151. /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/migrations/20260815000000_books.cjs +0 -0
  152. /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/src/server/types.js +0 -0
@@ -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 { bookResource } from "@local/books/shared";
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: bookResource,
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: bookResource,
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`; its normal npm dependencies provide
15
- the database runtime, Knex, and `mysql2` without a package-setup wizard.
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. `example/.env.example` names
54
- the connection values and separate test databases without a secret.
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_*`
@@ -9,6 +9,7 @@
9
9
  "test": "jskit test --databases"
10
10
  },
11
11
  "dependencies": {
12
- "@jskit-ai/database-runtime-mysql": "0.1.260"
12
+ "@jskit-ai/database-runtime": "0.1.264",
13
+ "@jskit-ai/database-runtime-mysql": "0.1.262"
13
14
  }
14
15
  }
@@ -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`; its normal npm dependencies provide the
15
- database runtime, Knex, and `pg` without a package-setup wizard.
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
- `example/.env.example` names the five connection values without a secret.
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
@@ -9,6 +9,7 @@
9
9
  "test": "jskit test --databases"
10
10
  },
11
11
  "dependencies": {
12
- "@jskit-ai/database-runtime-postgres": "0.1.259"
12
+ "@jskit-ai/database-runtime": "0.1.264",
13
+ "@jskit-ai/database-runtime-postgres": "0.1.261"
13
14
  }
14
15
  }
@@ -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.
@@ -2,7 +2,7 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/realtime": "0.2.19",
6
- "@jskit-ai/shell-web": "0.2.16"
5
+ "@jskit-ai/realtime": "0.2.21",
6
+ "@jskit-ai/shell-web": "0.2.18"
7
7
  }
8
8
  }
@@ -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, trx })`; only `true` permits a grant.
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, feature, json-rest, knex, orchestration, package, provider, repository, routes, server
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 an explicit custom Knex repository for a domain that
21
- cannot fit the normal resource APIs.
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 resource patterns when ordinary resource
27
- operations fit. Do not select custom Knex merely because SQL is familiar; it
28
- is an explicit exception for persistence behavior the higher-level APIs cannot
29
- express.
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 Knex client is database-runtime's `RuntimeKnex`.
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 repository owns persistence and accepts transaction/context options.
84
- - The normal persistence lane uses a public or documented high-level resource
85
- API; custom Knex is an explicit, reviewed exception.
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 custom persistence variation uses `createWithTransaction()` from
101
- `@jskit-ai/database-runtime/shared`.
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` and
151
- `example/variations/customKnexRepository.js` show the explicit custom
152
- persistence lane. `example/variations/types.js` declares the
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 transaction and context forwarding.
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
 
@@ -5,7 +5,7 @@
5
5
  "private": true,
6
6
  "type": "module",
7
7
  "dependencies": {
8
- "@jskit-ai/kernel": "0.2.13",
8
+ "@jskit-ai/kernel": "0.2.15",
9
9
  "json-rest-schema": "^1.0.17"
10
10
  },
11
11
  "exports": {
@@ -1,19 +1,18 @@
1
1
  import { defineFeature } from "@jskit-ai/kernel/server/features";
2
- import { createInvoiceRollup } from "./customKnexRepository.js";
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
- requires: {
9
- database: "runtime.database"
10
- },
9
+ owns: [invoiceResource],
11
10
  provides: {
12
11
  invoiceRollup: "feature.invoice-rollup"
13
12
  },
14
- /** @param {{ database: import("@jskit-ai/database-runtime/server/types").RuntimeDatabase }} capabilities */
15
- setup({ database }) {
16
- return { invoiceRollup: createInvoiceRollup({ knex: database.knex }) };
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 ACTION_GET_ROLLUP_STATUS = "invoices.rollup.status.read";
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: ACTION_GET_ROLLUP_STATUS,
18
+ id: ACTION_READ_MONTHLY_TOTALS,
19
19
  input: emptyInputValidator,
20
20
  output: null,
21
- execute: (input, context) => invoiceRollup.getStatus(input, { context })
21
+ execute: (input, context) => invoiceRollup.totalsByMonth(input, { context })
22
22
  })
23
23
  ]);
24
24
  }
25
25
 
26
- export { ACTION_GET_ROLLUP_STATUS, createInvoiceRollupActions };
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("./customKnexRepository.js").createInvoiceRollup>} InvoiceRollup */
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/session data through the resource.
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
- `defineCrudJsonApiFeature()` from `@jskit-ai/crud-core` for standard mechanics,
31
- and the resource contracts from `@jskit-ai/resource-crud-core`.
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
- - The resource is the field contract; the feature projects its repository,
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`, while reusing the same
58
- service contract. Each package's
59
- `src/server/types.js` declares the type of the capability it provides. Copy only the composition
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
- - copying authentication credential tables into the user resource
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 `defineCrudJsonApiFeature()`
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