@jskit-ai/crud-server-generator 0.1.26
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/package.descriptor.mjs +218 -0
- package/package.json +22 -0
- package/src/server/CrudServiceProvider.js +11 -0
- package/src/server/actionIds.js +22 -0
- package/src/server/actions.js +152 -0
- package/src/server/buildTemplateContext.js +871 -0
- package/src/server/crudModuleConfig.js +1 -0
- package/src/server/registerRoutes.js +234 -0
- package/src/server/repository.js +162 -0
- package/src/server/service.js +96 -0
- package/src/shared/crud/crudResource.js +191 -0
- package/src/shared/index.js +1 -0
- package/templates/migrations/crud_initial.cjs +17 -0
- package/templates/src/local-package/package.descriptor.mjs +67 -0
- package/templates/src/local-package/package.json +10 -0
- package/templates/src/local-package/server/CrudServiceProvider.js +84 -0
- package/templates/src/local-package/server/actionIds.js +9 -0
- package/templates/src/local-package/server/actions.js +151 -0
- package/templates/src/local-package/server/registerRoutes.js +197 -0
- package/templates/src/local-package/server/repository.js +161 -0
- package/templates/src/local-package/server/service.js +96 -0
- package/templates/src/local-package/shared/crudResource.js +109 -0
- package/templates/src/local-package/shared/index.js +3 -0
- package/test/buildTemplateContext.test.js +256 -0
- package/test/crudModuleConfig.test.js +225 -0
- package/test/crudResource.test.js +41 -0
- package/test/crudServerGuards.test.js +61 -0
- package/test/crudService.test.js +83 -0
- package/test/routeInputContracts.test.js +215 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
export default Object.freeze({
|
|
2
|
+
packageVersion: 1,
|
|
3
|
+
packageId: "@jskit-ai/crud-server-generator",
|
|
4
|
+
version: "0.1.26",
|
|
5
|
+
kind: "generator",
|
|
6
|
+
description: "CRUD server generator with routes, actions, and persistence scaffolding.",
|
|
7
|
+
options: {
|
|
8
|
+
namespace: {
|
|
9
|
+
required: true,
|
|
10
|
+
inputType: "text",
|
|
11
|
+
defaultValue: "",
|
|
12
|
+
promptLabel: "CRUD namespace",
|
|
13
|
+
promptHint: "Required slug (example: customers, appointments, vendors)."
|
|
14
|
+
},
|
|
15
|
+
surface: {
|
|
16
|
+
required: true,
|
|
17
|
+
inputType: "text",
|
|
18
|
+
defaultFromConfig: "surfaceDefaultId",
|
|
19
|
+
promptLabel: "Target surface",
|
|
20
|
+
promptHint: "Defaults to config.public.surfaceDefaultId. Must match an enabled surface id."
|
|
21
|
+
},
|
|
22
|
+
"ownership-filter": {
|
|
23
|
+
required: true,
|
|
24
|
+
inputType: "text",
|
|
25
|
+
defaultValue: "auto",
|
|
26
|
+
promptLabel: "Ownership filter",
|
|
27
|
+
promptHint: "auto | public | user | workspace | workspace_user"
|
|
28
|
+
},
|
|
29
|
+
"directory-prefix": {
|
|
30
|
+
required: false,
|
|
31
|
+
inputType: "text",
|
|
32
|
+
defaultValue: "",
|
|
33
|
+
promptLabel: "Route path prefix",
|
|
34
|
+
promptHint: "Optional subpath prepended to the CRUD route path (example: crm or ops/team-a)."
|
|
35
|
+
},
|
|
36
|
+
"table-name": {
|
|
37
|
+
required: true,
|
|
38
|
+
inputType: "text",
|
|
39
|
+
defaultValue: "",
|
|
40
|
+
promptLabel: "Table name",
|
|
41
|
+
promptHint: "Required existing MySQL table to introspect for CRUD schema generation."
|
|
42
|
+
},
|
|
43
|
+
"id-column": {
|
|
44
|
+
required: false,
|
|
45
|
+
inputType: "text",
|
|
46
|
+
defaultValue: "id",
|
|
47
|
+
promptLabel: "Id column",
|
|
48
|
+
promptHint: "Primary key column used by CRUD endpoints (default: id)."
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
optionPolicies: {
|
|
52
|
+
surfaceVisibility: {
|
|
53
|
+
visibilityOption: "ownership-filter"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
dependsOn: [
|
|
57
|
+
"@jskit-ai/auth-core",
|
|
58
|
+
"@jskit-ai/crud-core",
|
|
59
|
+
"@jskit-ai/database-runtime",
|
|
60
|
+
"@jskit-ai/http-runtime",
|
|
61
|
+
"@jskit-ai/realtime",
|
|
62
|
+
"@jskit-ai/users-core"
|
|
63
|
+
],
|
|
64
|
+
capabilities: {
|
|
65
|
+
provides: ["crud"],
|
|
66
|
+
requires: [
|
|
67
|
+
"runtime.actions",
|
|
68
|
+
"runtime.database",
|
|
69
|
+
"auth.policy",
|
|
70
|
+
"users.core"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
runtime: {
|
|
74
|
+
server: {
|
|
75
|
+
providers: [
|
|
76
|
+
{
|
|
77
|
+
entrypoint: "src/server/CrudServiceProvider.js",
|
|
78
|
+
export: "CrudServiceProvider"
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
metadata: {
|
|
84
|
+
apiSummary: {
|
|
85
|
+
surfaces: [
|
|
86
|
+
{
|
|
87
|
+
subpath: "./server",
|
|
88
|
+
summary: "Scaffold package runtime provider (no-op) plus reference CRUD server modules."
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
subpath: "./shared",
|
|
92
|
+
summary: "Exports shared CRUD resource and module config helpers."
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
containerTokens: {
|
|
96
|
+
server: []
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
mutations: {
|
|
101
|
+
dependencies: {
|
|
102
|
+
runtime: {
|
|
103
|
+
"@jskit-ai/auth-core": "0.1.17",
|
|
104
|
+
"@jskit-ai/crud-core": "0.1.26",
|
|
105
|
+
"@jskit-ai/database-runtime": "0.1.18",
|
|
106
|
+
"@jskit-ai/http-runtime": "0.1.17",
|
|
107
|
+
"@jskit-ai/kernel": "0.1.18",
|
|
108
|
+
"@jskit-ai/realtime": "0.1.17",
|
|
109
|
+
"@jskit-ai/users-core": "0.1.27",
|
|
110
|
+
"@local/${option:namespace|kebab}": "file:packages/${option:namespace|kebab}",
|
|
111
|
+
"typebox": "^1.0.81"
|
|
112
|
+
},
|
|
113
|
+
dev: {}
|
|
114
|
+
},
|
|
115
|
+
packageJson: {
|
|
116
|
+
scripts: {}
|
|
117
|
+
},
|
|
118
|
+
procfile: {},
|
|
119
|
+
files: [
|
|
120
|
+
{
|
|
121
|
+
op: "install-migration",
|
|
122
|
+
from: "templates/migrations/crud_initial.cjs",
|
|
123
|
+
toDir: "migrations",
|
|
124
|
+
extension: ".cjs",
|
|
125
|
+
reason: "Install CRUD schema migration.",
|
|
126
|
+
category: "crud",
|
|
127
|
+
id: "crud-initial-schema-${option:namespace|snake}",
|
|
128
|
+
templateContext: {
|
|
129
|
+
entrypoint: "src/server/buildTemplateContext.js",
|
|
130
|
+
export: "buildTemplateContext"
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
from: "templates/src/local-package/package.json",
|
|
135
|
+
to: "packages/${option:namespace|kebab}/package.json",
|
|
136
|
+
reason: "Install app-local CRUD package manifest.",
|
|
137
|
+
category: "crud",
|
|
138
|
+
id: "crud-local-package-json-${option:namespace|snake}"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
from: "templates/src/local-package/package.descriptor.mjs",
|
|
142
|
+
to: "packages/${option:namespace|kebab}/package.descriptor.mjs",
|
|
143
|
+
reason: "Install app-local CRUD package descriptor.",
|
|
144
|
+
category: "crud",
|
|
145
|
+
id: "crud-local-package-descriptor-${option:namespace|snake}"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
from: "templates/src/local-package/server/CrudServiceProvider.js",
|
|
149
|
+
to: "packages/${option:namespace|kebab}/src/server/${option:namespace|pascal}ServiceProvider.js",
|
|
150
|
+
reason: "Install app-local CRUD server provider.",
|
|
151
|
+
category: "crud",
|
|
152
|
+
id: "crud-local-package-server-provider-${option:namespace|snake}",
|
|
153
|
+
templateContext: {
|
|
154
|
+
entrypoint: "src/server/buildTemplateContext.js",
|
|
155
|
+
export: "buildTemplateContext"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
from: "templates/src/local-package/server/actions.js",
|
|
160
|
+
to: "packages/${option:namespace|kebab}/src/server/actions.js",
|
|
161
|
+
reason: "Install app-local CRUD action definitions.",
|
|
162
|
+
category: "crud",
|
|
163
|
+
id: "crud-local-package-server-actions-${option:namespace|snake}"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
from: "templates/src/local-package/server/actionIds.js",
|
|
167
|
+
to: "packages/${option:namespace|kebab}/src/server/actionIds.js",
|
|
168
|
+
reason: "Install app-local CRUD action IDs.",
|
|
169
|
+
category: "crud",
|
|
170
|
+
id: "crud-local-package-server-action-ids-${option:namespace|snake}"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
from: "templates/src/local-package/server/registerRoutes.js",
|
|
174
|
+
to: "packages/${option:namespace|kebab}/src/server/registerRoutes.js",
|
|
175
|
+
reason: "Install app-local CRUD route registration.",
|
|
176
|
+
category: "crud",
|
|
177
|
+
id: "crud-local-package-server-routes-${option:namespace|snake}"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
from: "templates/src/local-package/server/repository.js",
|
|
181
|
+
to: "packages/${option:namespace|kebab}/src/server/repository.js",
|
|
182
|
+
reason: "Install app-local CRUD repository.",
|
|
183
|
+
category: "crud",
|
|
184
|
+
id: "crud-local-package-server-repository-${option:namespace|snake}",
|
|
185
|
+
templateContext: {
|
|
186
|
+
entrypoint: "src/server/buildTemplateContext.js",
|
|
187
|
+
export: "buildTemplateContext"
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
from: "templates/src/local-package/server/service.js",
|
|
192
|
+
to: "packages/${option:namespace|kebab}/src/server/service.js",
|
|
193
|
+
reason: "Install app-local CRUD service.",
|
|
194
|
+
category: "crud",
|
|
195
|
+
id: "crud-local-package-server-service-${option:namespace|snake}"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
from: "templates/src/local-package/shared/index.js",
|
|
199
|
+
to: "packages/${option:namespace|kebab}/src/shared/index.js",
|
|
200
|
+
reason: "Install app-local CRUD shared exports.",
|
|
201
|
+
category: "crud",
|
|
202
|
+
id: "crud-local-package-shared-index-${option:namespace|snake}"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
from: "templates/src/local-package/shared/crudResource.js",
|
|
206
|
+
to: "packages/${option:namespace|kebab}/src/shared/${option:namespace|singular|camel}Resource.js",
|
|
207
|
+
reason: "Install app-local CRUD resource.",
|
|
208
|
+
category: "crud",
|
|
209
|
+
id: "crud-local-package-shared-resource-${option:namespace|snake}",
|
|
210
|
+
templateContext: {
|
|
211
|
+
entrypoint: "src/server/buildTemplateContext.js",
|
|
212
|
+
export: "buildTemplateContext"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
text: []
|
|
217
|
+
}
|
|
218
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jskit-ai/crud-server-generator",
|
|
3
|
+
"version": "0.1.26",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "node --test"
|
|
7
|
+
},
|
|
8
|
+
"exports": {
|
|
9
|
+
"./server/CrudServiceProvider": "./src/server/CrudServiceProvider.js",
|
|
10
|
+
"./server/crudModuleConfig": "./src/server/crudModuleConfig.js",
|
|
11
|
+
"./shared": "./src/shared/index.js",
|
|
12
|
+
"./shared/crud/crudResource": "./src/shared/crud/crudResource.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@jskit-ai/crud-core": "0.1.26",
|
|
16
|
+
"@jskit-ai/database-runtime": "0.1.18",
|
|
17
|
+
"@jskit-ai/http-runtime": "0.1.17",
|
|
18
|
+
"@jskit-ai/kernel": "0.1.18",
|
|
19
|
+
"@jskit-ai/users-core": "0.1.27",
|
|
20
|
+
"typebox": "^1.0.81"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function requireActionIdPrefix(actionIdPrefix) {
|
|
2
|
+
const prefix = String(actionIdPrefix || "").trim();
|
|
3
|
+
if (!prefix) {
|
|
4
|
+
throw new TypeError("createActionIds requires actionIdPrefix.");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
return prefix;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createActionIds(actionIdPrefix) {
|
|
11
|
+
const prefix = requireActionIdPrefix(actionIdPrefix);
|
|
12
|
+
|
|
13
|
+
return Object.freeze({
|
|
14
|
+
list: `${prefix}.list`,
|
|
15
|
+
view: `${prefix}.view`,
|
|
16
|
+
create: `${prefix}.create`,
|
|
17
|
+
update: `${prefix}.update`,
|
|
18
|
+
delete: `${prefix}.delete`
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { requireActionIdPrefix, createActionIds };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cursorPaginationQueryValidator,
|
|
3
|
+
recordIdParamsValidator
|
|
4
|
+
} from "@jskit-ai/kernel/shared/validators";
|
|
5
|
+
import { workspaceSlugParamsValidator } from "@jskit-ai/users-core/server/validators/routeParamsValidator";
|
|
6
|
+
import { crudResource } from "../shared/crud/crudResource.js";
|
|
7
|
+
import { createActionIds } from "./actionIds.js";
|
|
8
|
+
|
|
9
|
+
function requireActionSurface(surface = "") {
|
|
10
|
+
const normalizedSurface = String(surface || "").trim().toLowerCase();
|
|
11
|
+
if (!normalizedSurface) {
|
|
12
|
+
throw new TypeError("createActions requires a non-empty surface.");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return normalizedSurface;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function createActions({ actionIdPrefix, surface = "" } = {}) {
|
|
19
|
+
const actionIds = createActionIds(actionIdPrefix);
|
|
20
|
+
const actionSurface = requireActionSurface(surface);
|
|
21
|
+
|
|
22
|
+
return Object.freeze([
|
|
23
|
+
{
|
|
24
|
+
id: actionIds.list,
|
|
25
|
+
version: 1,
|
|
26
|
+
kind: "query",
|
|
27
|
+
channels: ["api", "automation", "internal"],
|
|
28
|
+
surfaces: [actionSurface],
|
|
29
|
+
permission: {
|
|
30
|
+
require: "authenticated"
|
|
31
|
+
},
|
|
32
|
+
inputValidator: [workspaceSlugParamsValidator, cursorPaginationQueryValidator],
|
|
33
|
+
outputValidator: crudResource.operations.list.outputValidator,
|
|
34
|
+
idempotency: "none",
|
|
35
|
+
audit: {
|
|
36
|
+
actionName: actionIds.list
|
|
37
|
+
},
|
|
38
|
+
observability: {},
|
|
39
|
+
async execute(input, context, deps) {
|
|
40
|
+
return deps.crudService.listRecords(input, {
|
|
41
|
+
context,
|
|
42
|
+
visibilityContext: context?.visibilityContext
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: actionIds.view,
|
|
48
|
+
version: 1,
|
|
49
|
+
kind: "query",
|
|
50
|
+
channels: ["api", "automation", "internal"],
|
|
51
|
+
surfaces: [actionSurface],
|
|
52
|
+
permission: {
|
|
53
|
+
require: "authenticated"
|
|
54
|
+
},
|
|
55
|
+
inputValidator: [workspaceSlugParamsValidator, recordIdParamsValidator],
|
|
56
|
+
outputValidator: crudResource.operations.view.outputValidator,
|
|
57
|
+
idempotency: "none",
|
|
58
|
+
audit: {
|
|
59
|
+
actionName: actionIds.view
|
|
60
|
+
},
|
|
61
|
+
observability: {},
|
|
62
|
+
async execute(input, context, deps) {
|
|
63
|
+
return deps.crudService.getRecord(input.recordId, {
|
|
64
|
+
context,
|
|
65
|
+
visibilityContext: context?.visibilityContext
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: actionIds.create,
|
|
71
|
+
version: 1,
|
|
72
|
+
kind: "command",
|
|
73
|
+
channels: ["api", "automation", "internal"],
|
|
74
|
+
surfaces: [actionSurface],
|
|
75
|
+
permission: {
|
|
76
|
+
require: "authenticated"
|
|
77
|
+
},
|
|
78
|
+
inputValidator: [
|
|
79
|
+
workspaceSlugParamsValidator,
|
|
80
|
+
{
|
|
81
|
+
payload: crudResource.operations.create.bodyValidator
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
outputValidator: crudResource.operations.create.outputValidator,
|
|
85
|
+
idempotency: "optional",
|
|
86
|
+
audit: {
|
|
87
|
+
actionName: actionIds.create
|
|
88
|
+
},
|
|
89
|
+
observability: {},
|
|
90
|
+
async execute(input, context, deps) {
|
|
91
|
+
return deps.crudService.createRecord(input.payload, {
|
|
92
|
+
context,
|
|
93
|
+
visibilityContext: context?.visibilityContext
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: actionIds.update,
|
|
99
|
+
version: 1,
|
|
100
|
+
kind: "command",
|
|
101
|
+
channels: ["api", "automation", "internal"],
|
|
102
|
+
surfaces: [actionSurface],
|
|
103
|
+
permission: {
|
|
104
|
+
require: "authenticated"
|
|
105
|
+
},
|
|
106
|
+
inputValidator: [
|
|
107
|
+
workspaceSlugParamsValidator,
|
|
108
|
+
recordIdParamsValidator,
|
|
109
|
+
{
|
|
110
|
+
patch: crudResource.operations.patch.bodyValidator
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
outputValidator: crudResource.operations.patch.outputValidator,
|
|
114
|
+
idempotency: "optional",
|
|
115
|
+
audit: {
|
|
116
|
+
actionName: actionIds.update
|
|
117
|
+
},
|
|
118
|
+
observability: {},
|
|
119
|
+
async execute(input, context, deps) {
|
|
120
|
+
return deps.crudService.updateRecord(input.recordId, input.patch, {
|
|
121
|
+
context,
|
|
122
|
+
visibilityContext: context?.visibilityContext
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: actionIds.delete,
|
|
128
|
+
version: 1,
|
|
129
|
+
kind: "command",
|
|
130
|
+
channels: ["api", "automation", "internal"],
|
|
131
|
+
surfaces: [actionSurface],
|
|
132
|
+
permission: {
|
|
133
|
+
require: "authenticated"
|
|
134
|
+
},
|
|
135
|
+
inputValidator: [workspaceSlugParamsValidator, recordIdParamsValidator],
|
|
136
|
+
outputValidator: crudResource.operations.delete.outputValidator,
|
|
137
|
+
idempotency: "optional",
|
|
138
|
+
audit: {
|
|
139
|
+
actionName: actionIds.delete
|
|
140
|
+
},
|
|
141
|
+
observability: {},
|
|
142
|
+
async execute(input, context, deps) {
|
|
143
|
+
return deps.crudService.deleteRecord(input.recordId, {
|
|
144
|
+
context,
|
|
145
|
+
visibilityContext: context?.visibilityContext
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export { createActions };
|