@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.
- package/guide/agent/app-extras/assistant.md +14 -2
- package/guide/agent/app-extras/realtime.md +38 -1
- package/guide/agent/app-setup/authentication.md +64 -14
- package/guide/agent/app-setup/console.md +8 -0
- package/guide/agent/app-setup/database-layer.md +23 -103
- package/guide/agent/app-setup/existing-application-migration.md +105 -72
- package/guide/agent/app-setup/multi-homing.md +94 -32
- package/guide/agent/app-setup/quickstart.md +1 -1
- package/guide/agent/app-setup/run-an-accounts-hub.md +1 -1
- package/guide/agent/app-setup/upgrading-jskit.md +8 -3
- package/guide/agent/app-setup/users.md +8 -5
- package/guide/agent/framework/application-operations.md +1 -1
- package/guide/agent/framework/crud-operations.md +63 -45
- package/guide/agent/framework/data-access.md +1031 -0
- package/guide/agent/framework/sanity-checks.md +26 -27
- package/guide/agent/framework/typed-features.md +114 -247
- package/guide/agent/index.md +2 -0
- package/package.json +6 -3
- package/patterns/feature-package/PATTERN.md +25 -17
- package/patterns/feature-package/example/booking-engine/package.json +1 -1
- package/patterns/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
- package/patterns/feature-package/example/variations/invoiceResource.js +15 -0
- package/patterns/feature-package/example/variations/invoiceRollupActions.js +4 -4
- package/patterns/feature-package/example/variations/ownedTableRepository.js +27 -0
- package/patterns/feature-package/example/variations/types.js +1 -1
- package/patterns/minimal-foundation/example/AGENTS.md +4 -0
- package/patterns/minimal-foundation/example/package.json +4 -4
- package/patterns/minimal-foundation/example/server.js +4 -1
- package/patterns/shell-foundation/example/AGENTS.md +4 -0
- package/patterns/shell-foundation/example/package.json +5 -5
- package/patterns/shell-foundation/example/packages/main/package.json +1 -1
- package/patterns/shell-foundation/example/server.js +4 -1
- package/reference/autogen/KERNEL_MAP.md +2 -1
- package/reference/autogen/PATTERN_INDEX.md +70 -59
- package/reference/autogen/README.md +1 -1
- package/reference/autogen/packages/agent-docs.md +7 -3
- package/reference/autogen/packages/agent-runtime.md +48 -11
- package/reference/autogen/packages/architecture-inspector.md +6 -2
- package/reference/autogen/packages/assistant-core.md +4 -4
- package/reference/autogen/packages/assistant-runtime.md +33 -13
- package/reference/autogen/packages/auth-provider-local-core.md +3 -1
- package/reference/autogen/packages/auth-provider-local-db-core.md +65 -14
- package/reference/autogen/packages/auth-provider-oidc-core.md +5 -1
- package/reference/autogen/packages/auth-provider-supabase-core.md +1 -1
- package/reference/autogen/packages/connectors-core.md +22 -2
- package/reference/autogen/packages/console-core.md +9 -5
- package/reference/autogen/packages/crud-core.md +38 -173
- package/reference/autogen/packages/database-runtime.md +8 -32
- package/reference/autogen/packages/http-runtime.md +3 -3
- package/reference/autogen/packages/http-web.md +12 -0
- package/reference/autogen/packages/kernel.md +25 -2
- package/reference/autogen/packages/oidc-issuer-core.md +4 -0
- package/reference/autogen/packages/payments-core.md +29 -6
- package/reference/autogen/packages/realtime.md +15 -6
- package/reference/autogen/packages/resource-core.md +30 -5
- package/reference/autogen/packages/resource-crud-core.md +30 -34
- package/reference/autogen/packages/resource-runtime.md +198 -0
- package/reference/autogen/packages/rewarded-core.md +16 -10
- package/reference/autogen/packages/shell-web.md +1 -0
- package/reference/autogen/packages/users-core.md +26 -14
- package/reference/autogen/packages/workspaces-core.md +17 -10
- package/reference/autogen/tooling/config-eslint.md +69 -0
- package/reference/autogen/tooling/jskit-catalog.md +40 -1
- package/reference/autogen/tooling/jskit-release.md +3 -0
- package/skills/jskit/SKILL.md +4 -1
- package/skills/jskit/references/app-operations.md +1 -1
- package/skills/jskit/references/crud-operations.md +62 -44
- package/skills/jskit/references/data-access.md +1026 -0
- package/skills/jskit/references/database-layer.md +411 -0
- package/skills/jskit/references/existing-application-migration.md +105 -72
- package/skills/jskit/references/heuristics/INDEX.md +6 -4
- package/skills/jskit/references/heuristics/crud-authoring.md +22 -14
- package/skills/jskit/references/heuristics/data-access.md +19 -0
- package/skills/jskit/references/heuristics/filters.md +6 -6
- package/skills/jskit/references/heuristics/row-policies.md +40 -16
- package/skills/jskit/references/heuristics/server-search.md +30 -42
- package/skills/jskit/references/heuristics/table-resource-fields.md +78 -0
- package/skills/jskit/references/heuristics/typed-features.md +3 -3
- package/skills/jskit/references/multi-homing.md +94 -32
- package/skills/jskit/references/pattern-index.md +70 -59
- package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +4 -0
- package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +4 -4
- package/skills/jskit/references/patterns/app/minimal-foundation/example/server.js +4 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +4 -0
- package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +5 -5
- package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/server.js +4 -1
- package/skills/jskit/references/patterns/assistant/assistant-surface/PATTERN.md +5 -2
- package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +3 -2
- package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +15 -0
- package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
- package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
- package/skills/jskit/references/patterns/crud/crud-contract/PATTERN.md +133 -0
- package/skills/jskit/references/patterns/crud/crud-contract/example/bookResource.js +74 -0
- package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/PATTERN.md +69 -37
- package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/package.json +5 -4
- package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/server/BooksFeature.js +13 -0
- package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/bookResource.js +74 -0
- package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/index.js +1 -0
- package/skills/jskit/references/patterns/crud/crud-screen-set/PATTERN.md +26 -8
- package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookEditPage.vue +2 -2
- package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookListPage.vue +2 -2
- package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookNewPage.vue +2 -2
- package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookViewPage.vue +3 -3
- package/skills/jskit/references/patterns/database/mysql-application/PATTERN.md +9 -4
- package/skills/jskit/references/patterns/database/mysql-application/example/package.json +2 -1
- package/skills/jskit/references/patterns/database/mysql-application/example/scripts/prepare-database.js +8 -0
- package/skills/jskit/references/patterns/database/postgres-application/PATTERN.md +8 -4
- package/skills/jskit/references/patterns/database/postgres-application/example/package.json +2 -1
- package/skills/jskit/references/patterns/database/postgres-application/example/scripts/prepare-database.js +8 -0
- package/skills/jskit/references/patterns/database/table-resource/PATTERN.md +117 -0
- package/skills/jskit/references/patterns/database/table-resource/example/orderResource.js +25 -0
- package/skills/jskit/references/patterns/realtime/realtime-application/PATTERN.md +7 -0
- package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
- package/skills/jskit/references/patterns/rewards/google-rewarded/PATTERN.md +1 -1
- package/skills/jskit/references/patterns/server/feature-package/PATTERN.md +25 -17
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
- package/skills/jskit/references/patterns/server/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
- package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceResource.js +15 -0
- package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceRollupActions.js +4 -4
- package/skills/jskit/references/patterns/server/feature-package/example/variations/ownedTableRepository.js +27 -0
- package/skills/jskit/references/patterns/server/feature-package/example/variations/types.js +1 -1
- package/skills/jskit/references/patterns/users/user-administration-server/PATTERN.md +59 -10
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +5 -4
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/server/UsersFeature.js +7 -5
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/index.js +1 -1
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userCrud.js +21 -0
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +11 -5
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/UsersWorkspaceFeature.js +14 -5
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/workspaceMembersOnly.js +52 -0
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/index.js +1 -1
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userCrud.js +21 -0
- package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +9 -9
- package/skills/jskit/references/patterns/workspaces/workspace-surfaces/PATTERN.md +16 -0
- package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/pages/home/workspaces/index.vue +2 -0
- package/skills/jskit/references/sanity-checks.md +27 -28
- package/skills/jskit/references/typed-features.md +114 -247
- package/patterns/feature-package/example/variations/customKnexRepository.js +0 -40
- package/reference/autogen/packages/json-rest-api-core.md +0 -66
- package/skills/jskit/references/heuristics/crud-repository-mapping.md +0 -140
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/server/BooksFeature.js +0 -13
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/bookResource.js +0 -83
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/index.js +0 -1
- package/skills/jskit/references/patterns/crud/resource-contract/PATTERN.md +0 -155
- package/skills/jskit/references/patterns/crud/resource-contract/example/bookResource.js +0 -87
- package/skills/jskit/references/patterns/server/feature-package/example/variations/customKnexRepository.js +0 -40
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userResource.js +0 -69
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userResource.js +0 -56
- /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/migrations/20260815000000_books.cjs +0 -0
- /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/src/server/types.js +0 -0
|
@@ -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
|
|
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
|
|
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.
|
|
75
|
-
from `
|
|
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`
|
|
81
|
-
show the small product-owned form
|
|
82
|
-
filters and actions explicit without
|
|
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.
|
package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookEditPage.vue
CHANGED
|
@@ -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 {
|
|
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:
|
|
20
|
+
resource: bookCrud,
|
|
21
21
|
operationName: "patch",
|
|
22
22
|
formFields: bookFormFields,
|
|
23
23
|
addEditOptions: {
|
package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookListPage.vue
CHANGED
|
@@ -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 {
|
|
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:
|
|
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 {
|
|
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:
|
|
14
|
+
resource: bookCrud,
|
|
15
15
|
operationName: "create",
|
|
16
16
|
formFields: bookFormFields,
|
|
17
17
|
addEditOptions: {
|
package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookViewPage.vue
CHANGED
|
@@ -3,13 +3,13 @@ import CrudDeleteAction from "@jskit-ai/http-web/client/components/CrudDeleteAct
|
|
|
3
3
|
import CrudViewScreen from "@jskit-ai/http-web/client/components/CrudViewScreen";
|
|
4
4
|
import { useCrudDeleteAction } from "@jskit-ai/http-web/client/composables/useCrudDeleteAction";
|
|
5
5
|
import { useCrudViewScreen } from "@jskit-ai/http-web/client/composables/useCrudViewScreen";
|
|
6
|
-
import {
|
|
6
|
+
import { bookCrud } from "@local/books/shared";
|
|
7
7
|
|
|
8
8
|
// Up returns to the page the book was opened from, or else to the list.
|
|
9
9
|
definePage({ meta: { jskit: { title: "Book", parent: "/books" } } });
|
|
10
10
|
|
|
11
11
|
const screen = useCrudViewScreen({
|
|
12
|
-
resource:
|
|
12
|
+
resource: bookCrud,
|
|
13
13
|
resourceNamespace: "books",
|
|
14
14
|
apiUrlTemplate: "/books/:bookId",
|
|
15
15
|
recordIdParam: "bookId",
|
|
@@ -22,7 +22,7 @@ const screen = useCrudViewScreen({
|
|
|
22
22
|
|
|
23
23
|
const deleteAction = useCrudDeleteAction({
|
|
24
24
|
screen,
|
|
25
|
-
resource:
|
|
25
|
+
resource: bookCrud,
|
|
26
26
|
resourceNamespace: "books",
|
|
27
27
|
apiUrlTemplate: "/books/:bookId"
|
|
28
28
|
});
|
|
@@ -11,8 +11,10 @@ requires: @jskit-ai/database-runtime-mysql
|
|
|
11
11
|
## Use when
|
|
12
12
|
|
|
13
13
|
Use this pattern when an application deliberately chooses MySQL or MariaDB.
|
|
14
|
-
Install `@jskit-ai/database-runtime-mysql
|
|
15
|
-
the
|
|
14
|
+
Install `@jskit-ai/database-runtime` and `@jskit-ai/database-runtime-mysql`
|
|
15
|
+
without a package-setup wizard: the runtime brings Knex and the driver brings
|
|
16
|
+
`mysql2`. The driver takes the runtime as a peer, so the application holds its
|
|
17
|
+
one copy.
|
|
16
18
|
|
|
17
19
|
## Do not use when
|
|
18
20
|
|
|
@@ -50,8 +52,11 @@ Knex discovers package-owned migrations from the installed dependency graph.
|
|
|
50
52
|
`example/package.json` contains the runtime dependency and database scripts.
|
|
51
53
|
`example/knexfile.js` loads an optional local `.env` and fixes the MySQL dialect.
|
|
52
54
|
`example/scripts/prepare-database.js` is the portable migrate-then-seed
|
|
53
|
-
entrypoint for managed sessions and deployments.
|
|
54
|
-
the
|
|
55
|
+
entrypoint for managed sessions and deployments. It loads the optional `.env`
|
|
56
|
+
as the knexfile does, and migrates the database `DB_NAME` names without
|
|
57
|
+
creating it: the hosting environment or a database administrator creates it
|
|
58
|
+
first. `example/.env.example` names the connection values and separate test
|
|
59
|
+
databases without a secret.
|
|
55
60
|
|
|
56
61
|
`example/.github/workflows/verify.yml` is a normal app-owned CI workflow with
|
|
57
62
|
exactly one database service: `mariadb` (`mariadb:11.4`, `MARIADB_*`
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import { prepareDatabaseFromApp } from "@jskit-ai/database-runtime/server/databaseSetup";
|
|
2
2
|
|
|
3
|
+
try {
|
|
4
|
+
process.loadEnvFile(".env");
|
|
5
|
+
} catch (error) {
|
|
6
|
+
if (error?.code !== "ENOENT") {
|
|
7
|
+
throw error;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
await prepareDatabaseFromApp({ client: "mysql2", environment: process.env });
|
|
@@ -11,8 +11,9 @@ requires: @jskit-ai/database-runtime-postgres
|
|
|
11
11
|
## Use when
|
|
12
12
|
|
|
13
13
|
Use this pattern when an application deliberately chooses PostgreSQL. Install
|
|
14
|
-
`@jskit-ai/database-runtime-postgres
|
|
15
|
-
|
|
14
|
+
`@jskit-ai/database-runtime` and `@jskit-ai/database-runtime-postgres` without
|
|
15
|
+
a package-setup wizard: the runtime brings Knex and the driver brings `pg`. The
|
|
16
|
+
driver takes the runtime as a peer, so the application holds its one copy.
|
|
16
17
|
|
|
17
18
|
## Do not use when
|
|
18
19
|
|
|
@@ -50,8 +51,11 @@ Knex discovers package-owned migrations from the installed dependency graph.
|
|
|
50
51
|
`example/package.json` contains the runtime dependency and database scripts.
|
|
51
52
|
`example/knexfile.js` loads an optional local `.env` and fixes the PostgreSQL
|
|
52
53
|
dialect. `example/scripts/prepare-database.js` is the portable
|
|
53
|
-
migrate-then-seed entrypoint for managed sessions and deployments.
|
|
54
|
-
|
|
54
|
+
migrate-then-seed entrypoint for managed sessions and deployments. It loads
|
|
55
|
+
the optional `.env` as the knexfile does, and migrates the database `DB_NAME`
|
|
56
|
+
names without creating it: the hosting environment or a database
|
|
57
|
+
administrator creates it first. `example/.env.example` names the five
|
|
58
|
+
connection values without a secret.
|
|
55
59
|
|
|
56
60
|
Tests get their own databases from
|
|
57
61
|
`@jskit-ai/database-runtime/server/testDatabases`. The `test` script runs
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import { prepareDatabaseFromApp } from "@jskit-ai/database-runtime/server/databaseSetup";
|
|
2
2
|
|
|
3
|
+
try {
|
|
4
|
+
process.loadEnvFile(".env");
|
|
5
|
+
} catch (error) {
|
|
6
|
+
if (error?.code !== "ENOENT") {
|
|
7
|
+
throw error;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
await prepareDatabaseFromApp({ client: "pg", environment: process.env });
|
|
@@ -0,0 +1,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.
|
|
@@ -38,7 +38,7 @@ capability composition beside `RewardedClientProvider` from
|
|
|
38
38
|
`client.rewarded-delivery`; the generic UI supplies `client.rewarded`.
|
|
39
39
|
Use `useRewardedRuntime().requireUnlock({ gateKey, workspaceSlug })` for gates.
|
|
40
40
|
Separately supply server capability `rewarded.grant-policy` with
|
|
41
|
-
`authorizeGrant({ session, context,
|
|
41
|
+
`authorizeGrant({ session, context, options })`; only `true` permits a grant.
|
|
42
42
|
This delivery example does not provide or choose the server policy.
|
|
43
43
|
|
|
44
44
|
## Example files
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
id: server/feature-package
|
|
3
3
|
title: App-local server feature package
|
|
4
4
|
summary: Define a server feature through explicit capabilities and first-class actions, adding services or repositories only when its domain earns them.
|
|
5
|
-
keywords: actions,
|
|
5
|
+
keywords: actions, aggregate, feature, handle, orchestration, owns, package, provider, repository, routes, server, table resource
|
|
6
6
|
requires: @jskit-ai/kernel
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -17,16 +17,18 @@ actions whose implementations capture those exact dependencies.
|
|
|
17
17
|
|
|
18
18
|
The concrete `booking-engine` example is intentionally repository-free. The
|
|
19
19
|
`variations/` directory preserves the two important escalations: a meaningful
|
|
20
|
-
orchestration service and
|
|
21
|
-
|
|
20
|
+
orchestration service and a repository over a table the feature owns, for a
|
|
21
|
+
domain whose operations are not CRUD screens.
|
|
22
22
|
|
|
23
23
|
## Do not use when
|
|
24
24
|
|
|
25
25
|
Do not create a package for a tiny composition callback or one route with no
|
|
26
|
-
domain behavior. Use the CRUD
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
domain behavior. Use the CRUD feature pattern when the table's operations are
|
|
27
|
+
screens over HTTP. Do not reach for Knex because SQL is familiar: a table's
|
|
28
|
+
reads and writes go through its handle, and a report the handle cannot express
|
|
29
|
+
is one `scopedQuery()` method, as the
|
|
30
|
+
[data access guide](https://mobily-enterprises.github.io/jskit-ai/guide/framework/data-access)
|
|
31
|
+
says.
|
|
30
32
|
|
|
31
33
|
## Product decisions
|
|
32
34
|
|
|
@@ -50,7 +52,7 @@ questionnaire.
|
|
|
50
52
|
- Every factory, action creator and hook that receives a collaborator names its
|
|
51
53
|
type in a JSDoc `@param`: `import("./types.js").<Name>` or
|
|
52
54
|
`ReturnType<typeof createX>` inside the package, the owner's `./server/types`
|
|
53
|
-
across packages; a
|
|
55
|
+
across packages; a table's handle is resource-runtime's `ResourceHandle`.
|
|
54
56
|
- Collaborators are required. There is no `= null`, `= {}` or no-op default,
|
|
55
57
|
no `typeof` probe, no optional call and no "requires x" guard: the capability
|
|
56
58
|
runtime refuses to start without a required token.
|
|
@@ -80,9 +82,11 @@ questionnaire.
|
|
|
80
82
|
- One copy of each pure helper per package, imported directly. Files stay under
|
|
81
83
|
750 lines and functions under 600, one concern per factory.
|
|
82
84
|
- The service orchestrates the feature and does not issue SQL directly.
|
|
83
|
-
- A
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
- A feature that owns a table lists its table resource in `owns`; setup
|
|
86
|
+
receives the table's handle, and only its repository uses it. One table has
|
|
87
|
+
one owner.
|
|
88
|
+
- A repository holds the owner's queries on its handles and passes each call's
|
|
89
|
+
options (context and transaction) on.
|
|
86
90
|
- Ordinary HTTP and assistant exposure is projected from actions. Explicit
|
|
87
91
|
route code exists only for a genuinely custom transport.
|
|
88
92
|
- Register routes, actions, contributors, and other catalogue entries during
|
|
@@ -97,8 +101,8 @@ questionnaire.
|
|
|
97
101
|
The provider uses `defineFeature()` from
|
|
98
102
|
`@jskit-ai/kernel/server/features`. The runtime supplies named capabilities
|
|
99
103
|
and the feature publishes its actions through the dedicated action catalogue.
|
|
100
|
-
The
|
|
101
|
-
|
|
104
|
+
The persistence variation owns its table with `defineFeature({ owns })` and
|
|
105
|
+
reads totals with the handle's `aggregate()`.
|
|
102
106
|
|
|
103
107
|
## Example files
|
|
104
108
|
|
|
@@ -147,9 +151,11 @@ const SeasonalAvailabilityFeature = defineFeature({
|
|
|
147
151
|
`example/variations/availabilityEngineActions.js` and
|
|
148
152
|
`example/variations/orchestratorService.js` show the repository-free lane.
|
|
149
153
|
`example/variations/InvoiceRollupFeature.js`,
|
|
150
|
-
`example/variations/invoiceRollupActions.js
|
|
151
|
-
`example/variations/
|
|
152
|
-
|
|
154
|
+
`example/variations/invoiceRollupActions.js`,
|
|
155
|
+
`example/variations/invoiceResource.js` and
|
|
156
|
+
`example/variations/ownedTableRepository.js` show the persistence lane: the
|
|
157
|
+
feature owns the invoices table, and its repository reads monthly totals with
|
|
158
|
+
`aggregate()`, scoped to the caller's workspace by the handle. `example/variations/types.js` declares the
|
|
153
159
|
`AvailabilityEngine` and `InvoiceRollup` types.
|
|
154
160
|
|
|
155
161
|
The [typed features guide](https://mobily-enterprises.github.io/jskit-ai/guide/framework/typed-features)
|
|
@@ -172,7 +178,9 @@ illustrative status query with narrow product language before shipping.
|
|
|
172
178
|
denied access.
|
|
173
179
|
- Exercise HTTP routes only when the package owns them.
|
|
174
180
|
- Test service orchestration independently from persistence.
|
|
175
|
-
- Test repository
|
|
181
|
+
- Test each repository path against a second workspace's rows
|
|
182
|
+
(`expectScoped`) and at two sizes (`expectFlatQueries`), from
|
|
183
|
+
`@jskit-ai/resource-runtime/test`.
|
|
176
184
|
- Rebuild any schema from immutable migrations in a disposable database.
|
|
177
185
|
- Run the app's package, server, and integration verification.
|
|
178
186
|
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { defineFeature } from "@jskit-ai/kernel/server/features";
|
|
2
|
-
import {
|
|
2
|
+
import { invoiceResource } from "./invoiceResource.js";
|
|
3
|
+
import { createInvoiceRollup } from "./ownedTableRepository.js";
|
|
3
4
|
import { createInvoiceRollupActions } from "./invoiceRollupActions.js";
|
|
4
5
|
|
|
5
6
|
const InvoiceRollupFeature = defineFeature({
|
|
6
7
|
id: "feature.invoice-rollup",
|
|
7
8
|
domain: "invoices",
|
|
8
|
-
|
|
9
|
-
database: "runtime.database"
|
|
10
|
-
},
|
|
9
|
+
owns: [invoiceResource],
|
|
11
10
|
provides: {
|
|
12
11
|
invoiceRollup: "feature.invoice-rollup"
|
|
13
12
|
},
|
|
14
|
-
/** @param {{
|
|
15
|
-
setup({
|
|
16
|
-
return { invoiceRollup: createInvoiceRollup({
|
|
13
|
+
/** @param {{ resources: { invoices: import("@jskit-ai/resource-runtime/server/types").ResourceHandle } }} capabilities */
|
|
14
|
+
setup({ resources }) {
|
|
15
|
+
return { invoiceRollup: createInvoiceRollup({ invoices: resources.invoices }) };
|
|
17
16
|
},
|
|
18
17
|
actions({ invoiceRollup }) {
|
|
19
18
|
return createInvoiceRollupActions({ invoiceRollup });
|