@jskit-ai/agent-docs 0.1.283 → 0.1.284

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 (151) 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 +64 -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 +105 -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 +8 -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 +1031 -0
  15. package/guide/agent/framework/sanity-checks.md +26 -27
  16. package/guide/agent/framework/typed-features.md +114 -247
  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 +2 -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 +65 -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 +38 -173
  48. package/reference/autogen/packages/database-runtime.md +8 -32
  49. package/reference/autogen/packages/http-runtime.md +3 -3
  50. package/reference/autogen/packages/http-web.md +12 -0
  51. package/reference/autogen/packages/kernel.md +25 -2
  52. package/reference/autogen/packages/oidc-issuer-core.md +4 -0
  53. package/reference/autogen/packages/payments-core.md +29 -6
  54. package/reference/autogen/packages/realtime.md +15 -6
  55. package/reference/autogen/packages/resource-core.md +30 -5
  56. package/reference/autogen/packages/resource-crud-core.md +30 -34
  57. package/reference/autogen/packages/resource-runtime.md +198 -0
  58. package/reference/autogen/packages/rewarded-core.md +16 -10
  59. package/reference/autogen/packages/shell-web.md +1 -0
  60. package/reference/autogen/packages/users-core.md +26 -14
  61. package/reference/autogen/packages/workspaces-core.md +17 -10
  62. package/reference/autogen/tooling/config-eslint.md +69 -0
  63. package/reference/autogen/tooling/jskit-catalog.md +36 -1
  64. package/reference/autogen/tooling/jskit-release.md +3 -0
  65. package/skills/jskit/SKILL.md +4 -1
  66. package/skills/jskit/references/app-operations.md +1 -1
  67. package/skills/jskit/references/crud-operations.md +62 -44
  68. package/skills/jskit/references/data-access.md +1026 -0
  69. package/skills/jskit/references/database-layer.md +411 -0
  70. package/skills/jskit/references/existing-application-migration.md +105 -72
  71. package/skills/jskit/references/heuristics/INDEX.md +6 -4
  72. package/skills/jskit/references/heuristics/crud-authoring.md +22 -14
  73. package/skills/jskit/references/heuristics/data-access.md +19 -0
  74. package/skills/jskit/references/heuristics/filters.md +6 -6
  75. package/skills/jskit/references/heuristics/row-policies.md +40 -16
  76. package/skills/jskit/references/heuristics/server-search.md +30 -42
  77. package/skills/jskit/references/heuristics/table-resource-fields.md +78 -0
  78. package/skills/jskit/references/heuristics/typed-features.md +3 -3
  79. package/skills/jskit/references/multi-homing.md +94 -32
  80. package/skills/jskit/references/pattern-index.md +70 -59
  81. package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +4 -0
  82. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +4 -4
  83. package/skills/jskit/references/patterns/app/minimal-foundation/example/server.js +4 -1
  84. package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +4 -0
  85. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +5 -5
  86. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  87. package/skills/jskit/references/patterns/app/shell-foundation/example/server.js +4 -1
  88. package/skills/jskit/references/patterns/assistant/assistant-surface/PATTERN.md +5 -2
  89. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +3 -2
  90. package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +15 -0
  91. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
  92. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  93. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  94. package/skills/jskit/references/patterns/crud/crud-contract/PATTERN.md +133 -0
  95. package/skills/jskit/references/patterns/crud/crud-contract/example/bookResource.js +74 -0
  96. package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/PATTERN.md +69 -37
  97. package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/package.json +5 -4
  98. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/server/BooksFeature.js +13 -0
  99. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/bookResource.js +74 -0
  100. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/index.js +1 -0
  101. package/skills/jskit/references/patterns/crud/crud-screen-set/PATTERN.md +26 -8
  102. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookEditPage.vue +2 -2
  103. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookListPage.vue +2 -2
  104. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookNewPage.vue +2 -2
  105. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookViewPage.vue +3 -3
  106. package/skills/jskit/references/patterns/database/mysql-application/PATTERN.md +9 -4
  107. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +2 -1
  108. package/skills/jskit/references/patterns/database/mysql-application/example/scripts/prepare-database.js +8 -0
  109. package/skills/jskit/references/patterns/database/postgres-application/PATTERN.md +8 -4
  110. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +2 -1
  111. package/skills/jskit/references/patterns/database/postgres-application/example/scripts/prepare-database.js +8 -0
  112. package/skills/jskit/references/patterns/database/table-resource/PATTERN.md +117 -0
  113. package/skills/jskit/references/patterns/database/table-resource/example/orderResource.js +25 -0
  114. package/skills/jskit/references/patterns/realtime/realtime-application/PATTERN.md +7 -0
  115. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  116. package/skills/jskit/references/patterns/rewards/google-rewarded/PATTERN.md +1 -1
  117. package/skills/jskit/references/patterns/server/feature-package/PATTERN.md +25 -17
  118. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  119. package/skills/jskit/references/patterns/server/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
  120. package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceResource.js +15 -0
  121. package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceRollupActions.js +4 -4
  122. package/skills/jskit/references/patterns/server/feature-package/example/variations/ownedTableRepository.js +27 -0
  123. package/skills/jskit/references/patterns/server/feature-package/example/variations/types.js +1 -1
  124. package/skills/jskit/references/patterns/users/user-administration-server/PATTERN.md +59 -10
  125. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +5 -4
  126. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/server/UsersFeature.js +7 -5
  127. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/index.js +1 -1
  128. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userCrud.js +21 -0
  129. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +11 -5
  130. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/UsersWorkspaceFeature.js +14 -5
  131. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/workspaceMembersOnly.js +52 -0
  132. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/index.js +1 -1
  133. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userCrud.js +21 -0
  134. package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +9 -9
  135. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/PATTERN.md +16 -0
  136. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/pages/home/workspaces/index.vue +2 -0
  137. package/skills/jskit/references/sanity-checks.md +27 -28
  138. package/skills/jskit/references/typed-features.md +114 -247
  139. package/patterns/feature-package/example/variations/customKnexRepository.js +0 -40
  140. package/reference/autogen/packages/json-rest-api-core.md +0 -66
  141. package/skills/jskit/references/heuristics/crud-repository-mapping.md +0 -140
  142. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/server/BooksFeature.js +0 -13
  143. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/bookResource.js +0 -83
  144. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/index.js +0 -1
  145. package/skills/jskit/references/patterns/crud/resource-contract/PATTERN.md +0 -155
  146. package/skills/jskit/references/patterns/crud/resource-contract/example/bookResource.js +0 -87
  147. package/skills/jskit/references/patterns/server/feature-package/example/variations/customKnexRepository.js +0 -40
  148. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userResource.js +0 -69
  149. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userResource.js +0 -56
  150. /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/migrations/20260815000000_books.cjs +0 -0
  151. /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/src/server/types.js +0 -0
