@jskit-ai/crud-server-generator 0.1.26 → 0.1.28

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.
@@ -7,76 +7,13 @@ import {
7
7
  normalizeObjectInput,
8
8
  createCursorListValidator
9
9
  } from "@jskit-ai/kernel/shared/validators";
10
- import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
11
-
12
- function normalizeNumberField(value, { fieldLabel = "Number field" } = {}) {
13
- const normalized = Number(value);
14
- if (!Number.isFinite(normalized)) {
15
- throw new TypeError(`${fieldLabel} must be a valid number.`);
16
- }
17
-
18
- return normalized;
19
- }
20
-
21
- function normalizeDateTimeField(value, { fieldLabel = "Date field" } = {}) {
22
- try {
23
- return toIsoString(value);
24
- } catch {
25
- throw new TypeError(`${fieldLabel} must be a valid date/time.`);
26
- }
27
- }
28
-
29
- function normalizeDatabaseDateTimeField(value, { fieldLabel = "Date field" } = {}) {
30
- try {
31
- return toDatabaseDateTimeUtc(value);
32
- } catch {
33
- throw new TypeError(`${fieldLabel} must be a valid date/time.`);
34
- }
35
- }
36
-
37
- function normalizeRecordInput(payload = {}) {
38
- const source = normalizeObjectInput(payload);
39
- const normalized = {};
40
-
41
- if (Object.hasOwn(source, "textField")) {
42
- normalized.textField = normalizeText(source.textField);
43
- }
44
-
45
- if (Object.hasOwn(source, "dateField")) {
46
- normalized.dateField = normalizeDatabaseDateTimeField(source.dateField, {
47
- fieldLabel: "Date field"
48
- });
49
- }
50
-
51
- if (Object.hasOwn(source, "numberField")) {
52
- normalized.numberField = normalizeNumberField(source.numberField, {
53
- fieldLabel: "Number field"
54
- });
55
- }
56
-
57
- return normalized;
58
- }
59
-
60
- function normalizeRecordOutput(payload = {}) {
61
- const source = normalizeObjectInput(payload);
10
+ import {
11
+ normalizeText,
12
+ normalizeFiniteNumber,
13
+ normalizeIfPresent
14
+ } from "@jskit-ai/kernel/shared/support/normalize";
62
15
 
63
- return {
64
- id: Number(source.id),
65
- textField: normalizeText(source.textField),
66
- dateField: normalizeDateTimeField(source.dateField, {
67
- fieldLabel: "Date field"
68
- }),
69
- numberField: normalizeNumberField(source.numberField, {
70
- fieldLabel: "Number field"
71
- }),
72
- createdAt: normalizeDateTimeField(source.createdAt, {
73
- fieldLabel: "Created at"
74
- }),
75
- updatedAt: normalizeDateTimeField(source.updatedAt, {
76
- fieldLabel: "Updated at"
77
- })
78
- };
79
- }
16
+ const RESOURCE_LOOKUP_CONTAINER_KEY = "lookups";
80
17
 
81
18
  const recordOutputSchema = Type.Object(
82
19
  {
@@ -85,7 +22,8 @@ const recordOutputSchema = Type.Object(
85
22
  dateField: Type.String({ minLength: 1 }),
86
23
  numberField: Type.Number(),
87
24
  createdAt: Type.String({ minLength: 1 }),
88
- updatedAt: Type.String({ minLength: 1 })
25
+ updatedAt: Type.String({ minLength: 1 }),
26
+ [RESOURCE_LOOKUP_CONTAINER_KEY]: Type.Optional(Type.Record(Type.String(), Type.Unknown()))
89
27
  },
90
28
  { additionalProperties: false }
91
29
  );
@@ -126,13 +64,79 @@ const recordBodySchema = Type.Object(
126
64
  }
127
65
  );
128
66
 
67
+ const patchBodySchema = Type.Partial(recordBodySchema, { additionalProperties: false });
68
+
129
69
  const recordOutputValidator = Object.freeze({
130
70
  schema: recordOutputSchema,
131
- normalize: normalizeRecordOutput
71
+ normalize(payload = {}) {
72
+ const source = normalizeObjectInput(payload);
73
+ const normalized = {
74
+ id: Number(source.id),
75
+ textField: normalizeText(source.textField),
76
+ dateField: toIsoString(source.dateField),
77
+ numberField: normalizeFiniteNumber(source.numberField),
78
+ createdAt: normalizeIfPresent(source.createdAt, toIsoString),
79
+ updatedAt: normalizeIfPresent(source.updatedAt, toIsoString)
80
+ };
81
+ if (Object.hasOwn(source, RESOURCE_LOOKUP_CONTAINER_KEY)) {
82
+ normalized[RESOURCE_LOOKUP_CONTAINER_KEY] = source[RESOURCE_LOOKUP_CONTAINER_KEY];
83
+ }
84
+
85
+ return normalized;
86
+ }
132
87
  });
