@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
@@ -10,8 +10,9 @@
10
10
  "./shared": "./src/shared/index.js"
11
11
  },
12
12
  "dependencies": {
13
- "@jskit-ai/crud-core": "0.2.14",
14
- "@jskit-ai/resource-crud-core": "0.1.204"
13
+ "@jskit-ai/crud-core": "0.2.16",
14
+ "@jskit-ai/resource-crud-core": "0.1.206",
15
+ "@jskit-ai/users-core": "0.2.17"
15
16
  },
16
17
  "jskit": {
17
18
  "kind": "runtime",
@@ -20,10 +21,10 @@
20
21
  "app.users"
21
22
  ],
22
23
  "requires": [
24
+ "crud.contracts",
23
25
  "runtime.http",
24
- "runtime.json-rest-api",
25
26
  "runtime.actions",
26
- "runtime.database"
27
+ "users.profiles"
27
28
  ]
28
29
  },
29
30
  "runtime": {
@@ -1,12 +1,14 @@
1
- import { defineCrudJsonApiFeature } from "@jskit-ai/crud-core/server/defineCrudJsonApiFeature";
2
- import { resource } from "../shared/userResource.js";
1
+ import { defineCrudFeature } from "@jskit-ai/crud-core/server/defineCrudFeature";
2
+ import { userCrud } from "../shared/userCrud.js";
3
3
 
4
- const UsersFeature = defineCrudJsonApiFeature({
5
- resource,
4
+ // users-core owns the users table; this feature serves the administration
5
+ // CRUD contract through its owner's repository.
6
+ const UsersFeature = defineCrudFeature({
7
+ crud: userCrud,
8
+ ownerRepository: "users.profiles",
6
9
  id: "app.users",
7
10
  capability: "app.users",
8
11
  surface: "home",
9
- ownershipFilter: "public",
10
12
  relativePath: "/users"
11
13
  });
12
14
 
@@ -1 +1 @@
1
- export { resource } from "./userResource.js";
1
+ export { userCrud } from "./userCrud.js";
@@ -0,0 +1,21 @@
1
+ import { defineCrudContract } from "@jskit-ai/resource-crud-core/shared/crudContract";
2
+ import { userProfileTableResource } from "@jskit-ai/users-core/shared/resources/userProfileResource";
3
+
4
+ // The users list and view. The users table has one table resource, users-core's
5
+ // userProfileTableResource; this contract chooses the operations and the fields they
6
+ // return, and the list searches with the resource's q (display name, email and
7
+ // username). It shows only the fields it names, so the sign-in identity and the
8
+ // avatar's storage stay out of administration.
9
+ const userCrud = defineCrudContract(userProfileTableResource, {
10
+ namespace: "users",
11
+ operations: ["list", "view"],
12
+ fields: {
13
+ displayName: { output: "optional" },
14
+ email: { output: "optional" },
15
+ username: { output: "optional" },
16
+ createdAt: { output: "required" },
17
+ updatedAt: { output: "required" }
18
+ }
19
+ });
20
+
21
+ export { userCrud };
@@ -10,9 +10,14 @@
10
10
  "./shared": "./src/shared/index.js"
11
11
  },
12
12
  "dependencies": {
13
- "@jskit-ai/crud-core": "0.2.14",
14
- "@jskit-ai/resource-crud-core": "0.1.204",
15
- "@jskit-ai/workspaces-core": "0.2.15"
13
+ "@jskit-ai/crud-core": "0.2.16",
14
+ "@jskit-ai/resource-crud-core": "0.1.206",
15
+ "@jskit-ai/users-core": "0.2.17",
16
+ "@jskit-ai/workspaces-core": "0.2.17"
17
+ },
18
+ "peerDependencies": {
19
+ "@jskit-ai/kernel": "0.2.15",
20
+ "@jskit-ai/resource-runtime": "0.2.13"
16
21
  },
17
22
  "jskit": {
18
23
  "kind": "runtime",
@@ -21,10 +26,11 @@
21
26
  "app.users-workspace"
22
27
  ],
23
28
  "requires": [
29
+ "crud.contracts",
24
30
  "runtime.http",
25
- "runtime.json-rest-api",
26
31
  "runtime.actions",
27
- "runtime.database"
32
+ "users.profiles",
33
+ "workspaces.memberships"
28
34
  ]
29
35
  },
30
36
  "runtime": {
@@ -1,13 +1,22 @@
1
- import { defineCrudJsonApiFeature } from "@jskit-ai/crud-core/server/defineCrudJsonApiFeature";
1
+ import { defineCrudFeature } from "@jskit-ai/crud-core/server/defineCrudFeature";
2
2
  import { WORKSPACE_CRUD_SCOPE } from "@jskit-ai/workspaces-core/server/support/workspaceCrud";
3
- import { resource } from "../shared/userResource.js";
3
+ import { userCrud } from "../shared/userCrud.js";
4
+ import { workspaceMembersOnly } from "./workspaceMembersOnly.js";
4
5
 
5
- const UsersWorkspaceFeature = defineCrudJsonApiFeature({
6
- resource,
6
+ // users-core owns the users table; this feature serves the administration
7
+ // CRUD contract on a workspace's admin surface through its owner's
8
+ // repository. The routes are a workspace's, so they resolve the caller's
9
+ // workspace and check its crud.users.* permissions, and the repository is
10
+ // narrowed to that workspace's members: the users table is every user's.
11
+ const UsersWorkspaceFeature = defineCrudFeature({
12
+ crud: userCrud,
13
+ ownerRepository: "users.profiles",
14
+ requires: { memberships: "workspaces.memberships" },
15
+ decorateRepository: ({ repository, memberships }) => workspaceMembersOnly(repository, memberships),
16
+ visibility: "workspace",
7
17
  id: "app.users-workspace",
8
18
  capability: "app.users-workspace",
9
19
  surface: "admin",
10
- ownershipFilter: "workspace",
11
20
  relativePath: "/users",
12
21
  scope: WORKSPACE_CRUD_SCOPE
13
22
  });
@@ -0,0 +1,52 @@
1
+ import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
2
+ import { passedOn, resolveWorkspaceScopeValue } from "@jskit-ai/resource-runtime/server";
3
+
4
+ /**
5
+ * users-core's users repository narrowed to the caller's workspace: a list reads only the workspace's active members,
6
+ * and anyone else is a missing record, so a workspace admin never learns who else has an account. The users table is
7
+ * public, so its handle alone would list every user; the narrowing reads the workspace's memberships first. It answers
8
+ * only the reads it narrows (query and get, with pageLimits): the rest of users.profiles, such as findByEmail or
9
+ * countUsers, never passes through this feature's repository, so nothing reaches users past the narrowing through it.
10
+ * @param {import("@jskit-ai/crud-core/server/types").CrudRepository} repository
11
+ * @param {import("@jskit-ai/workspaces-core/server/types").WorkspaceMembershipsRepository} memberships
12
+ */
13
+ function workspaceMembersOnly(repository, memberships) {
14
+ // The caller's workspace's active members; none when the context names no workspace. The memberships get only the
15
+ // context and transaction: the list's own settings (its format, missing) are for the users read.
16
+ function memberIds(options) {
17
+ return memberships.activeMemberIds(resolveWorkspaceScopeValue(options.context), passedOn(options));
18
+ }
19
+
20
+ /**
21
+ * One page of the workspace's members the params select.
22
+ * @param {Record<string, any>} params
23
+ * @param {{ context?: object }} options
24
+ */
25
+ async function query(params, options) {
26
+ const members = await memberIds(options);
27
+ const requested = params.filters?.id;
28
+ // An empty list matches no row: a workspace with no members lists nobody.
29
+ const id = requested === undefined ? members : [requested].flat().map(String).filter((memberId) => members.includes(memberId));
30
+ return repository.query({ ...params, filters: { ...params.filters, id } }, options);
31
+ }
32
+
33
+ /**
34
+ * A member of the workspace, or a missing record for anyone else.
35
+ * @param {unknown} id
36
+ * @param {Record<string, any>} params
37
+ * @param {{ context?: object, missing?: "skip" }} options
38
+ */
39
+ async function get(id, params, options) {
40
+ if ((await memberIds(options)).includes(String(id))) {
41
+ return repository.get(id, params, options);
42
+ }
43
+ if (options.missing === "skip") {
44
+ return null;
45
+ }
46
+ throw new AppError(404, "Document not found.", { code: "crud.document.notFound" });
47
+ }
48
+
49
+ return Object.freeze({ query, get, pageLimits: repository.pageLimits });
50
+ }
51
+
52
+ export { workspaceMembersOnly };
@@ -1 +1 @@
1
- export { resource } from "./userResource.js";
1
+ export { userCrud } from "./userCrud.js";
@@ -0,0 +1,21 @@
1
+ import { defineCrudContract } from "@jskit-ai/resource-crud-core/shared/crudContract";
2
+ import { userProfileTableResource } from "@jskit-ai/users-core/shared/resources/userProfileResource";
3
+
4
+ // The users list and view. The users table has one table resource, users-core's
5
+ // userProfileTableResource; this contract chooses the operations and the fields they
6
+ // return, and the list searches with the resource's q (display name, email and
7
+ // username). It shows only the fields it names, so the sign-in identity and the
8
+ // avatar's storage stay out of administration.
9
+ const userCrud = defineCrudContract(userProfileTableResource, {
10
+ namespace: "users",
11
+ operations: ["list", "view"],
12
+ fields: {
13
+ displayName: { output: "optional" },
14
+ email: { output: "optional" },
15
+ username: { output: "optional" },
16
+ createdAt: { output: "required" },
17
+ updatedAt: { output: "required" }
18
+ }
19
+ });
20
+
21
+ export { userCrud };
@@ -66,20 +66,20 @@ catalogue entries. Updating labels requires no database migration.
66
66
  Provide one function as the optional `workspaces.invite-acceptance-participant`
67
67
  capability, from a small application feature that requires only what its writes
68
68
  need (it cannot require `workspaces.core`, which receives it). The built-in
69
- `workspace.invite.redeem` action invokes `fn({ invite, user }, { trx, context })`
69
+ `workspace.invite.redeem` action invokes `fn({ invite, user }, txOptions)`
70
70
  after validating the recipient, state and expiry and activating membership in the
71
- uncommitted transaction. Forward `trx` to every application owner and propagate
72
- failures. JSKIT marks accepted and commits after the function succeeds. Keep
71
+ uncommitted transaction. Pass `txOptions` to every application owner's calls and
72
+ propagate failures. JSKIT marks accepted and commits after the function succeeds. Keep
73
73
  external effects out of it. Without the capability, acceptance activates the
74
74
  membership only. Allow ordinary invitations; reject stale, revoked, conflicting
75
75
  or unexpectedly missing application associations.
76
76
 
77
- Use `members.prepareInvite(workspace, actor,
78
- payload, { trx, context })` in `repositories.workspaceInvites.withTransaction`
79
- or the same JSON REST API's managed `transaction`. It returns
77
+ Call `members.prepareInvite(workspace, actor, payload, txOptions)` inside
78
+ `repositories.workspaceInvites.withTransaction(options, async (txOptions) => …)`
79
+ or another resource handle's `withTransaction`. It returns
80
80
  `{ createdInviteId, inviteTokenPreview }` without delivery. Lock the workspace
81
- with `repositories.workspaceInvites.lockWorkspaceForInvitations(workspace.id,
82
- { trx })` before application draft/receipt rows, and save the exact invitation
81
+ with `lock(workspace.id, txOptions)` of the `workspaces` capability before
82
+ application draft/receipt rows, and save the exact invitation
83
83
  association and retry receipt before commit. Failure restores the previous
84
84
  pending invitation. Keep access drafts separate: preparation creates a real token.
85
85
 
@@ -92,7 +92,7 @@ failure. A provider may accept a message before failing; the application owns
92
92
  receipts and retries. Keep tokens/URLs out of logs and use controlled mailers in tests.
93
93
 
94
94
  `createInvite`, acceptance and refusal own their managed transactions and
95
- reject a borrowed `options.trx`. Use preparation followed by delivery after commit for
95
+ refuse a call made inside one (`withTransaction({ ...options, outermost: true }, work)`). Use preparation followed by delivery after commit for
96
96
  transactional creation; put related acceptance writes in the participant. Revocation may join
97
97
  a managed transaction. Raw Knex/savepoint transactions cannot own resource
98
98
  writes. Invitation mutations lock workspace then invitation rows, revalidate
@@ -54,6 +54,22 @@ the **Your workspaces** page at `src/pages/home/workspaces/index.vue`, member
54
54
  and settings routes, not-found state, app-owned invitation widgets, and the
55
55
  components that an application's client provider registers.
56
56
 
57
+ `example/src/surfaces/<surface>/` and `example/src/pages/admin/` are each
58
+ surface's own pages; the app puts them under the surface's `pagesRoot`. With
59
+ `pagesRoot: "w/[workspaceSlug]"` for `app` and `"w/[workspaceSlug]/admin"` for
60
+ `admin`:
61
+
62
+ | Example file | App file |
63
+ | --- | --- |
64
+ | `src/surfaces/app/root.vue` | `src/pages/w/[workspaceSlug].vue` |
65
+ | `src/surfaces/app/index.vue` | `src/pages/w/[workspaceSlug]/index.vue` |
66
+ | `src/surfaces/admin/root.vue` | `src/pages/w/[workspaceSlug]/admin.vue` |
67
+ | `src/surfaces/admin/index.vue` | `src/pages/w/[workspaceSlug]/admin/index.vue` |
68
+ | `src/pages/admin/...` | `src/pages/w/[workspaceSlug]/admin/...` |
69
+
70
+ The surface definitions and access policies they need are in the human
71
+ guide's **Workspace tenancy → Surfaces and where `/` lands**.
72
+
57
73
  ## Variation points
58
74
 
59
75
  Change surfaces, routes, section owners, labels, icons, placement order,
@@ -4,4 +4,6 @@
4
4
 
5
5
  <script setup>
6
6
  import WorkspacesClientElement from "@jskit-ai/workspaces-web/client/components/WorkspacesClientElement";
7
+
8
+ definePage({ meta: { jskit: { parent: "/" } } });
7
9
  </script>
@@ -3,10 +3,10 @@
3
3
  # Sanity checks
4
4
 
5
5
  Before calling a list, a save, a test, a refactor, a screen or a deployment
6
- done, measure what it actually costs and does, judge whether that is sane, and
7
- fix the cause when it is not. This page is the JSKIT part of that: the
8
- pitfalls JSKIT's own packages cause, then which JSKIT call, helper or setting
9
- applies each check, with one line on what the check catches and why.
6
+ done, measure what it costs and does, judge whether that is sane, and fix the
7
+ cause when it is not. This page holds JSKIT's part: the pitfalls JSKIT's own
8
+ packages cause, then the JSKIT call, helper or setting that applies each
9
+ check, with one line on what it catches and why.
10
10
 
11
11
  ## JSKIT's own pitfalls
12
12
 
@@ -66,36 +66,35 @@ expects the relationship id or an unselected field never gets it.
66
66
 
67
67
  ### Every row
68
68
 
69
- - An unpaged JSON:API query returns the first 20 rows, and a page above the
70
- resource's `queryMaxLimit` (100 unless it sets one) is cut to that size,
71
- both without an error; CRUD `queryDocuments()` refuses such a `limit`. A
72
- read its callers treat as complete uses `queryAllJsonRestCollectionRows()`
73
- or the repository's `queryEveryDocument()`, because the missing rows
74
- otherwise vanish from every picker and delivery built on it
75
- ([Every row](typed-features.md#every-row)).
76
- - A lookup by a unique key matches at most one row, so it may read `rows[0]`
77
- of a plain query.
69
+ - A handle's `query()` answers one page and refuses a missing or oversized
70
+ `page.size`, because json-rest-api would otherwise cut the list without an
71
+ error. A read its callers treat as complete uses `queryAll()`, because the
72
+ missing rows otherwise vanish from every picker and delivery built on it
73
+ ([Reads](data-access.md#reads-shaped-for-the-screen)).
74
+ - A lookup by a unique key matches at most one row: `get(id)`, or a `query()`
75
+ of page size 1.
78
76
 
79
77
  ### Query counts and batches
80
78
 
81
- - Count a path's queries with `countQueries()` from
82
- `@jskit-ai/database-runtime/server/testDatabases` at a few rows and at a
79
+ - Count a path's queries with `expectFlatQueries()` from
80
+ `@jskit-ai/resource-runtime/test`, or `countQueries()` from
81
+ `@jskit-ai/database-runtime/server/testDatabases`, at a few rows and at a
83
82
  realistic number; the count should follow tables and pages, not rows
84
- ([Query counts at realistic
85
- sizes](typed-features.md#query-counts-at-realistic-sizes)).
83
+ ([Tests](data-access.md#tests)).
86
84
  - A JSON:API read adds a few lookup and visibility queries per page and per
87
- relationship field. That follows pages, so it is expected.
88
- - `getDocumentById()`, `queryDocuments()` or a service `get` inside a loop is
89
- the usual per-row read; batch it with an id-list `searchSchema` filter or
90
- `include`, grouped with `Map.groupBy()` ([Lists read related rows in
91
- batches](typed-features.md#lists-read-related-rows-in-batches)).
85
+ relationship field, and json-rest-api reads each relationship's related ids
86
+ in one statement however many rows the page has. Both follow pages, not
87
+ rows, so `expectFlatQueries` passes them.
88
+ - A handle's `get()` or `query()`, or a service `get`, inside a loop is the
89
+ usual per-row read; batch it with an id-list filter through `queryAll()` or
90
+ `include`, grouped with `Map.groupBy()`
91
+ ([Reads](data-access.md#reads-shaped-for-the-screen)).
92
92
  - A `rowPolicy` adds one correlated predicate to the query it is given, so
93
93
  its cost follows the rows read, not the subject's history. It runs on every
94
- single-record read, including a write's default `returning: "full"` read
95
- back, so a write that discards its result passes `returning: "minimal"` and
96
- a batch of creates uses `createDocuments()`, in the owner's transaction
97
- ([Bounded access
98
- checks](typed-features.md#bounded-access-checks)).
94
+ single-record read, including a write's default `returning: "record"` read
95
+ back, so a write that discards its result passes `returning: "id"` or
96
+ `"none"`, and a batch uses the many-row writes (`createMany()`,
97
+ `patchMany()`) ([Layer 1](data-access.md#layer-1-the-table-resource)).
99
98
 
100
99
  ### Tests
101
100
 
@@ -113,7 +112,7 @@ expects the relationship id or an unselected field never gets it.
113
112
  duration, so slow preparation shows ([Test databases are prepared
114
113
  once](typed-features.md#test-databases-are-prepared-once),
115
114
  [integration test
116
- databases](/guide/app-setup/database-layer#integration-test-databases)).
115
+ databases](database-layer.md#integration-test-databases)).
117
116
 
118
117
  ### Inputs and access
119
118