@@ -0,0 +1,74 @@
1
+ import { defineTableResource } from "@jskit-ai/resource-core/shared/tableResource";
2
+ import { defineCrudContract } from "@jskit-ai/resource-crud-core/shared/crudContract";
3
+
4
+ const bookResource = defineTableResource({
5
+ name: "books",
6
+ tableName: "books",
7
+ ownership: "user",
8
+ schema: {
9
+ userId: {
10
+ type: "id",
11
+ required: true,
12
+ hidden: true
13
+ },
14
+ title: {
15
+ type: "string",
16
+ maxLength: 255,
17
+ required: true,
18
+ search: true
19
+ },
20
+ author: {
21
+ type: "string",
22
+ maxLength: 255,
23
+ required: true,
24
+ search: true
25
+ },
26
+ notes: {
27
+ type: "string",
28
+ maxLength: 65535,
29
+ nullable: true,
30
+ search: true
31
+ },
32
+ createdAt: {
33
+ type: "dateTime",
34
+ temporalPrecision: 0,
35
+ storage: { writeSerializer: "datetime-utc" }
36
+ },
37
+ updatedAt: {
38
+ type: "dateTime",
39
+ temporalPrecision: 0,
40
+ storage: { writeSerializer: "datetime-utc" }
41
+ }
42
+ },
43
+ searchSchema: {
44
+ q: {
45
+ type: "string",
46
+ oneOf: ["title", "author", "notes"],
47
+ filterOperator: "like",
48
+ splitBy: " ",
49
+ matchAll: true
50
+ }
51
+ },
52
+ defaultSort: ["-createdAt"]
53
+ });
54
+
55
+ const bookCrud = defineCrudContract(bookResource, {
56
+ namespace: "books",
57
+ apiAccess: "authenticated",
58
+ fields: {
59
+ title: { output: "required", create: "required", patch: "optional" },
60
+ author: { output: "required", create: "required", patch: "optional" },
61
+ notes: { output: "required", create: "optional", patch: "optional" },
62
+ createdAt: { output: "required" },
63
+ updatedAt: { output: "required" }
64
+ },
65
+ messages: {
66
+ validation: "Fix invalid values and try again.",
67
+ saveSuccess: "Book saved.",
68
+ saveError: "Unable to save this book.",
69
+ deleteSuccess: "Book deleted.",
70
+ deleteError: "Unable to delete this book."
71
+ }
72
+ });
73
+
74
+ export { bookCrud, bookResource };
@@ -0,0 +1 @@
1
+ export { bookCrud, bookResource } from "./bookResource.js";
@@ -3,7 +3,7 @@ id: crud/crud-screen-set
3
3
  title: CRUD list, view, create, and edit screens
