@jskit-ai/crud-server-generator 0.1.71 → 0.1.72
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 +23 -11
- package/package.json +7 -7
- package/src/server/buildTemplateContext.js +24 -4
- package/templates/src/local-package/server/registerRoutes.js +5 -0
- package/test/buildTemplateContext.test.js +37 -0
- package/test/packageDescriptor.test.js +9 -0
- package/test/routeInputContracts.test.js +36 -0
- package/test-support/templateServerFixture.js +3 -1
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/crud-server-generator",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.72",
|
|
5
5
|
kind: "generator",
|
|
6
6
|
description: "CRUD server generator with routes, actions, and persistence scaffolding.",
|
|
7
7
|
options: {
|
|
@@ -56,6 +56,13 @@ export default Object.freeze({
|
|
|
56
56
|
defaultValue: "",
|
|
57
57
|
promptLabel: "Force overwrite",
|
|
58
58
|
promptHint: "Overwrite generated scaffold files if the namespace package directory already exists."
|
|
59
|
+
},
|
|
60
|
+
internal: {
|
|
61
|
+
required: false,
|
|
62
|
+
inputType: "flag",
|
|
63
|
+
defaultValue: "",
|
|
64
|
+
promptLabel: "Internal HTTP routes",
|
|
65
|
+
promptHint: "Mark generated CRUD HTTP routes as internal-only so they are not publicly registered."
|
|
59
66
|
}
|
|
60
67
|
},
|
|
61
68
|
optionPolicies: {
|
|
@@ -103,7 +110,8 @@ export default Object.freeze({
|
|
|
103
110
|
"table-name",
|
|
104
111
|
"id-column",
|
|
105
112
|
"directory-prefix",
|
|
106
|
-
"force"
|
|
113
|
+
"force",
|
|
114
|
+
"internal"
|
|
107
115
|
],
|
|
108
116
|
createTarget: {
|
|
109
117
|
pathTemplate: "packages/${option:namespace|kebab}",
|
|
@@ -152,14 +160,14 @@ export default Object.freeze({
|
|
|
152
160
|
mutations: {
|
|
153
161
|
dependencies: {
|
|
154
162
|
runtime: {
|
|
155
|
-
"@jskit-ai/auth-core": "0.1.
|
|
156
|
-
"@jskit-ai/crud-core": "0.1.
|
|
157
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
158
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
159
|
-
"@jskit-ai/json-rest-api-core": "0.1.
|
|
160
|
-
"@jskit-ai/kernel": "0.1.
|
|
161
|
-
"@jskit-ai/realtime": "0.1.
|
|
162
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
163
|
+
"@jskit-ai/auth-core": "0.1.63",
|
|
164
|
+
"@jskit-ai/crud-core": "0.1.72",
|
|
165
|
+
"@jskit-ai/database-runtime": "0.1.64",
|
|
166
|
+
"@jskit-ai/http-runtime": "0.1.63",
|
|
167
|
+
"@jskit-ai/json-rest-api-core": "0.1.9",
|
|
168
|
+
"@jskit-ai/kernel": "0.1.64",
|
|
169
|
+
"@jskit-ai/realtime": "0.1.63",
|
|
170
|
+
"@jskit-ai/resource-crud-core": "0.1.9",
|
|
163
171
|
"@local/${option:namespace|kebab}": "file:packages/${option:namespace|kebab}"
|
|
164
172
|
},
|
|
165
173
|
dev: {}
|
|
@@ -194,7 +202,11 @@ export default Object.freeze({
|
|
|
194
202
|
to: "packages/${option:namespace|kebab}/package.descriptor.mjs",
|
|
195
203
|
reason: "Install app-local CRUD package descriptor.",
|
|
196
204
|
category: "crud",
|
|
197
|
-
id: "crud-local-package-descriptor-${option:namespace|snake}"
|
|
205
|
+
id: "crud-local-package-descriptor-${option:namespace|snake}",
|
|
206
|
+
templateContext: {
|
|
207
|
+
entrypoint: "src/server/buildTemplateContext.js",
|
|
208
|
+
export: "buildTemplateContext"
|
|
209
|
+
}
|
|
198
210
|
},
|
|
199
211
|
{
|
|
200
212
|
from: "templates/src/local-package/server/CrudProvider.js",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/crud-server-generator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.72",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@babel/parser": "^7.29.2",
|
|
16
|
-
"@jskit-ai/crud-core": "0.1.
|
|
17
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
18
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
19
|
-
"@jskit-ai/json-rest-api-core": "0.1.
|
|
20
|
-
"@jskit-ai/kernel": "0.1.
|
|
21
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
16
|
+
"@jskit-ai/crud-core": "0.1.72",
|
|
17
|
+
"@jskit-ai/database-runtime": "0.1.64",
|
|
18
|
+
"@jskit-ai/http-runtime": "0.1.63",
|
|
19
|
+
"@jskit-ai/json-rest-api-core": "0.1.9",
|
|
20
|
+
"@jskit-ai/kernel": "0.1.64",
|
|
21
|
+
"@jskit-ai/resource-crud-core": "0.1.9",
|
|
22
22
|
"recast": "^0.23.11"
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
loadAppConfigFromModuleUrl,
|
|
16
16
|
resolveRequiredAppRoot
|
|
17
17
|
} from "@jskit-ai/kernel/server/support";
|
|
18
|
+
import { normalizeBoolean } from "@jskit-ai/kernel/shared/support/normalize";
|
|
18
19
|
import { normalizeCrudLookupNamespace } from "@jskit-ai/kernel/shared/support/crudLookup";
|
|
19
20
|
import { toCamelCase, toSnakeCase } from "@jskit-ai/kernel/shared/support/stringCase";
|
|
20
21
|
import descriptor from "../../package.descriptor.mjs";
|
|
@@ -78,6 +79,19 @@ function asRecord(value) {
|
|
|
78
79
|
return value;
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
function resolveInternalRouteOption(options = {}) {
|
|
83
|
+
if (!Object.prototype.hasOwnProperty.call(options, "internal")) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
if (options.internal === "" || options.internal === true) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (options.internal === false) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return normalizeBoolean(options.internal);
|
|
93
|
+
}
|
|
94
|
+
|
|
81
95
|
function normalizeRequestedOwnershipFilter(value, { strict = false } = {}) {
|
|
82
96
|
const normalized = normalizeText(value).toLowerCase();
|
|
83
97
|
if (OWNERSHIP_FILTER_VALUES.has(normalized)) {
|
|
@@ -1823,7 +1837,8 @@ function buildReplacementsFromSnapshot({
|
|
|
1823
1837
|
snapshot,
|
|
1824
1838
|
resolvedOwnershipFilter,
|
|
1825
1839
|
surfaceRequiresWorkspace = true,
|
|
1826
|
-
surfaceId = ""
|
|
1840
|
+
surfaceId = "",
|
|
1841
|
+
routeInternal = false
|
|
1827
1842
|
}) {
|
|
1828
1843
|
const requiresNamedPermissions = surfaceRequiresWorkspace === true;
|
|
1829
1844
|
const scaffoldColumns = resolveScaffoldColumns(snapshot);
|
|
@@ -1888,6 +1903,7 @@ function buildReplacementsFromSnapshot({
|
|
|
1888
1903
|
__JSKIT_CRUD_ROUTE_WORKSPACE_SUPPORT_IMPORTS__: renderRouteWorkspaceSupportImports({
|
|
1889
1904
|
surfaceRequiresWorkspace
|
|
1890
1905
|
}),
|
|
1906
|
+
__JSKIT_CRUD_ROUTE_INTERNAL_LINE__: routeInternal === true ? " internal: true," : "",
|
|
1891
1907
|
__JSKIT_CRUD_ROUTE_CONTRACTS_RESOURCE_ARGS__: surfaceRequiresWorkspace ? ",\n routeParamsValidator" : "",
|
|
1892
1908
|
__JSKIT_CRUD_ROUTE_VALIDATOR_CONSTANTS__: renderRouteValidatorConstants({
|
|
1893
1909
|
surfaceRequiresWorkspace
|
|
@@ -1969,7 +1985,8 @@ function createCacheKey({ appRoot, options }) {
|
|
|
1969
1985
|
surface: normalizeText(options?.surface),
|
|
1970
1986
|
ownershipFilter: normalizeText(options?.["ownership-filter"]),
|
|
1971
1987
|
tableName: normalizeText(options?.["table-name"]),
|
|
1972
|
-
idColumn: normalizeText(options?.["id-column"])
|
|
1988
|
+
idColumn: normalizeText(options?.["id-column"]),
|
|
1989
|
+
internal: resolveInternalRouteOption(options)
|
|
1973
1990
|
}
|
|
1974
1991
|
};
|
|
1975
1992
|
|
|
@@ -2010,13 +2027,15 @@ async function buildCrudTemplateContext(input = {}) {
|
|
|
2010
2027
|
options,
|
|
2011
2028
|
surface: resolvedSurface
|
|
2012
2029
|
});
|
|
2030
|
+
const routeInternal = resolveInternalRouteOption(options);
|
|
2013
2031
|
|
|
2014
2032
|
return buildReplacementsFromSnapshot({
|
|
2015
2033
|
namespace,
|
|
2016
2034
|
snapshot,
|
|
2017
2035
|
resolvedOwnershipFilter,
|
|
2018
2036
|
surfaceRequiresWorkspace,
|
|
2019
|
-
surfaceId: resolvedSurface
|
|
2037
|
+
surfaceId: resolvedSurface,
|
|
2038
|
+
routeInternal
|
|
2020
2039
|
});
|
|
2021
2040
|
}
|
|
2022
2041
|
|
|
@@ -2058,7 +2077,8 @@ const __testables = Object.freeze({
|
|
|
2058
2077
|
renderActionInputExpressions,
|
|
2059
2078
|
renderRouteValidatorConstants,
|
|
2060
2079
|
renderRouteParamsValidatorLine,
|
|
2061
|
-
renderRouteInputLines
|
|
2080
|
+
renderRouteInputLines,
|
|
2081
|
+
resolveInternalRouteOption
|
|
2062
2082
|
});
|
|
2063
2083
|
|
|
2064
2084
|
export {
|
|
@@ -38,6 +38,7 @@ function registerRoutes(
|
|
|
38
38
|
{
|
|
39
39
|
auth: "required",
|
|
40
40
|
surface: normalizedRouteSurface,
|
|
41
|
+
__JSKIT_CRUD_ROUTE_INTERNAL_LINE__
|
|
41
42
|
visibility: checkRouteVisibility(routeOwnershipFilter),
|
|
42
43
|
meta: {
|
|
43
44
|
tags: ["crud"],
|
|
@@ -64,6 +65,7 @@ __JSKIT_CRUD_LIST_ROUTE_INPUT_LINES__
|
|
|
64
65
|
{
|
|
65
66
|
auth: "required",
|
|
66
67
|
surface: normalizedRouteSurface,
|
|
68
|
+
__JSKIT_CRUD_ROUTE_INTERNAL_LINE__
|
|
67
69
|
visibility: checkRouteVisibility(routeOwnershipFilter),
|
|
68
70
|
meta: {
|
|
69
71
|
tags: ["crud"],
|
|
@@ -89,6 +91,7 @@ __JSKIT_CRUD_VIEW_ROUTE_INPUT_LINES__
|
|
|
89
91
|
{
|
|
90
92
|
auth: "required",
|
|
91
93
|
surface: normalizedRouteSurface,
|
|
94
|
+
__JSKIT_CRUD_ROUTE_INTERNAL_LINE__
|
|
92
95
|
visibility: checkRouteVisibility(routeOwnershipFilter),
|
|
93
96
|
meta: {
|
|
94
97
|
tags: ["crud"],
|
|
@@ -114,6 +117,7 @@ __JSKIT_CRUD_CREATE_ROUTE_INPUT_LINES__
|
|
|
114
117
|
{
|
|
115
118
|
auth: "required",
|
|
116
119
|
surface: normalizedRouteSurface,
|
|
120
|
+
__JSKIT_CRUD_ROUTE_INTERNAL_LINE__
|
|
117
121
|
visibility: checkRouteVisibility(routeOwnershipFilter),
|
|
118
122
|
meta: {
|
|
119
123
|
tags: ["crud"],
|
|
@@ -139,6 +143,7 @@ __JSKIT_CRUD_UPDATE_ROUTE_INPUT_LINES__
|
|
|
139
143
|
{
|
|
140
144
|
auth: "required",
|
|
141
145
|
surface: normalizedRouteSurface,
|
|
146
|
+
__JSKIT_CRUD_ROUTE_INTERNAL_LINE__
|
|
142
147
|
visibility: checkRouteVisibility(routeOwnershipFilter),
|
|
143
148
|
meta: {
|
|
144
149
|
tags: ["crud"],
|
|
@@ -235,6 +235,43 @@ test("resolveOwnershipFilterForGeneration rejects explicit ownership filters whe
|
|
|
235
235
|
);
|
|
236
236
|
});
|
|
237
237
|
|
|
238
|
+
test("buildReplacementsFromSnapshot renders an internal route line only when requested", () => {
|
|
239
|
+
const snapshot = createSnapshot();
|
|
240
|
+
|
|
241
|
+
const publicReplacements = __testables.buildReplacementsFromSnapshot({
|
|
242
|
+
namespace: "customers",
|
|
243
|
+
snapshot,
|
|
244
|
+
resolvedOwnershipFilter: "workspace_user",
|
|
245
|
+
surfaceRequiresWorkspace: true,
|
|
246
|
+
surfaceId: "admin",
|
|
247
|
+
routeInternal: false
|
|
248
|
+
});
|
|
249
|
+
const internalReplacements = __testables.buildReplacementsFromSnapshot({
|
|
250
|
+
namespace: "customers",
|
|
251
|
+
snapshot,
|
|
252
|
+
resolvedOwnershipFilter: "workspace_user",
|
|
253
|
+
surfaceRequiresWorkspace: true,
|
|
254
|
+
surfaceId: "admin",
|
|
255
|
+
routeInternal: true
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
assert.equal(publicReplacements.__JSKIT_CRUD_ROUTE_INTERNAL_LINE__, "");
|
|
259
|
+
assert.equal(internalReplacements.__JSKIT_CRUD_ROUTE_INTERNAL_LINE__, " internal: true,");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("resolveInternalRouteOption rejects invalid internal flag values instead of silently generating public routes", () => {
|
|
263
|
+
assert.throws(
|
|
264
|
+
() => __testables.resolveInternalRouteOption({ internal: "maybe" }),
|
|
265
|
+
/Boolean field must be true or false/
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("resolveInternalRouteOption treats an empty-string flag presence as true", () => {
|
|
270
|
+
assert.equal(__testables.resolveInternalRouteOption({ internal: "" }), true);
|
|
271
|
+
assert.equal(__testables.resolveInternalRouteOption({ internal: "true" }), true);
|
|
272
|
+
assert.equal(__testables.resolveInternalRouteOption({ internal: "false" }), false);
|
|
273
|
+
});
|
|
274
|
+
|
|
238
275
|
test("resolveCrudGenerationTableName defaults table-name from namespace", () => {
|
|
239
276
|
assert.equal(
|
|
240
277
|
__testables.resolveCrudGenerationTableName({
|
|
@@ -16,8 +16,10 @@ test("crud-server-generator surface option validates against enabled surface ids
|
|
|
16
16
|
descriptor.options?.["table-name"]?.defaultFromOptionTemplate,
|
|
17
17
|
"${option:namespace}"
|
|
18
18
|
);
|
|
19
|
+
assert.equal(descriptor.options?.internal?.inputType, "flag");
|
|
19
20
|
assert.equal(descriptor.metadata?.generatorSubcommands?.scaffold?.optionNames?.includes("surface"), true);
|
|
20
21
|
assert.equal(descriptor.metadata?.generatorSubcommands?.scaffold?.optionNames?.includes("force"), true);
|
|
22
|
+
assert.equal(descriptor.metadata?.generatorSubcommands?.scaffold?.optionNames?.includes("internal"), true);
|
|
21
23
|
assert.equal(descriptor.metadata?.generatorSubcommands?.scaffold?.createTarget?.pathTemplate, "packages/${option:namespace|kebab}");
|
|
22
24
|
});
|
|
23
25
|
|
|
@@ -30,10 +32,17 @@ test("crud-server-generator no longer installs a separate jsonRestResource serve
|
|
|
30
32
|
|
|
31
33
|
test("crud-server-generator wires action and role mutations through template context", () => {
|
|
32
34
|
const files = descriptor.mutations?.files || [];
|
|
35
|
+
const descriptorTemplate = files.find((entry) => entry.from === "templates/src/local-package/package.descriptor.mjs");
|
|
33
36
|
const actionsTemplate = files.find((entry) => entry.from === "templates/src/local-package/server/actions.js");
|
|
34
37
|
const routesTemplate = files.find((entry) => entry.from === "templates/src/local-package/server/registerRoutes.js");
|
|
35
38
|
const roleGrantMutation = (descriptor.mutations?.text || []).find((entry) => entry.file === "config/roles.js");
|
|
36
39
|
|
|
40
|
+
assert.ok(descriptorTemplate);
|
|
41
|
+
assert.deepEqual(descriptorTemplate.templateContext, {
|
|
42
|
+
entrypoint: "src/server/buildTemplateContext.js",
|
|
43
|
+
export: "buildTemplateContext"
|
|
44
|
+
});
|
|
45
|
+
|
|
37
46
|
assert.ok(actionsTemplate);
|
|
38
47
|
assert.deepEqual(actionsTemplate.templateContext, {
|
|
39
48
|
entrypoint: "src/server/buildTemplateContext.js",
|
|
@@ -9,12 +9,17 @@ const nonWorkspaceFixture = await createTemplateServerFixture({
|
|
|
9
9
|
surfaceRequiresWorkspace: false,
|
|
10
10
|
requiresNamedPermissions: false
|
|
11
11
|
});
|
|
12
|
+
const internalFixture = await createTemplateServerFixture({
|
|
13
|
+
routeInternal: true
|
|
14
|
+
});
|
|
12
15
|
const { registerRoutes: registerWorkspaceRoutes } = await workspaceFixture.importServerModule("registerRoutes.js");
|
|
13
16
|
const { registerRoutes: registerNonWorkspaceRoutes } = await nonWorkspaceFixture.importServerModule("registerRoutes.js");
|
|
17
|
+
const { registerRoutes: registerInternalRoutes } = await internalFixture.importServerModule("registerRoutes.js");
|
|
14
18
|
|
|
15
19
|
after(async () => {
|
|
16
20
|
await workspaceFixture.cleanup();
|
|
17
21
|
await nonWorkspaceFixture.cleanup();
|
|
22
|
+
await internalFixture.cleanup();
|
|
18
23
|
});
|
|
19
24
|
|
|
20
25
|
function createReplyDouble() {
|
|
@@ -436,3 +441,34 @@ test("crud view route forwards include query input", async () => {
|
|
|
436
441
|
include: "vetId"
|
|
437
442
|
});
|
|
438
443
|
});
|
|
444
|
+
|
|
445
|
+
test("crud routes mark every generated HTTP route as internal when requested", () => {
|
|
446
|
+
const registeredRoutes = [];
|
|
447
|
+
const router = {
|
|
448
|
+
register(method, path, route, handler) {
|
|
449
|
+
registeredRoutes.push({
|
|
450
|
+
method,
|
|
451
|
+
path,
|
|
452
|
+
route,
|
|
453
|
+
handler
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
const app = {
|
|
458
|
+
make(token) {
|
|
459
|
+
if (token !== "jskit.http.router") {
|
|
460
|
+
throw new Error(`Unexpected token: ${String(token)}`);
|
|
461
|
+
}
|
|
462
|
+
return router;
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
registerInternalRoutes(app, {
|
|
467
|
+
routeRelativePath: "/customers"
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
assert.equal(registeredRoutes.length, 5);
|
|
471
|
+
for (const route of registeredRoutes) {
|
|
472
|
+
assert.equal(route.route.internal, true);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
@@ -17,7 +17,8 @@ const CRUD_NAMESPACE = Object.freeze({
|
|
|
17
17
|
function buildTemplateReplacements({
|
|
18
18
|
surfaceRequiresWorkspace = true,
|
|
19
19
|
requiresNamedPermissions = surfaceRequiresWorkspace === true,
|
|
20
|
-
surfaceId = surfaceRequiresWorkspace ? "admin" : "home"
|
|
20
|
+
surfaceId = surfaceRequiresWorkspace ? "admin" : "home",
|
|
21
|
+
routeInternal = false
|
|
21
22
|
} = {}) {
|
|
22
23
|
const routeWorkspaceSupportImports = surfaceRequiresWorkspace
|
|
23
24
|
? [
|
|
@@ -141,6 +142,7 @@ function buildTemplateReplacements({
|
|
|
141
142
|
["__JSKIT_CRUD_ROUTE_SURFACE_REQUIRES_WORKSPACE__", String(surfaceRequiresWorkspace === true)],
|
|
142
143
|
["__JSKIT_CRUD_ROUTE_BASE__", JSON.stringify(surfaceRequiresWorkspace ? "/w/:workspaceSlug" : "/")],
|
|
143
144
|
["__JSKIT_CRUD_ROUTE_WORKSPACE_SUPPORT_IMPORTS__", routeWorkspaceSupportImports],
|
|
145
|
+
["__JSKIT_CRUD_ROUTE_INTERNAL_LINE__", routeInternal === true ? " internal: true," : ""],
|
|
144
146
|
["__JSKIT_CRUD_ROUTE_CONTRACTS_RESOURCE_ARGS__", surfaceRequiresWorkspace ? ",\n routeParamsValidator" : ""],
|
|
145
147
|
["__JSKIT_CRUD_ROUTE_VALIDATOR_CONSTANTS__", surfaceRequiresWorkspace
|
|
146
148
|
? [
|