133
88
 
89
+ const listOutputValidator = createCursorListValidator(recordOutputValidator);
90
+
91
+ const createBodyValidator = Object.freeze({
92
+ schema: recordBodySchema,
93
+ normalize(payload = {}) {
94
+ const source = normalizeObjectInput(payload);
95
+ const normalized = {};
96
+
97
+ if (Object.hasOwn(source, "textField")) {
98
+ normalized.textField = normalizeText(source.textField);
99
+ }
100
+ if (Object.hasOwn(source, "dateField")) {
101
+ normalized.dateField = toDatabaseDateTimeUtc(source.dateField);
102
+ }
103
+ if (Object.hasOwn(source, "numberField")) {
104
+ normalized.numberField = normalizeFiniteNumber(source.numberField);
105
+ }
106
+
107
+ return normalized;
108
+ }
109
+ });
110
+
111
+ const patchBodyValidator = Object.freeze({
112
+ schema: patchBodySchema,
113
+ normalize: createBodyValidator.normalize
114
+ });
115
+
116
+ const deleteOutputValidator = Object.freeze({
117
+ schema: Type.Object(
118
+ {
119
+ id: Type.Integer({ minimum: 1 }),
120
+ deleted: Type.Literal(true)
121
+ },
122
+ { additionalProperties: false }
123
+ ),
124
+ normalize(payload = {}) {
125
+ const source = normalizeObjectInput(payload);
126
+
127
+ return {
128
+ id: Number(source.id),
129
+ deleted: true
130
+ };
131
+ }
132
+ });
133
+
134
+ const CRUD_RESOURCE_FIELD_META = [];
135
+
134
136
  const crudResource = {
135
137
  resource: "crud",
138
+ tableName: "crud",
139
+ idColumn: "id",
136
140
  messages: {
137
141
  validation: "Fix invalid CRUD values and try again.",
138
142
  saveSuccess: "Record saved.",
@@ -140,10 +144,20 @@ const crudResource = {
140
144
  deleteSuccess: "Record deleted.",
141
145
  deleteError: "Unable to delete record."
142
146
  },
147
+ contract: {
148
+ lookup: {
149
+ containerKey: RESOURCE_LOOKUP_CONTAINER_KEY,
150
+ defaultInclude: "*", // Set "none" to disable lookup hydration unless include=... is passed.
151
+ maxDepth: 3 // Lower this to limit nested lookup hydration depth.
152
+ }
153
+ },
143
154
  operations: {
144
155
  list: {
156
+ realtime: {
157
+ events: ["crud.record.changed"] // Add more events e.g. for lookup records
158
+ },
145
159
  method: "GET",
146
- outputValidator: createCursorListValidator(recordOutputValidator)
160
+ outputValidator: listOutputValidator
147
161
  },
148
162
  view: {
149
163
  method: "GET",
@@ -151,41 +165,22 @@ const crudResource = {
151
165
  },
152
166
  create: {
153
167
  method: "POST",
154
- bodyValidator: {
155
- schema: recordBodySchema,
156
- normalize: normalizeRecordInput
157
- },
168
+ bodyValidator: createBodyValidator,
158
169
  outputValidator: recordOutputValidator
159
170
  },
160
171
  patch: {
161
172
  method: "PATCH",
162
- bodyValidator: {
163
- schema: Type.Partial(recordBodySchema, { additionalProperties: false }),
164
- normalize: normalizeRecordInput
165
- },
173
+ bodyValidator: patchBodyValidator,
166
174
  outputValidator: recordOutputValidator
167
175
  },
168
176
  delete: {
169
177
  method: "DELETE",
170
- outputValidator: {
171
- schema: Type.Object(
172
- {
173
- id: Type.Integer({ minimum: 1 }),
174
- deleted: Type.Literal(true)
175
- },
176
- { additionalProperties: false }
177
- ),
178
- normalize(payload = {}) {
179
- const source = normalizeObjectInput(payload);
180
-
181
- return {
182
- id: Number(source.id),
183
- deleted: true
184
- };
185
- }
186
- }
178
+ outputValidator: deleteOutputValidator
187
179
  }
188
- }
180
+ },
181
+ fieldMeta: CRUD_RESOURCE_FIELD_META
189
182
  };
190
183
 
184
+ void CRUD_RESOURCE_FIELD_META;
185
+
191
186
  export { crudResource };
@@ -9,6 +9,7 @@ exports.up = async function up(knex) {
9
9
  await knex.schema.createTable(TABLE_NAME, (table) => {
10
10
  __JSKIT_CRUD_MIGRATION_COLUMN_LINES__
11
11
  __JSKIT_CRUD_MIGRATION_INDEX_LINES__
12
+ __JSKIT_CRUD_MIGRATION_FOREIGN_KEY_LINES__
12
13
  });
13
14
  };
14
15
 
@@ -27,8 +27,8 @@ export default Object.freeze({
27
27
  server: {
28
28
  providers: [
29
29
  {
30
- entrypoint: "src/server/${option:namespace|pascal}ServiceProvider.js",
31
- export: "${option:namespace|pascal}ServiceProvider"
30
+ entrypoint: "src/server/${option:namespace|pascal}Provider.js",
31
+ export: "${option:namespace|pascal}Provider"
32
32
  }
33
33
  ]
34
34
  }
@@ -1,5 +1,9 @@
1
1
  import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
2
2
  import { resolveCrudSurfacePolicyFromAppConfig } from "@jskit-ai/crud-core/server/crudModuleConfig";
3
+ import {
4
+ createCrudLookupProviderResolver,
5
+ createCrudLookupProvider
6
+ } from "@jskit-ai/crud-core/server/lookupProviders";
3
7
  import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
4
8
  import { createRepository } from "./repository.js";
5
9
  import {
@@ -8,8 +12,6 @@ import {
8
12
  } from "./service.js";
9
13
  import { createActions } from "./actions.js";
10
14
  import { registerRoutes } from "./registerRoutes.js";
11
- const NAMESPACE_${option:namespace|snake|upper}_TABLE_NAME = __JSKIT_CRUD_TABLE_NAME__;
12
- const NAMESPACE_${option:namespace|snake|upper}_ID_COLUMN = __JSKIT_CRUD_ID_COLUMN__;
13
15
  const CRUD_MODULE_CONFIG = Object.freeze({
14
16
  namespace: "${option:namespace|snake}",
15
17
  surface: "${option:surface|lower}",
@@ -19,18 +21,18 @@ const CRUD_MODULE_CONFIG = Object.freeze({
19
21
 
20
22
  function resolveCrudPolicyFromApp(app) {
21
23
  return resolveCrudSurfacePolicyFromAppConfig(CRUD_MODULE_CONFIG, resolveAppConfig(app), {
22
- context: "${option:namespace|pascal}ServiceProvider"
24
+ context: "${option:namespace|pascal}Provider"
23
25
  });
24
26
  }
25
27
 
26
- class ${option:namespace|pascal}ServiceProvider {
28
+ class ${option:namespace|pascal}Provider {
27
29
  static id = "crud.${option:namespace|snake}";
28
30
 
29
31
  static dependsOn = ["runtime.actions", "runtime.database", "auth.policy.fastify", "local.main", "users.core"];
30
32
 
31
33
  register(app) {
32
34
  if (!app || typeof app.singleton !== "function" || typeof app.service !== "function" || typeof app.actions !== "function") {
33
- throw new Error("${option:namespace|pascal}ServiceProvider requires application singleton()/service()/actions().");
35
+ throw new Error("${option:namespace|pascal}Provider requires application singleton()/service()/actions().");
34
36
  }
35
37
 
36
38
  const crudPolicy = resolveCrudPolicyFromApp(app);
@@ -38,11 +40,14 @@ class ${option:namespace|pascal}ServiceProvider {
38
40
  app.singleton("repository.${option:namespace|snake}", (scope) => {
39
41
  const knex = scope.make("jskit.database.knex");
40
42
  return createRepository(knex, {
41
- tableName: NAMESPACE_${option:namespace|snake|upper}_TABLE_NAME,
42
- idColumn: NAMESPACE_${option:namespace|snake|upper}_ID_COLUMN
43
+ resolveLookupProvider: createCrudLookupProviderResolver(scope)
43
44
  });
44
45
  });
45
46
 
47
+ app.singleton("crud.lookup.${option:namespace|snake}", (scope) => {
48
+ return createCrudLookupProvider(scope.make("repository.${option:namespace|snake}"));
49
+ });
50
+
46
51
  app.service(
47
52
  "crud.${option:namespace|snake}",
48
53
  (scope) => {
@@ -81,4 +86,4 @@ class ${option:namespace|pascal}ServiceProvider {
81
86
  }
82
87
  }
83
88
 
84
- export { ${option:namespace|pascal}ServiceProvider };
89
+ export { ${option:namespace|pascal}Provider };
@@ -2,10 +2,17 @@ import {
2
2
  cursorPaginationQueryValidator,
3
3
  recordIdParamsValidator
4
4
  } from "@jskit-ai/kernel/shared/validators";
5
+ import {
6
+ listSearchQueryValidator,
7
+ lookupIncludeQueryValidator,
8
+ createCrudParentFilterQueryValidator
9
+ } from "@jskit-ai/crud-core/server/listQueryValidators";
5
10
  import { workspaceSlugParamsValidator } from "@jskit-ai/users-core/server/validators/routeParamsValidator";
6
- import { ${option:namespace|singular|camel}Resource } from "../shared/${option:namespace|singular|camel}Resource.js";
11
+ import { resource } from "../shared/${option:namespace|singular|camel}Resource.js";
7
12
  import { actionIds } from "./actionIds.js";
8
13
 
14
+ const listParentFilterQueryValidator = createCrudParentFilterQueryValidator(resource);
15
+
9
16
  function requireActionSurface(surface = "") {
10
17
  const normalizedSurface = String(surface || "").trim().toLowerCase();
11
18
  if (!normalizedSurface) {
@@ -28,8 +35,14 @@ function createActions({ surface = "" } = {}) {
28
35
  permission: {
29
36
  require: "authenticated"
30
37
  },
31
- inputValidator: [workspaceSlugParamsValidator, cursorPaginationQueryValidator],
32
- outputValidator: ${option:namespace|singular|camel}Resource.operations.list.outputValidator,
38
+ inputValidator: [
39
+ workspaceSlugParamsValidator,
40
+ cursorPaginationQueryValidator,
41
+ listSearchQueryValidator,
42
+ listParentFilterQueryValidator,
43
+ lookupIncludeQueryValidator
44
+ ],
45
+ outputValidator: resource.operations.list.outputValidator,
33
46
  idempotency: "none",
34
47
  audit: {
35
48
  actionName: actionIds.list
@@ -51,7 +64,7 @@ function createActions({ surface = "" } = {}) {
51
64
  permission: {
52
65
  require: "authenticated"
53
66
  },
54
- inputValidator: [workspaceSlugParamsValidator, recordIdParamsValidator],
67
+ inputValidator: [workspaceSlugParamsValidator, recordIdParamsValidator, lookupIncludeQueryValidator],
55
68
  outputValidator: ${option:namespace|singular|camel}Resource.operations.view.outputValidator,
56
69
  idempotency: "none",
57
70
  audit: {
@@ -61,7 +74,8 @@ function createActions({ surface = "" } = {}) {
61
74
  async execute(input, context, deps) {
62
75
  return deps.${option:namespace|camel}Service.getRecord(input.recordId, {
63
76
  context,
64
- visibilityContext: context?.visibilityContext
77
+ visibilityContext: context?.visibilityContext,
78
+ include: input.include
65
79
  });
66
80
  }
67
81
  },
@@ -77,10 +91,10 @@ function createActions({ surface = "" } = {}) {
77
91
  inputValidator: [
78
92
  workspaceSlugParamsValidator,
79
93
  {
80
- payload: ${option:namespace|singular|camel}Resource.operations.create.bodyValidator
94
+ payload: resource.operations.create.bodyValidator
81
95
  }
82
96
  ],
83
- outputValidator: ${option:namespace|singular|camel}Resource.operations.create.outputValidator,
97
+ outputValidator: resource.operations.create.outputValidator,
84
98
  idempotency: "optional",
85
99
  audit: {
86
100
  actionName: actionIds.create
@@ -106,10 +120,10 @@ function createActions({ surface = "" } = {}) {
106
120
  workspaceSlugParamsValidator,
107
121
  recordIdParamsValidator,
108
122
  {
109
- patch: ${option:namespace|singular|camel}Resource.operations.patch.bodyValidator
123
+ patch: resource.operations.patch.bodyValidator
110
124
  }
111
125
  ],
112
- outputValidator: ${option:namespace|singular|camel}Resource.operations.patch.outputValidator,
126
+ outputValidator: resource.operations.patch.outputValidator,
113
127
  idempotency: "optional",
114
128
  audit: {
115
129
  actionName: actionIds.update
@@ -132,7 +146,7 @@ function createActions({ surface = "" } = {}) {
132
146
  require: "authenticated"
133
147
  },
134
148
  inputValidator: [workspaceSlugParamsValidator, recordIdParamsValidator],
135
- outputValidator: ${option:namespace|singular|camel}Resource.operations.delete.outputValidator,
149
+ outputValidator: resource.operations.delete.outputValidator,
136
150
  idempotency: "optional",
137
151
  audit: {
138
152
  actionName: actionIds.delete
@@ -1,15 +1,22 @@
1
1
  import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
2
2
  import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
3
+ import {
4
+ listSearchQueryValidator,
5
+ lookupIncludeQueryValidator,
6
+ createCrudParentFilterQueryValidator
7
+ } from "@jskit-ai/crud-core/server/listQueryValidators";
3
8
  import {
4
9
  cursorPaginationQueryValidator,
5
10
  recordIdParamsValidator
6
11
  } from "@jskit-ai/kernel/shared/validators";
7
12
  import { routeParamsValidator } from "@jskit-ai/users-core/server/validators/routeParamsValidator";
8
- import { normalizeScopedRouteVisibility } from "@jskit-ai/users-core/shared/support/usersVisibility";
13
+ import { checkRouteVisibility } from "@jskit-ai/users-core/shared/support/usersVisibility";
9
14
  import { buildWorkspaceInputFromRouteParams } from "@jskit-ai/users-core/server/support/workspaceRouteInput";
10
15
  import { resolveApiBasePath } from "@jskit-ai/users-core/shared/support/usersApiPaths";
11
16
  import { actionIds } from "./actionIds.js";
12
- import { ${option:namespace|singular|camel}Resource } from "../shared/${option:namespace|singular|camel}Resource.js";
17
+ import { resource } from "../shared/${option:namespace|singular|camel}Resource.js";
18
+
19
+ const listParentFilterQueryValidator = createCrudParentFilterQueryValidator(resource);
13
20
 
14
21
  function registerRoutes(
15
22
  app,
@@ -20,17 +27,7 @@ function registerRoutes(
20
27
  routeRelativePath = ""
21
28
  } = {}
22
29
  ) {
23
- if (!app || typeof app.make !== "function") {
24
- throw new Error("registerRoutes requires application make().");
25
- }
26
- if (!String(routeRelativePath || "").trim()) {
27
- throw new Error("registerRoutes requires routeRelativePath.");
28
- }
29
-
30
30
  const router = app.make("jskit.http.router");
31
- const routeVisibility = normalizeScopedRouteVisibility(routeOwnershipFilter, {
32
- fallback: "public"
33
- });
34
31
  const normalizedRouteSurface = normalizeSurfaceId(routeSurface);
35
32
  const routeBase = resolveApiBasePath({
36
33
  surfaceRequiresWorkspace: routeSurfaceRequiresWorkspace === true,
@@ -43,27 +40,27 @@ function registerRoutes(
43
40
  {
44
41
  auth: "required",
45
42
  surface: normalizedRouteSurface,
46
- visibility: routeVisibility,
43
+ visibility: checkRouteVisibility(routeOwnershipFilter),
47
44
  meta: {
48
45
  tags: ["crud"],
49
46
  summary: "List records."
50
47
  },
51
48
  paramsValidator: routeParamsValidator,
52
- queryValidator: cursorPaginationQueryValidator,
49
+ queryValidator: [
50
+ cursorPaginationQueryValidator,
51
+ listSearchQueryValidator,
52
+ listParentFilterQueryValidator,
53
+ lookupIncludeQueryValidator
54
+ ],
53
55
  responseValidators: withStandardErrorResponses({
54
- 200: ${option:namespace|singular|camel}Resource.operations.list.outputValidator
56
+ 200: resource.operations.list.outputValidator
55
57
  })
56
58
  },
57
59
  async function (request, reply) {
58
60
  const listInput = {
59
- ...buildWorkspaceInputFromRouteParams(request.input.params)
61
+ ...buildWorkspaceInputFromRouteParams(request.input.params),
62
+ ...(request.input.query || {})
60
63
  };
61
- if (request.input.query.cursor != null) {
62
- listInput.cursor = request.input.query.cursor;
63
- }
64
- if (request.input.query.limit != null) {
65
- listInput.limit = request.input.query.limit;
66
- }
67
64
  const response = await request.executeAction({
68
65
  actionId: actionIds.list,
69
66
  input: listInput
@@ -78,14 +75,15 @@ function registerRoutes(
78
75
  {
79
76
  auth: "required",
80
77
  surface: normalizedRouteSurface,
81
- visibility: routeVisibility,
78
+ visibility: checkRouteVisibility(routeOwnershipFilter),
82
79
  meta: {
83
80
  tags: ["crud"],
84
81
  summary: "View a record."
85
82
  },
86
83
  paramsValidator: [routeParamsValidator, recordIdParamsValidator],
84
+ queryValidator: [lookupIncludeQueryValidator],
87
85
  responseValidators: withStandardErrorResponses({
88
- 200: ${option:namespace|singular|camel}Resource.operations.view.outputValidator
86
+ 200: resource.operations.view.outputValidator
89
87
  })
90
88
  },
91
89
  async function (request, reply) {
@@ -93,7 +91,8 @@ function registerRoutes(
93
91
  actionId: actionIds.view,
94
92
  input: {
95
93
  ...buildWorkspaceInputFromRouteParams(request.input.params),
96
- recordId: request.input.params.recordId
94
+ recordId: request.input.params.recordId,
95
+ ...(request.input.query || {})
97
96
  }
98
97
  });
99
98
  reply.code(200).send(response);
@@ -106,16 +105,16 @@ function registerRoutes(
106
105
  {
107
106
  auth: "required",
108
107
  surface: normalizedRouteSurface,
109
- visibility: routeVisibility,
108
+ visibility: checkRouteVisibility(routeOwnershipFilter),
110
109
  meta: {
111
110
  tags: ["crud"],
112
111
  summary: "Create a record."
113
112
  },
114
113
  paramsValidator: routeParamsValidator,
115
- bodyValidator: ${option:namespace|singular|camel}Resource.operations.create.bodyValidator,
114
+ bodyValidator: resource.operations.create.bodyValidator,
116
115
  responseValidators: withStandardErrorResponses(
117
116
  {
118
- 201: ${option:namespace|singular|camel}Resource.operations.create.outputValidator
117
+ 201: resource.operations.create.outputValidator
119
118
  },
120
119
  { includeValidation400: true }
121
120
  )
@@ -138,16 +137,16 @@ function registerRoutes(
138
137
  {
139
138
  auth: "required",
140
139
  surface: normalizedRouteSurface,
141
- visibility: routeVisibility,
140
+ visibility: checkRouteVisibility(routeOwnershipFilter),
142
141
  meta: {
143
142
  tags: ["crud"],
144
143
  summary: "Update a record."
145
144
  },
146
145
  paramsValidator: [routeParamsValidator, recordIdParamsValidator],
147
- bodyValidator: ${option:namespace|singular|camel}Resource.operations.patch.bodyValidator,
146
+ bodyValidator: resource.operations.patch.bodyValidator,
148
147
  responseValidators: withStandardErrorResponses(
149
148
  {
150
- 200: ${option:namespace|singular|camel}Resource.operations.patch.outputValidator
149
+ 200: resource.operations.patch.outputValidator
151
150
  },
152
151
  { includeValidation400: true }
153
152
  )
@@ -171,14 +170,14 @@ function registerRoutes(
171
170
  {
172
171
  auth: "required",
173
172
  surface: normalizedRouteSurface,
174
- visibility: routeVisibility,
173
+ visibility: checkRouteVisibility(routeOwnershipFilter),
175
174
  meta: {
176
175
  tags: ["crud"],
177
176
  summary: "Delete a record."
178
177
  },
179
178
  paramsValidator: [routeParamsValidator, recordIdParamsValidator],
180
179
  responseValidators: withStandardErrorResponses({
181
- 200: ${option:namespace|singular|camel}Resource.operations.delete.outputValidator
180
+ 200: resource.operations.delete.outputValidator
182
181
  })
183
182
  },
184
183
  async function (request, reply) {