4
4
  summary: Build a routed CRUD user interface as thin application pages over JSKIT's shared screen components and composables.
5
5
  keywords: actions, add, crud, delete, edit, filters, list, material, routes, view, vue
6
- requires: @jskit-ai/http-web, @jskit-ai/resource-crud-core
6
+ requires: @jskit-ai/http-web, @jskit-ai/resource-crud-core, @jskit-ai/resource-core
7
7
  ---
8
8
 
9
9
  # CRUD list, view, create, and edit screens
@@ -27,7 +27,7 @@ supporting detail content; those are public extension seams.
27
27
 
28
28
  Decide which operations exist, the route hierarchy, record title, visible
29
29
  fields, create/edit participation, destructive confirmation language, access
30
- surface, filters, and domain actions. The resource contract and product intent
30
+ surface, filters, and domain actions. The CRUD contract and product intent
31
31
  are authoritative. Do not derive these decisions from a live table or a field
32
32
  questionnaire.
33
33
 
@@ -49,7 +49,7 @@ questionnaire.
49
49
  history entry, so neither Up nor Back reopens it.
50
50
  - View fields are two-line list items with the value first:
51
51
  `<v-list-item :title="record.author" subtitle="Author" />`.
52
- - Transport and validators come from the shared resource contract.
52
+ - Transport and validators come from the shared CRUD contract.
53
53
  - Initial visible loading uses structure-matching skeletons; pending buttons use
54
54
  stable disabled labels, never spinners.
55
55
  - Resource-load errors stay inside the screen with retry. Command success and
@@ -71,15 +71,18 @@ questionnaire.
71
71
  Use `CrudListScreen`, `CrudViewScreen`, `CrudAddEditScreen`, and
72
72
  `CrudDeleteAction` with their matching composables from `@jskit-ai/http-web`.
73
73
  Use `defineCrudListFilters()`, `defineCrudListRowActions()`, and
74
- `defineCrudListBulkActions()` for optional list extensions. The resource comes
75
- from `defineCrudResource()` in `@jskit-ai/resource-crud-core`.
74
+ `defineCrudListBulkActions()` for optional list extensions. Screens take the
75
+ CRUD contract from `defineCrudContract()` in `@jskit-ai/resource-crud-core`,
76
+ built on the table resource from `defineTableResource()` in
77
+ `@jskit-ai/resource-core`.
76
78
 
77
79
  ## Example files
78
80
 
79
81
  `example/books/` contains concrete list, view, new, and edit pages for the
