@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.
@@ -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.71",
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.62",
156
- "@jskit-ai/crud-core": "0.1.71",
157
- "@jskit-ai/database-runtime": "0.1.63",
158
- "@jskit-ai/http-runtime": "0.1.62",
159
- "@jskit-ai/json-rest-api-core": "0.1.8",
160
- "@jskit-ai/kernel": "0.1.63",
161
- "@jskit-ai/realtime": "0.1.62",
162
- "@jskit-ai/resource-crud-core": "0.1.8",
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.71",
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.71",
17
- "@jskit-ai/database-runtime": "0.1.63",
18
- "@jskit-ai/http-runtime": "0.1.62",
19
- "@jskit-ai/json-rest-api-core": "0.1.8",
20
- "@jskit-ai/kernel": "0.1.63",
21
- "@jskit-ai/resource-crud-core": "0.1.8",
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
  ? [