@jskit-ai/agent-docs 0.1.282 → 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 +40 -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
@@ -1,6 +1,6 @@
1
1
  import Fastify from "fastify";
2
2
  import SerializerSelector from "@fastify/fast-json-stringify-compiler";
3
- import { createCachedResponseSerializerFactory } from "@jskit-ai/kernel/server/http";
3
+ import { createCachedResponseSerializerFactory, resolveTrustedProxies } from "@jskit-ai/kernel/server/http";
4
4
  import fastifyStatic from "@fastify/static";
5
5
  import { buildLoggerOptions } from "./server/lib/loggerOptions.js";
6
6
  import { resolveRuntimeEnv } from "./server/lib/runtimeEnv.js";
@@ -94,6 +94,9 @@ function canServeStaticFile(distRoot, relativePath) {
94
94
  async function createServer({ runtimeEnv }) {
95
95
  const app = Fastify({
96
96
  logger: buildLoggerOptions(),
97
+ // The proxies in front of the server (TRUSTED_PROXIES; loopback without it), so request.ip and the rate limits
98
+ // counted by it are each client's address behind them.
99
+ trustProxy: resolveTrustedProxies(runtimeEnv),
97
100
  schemaController: {
98
101
  compilersFactory: {
99
102
  buildSerializer: createCachedResponseSerializerFactory(SerializerSelector())
@@ -8,6 +8,10 @@ Framework references:
8
8
  - `https://mobily-enterprises.github.io/jskit-ai/guide/`
9
9
  - `https://mobily-enterprises.github.io/jskit-ai/patterns/`
10
10
 
11
+ Before writing or reviewing code that reads or writes a database table, read
12
+ the data access guide at
13
+ `https://mobily-enterprises.github.io/jskit-ai/guide/framework/data-access`.
14
+
11
15
  Before writing or reviewing server code (a feature, provider, action, service,
12
16
  repository, route, error or test), read the typed features guide at
13
17
  `https://mobily-enterprises.github.io/jskit-ai/guide/framework/typed-features`.
@@ -34,7 +34,7 @@
34
34
  "dependencies": {
35
35
  "@local/main": "0.1.0",
36
36
  "@fastify/static": "^10.1.3",
37
- "@jskit-ai/kernel": "0.2.13",
37
+ "@jskit-ai/kernel": "0.2.14",
38
38
  "@jskit-ai/material-web": "0.1.3",
39
39
  "@tanstack/vue-query": "^5.101.0",
40
40
  "fastify": "^5.12.7",
@@ -44,14 +44,14 @@
44
44
  "vue": "^3.5.44",
45
45
  "vue-router": "^5.3.1",
46
46
  "vuetify": "^4.3.0",
47
- "@jskit-ai/http-runtime": "0.2.13",
48
- "@jskit-ai/shell-web": "0.2.16",
47
+ "@jskit-ai/http-runtime": "0.2.14",
48
+ "@jskit-ai/shell-web": "0.2.17",
49
49
  "@fastify/fast-json-stringify-compiler": "^5.1.0",
50
50
  "@vue/devtools-api": "^8.2.1"
51
51
  },
52
52
  "devDependencies": {
53
- "@jskit-ai/config-eslint": "0.1.246",
54
- "@jskit-ai/jskit-catalog": "0.2.34",
53
+ "@jskit-ai/config-eslint": "0.1.247",
54
+ "@jskit-ai/jskit-catalog": "0.2.36",
55
55
  "@playwright/test": "1.61.1",
56
56
  "@vitejs/plugin-vue": "^6.0.7",
57
57
  "eslint": "^10.8.0",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "description": "App-local runtime composition and lightweight glue.",
11
11
  "dependencies": {
12
- "@jskit-ai/kernel": "0.2.13"
12
+ "@jskit-ai/kernel": "0.2.14"
13
13
  },
14
14
  "jskit": {
15
15
  "kind": "runtime",
@@ -1,6 +1,6 @@
1
1
  import Fastify from "fastify";
2
2
  import SerializerSelector from "@fastify/fast-json-stringify-compiler";
3
- import { createCachedResponseSerializerFactory } from "@jskit-ai/kernel/server/http";
3
+ import { createCachedResponseSerializerFactory, resolveTrustedProxies } from "@jskit-ai/kernel/server/http";
4
4
  import fastifyStatic from "@fastify/static";
5
5
  import { buildLoggerOptions } from "./server/lib/loggerOptions.js";
6
6
  import { resolveRuntimeEnv } from "./server/lib/runtimeEnv.js";
@@ -94,6 +94,9 @@ function canServeStaticFile(distRoot, relativePath) {
94
94
  async function createServer({ runtimeEnv }) {
95
95
  const app = Fastify({
96
96
  logger: buildLoggerOptions(),
97
+ // The proxies in front of the server (TRUSTED_PROXIES; loopback without it), so request.ip and the rate limits
98
+ // counted by it are each client's address behind them.
99
+ trustProxy: resolveTrustedProxies(runtimeEnv),
97
100
  schemaController: {
98
101
  compilersFactory: {
99
102
  buildSerializer: createCachedResponseSerializerFactory(SerializerSelector())
@@ -48,8 +48,11 @@ in source or a questionnaire transcript.
48
48
 
49
49
  Use `AssistantSurfaceClientElement` and `AssistantSettingsClientElement` from
50
50
  `@jskit-ai/assistant-runtime/client`. Install the runtime package through the
51
- normal package-capability path. Configure `assistantSurfaces` in public config
52
- and `assistantServer` in server config.
51
+ normal package-capability path. Install `@jskit-ai/resource-runtime` and
52
+ register `AssistantStoreFeature` from `@jskit-ai/assistant-runtime/server` in
53
+ the ordinary server feature list; it owns the assistant's tables and provides the
54
+ repositories the database-backed surface uses. Configure `assistantSurfaces` in
55
+ public config and `assistantServer` in server config.
53
56
 
54
57
  ## Example files
55
58
 
@@ -2,7 +2,8 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/database-runtime-mysql": "0.1.260",
6
- "@jskit-ai/oidc-issuer-core": "0.1.21"
5
+ "@jskit-ai/database-runtime": "0.1.263",
6
+ "@jskit-ai/database-runtime-mysql": "0.1.261",
7
+ "@jskit-ai/oidc-issuer-core": "0.1.22"
7
8
  }
8
9
  }
@@ -120,6 +120,21 @@ the standard app-owned managed-preview identity executable,
120
120
  surface and placement declarations into the application's ordinary config and
121
121
  placement files.
122
122
 
123
+ The pages live under `src/pages/auth/`, so the app defines an `auth` surface
124
+ open to everyone in `config/public.js`, under the foundations' `public` access
125
+ policy (`surfaceAccessPolicies.public = {}`):
126
+
127
+ ```js
128
+ config.surfaceDefinitions.auth = {
129
+ id: "auth",
130
+ label: "Sign in",
131
+ pagesRoot: "auth",
132
+ requiresAuth: false,
133
+ requiresWorkspace: false,
134
+ accessPolicyId: "public"
135
+ };
136
+ ```
137
+
123
138
  ## Variation points
124
139
 
125
140
  Change provider, routes, labels, page wrappers, permitted login methods, profile
@@ -2,7 +2,7 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/auth-provider-oidc-core": "0.2.20",
6
- "@jskit-ai/auth-web": "0.2.18"
5
+ "@jskit-ai/auth-provider-oidc-core": "0.2.21",
6
+ "@jskit-ai/auth-web": "0.2.19"
7
7
  }
8
8
  }
@@ -2,6 +2,6 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/auth-provider-supabase-core": "0.2.15"
5
+ "@jskit-ai/auth-provider-supabase-core": "0.2.16"
6
6
  }
7
7
  }
@@ -7,10 +7,10 @@
7
7
  "db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
8
8
  },
9
9
  "dependencies": {
10
- "@jskit-ai/connectors-core": "0.2.14",
11
- "@jskit-ai/connector-google-calendar": "0.2.14",
12
- "@jskit-ai/database-runtime": "0.1.262",
13
- "@jskit-ai/database-runtime-mysql": "0.1.260",
10
+ "@jskit-ai/connectors-core": "0.2.15",
11
+ "@jskit-ai/connector-google-calendar": "0.2.15",
12
+ "@jskit-ai/database-runtime": "0.1.263",
13
+ "@jskit-ai/database-runtime-mysql": "0.1.261",
14
14
  "knex": "^3.1.0"
15
15
  }
16
16
  }
@@ -0,0 +1,133 @@
1
+ ---
2
+ id: crud/crud-contract
3
+ title: Owner-scoped CRUD contract
4
+ summary: Define an application table resource whose records belong to the current user, and the authenticated CRUD contract that serves it.
5
+ keywords: authenticated, crud, database, owner-scoped, resource, table, user
6
+ requires: @jskit-ai/resource-core, @jskit-ai/resource-crud-core
7
+ ---
8
+
9
+ # Owner-scoped CRUD contract
10
+
11
+ ## Use when
12
+
13
+ Use this pattern when an authenticated application stores records that belong
14
+ to one user and standard list, view, create, patch, and delete behaviour fits
15
+ the product.
16
+
17
+ The example is intentionally a normal source file. Copy it directly when the
18
+ shape fits, adapt its fields and language for another entity, or read it as a
19
+ reference for `defineTableResource()` and `defineCrudContract()`.
20
+
21
+ ## Do not use when
22
+
23
+ Do not use this pattern for public records, workspace-owned records, inherited
24
+ ownership, composite identities, or a domain whose operations do not match
25
+ normal CRUD behaviour. Select a pattern matching the real ownership and
26
+ operation model instead of changing `ownership` merely to make a test pass.
27
+
28
+ ## Product decisions
29
+
30
+ Know these decisions before adapting the example:
31
+
32
+ - whether records truly belong to the current user
33
+ - which fields are required, optional, nullable, searchable, or hidden
34
+ - whether a value is free text or a constrained vocabulary
35
+ - which operations the product actually exposes
36
+ - the user-facing success and failure language
37
+
38
+ Those decisions come from the product conversation or existing application,
39
+ not from a JSKIT questionnaire.
40
+
41
+ ## Invariants
42
+
43
+ - The persisted table has a normal single-column primary key.
44
+ - User ownership is represented by a non-null `user_id` column.
45
+ - The resource's hidden `userId` field maps to that ownership column.
46
+ - `ownership: "user"` is used only for genuine current-user ownership.
47
+ - The table has exactly one table resource; the CRUD contract is built on it.
48
+ - Each field the API shows or accepts has an entry in the CRUD contract's
49
+ `fields` stating its output, create and patch participation; a field without
50
+ one is neither shown nor accepted (see Field operations and lookups below).
51
+ - Temporal database values use the declared storage serializer.
52
+ - The source-controlled migration and the table resource agree.
53
+ - Server policy tests prove that one user cannot read or mutate another user's
54
+ records.
55
+
56
+ ## Framework APIs
57
+
58
+ The example uses `defineTableResource()` from
59
+ `@jskit-ai/resource-core/shared/tableResource` for the table's facts (name,
60
+ table, ownership, fields, search and sort) and `defineCrudContract()` from
61
+ `@jskit-ai/resource-crud-core/shared/crudContract` for its HTTP surface
62
+ (its required `namespace`, operations, access, field operations and messages). The CRUD contract derives
63
+ the normal CRUD operation validators from the table resource's fields.
64
+
65
+ Higher layers should consume this resource through JSKIT's CRUD service,
66
+ repository, route, and UI APIs. They should not rebuild field validators or
67
+ serialize requests independently.
68
+
69
+ ## Field operations and lookups
70
+
71
+ The CRUD contract's `fields` name, for each field, the operations it takes part
72
+ in: `output` (the record in every response), and `create` and `patch` (the
73
+ request bodies; an enabled `replace` uses the `create` fields unless a field
74
+ names `replace`). Each takes `"required"`, `"optional"` or an object, for
75
+ example `title: { output: "required", create: "required", patch: "optional" }`.
76
+
77
+ A contract shows and accepts only the fields it names: a field without an entry
78
+ is not in any output, a body that sends it is refused, and the CRUD service
79
+ leaves it out of the records it answers. So a column added to the table later
80
+ stays out of every API until a contract names it. Every record output carries
81
+ its `id` without an entry, and a stored `createdAt` or `updatedAt` is output
82
+ without one.
83
+
84
+ A stored `createdAt` or `updatedAt` is output only, and a contract that lets a
85
+ request write one (a `create`, `patch` or `replace` entry, or an explicit body
86
+ that takes it) is refused: the handle stamps `createdAt` on a create and
87
+ `updatedAt` on every write, replacing what a request sends. Only a system
88
+ context, as an import uses, sets its own.
89
+
90
+ A lookup field (`lookup: "<namespace>"`) is the table resource's: the
91
+ `database/table-resource` pattern lists what it expands to.
92
+
93
+ ## Example files
94
+
95
+ - `example/bookResource.js` is a complete table resource (`bookResource`) and
96
+ CRUD contract (`bookCrud`) for personal book records.
97
+
98
+ The example deliberately uses a concrete domain. Rename and edit normal source
99
+ rather than feeding it through placeholder interpolation.
100
+
101
+ ## Variation points
102
+
103
+ Safe adaptations include:
104
+
105
+ - resource name, table name and CRUD namespace
106
+ - product fields and validation
107
+ - searchable fields and default sort
108
+ - enabled CRUD operations
109
+ - user-facing messages
110
+ - lookup relationships supported by the resource APIs
111
+
112
+ Changing ownership, access, identity, or persistence architecture selects a
113
+ different pattern and requires corresponding policy and migration evidence.
114
+
115
+ ## Verification
116
+
117
+ - Import the CRUD contract and prove all intended operations exist.
118
+ - Validate representative accepted and rejected field values.
119
+ - Rebuild the table from source-controlled migrations in a disposable database.
120
+ - Run positive current-user CRUD tests.
121
+ - Run negative cross-user read and mutation tests.
122
+ - Run the application's focused tests and broad verifier at sign-off.
123
+
124
+ ## Avoid
125
+
126
+ - generator provenance or scaffold-shape metadata
127
+ - a field-by-field questionnaire
128
+ - a live table as the only source of schema truth
129
+ - app-specific request serializers that duplicate the CRUD contract
130
+ - a second table resource for the same table
131
+ - hidden ownership aliases other than the framework's explicit ownership field
132
+ - durable receipts recording that this pattern was copied
133
+ - changing an installed baseline migration instead of adding a new migration
@@ -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 };
@@ -1,19 +1,20 @@
1
1
  ---
2
- id: crud/json-api-resource-package
3
- title: JSON API CRUD resource package
4
- summary: Build a complete application-owned CRUD server package from a migration, resource contract, and one framework module declaration.
5
- keywords: actions, crud, database, json-api, migration, permissions, provider, repository, routes, service
6
- requires: @jskit-ai/crud-core, @jskit-ai/resource-crud-core
2
+ id: crud/crud-feature-package
3
+ title: CRUD feature package
4
+ summary: Build a complete application-owned CRUD server package from a migration, a table resource with its CRUD contract, and one framework module declaration.
5
+ keywords: actions, crud, database, defineCrudFeature, json-api, migration, owns, permissions, repository, routes, service, table resource
6
+ requires: @jskit-ai/crud-core, @jskit-ai/resource-core, @jskit-ai/resource-crud-core
7
7
  ---
8
8
 
9
- # JSON API CRUD resource package
9
+ # CRUD feature package
10
10
 
11
11
  ## Use when
12
12
 
13
13
  Use this pattern when a product entity needs conventional JSON API list, view,
14
14
  create, patch, and delete behavior. JSKIT owns the repeated repository, service,
15
15
  action, route, and provider mechanics. The application owns the migration,
16
- resource contract, access surface, ownership policy, and package composition.
16
+ table resource, CRUD contract, access surface, ownership policy, and package
17
+ composition.
17
18
 
18
19
  ## Do not use when
19
20
 
@@ -30,8 +31,10 @@ from a generator questionnaire or treat a live table as the sole contract.
30
31
 
31
32
  ## Invariants
32
33
 
33
- - Migration and resource storage metadata describe the same table.
34
- - `apiAccess`, surface access, and `autofilter` agree.
34
+ - The migration and the table resource's storage metadata describe the same
35
+ table, and that table has no other table resource.
36
+ - The CRUD contract's `apiAccess`, surface access, and the table resource's
37
+ `ownership` agree.
35
38
  - Public routes use public ownership only.
36
39
  - Workspace routes receive explicit scope validators and route-to-action input:
37
40
  workspaces-core's `WORKSPACE_CRUD_SCOPE`, never a copied scope literal.
@@ -55,10 +58,15 @@ from a generator questionnaire or treat a live table as the sole contract.
55
58
 
56
59
  ## Framework APIs
57
60
 
58
- Use `defineCrudResource()` from `@jskit-ai/resource-crud-core` and
59
- `defineCrudJsonApiFeature()` from `@jskit-ai/crud-core`. The latter composes the
60
- JSON REST resource, repository, service, standard CRUD actions with their
61
- record-changed events, route contracts, access policy, and HTTP routes.
61
+ Use `defineTableResource()` from `@jskit-ai/resource-core`,
62
+ `defineCrudContract()` from `@jskit-ai/resource-crud-core` and
63
+ `defineCrudFeature()` from `@jskit-ai/crud-core`. The feature takes the CRUD
64
+ contract as `crud` and owns its table resource: its repository is the table's
65
+ handle, and it composes the CRUD service, standard CRUD actions with their
66
+ record-changed events, route contracts, access policy, and HTTP routes. The
67
+ routes' visibility and the rows each call reaches follow the table resource's
68
+ `ownership`. More tables the feature owns go in `owns`, and so do the CRUD
69
+ table's server-only extras (`{ resource: bookResource, rowPolicy, hooks }`).
62
70
 
63
71
  ## Example files
64
72
 
@@ -80,8 +88,8 @@ For a package-owned migration instead, declare its actual directory in
80
88
  `package.json#jskit.migrations.directories`, as described in the database-layer
81
89
  guide.
82
90
 
83
- `BooksFeature.js` is deliberately tiny: it binds the readable resource contract
84
- to the framework module and exports the resulting provider.
91
+ `BooksFeature.js` is deliberately tiny: it binds the `bookCrud` contract to the
92
+ framework module and exports the resulting provider.
85
93
  `types.js` declares the type of the `app.books` capability, for features that
86
94
  require it.
87
95
 
@@ -104,18 +112,17 @@ an unfiltered collection can satisfy a matching-row-only assertion.
104
112
 
105
113
  ## Variation points
106
114
 
107
- Change the package name, resource fields, table, surface, ownership filter,
115
+ Change the package name, resource fields, table, ownership, surface,
108
116
  relative route, messages, and migration. Supply explicit permissions when the
109
117
  surface uses named policy. For workspace ownership, pass `scope:
110
118
  WORKSPACE_CRUD_SCOPE` (`@jskit-ai/workspaces-core/server/support/workspaceCrud`):
111
119
  the `/w/:workspaceSlug` route base, the workspace route and action validators
112
- and the slug input. A repository method of such a feature reads the workspace
113
- id with `resolveWorkspaceScopeValue(options.context)`
114
- (`@jskit-ai/json-rest-api-core/server/jsonRestApiHost`), and a
115
- hand-written workspace route's path is
120
+ and the slug input. A repository method of such a feature passes the call's
121
+ `options` on to its handle, which scopes every read and write to the caller's
122
+ workspace, and a hand-written workspace route's path is
116
123
  `resolveWorkspaceRoutePath("relative-path")`
117
124
  (`@jskit-ai/workspaces-core/server/support/workspaceRoutePaths`). The feature exposes the
118
- operations declared by the resource. Use `operations` only to narrow that
125
+ operations declared by the CRUD contract. Use `operations` only to narrow that
119
126
  surface further; do not register unused mutation actions and routes. Use
120
127
  `routes: false` only for an internal
121
128
  capability/action surface that deliberately has no HTTP API.
@@ -140,24 +147,29 @@ function of the action execution.
140
147
 
141
148
  When otherwise-standard CRUD earns product behavior, name the exact
142
149
  capabilities in `requires` and provide `decorateService({ service, repository,
143
- resource, ...dependencies })`. Retain the standard methods, override only the
144
- methods whose CRUD behavior changes, and add resource-related domain methods
145
- such as `confirmBooking()` or `sendReminder()` directly to that service. Those
146
- additional methods remain available through the resource capability. An
147
- overridden `createDocument()` returns the created record, with its id, as a
150
+ crud, ...dependencies })`. Retain the standard methods (`list`, `view`,
151
+ `create`, `patch`, `delete`), override only the methods whose CRUD behavior
152
+ changes, and add resource-related domain methods such as `confirmBooking()` or
153
+ `sendReminder()` directly to that service. Those additional methods remain
154
+ available through the resource capability. An overridden `create()` returns
155
+ the created record, with its id, as a
148
156
  tagged JSON:API result: a document result (`returnJsonApiDocument`), as the
149
157
  standard one does, or a data result (`returnJsonApiData(record)`). The create
150
158
  action's created and `record.changed` events read the record's id from it, and
151
159
  a create whose service returns an untagged document or record, or a result
152
160
  without the record's id, fails in every channel. When the
153
161
  product needs only a few additional queries or locking operations, provide
154
- `decorateRepository({ repository, database, http, jsonRestApi, resource,
155
- ...dependencies })`; return the standard methods plus only those unique
156
- persistence operations instead of copying the CRUD repository.
162
+ `decorateRepository({ repository, resources, crud, ...dependencies })`, where
163
+ `repository` is the CRUD table's handle and `resources` holds the handle of
164
+ every table the feature owns; return the handle's methods plus only those
165
+ unique persistence operations, each taking the call's `options` last and
166
+ passing them on untouched.
157
167
 
158
168
  Use `operationLifecycle` for behavior surrounding a standard operation. Its
159
169
  per-operation `before`, `execute`, and `after` phases share one repository
160
- transaction for create, update, and delete. An `execute` hook receives
170
+ transaction for create, update, and delete: each receives `options`, the call
171
+ options carrying the action context and that transaction, to pass on to every
172
+ repository or service call it makes. An `execute` hook receives
161
173
  `standard(nextInput)` so it can retain the framework write while adjusting its
162
174
  input or coordinating other repository work, and returns what `standard()`
163
175
  returns: the operation's tagged JSON:API result (null for delete). A create's
@@ -173,7 +185,10 @@ service and repository its hooks call. Put durable external delivery behind an
173
185
  outbox written inside the transaction; do not make an email or webhook the
174
186
  arbiter of whether an already-committed request appears successful.
175
187
 
176
- Declare resource-related non-CRUD commands in `actions`. Each named action has
188
+ Declare resource-related non-CRUD commands in `actions`. Each key is the
189
+ action's name: lower case, starting with a letter, with only letters, digits,
190
+ `_`, `.` and `-` (`cancel-day`, never `cancelDay`); its id is
191
+ `crud.<namespace>.<name>`. Each named action has
177
192
  a normal JSKIT input, permission, audit, events, and `execute({ input, context,
178
193
  service, repository, ...dependencies })` contract. Type that argument with
179
194
  `CrudCustomActionInvocation` from `@jskit-ai/crud-core/server/types`. Its first
@@ -183,13 +198,15 @@ by local name; its third is the repository the action calls, when
183
198
  `decorateRepository` adds the method. Its optional `route`
184
199
  declares an HTTP method, path below the resource base, explicit route contract,
185
200
  status code, and optional request-to-action input mapper. This is the normal
186
- home for `confirm`, `publish`, `cancel`, `reorder`, or `sendReminder`. Move to a
201
+ home for `confirm`, `publish`, `cancel`, `reorder`, or `send-reminder`. Move to a
187
202
  separate explicit feature only when the operation belongs to another domain or
188
203
  the resource is no longer principally CRUD.
189
204
 
190
205
  If a product-specific list filter needs additional transport validation and
191
206
  JSON API search mapping, pass its validator as `listFilterQueryValidator` and
192
- its storage mapping as `searchSchema`; keep that contract next to the feature.
207
+ put its storage mapping in the table resource's `searchSchema` (or, when it is
208
+ server-only, in the CRUD table's `owns` entry); keep that contract next to the
209
+ feature.
193
210
  Use `operationInputs.create` or `operationInputs.update` when a standard CRUD
194
211
  operation intentionally accepts a small virtual product field that is handled
195
212
  by the service decorator rather than stored on the resource row.
@@ -234,15 +251,15 @@ function decorateBookingsService({ service, repository }) {
234
251
  ```
235
252
 
236
253
  ```js
237
- const BookingsFeature = defineCrudJsonApiFeature({
238
- resource: bookingResource,
254
+ const BookingsFeature = defineCrudFeature({
255
+ crud: bookingCrud,
239
256
  surface: "app",
240
257
  decorateRepository: decorateBookingsRepository,
241
258
  decorateService: decorateBookingsService,
242
259
  operationLifecycle: {
243
260
  update: {
244
- async before({ input, service, trx, context }) {
245
- await service.assertUpdateAllowed(input.recordId, { trx, context });
261
+ async before({ input, service, options }) {
262
+ await service.assertUpdateAllowed(input.recordId, options);
246
263
  },
247
264
  async afterCommit({ result, service, context }) {
248
265
  await service.recordUpdateCompleted(result.value.data.id, { context });
@@ -266,6 +283,21 @@ const BookingsFeature = defineCrudJsonApiFeature({
266
283
  });
267
284
  ```
268
285
 
286
+ An action's `input` is one schema definition, `{ schema, mode }`, for
287
+ everything it validates; its route contract names each part of the request it
288
+ reads, `params`, `query` or `body`, each a schema definition, and the route
289
+ hands the action their fields merged. A workspace scope adds `workspaceSlug` to
290
+ both itself.
291
+
292
+ ```js
293
+ import { createSchema } from "json-rest-schema";
294
+ import { composeSchemaDefinitions, recordIdParamsValidator } from "@jskit-ai/kernel/shared/validators";
295
+
296
+ const confirmBookingBody = { schema: createSchema({ note: { type: "string", maxLength: 500 } }), mode: "patch" };
297
+ const confirmBookingInput = composeSchemaDefinitions([recordIdParamsValidator, confirmBookingBody], { mode: "patch" });
298
+ const confirmBookingRouteContract = { params: recordIdParamsValidator, body: confirmBookingBody };
299
+ ```
300
+
269
301
  ## Verification
270
302
 
271
303
  - Rebuild the table from migrations in disposable MySQL and PostgreSQL databases
@@ -9,8 +9,9 @@
9
9
  "./shared": "./src/shared/index.js"
10
10
  },
11
11
  "dependencies": {
12
- "@jskit-ai/crud-core": "0.2.14",
13
- "@jskit-ai/resource-crud-core": "0.1.204"
12
+ "@jskit-ai/crud-core": "0.2.15",
13
+ "@jskit-ai/resource-core": "0.1.205",
14
+ "@jskit-ai/resource-crud-core": "0.1.205"
14
15
  },
15
16
  "jskit": {
16
17
  "kind": "runtime",
@@ -19,10 +20,10 @@
19
20
  "app.books"
20
21
  ],
21
22
  "requires": [
23
+ "crud.contracts",
22
24
  "runtime.http",
23
- "runtime.json-rest-api",
24
25
  "runtime.actions",
25
- "runtime.database"
26
+ "runtime.resources"
26
27
  ]
27
28
  },
28
29
  "runtime": {
@@ -0,0 +1,13 @@
1
+ import { defineCrudFeature } from "@jskit-ai/crud-core/server/defineCrudFeature";
2
+ import { bookCrud } from "../shared/bookResource.js";
3
+
4
+ // Owns the books table and serves its CRUD contract.
5
+ const BooksFeature = defineCrudFeature({
6
+ crud: bookCrud,
7
+ id: "app.books",
8
+ capability: "app.books",
9
+ surface: "app",
10
+ relativePath: "/books"
11
+ });
12
+
13
+ export { BooksFeature };