80
- owner-scoped `bookResource` pattern. `BookFormFields.vue` and `formFields.js`
81
- show the small product-owned form seam. `listExtensions.js` makes optional
82
- filters and actions explicit without adding empty screen machinery elsewhere.
82
+ owner-scoped `bookResource` table resource and its `bookCrud` contract.
83
+ `BookFormFields.vue` and `formFields.js` show the small product-owned form
84
+ seam. `listExtensions.js` makes optional filters and actions explicit without
85
+ adding empty screen machinery elsewhere.
83
86
 
84
87
  ## Variation points
85
88
 
@@ -166,6 +169,21 @@ through to Vuetify.
166
169
  Here `lookups` is the page's `createCrudLookupFieldRuntime()` result for the
167
170
  same form fields.
168
171
 
172
+ ### Linked records in a list
173
+
174
+ A list that shows a field of a linked record asks for it by relationship name
175
+ in `useCrudListScreen({ ..., requestQueryParams: { include: "service" } })`,
176
+ and each row reads it from its `lookups`: `record.lookups.service.name`. The
177
+ contract decides which of the linked record's fields arrive: its id, its
178
+ relation's `labelKey`, and what `fields.serviceId.output.includeFields` names.
179
+
180
+ ```vue
181
+ <template #table-row="{ record }">
182
+ <td>{{ record.title }}</td>
183
+ <td>{{ record.lookups?.service?.name }}</td>
184
+ </template>
185
+ ```
186
+
169
187
  ## Verification
170
188
 
171
189
  - Mount each route with a representative resource response.
@@ -3,7 +3,7 @@ import { computed } from "vue";
3
3
  import { useRoute } from "vue-router";
4
4
  import CrudAddEditScreen from "@jskit-ai/http-web/client/components/CrudAddEditScreen";
5
5
  import { useCrudAddEditScreen } from "@jskit-ai/http-web/client/composables/useCrudAddEditScreen";
6
- import { bookResource } from "@local/books/shared";
6
+ import { bookCrud } from "@local/books/shared";
7
7
  import BookFormFields from "./BookFormFields.vue";
8
8
  import { bookFormFields } from "./formFields.js";
9
9
 
@@ -17,7 +17,7 @@ const screen = useCrudAddEditScreen({
17
17
  saveLabel: "Save changes",
18
18
  cancelTo: "/books/:bookId",
19
19
  preserveCancelQuery: true,
20
- resource: bookResource,
20
+ resource: bookCrud,
21
21
  operationName: "patch",
22
22
  formFields: bookFormFields,
23
23
  addEditOptions: {
@@ -1,14 +1,14 @@
1
1
  <script setup>
2
2
  import CrudListScreen from "@jskit-ai/http-web/client/components/CrudListScreen";
3
3
  import { useCrudListScreen } from "@jskit-ai/http-web/client/composables/useCrudListScreen";
4
- import { bookResource } from "@local/books/shared";
4
+ import { bookCrud } from "@local/books/shared";
5
5
  import { listBulkActions, listFilters, listRowActions } from "./listExtensions.js";
6
6
 
7
7
  // A main menu destination: the app bar shows its title and no Up arrow.
8
8
  definePage({ meta: { jskit: { title: "Books" } } });
9
9
 
10
10
  const screen = useCrudListScreen({
11
- resource: bookResource,
11
+ resource: bookCrud,
12
12
  resourceNamespace: "books",
13
13
  apiSuffix: "/books",
14
14
  recordIdParam: "bookId",
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import CrudAddEditScreen from "@jskit-ai/http-web/client/components/CrudAddEditScreen";
3
3
  import { useCrudAddEditScreen } from "@jskit-ai/http-web/client/composables/useCrudAddEditScreen";
4
- import { bookResource } from "@local/books/shared";
4
+ import { bookCrud } from "@local/books/shared";
5
5
  import BookFormFields from "./BookFormFields.vue";
6
6
  import { bookFormFields } from "./formFields.js";
7
7
 
@@ -11,7 +11,7 @@ const screen = useCrudAddEditScreen({
11
11
  mode: "new",
12
12
  saveLabel: "Save book",
13
13
  cancelTo: "/books",
14
- resource: bookResource,
14
+ resource: bookCrud,
15
15
  operationName: "create",
16
16
  formFields: bookFormFields,
17
17
  addEditOptions: {
@@ -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.263",
13
+ "@jskit-ai/database-runtime-mysql": "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: "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.263",
13
+ "@jskit-ai/database-runtime-postgres": "0.1.260"
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,117 @@
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, 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, or a workspace with rows shared with every
29
+ workspace
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.
42
+ - A field's default value is `defaultTo`; `default` is refused, since
43
+ json-rest-api never read it.
44
+ - A field with `defaultTo` is not `required`: a create is checked for
45
+ required fields before defaults apply, so `defineTableResource` refuses the
46
+ two together.
47
+ - `lockVersionField` names an integer field over a non-null integer column
48
+ with a starting value; `patch(id, changes, { ...options, lockVersion })` then refuses
49
+ stale edits with a 409.
50
+ - The definition is shared with the client, so it holds no server-only keys:
51
+ the row policy, hooks, server-only search entries and computed query fields
52
+ go in the owner's `owns` entry.
53
+ - The source-controlled migration and the resource describe the same table.
54
+
55
+ ## Framework APIs
56
+
57
+ `defineTableResource()` from `@jskit-ai/resource-core/shared/tableResource`
58
+ freezes the description and refuses the CRUD contract's keys (`namespace`,
59
+ `apiAccess`, `operations`, `messages`), `autofilter` and the server-only keys,
60
+ each with an error naming where it belongs.
61
+
62
+ A field that looks up a record of another resource names that resource's
63
+ namespace with `lookup`:
64
+
65
+ ```js
66
+ customerId: { type: "id", required: true, search: true, lookup: "customers" }
67
+ ```
68
+
69
+ `defineTableResource()` expands it, where `lookup` stood, to:
70
+
71
+ ```js
72
+ relation: { kind: "lookup", namespace: "customers", valueKey: "id" },
73
+ belongsTo: "customers", // the table resource named by the namespace, camel-cased
74
+ as: "customer", // the field key without its Id suffix
75
+ ui: { formControl: "autocomplete" }
76
+ ```
77
+
78
+ Keys you write beside `lookup` win: `belongsTo` and `as` replace the
79
+ filled-in values, and `relation` and `ui` are merged over them. A field whose
80
+ key has no `Id` suffix must write `as`.
81
+
82
+ The owner, on the server:
83
+
84
+ ```js
85
+ const OrdersFeature = defineFeature({
86
+ id: "orders",
87
+ owns: [{ resource: orderResource, rowPolicy: ordersTheActorMayHandle }],
88
+ provides: { orders: "orders" },
89
+ setup: ({ resources }) => ({ orders: createOrdersRepository({ orders: resources.orders }) })
90
+ });
91
+ ```
92
+
93
+ ## Example files
94
+
95
+ - `example/orderResource.js` is a workspace-owned orders table with a lookup,
96
+ defaults, a version number, a text search and a default sort.
97
+
98
+ ## Variation points
99
+
100
+ Rename the table and fields, pick the ownership kind the product's owner
101
+ columns support, add search entries and sorts. A computed output field is
102
+ `storage: { virtual: true }` here and a `queryFields` entry in `owns`.
103
+
104
+ ## Verification
105
+
106
+ - Import the resource and check its ownership, fields and lookups.
107
+ - Rebuild the table from source-controlled migrations in a disposable database.
108
+ - Give each repository path a scope test (`expectScoped`) and a flat-query
109
+ test (`expectFlatQueries`) from `@jskit-ai/resource-runtime/test`.
110
+
111
+ ## Avoid
112
+
113
+ - a second table resource for the same table
114
+ - `where workspace_id = ?` written by hand: ownership adds it
115
+ - a row policy, hooks or SQL in the shared definition
116
+ - `default:` instead of `defaultTo:`
117
+ - `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.20",
6
+ "@jskit-ai/shell-web": "0.2.17"
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.14",
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 });