@strapi/utils 5.36.0 → 5.37.0

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.
Files changed (72) hide show
  1. package/dist/content-api-constants.d.ts +17 -0
  2. package/dist/content-api-constants.d.ts.map +1 -0
  3. package/dist/content-api-constants.js +43 -0
  4. package/dist/content-api-constants.js.map +1 -0
  5. package/dist/content-api-constants.mjs +39 -0
  6. package/dist/content-api-constants.mjs.map +1 -0
  7. package/dist/content-api-route-params.d.ts +17 -0
  8. package/dist/content-api-route-params.d.ts.map +1 -0
  9. package/dist/content-api-route-params.js +21 -0
  10. package/dist/content-api-route-params.js.map +1 -0
  11. package/dist/content-api-route-params.mjs +18 -0
  12. package/dist/content-api-route-params.mjs.map +1 -0
  13. package/dist/content-types.d.ts +10 -2
  14. package/dist/content-types.d.ts.map +1 -1
  15. package/dist/content-types.js +22 -2
  16. package/dist/content-types.js.map +1 -1
  17. package/dist/content-types.mjs +19 -3
  18. package/dist/content-types.mjs.map +1 -1
  19. package/dist/convert-query-params.d.ts +5 -0
  20. package/dist/convert-query-params.d.ts.map +1 -1
  21. package/dist/convert-query-params.js +5 -4
  22. package/dist/convert-query-params.js.map +1 -1
  23. package/dist/convert-query-params.mjs +5 -4
  24. package/dist/convert-query-params.mjs.map +1 -1
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +9 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +2 -0
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/sanitize/index.d.ts +15 -1
  32. package/dist/sanitize/index.d.ts.map +1 -1
  33. package/dist/sanitize/index.js +73 -3
  34. package/dist/sanitize/index.js.map +1 -1
  35. package/dist/sanitize/index.mjs +74 -4
  36. package/dist/sanitize/index.mjs.map +1 -1
  37. package/dist/sanitize/visitors/index.d.ts +1 -0
  38. package/dist/sanitize/visitors/index.d.ts.map +1 -1
  39. package/dist/sanitize/visitors/index.js +2 -0
  40. package/dist/sanitize/visitors/index.js.map +1 -1
  41. package/dist/sanitize/visitors/index.mjs +1 -0
  42. package/dist/sanitize/visitors/index.mjs.map +1 -1
  43. package/dist/sanitize/visitors/remove-unrecognized-fields.d.ts +4 -0
  44. package/dist/sanitize/visitors/remove-unrecognized-fields.d.ts.map +1 -0
  45. package/dist/sanitize/visitors/remove-unrecognized-fields.js +43 -0
  46. package/dist/sanitize/visitors/remove-unrecognized-fields.js.map +1 -0
  47. package/dist/sanitize/visitors/remove-unrecognized-fields.mjs +41 -0
  48. package/dist/sanitize/visitors/remove-unrecognized-fields.mjs.map +1 -0
  49. package/dist/traverse-entity.d.ts +4 -0
  50. package/dist/traverse-entity.d.ts.map +1 -1
  51. package/dist/traverse-entity.js +13 -7
  52. package/dist/traverse-entity.js.map +1 -1
  53. package/dist/traverse-entity.mjs +13 -7
  54. package/dist/traverse-entity.mjs.map +1 -1
  55. package/dist/validate/index.d.ts +14 -1
  56. package/dist/validate/index.d.ts.map +1 -1
  57. package/dist/validate/index.js +83 -7
  58. package/dist/validate/index.js.map +1 -1
  59. package/dist/validate/index.mjs +83 -7
  60. package/dist/validate/index.mjs.map +1 -1
  61. package/dist/validate/validators.js +0 -1
  62. package/dist/validate/validators.js.map +1 -1
  63. package/dist/validate/validators.mjs +0 -1
  64. package/dist/validate/validators.mjs.map +1 -1
  65. package/dist/validate/visitors/throw-unrecognized-fields.d.ts.map +1 -1
  66. package/dist/validate/visitors/throw-unrecognized-fields.js +16 -32
  67. package/dist/validate/visitors/throw-unrecognized-fields.js.map +1 -1
  68. package/dist/validate/visitors/throw-unrecognized-fields.mjs +17 -33
  69. package/dist/validate/visitors/throw-unrecognized-fields.mjs.map +1 -1
  70. package/dist/zod-schema.d.ts +17 -0
  71. package/dist/zod-schema.d.ts.map +1 -0
  72. package/package.json +5 -5
@@ -2,6 +2,8 @@
2
2
 
3
3
  var fp = require('lodash/fp');
4
4
  var contentTypes = require('../content-types.js');
5
+ var contentApiConstants = require('../content-api-constants.js');
6
+ var contentApiRouteParams = require('../content-api-route-params.js');
5
7
  var async = require('../async.js');
6
8
  var index = require('./visitors/index.js');
7
9
  var sanitizers = require('./sanitizers.js');
@@ -10,20 +12,24 @@ var queryFilters = require('../traverse/query-filters.js');
10
12
  var querySort = require('../traverse/query-sort.js');
11
13
  var queryPopulate = require('../traverse/query-populate.js');
12
14
  require('../traverse/query-fields.js');
15
+ var removeUnrecognizedFields = require('./visitors/remove-unrecognized-fields.js');
13
16
  var removeRestrictedFields = require('./visitors/remove-restricted-fields.js');
14
17
  var removeRestrictedRelations = require('./visitors/remove-restricted-relations.js');
15
18
 
16
19
  const createAPISanitizers = (opts)=>{
17
20
  const { getModel } = opts;
18
- const sanitizeInput = (data, schema, { auth } = {})=>{
21
+ const sanitizeInput = (data, schema, { auth, strictParams = false, route } = {})=>{
19
22
  if (!schema) {
20
23
  throw new Error('Missing schema in sanitizeInput');
21
24
  }
22
25
  if (fp.isArray(data)) {
23
26
  return Promise.all(data.map((entry)=>sanitizeInput(entry, schema, {
24
- auth
27
+ auth,
28
+ strictParams,
29
+ route
25
30
  })));
26
31
  }
32
+ const allowedExtraRootKeys = contentApiRouteParams.getExtraRootKeysFromRouteBody(route);
27
33
  const nonWritableAttributes = contentTypes.getNonWritableAttributes(schema);
28
34
  const transforms = [
29
35
  // Remove first level ID in inputs
@@ -35,6 +41,14 @@ const createAPISanitizers = (opts)=>{
35
41
  getModel
36
42
  })
37
43
  ];
44
+ if (strictParams) {
45
+ // Remove unrecognized fields (allowedExtraRootKeys = registered input param keys)
46
+ transforms.push(traverseEntity(removeUnrecognizedFields, {
47
+ schema,
48
+ getModel,
49
+ allowedExtraRootKeys
50
+ }));
51
+ }
38
52
  if (auth) {
39
53
  // Remove restricted relations
40
54
  transforms.push(traverseEntity(removeRestrictedRelations(auth), {
@@ -44,6 +58,38 @@ const createAPISanitizers = (opts)=>{
44
58
  }
45
59
  // Apply sanitizers from registry if exists
46
60
  opts?.sanitizers?.input?.forEach((sanitizer)=>transforms.push(sanitizer(schema)));
61
+ /**
62
+ * For each extra root key from the route's body schema present in data, run Zod safeParse.
63
+ * If parsing fails, the key is removed from the output.
64
+ *
65
+ * Content-api sends the document payload as body.data; the controller calls sanitizeInput(body.data, ctx),
66
+ * so the input we receive here is the inner payload (keys like "relatedMedia", "name"), not the full body.
67
+ * The route's body schema is z.object({ data: ... }), so its shape includes "data". We skip "data" because
68
+ * the main document payload is already sanitized above by traverseEntity (removeUnrecognizedFields, etc.);
69
+ * relation ops (connect/disconnect/set) are handled there, not by the route's Zod schema. We only run
70
+ * Zod here for truly extra root keys added via addInputParams (e.g. clientMutationId).
71
+ */ const routeBodySanitizeTransform = async (data)=>{
72
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return data;
73
+ const obj = data;
74
+ const bodySchema = route?.request?.body?.['application/json'];
75
+ if (bodySchema && typeof bodySchema === 'object' && 'shape' in bodySchema) {
76
+ const shape = bodySchema.shape;
77
+ for (const key of Object.keys(shape)){
78
+ if (key === 'data' || !(key in obj)) continue;
79
+ const zodSchema = shape[key];
80
+ if (zodSchema && typeof zodSchema.safeParse === 'function') {
81
+ const result = zodSchema.safeParse(obj[key]);
82
+ if (result.success) {
83
+ obj[key] = result.data;
84
+ } else {
85
+ delete obj[key];
86
+ }
87
+ }
88
+ }
89
+ }
90
+ return data;
91
+ };
92
+ transforms.push(routeBodySanitizeTransform);
47
93
  return async.pipe(...transforms)(data);
48
94
  };
49
95
  const sanitizeOutput = async (data, schema, { auth } = {})=>{
@@ -75,7 +121,7 @@ const createAPISanitizers = (opts)=>{
75
121
  opts?.sanitizers?.output?.forEach((sanitizer)=>transforms.push(sanitizer(schema)));
76
122
  return async.pipe(...transforms)(data);
77
123
  };
78
- const sanitizeQuery = async (query, schema, { auth } = {})=>{
124
+ const sanitizeQuery = async (query, schema, { auth, strictParams = false, route } = {})=>{
79
125
  if (!schema) {
80
126
  throw new Error('Missing schema in sanitizeQuery');
81
127
  }
@@ -105,6 +151,30 @@ const createAPISanitizers = (opts)=>{
105
151
  populate: await sanitizePopulate(populate, schema)
106
152
  });
107
153
  }
154
+ const extraQueryKeys = contentApiRouteParams.getExtraQueryKeysFromRoute(route);
155
+ const routeQuerySchema = route?.request?.query;
156
+ if (routeQuerySchema) {
157
+ for (const key of extraQueryKeys){
158
+ if (key in query) {
159
+ const zodSchema = routeQuerySchema[key];
160
+ if (zodSchema && typeof zodSchema.safeParse === 'function') {
161
+ const result = zodSchema.safeParse(query[key]);
162
+ if (result.success) {
163
+ sanitizedQuery[key] = result.data;
164
+ } else {
165
+ delete sanitizedQuery[key];
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ if (strictParams) {
172
+ const allowedKeys = [
173
+ ...contentApiConstants.ALLOWED_QUERY_PARAM_KEYS,
174
+ ...extraQueryKeys
175
+ ];
176
+ return fp.pick(allowedKeys, sanitizedQuery);
177
+ }
108
178
  return sanitizedQuery;
109
179
  };
110
180
  const sanitizeFilters = (filters, schema, { auth } = {})=>{
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/sanitize/index.ts"],"sourcesContent":["import { CurriedFunction1 } from 'lodash';\nimport { isArray, cloneDeep, omit } from 'lodash/fp';\n\nimport { constants, getNonWritableAttributes } from '../content-types';\nimport { pipe as pipeAsync } from '../async';\n\nimport * as visitors from './visitors';\nimport * as sanitizers from './sanitizers';\nimport traverseEntity from '../traverse-entity';\n\nimport { traverseQueryFilters, traverseQuerySort, traverseQueryPopulate } from '../traverse';\nimport type { Model, Data } from '../types';\n\nexport interface Options {\n auth?: unknown;\n}\n\nexport interface Sanitizer {\n (schema: Model): CurriedFunction1<Data, Promise<Data>>;\n}\nexport interface SanitizeFunc {\n (data: unknown, schema: Model, options?: Options): Promise<unknown>;\n}\n\nexport interface APIOptions {\n sanitizers?: Sanitizers;\n getModel: (model: string) => Model;\n}\n\nexport interface Sanitizers {\n input?: Sanitizer[];\n output?: Sanitizer[];\n}\n\nconst createAPISanitizers = (opts: APIOptions) => {\n const { getModel } = opts;\n\n const sanitizeInput: SanitizeFunc = (data: unknown, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeInput');\n }\n if (isArray(data)) {\n return Promise.all(data.map((entry) => sanitizeInput(entry, schema, { auth })));\n }\n\n const nonWritableAttributes = getNonWritableAttributes(schema);\n\n const transforms = [\n // Remove first level ID in inputs\n omit(constants.ID_ATTRIBUTE),\n omit(constants.DOC_ID_ATTRIBUTE),\n // Remove non-writable attributes\n traverseEntity(visitors.removeRestrictedFields(nonWritableAttributes), { schema, getModel }),\n ];\n\n if (auth) {\n // Remove restricted relations\n transforms.push(\n traverseEntity(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n // Apply sanitizers from registry if exists\n opts?.sanitizers?.input?.forEach((sanitizer: Sanitizer) => transforms.push(sanitizer(schema)));\n\n return pipeAsync(...transforms)(data as Data);\n };\n\n const sanitizeOutput: SanitizeFunc = async (data, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeOutput');\n }\n if (isArray(data)) {\n const res = new Array(data.length);\n for (let i = 0; i < data.length; i += 1) {\n res[i] = await sanitizeOutput(data[i], schema, { auth });\n }\n return res;\n }\n\n const transforms = [\n (data: Data) => sanitizers.defaultSanitizeOutput({ schema, getModel }, data),\n ];\n\n if (auth) {\n transforms.push(\n traverseEntity(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n // Apply sanitizers from registry if exists\n opts?.sanitizers?.output?.forEach((sanitizer: Sanitizer) => transforms.push(sanitizer(schema)));\n\n return pipeAsync(...transforms)(data as Data);\n };\n\n const sanitizeQuery = async (\n query: Record<string, unknown>,\n schema: Model,\n { auth }: Options = {}\n ) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeQuery');\n }\n const { filters, sort, fields, populate } = query;\n\n const sanitizedQuery = cloneDeep(query);\n\n if (filters) {\n Object.assign(sanitizedQuery, { filters: await sanitizeFilters(filters, schema, { auth }) });\n }\n\n if (sort) {\n Object.assign(sanitizedQuery, { sort: await sanitizeSort(sort, schema, { auth }) });\n }\n\n if (fields) {\n Object.assign(sanitizedQuery, { fields: await sanitizeFields(fields, schema) });\n }\n\n if (populate) {\n Object.assign(sanitizedQuery, { populate: await sanitizePopulate(populate, schema) });\n }\n\n return sanitizedQuery;\n };\n\n const sanitizeFilters: SanitizeFunc = (filters, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeFilters');\n }\n if (isArray(filters)) {\n return Promise.all(filters.map((filter) => sanitizeFilters(filter, schema, { auth })));\n }\n\n const transforms = [sanitizers.defaultSanitizeFilters({ schema, getModel })];\n\n if (auth) {\n transforms.push(\n traverseQueryFilters(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n return pipeAsync(...transforms)(filters);\n };\n\n const sanitizeSort: SanitizeFunc = (sort, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeSort');\n }\n const transforms = [sanitizers.defaultSanitizeSort({ schema, getModel })];\n\n if (auth) {\n transforms.push(\n traverseQuerySort(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n return pipeAsync(...transforms)(sort);\n };\n\n const sanitizeFields: SanitizeFunc = (fields, schema: Model) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeFields');\n }\n const transforms = [sanitizers.defaultSanitizeFields({ schema, getModel })];\n\n return pipeAsync(...transforms)(fields);\n };\n\n const sanitizePopulate: SanitizeFunc = (populate, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizePopulate');\n }\n const transforms = [sanitizers.defaultSanitizePopulate({ schema, getModel })];\n\n if (auth) {\n transforms.push(\n traverseQueryPopulate(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n return pipeAsync(...transforms)(populate);\n };\n\n return {\n input: sanitizeInput,\n output: sanitizeOutput,\n query: sanitizeQuery,\n filters: sanitizeFilters,\n sort: sanitizeSort,\n fields: sanitizeFields,\n populate: sanitizePopulate,\n };\n};\n\nexport { createAPISanitizers, sanitizers, visitors };\n\nexport type APISanitiers = ReturnType<typeof createAPISanitizers>;\n"],"names":["createAPISanitizers","opts","getModel","sanitizeInput","data","schema","auth","Error","isArray","Promise","all","map","entry","nonWritableAttributes","getNonWritableAttributes","transforms","omit","constants","ID_ATTRIBUTE","DOC_ID_ATTRIBUTE","traverseEntity","visitors","push","sanitizers","input","forEach","sanitizer","pipeAsync","sanitizeOutput","res","Array","length","i","output","sanitizeQuery","query","filters","sort","fields","populate","sanitizedQuery","cloneDeep","Object","assign","sanitizeFilters","sanitizeSort","sanitizeFields","sanitizePopulate","filter","traverseQueryFilters","traverseQuerySort","traverseQueryPopulate"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/sanitize/index.ts"],"sourcesContent":["import { CurriedFunction1 } from 'lodash';\nimport { isArray, cloneDeep, omit, pick } from 'lodash/fp';\nimport type { z } from 'zod/v4';\n\nimport { constants, getNonWritableAttributes } from '../content-types';\nimport { ALLOWED_QUERY_PARAM_KEYS } from '../content-api-constants';\nimport {\n type RouteLike,\n getExtraQueryKeysFromRoute,\n getExtraRootKeysFromRouteBody,\n} from '../content-api-route-params';\nimport { pipe as pipeAsync } from '../async';\n\nimport * as visitors from './visitors';\nimport * as sanitizers from './sanitizers';\nimport traverseEntity from '../traverse-entity';\n\nimport { traverseQueryFilters, traverseQuerySort, traverseQueryPopulate } from '../traverse';\nimport type { Model, Data } from '../types';\n\nexport interface Options {\n auth?: unknown;\n /**\n * If true, removes fields that are not declared in the schema (input) or keeps only allowed query param keys (query).\n * Defaults to false for backward compatibility.\n * TODO: In Strapi 6, strictParams will default to true (and may be removed as an option)\n */\n strictParams?: boolean;\n /**\n * When set, extra query/input params are derived from the route's request schema (and validated/sanitized with Zod).\n * When absent, no extra params are allowed in strict mode.\n */\n route?: RouteLike;\n}\n\nexport interface Sanitizer {\n (schema: Model): CurriedFunction1<Data, Promise<Data>>;\n}\nexport interface SanitizeFunc {\n (data: unknown, schema: Model, options?: Options): Promise<unknown>;\n}\n\nexport type SanitizeQueryParamHandler = (\n value: unknown,\n schema: Model,\n options?: Options\n) => Promise<unknown>;\n\nexport type SanitizeBodyParamHandler = (\n value: unknown,\n schema: Model,\n options?: Options\n) => Promise<unknown>;\n\nexport interface APIOptions {\n sanitizers?: Sanitizers;\n getModel: (model: string) => Model;\n}\n\nexport interface Sanitizers {\n input?: Sanitizer[];\n output?: Sanitizer[];\n}\n\nconst createAPISanitizers = (opts: APIOptions) => {\n const { getModel } = opts;\n\n const sanitizeInput: SanitizeFunc = (\n data: unknown,\n schema: Model,\n { auth, strictParams = false, route } = {}\n ) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeInput');\n }\n if (isArray(data)) {\n return Promise.all(\n data.map((entry) => sanitizeInput(entry, schema, { auth, strictParams, route }))\n );\n }\n\n const allowedExtraRootKeys = getExtraRootKeysFromRouteBody(route);\n\n const nonWritableAttributes = getNonWritableAttributes(schema);\n\n const transforms = [\n // Remove first level ID in inputs\n omit(constants.ID_ATTRIBUTE),\n omit(constants.DOC_ID_ATTRIBUTE),\n // Remove non-writable attributes\n traverseEntity(visitors.removeRestrictedFields(nonWritableAttributes), { schema, getModel }),\n ];\n\n if (strictParams) {\n // Remove unrecognized fields (allowedExtraRootKeys = registered input param keys)\n transforms.push(\n traverseEntity(visitors.removeUnrecognizedFields, {\n schema,\n getModel,\n allowedExtraRootKeys,\n })\n );\n }\n\n if (auth) {\n // Remove restricted relations\n transforms.push(\n traverseEntity(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n // Apply sanitizers from registry if exists\n opts?.sanitizers?.input?.forEach((sanitizer: Sanitizer) => transforms.push(sanitizer(schema)));\n\n /**\n * For each extra root key from the route's body schema present in data, run Zod safeParse.\n * If parsing fails, the key is removed from the output.\n *\n * Content-api sends the document payload as body.data; the controller calls sanitizeInput(body.data, ctx),\n * so the input we receive here is the inner payload (keys like \"relatedMedia\", \"name\"), not the full body.\n * The route's body schema is z.object({ data: ... }), so its shape includes \"data\". We skip \"data\" because\n * the main document payload is already sanitized above by traverseEntity (removeUnrecognizedFields, etc.);\n * relation ops (connect/disconnect/set) are handled there, not by the route's Zod schema. We only run\n * Zod here for truly extra root keys added via addInputParams (e.g. clientMutationId).\n */\n const routeBodySanitizeTransform = async (data: Data): Promise<Data> => {\n if (!data || typeof data !== 'object' || Array.isArray(data)) return data;\n const obj = data as Record<string, unknown>;\n const bodySchema = route?.request?.body?.['application/json'];\n if (bodySchema && typeof bodySchema === 'object' && 'shape' in bodySchema) {\n const shape = (bodySchema as { shape: Record<string, z.ZodTypeAny> }).shape;\n for (const key of Object.keys(shape)) {\n if (key === 'data' || !(key in obj)) continue;\n const zodSchema = shape[key];\n if (zodSchema && typeof (zodSchema as z.ZodTypeAny).safeParse === 'function') {\n const result = (zodSchema as z.ZodTypeAny).safeParse(obj[key]);\n if (result.success) {\n obj[key] = result.data;\n } else {\n delete obj[key];\n }\n }\n }\n }\n return data;\n };\n (transforms as Array<(data: Data) => Data | Promise<Data>>).push(routeBodySanitizeTransform);\n\n return pipeAsync(...transforms)(data as Data);\n };\n\n const sanitizeOutput: SanitizeFunc = async (data, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeOutput');\n }\n if (isArray(data)) {\n const res = new Array(data.length);\n for (let i = 0; i < data.length; i += 1) {\n res[i] = await sanitizeOutput(data[i], schema, { auth });\n }\n return res;\n }\n\n const transforms = [\n (data: Data) => sanitizers.defaultSanitizeOutput({ schema, getModel }, data),\n ];\n\n if (auth) {\n transforms.push(\n traverseEntity(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n // Apply sanitizers from registry if exists\n opts?.sanitizers?.output?.forEach((sanitizer: Sanitizer) => transforms.push(sanitizer(schema)));\n\n return pipeAsync(...transforms)(data as Data);\n };\n\n const sanitizeQuery = async (\n query: Record<string, unknown>,\n schema: Model,\n { auth, strictParams = false, route }: Options = {}\n ) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeQuery');\n }\n const { filters, sort, fields, populate } = query;\n\n const sanitizedQuery = cloneDeep(query);\n\n if (filters) {\n Object.assign(sanitizedQuery, { filters: await sanitizeFilters(filters, schema, { auth }) });\n }\n\n if (sort) {\n Object.assign(sanitizedQuery, { sort: await sanitizeSort(sort, schema, { auth }) });\n }\n\n if (fields) {\n Object.assign(sanitizedQuery, { fields: await sanitizeFields(fields, schema) });\n }\n\n if (populate) {\n Object.assign(sanitizedQuery, { populate: await sanitizePopulate(populate, schema) });\n }\n\n const extraQueryKeys = getExtraQueryKeysFromRoute(route);\n const routeQuerySchema = route?.request?.query;\n if (routeQuerySchema) {\n for (const key of extraQueryKeys) {\n if (key in query) {\n const zodSchema = routeQuerySchema[key];\n if (zodSchema && typeof (zodSchema as z.ZodTypeAny).safeParse === 'function') {\n const result = (zodSchema as z.ZodTypeAny).safeParse(query[key]);\n if (result.success) {\n sanitizedQuery[key] = result.data;\n } else {\n delete sanitizedQuery[key];\n }\n }\n }\n }\n }\n\n if (strictParams) {\n const allowedKeys = [...ALLOWED_QUERY_PARAM_KEYS, ...extraQueryKeys];\n return pick(allowedKeys, sanitizedQuery) as Record<string, unknown>;\n }\n\n return sanitizedQuery;\n };\n\n const sanitizeFilters: SanitizeFunc = (filters, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeFilters');\n }\n if (isArray(filters)) {\n return Promise.all(filters.map((filter) => sanitizeFilters(filter, schema, { auth })));\n }\n\n const transforms = [sanitizers.defaultSanitizeFilters({ schema, getModel })];\n\n if (auth) {\n transforms.push(\n traverseQueryFilters(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n return pipeAsync(...transforms)(filters);\n };\n\n const sanitizeSort: SanitizeFunc = (sort, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeSort');\n }\n const transforms = [sanitizers.defaultSanitizeSort({ schema, getModel })];\n\n if (auth) {\n transforms.push(\n traverseQuerySort(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n return pipeAsync(...transforms)(sort);\n };\n\n const sanitizeFields: SanitizeFunc = (fields, schema: Model) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeFields');\n }\n const transforms = [sanitizers.defaultSanitizeFields({ schema, getModel })];\n\n return pipeAsync(...transforms)(fields);\n };\n\n const sanitizePopulate: SanitizeFunc = (populate, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizePopulate');\n }\n const transforms = [sanitizers.defaultSanitizePopulate({ schema, getModel })];\n\n if (auth) {\n transforms.push(\n traverseQueryPopulate(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n return pipeAsync(...transforms)(populate);\n };\n\n return {\n input: sanitizeInput,\n output: sanitizeOutput,\n query: sanitizeQuery,\n filters: sanitizeFilters,\n sort: sanitizeSort,\n fields: sanitizeFields,\n populate: sanitizePopulate,\n };\n};\n\nexport { createAPISanitizers, sanitizers, visitors };\n\nexport type APISanitiers = ReturnType<typeof createAPISanitizers>;\n"],"names":["createAPISanitizers","opts","getModel","sanitizeInput","data","schema","auth","strictParams","route","Error","isArray","Promise","all","map","entry","allowedExtraRootKeys","getExtraRootKeysFromRouteBody","nonWritableAttributes","getNonWritableAttributes","transforms","omit","constants","ID_ATTRIBUTE","DOC_ID_ATTRIBUTE","traverseEntity","visitors","push","sanitizers","input","forEach","sanitizer","routeBodySanitizeTransform","Array","obj","bodySchema","request","body","shape","key","Object","keys","zodSchema","safeParse","result","success","pipeAsync","sanitizeOutput","res","length","i","output","sanitizeQuery","query","filters","sort","fields","populate","sanitizedQuery","cloneDeep","assign","sanitizeFilters","sanitizeSort","sanitizeFields","sanitizePopulate","extraQueryKeys","getExtraQueryKeysFromRoute","routeQuerySchema","allowedKeys","ALLOWED_QUERY_PARAM_KEYS","pick","filter","traverseQueryFilters","traverseQuerySort","traverseQueryPopulate"],"mappings":"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"}
@@ -1,5 +1,7 @@
1
- import { isArray, omit, cloneDeep } from 'lodash/fp';
1
+ import { isArray, omit, cloneDeep, pick } from 'lodash/fp';
2
2
  import { getNonWritableAttributes, constants } from '../content-types.mjs';
3
+ import { ALLOWED_QUERY_PARAM_KEYS } from '../content-api-constants.mjs';
4
+ import { getExtraRootKeysFromRouteBody, getExtraQueryKeysFromRoute } from '../content-api-route-params.mjs';
3
5
  import { pipe } from '../async.mjs';
4
6
  import * as index from './visitors/index.mjs';
5
7
  export { index as visitors };
@@ -11,20 +13,24 @@ import traverseQueryFilters from '../traverse/query-filters.mjs';
11
13
  import traverseQuerySort from '../traverse/query-sort.mjs';
12
14
  import traverseQueryPopulate from '../traverse/query-populate.mjs';
13
15
  import '../traverse/query-fields.mjs';
16
+ import removeUnrecognizedFields from './visitors/remove-unrecognized-fields.mjs';
14
17
  import removeRestrictedFields from './visitors/remove-restricted-fields.mjs';
15
18
  import removeRestrictedRelations from './visitors/remove-restricted-relations.mjs';
16
19
 
17
20
  const createAPISanitizers = (opts)=>{
18
21
  const { getModel } = opts;
19
- const sanitizeInput = (data, schema, { auth } = {})=>{
22
+ const sanitizeInput = (data, schema, { auth, strictParams = false, route } = {})=>{
20
23
  if (!schema) {
21
24
  throw new Error('Missing schema in sanitizeInput');
22
25
  }
23
26
  if (isArray(data)) {
24
27
  return Promise.all(data.map((entry)=>sanitizeInput(entry, schema, {
25
- auth
28
+ auth,
29
+ strictParams,
30
+ route
26
31
  })));
27
32
  }
33
+ const allowedExtraRootKeys = getExtraRootKeysFromRouteBody(route);
28
34
  const nonWritableAttributes = getNonWritableAttributes(schema);
29
35
  const transforms = [
30
36
  // Remove first level ID in inputs
@@ -36,6 +42,14 @@ const createAPISanitizers = (opts)=>{
36
42
  getModel
37
43
  })
38
44
  ];
45
+ if (strictParams) {
46
+ // Remove unrecognized fields (allowedExtraRootKeys = registered input param keys)
47
+ transforms.push(traverseEntity(removeUnrecognizedFields, {
48
+ schema,
49
+ getModel,
50
+ allowedExtraRootKeys
51
+ }));
52
+ }
39
53
  if (auth) {
40
54
  // Remove restricted relations
41
55
  transforms.push(traverseEntity(removeRestrictedRelations(auth), {
@@ -45,6 +59,38 @@ const createAPISanitizers = (opts)=>{
45
59
  }
46
60
  // Apply sanitizers from registry if exists
47
61
  opts?.sanitizers?.input?.forEach((sanitizer)=>transforms.push(sanitizer(schema)));
62
+ /**
63
+ * For each extra root key from the route's body schema present in data, run Zod safeParse.
64
+ * If parsing fails, the key is removed from the output.
65
+ *
66
+ * Content-api sends the document payload as body.data; the controller calls sanitizeInput(body.data, ctx),
67
+ * so the input we receive here is the inner payload (keys like "relatedMedia", "name"), not the full body.
68
+ * The route's body schema is z.object({ data: ... }), so its shape includes "data". We skip "data" because
69
+ * the main document payload is already sanitized above by traverseEntity (removeUnrecognizedFields, etc.);
70
+ * relation ops (connect/disconnect/set) are handled there, not by the route's Zod schema. We only run
71
+ * Zod here for truly extra root keys added via addInputParams (e.g. clientMutationId).
72
+ */ const routeBodySanitizeTransform = async (data)=>{
73
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return data;
74
+ const obj = data;
75
+ const bodySchema = route?.request?.body?.['application/json'];
76
+ if (bodySchema && typeof bodySchema === 'object' && 'shape' in bodySchema) {
77
+ const shape = bodySchema.shape;
78
+ for (const key of Object.keys(shape)){
79
+ if (key === 'data' || !(key in obj)) continue;
80
+ const zodSchema = shape[key];
81
+ if (zodSchema && typeof zodSchema.safeParse === 'function') {
82
+ const result = zodSchema.safeParse(obj[key]);
83
+ if (result.success) {
84
+ obj[key] = result.data;
85
+ } else {
86
+ delete obj[key];
87
+ }
88
+ }
89
+ }
90
+ }
91
+ return data;
92
+ };
93
+ transforms.push(routeBodySanitizeTransform);
48
94
  return pipe(...transforms)(data);
49
95
  };
50
96
  const sanitizeOutput = async (data, schema, { auth } = {})=>{
@@ -76,7 +122,7 @@ const createAPISanitizers = (opts)=>{
76
122
  opts?.sanitizers?.output?.forEach((sanitizer)=>transforms.push(sanitizer(schema)));
77
123
  return pipe(...transforms)(data);
78
124
  };
79
- const sanitizeQuery = async (query, schema, { auth } = {})=>{
125
+ const sanitizeQuery = async (query, schema, { auth, strictParams = false, route } = {})=>{
80
126
  if (!schema) {
81
127
  throw new Error('Missing schema in sanitizeQuery');
82
128
  }
@@ -106,6 +152,30 @@ const createAPISanitizers = (opts)=>{
106
152
  populate: await sanitizePopulate(populate, schema)
107
153
  });
108
154
  }
155
+ const extraQueryKeys = getExtraQueryKeysFromRoute(route);
156
+ const routeQuerySchema = route?.request?.query;
157
+ if (routeQuerySchema) {
158
+ for (const key of extraQueryKeys){
159
+ if (key in query) {
160
+ const zodSchema = routeQuerySchema[key];
161
+ if (zodSchema && typeof zodSchema.safeParse === 'function') {
162
+ const result = zodSchema.safeParse(query[key]);
163
+ if (result.success) {
164
+ sanitizedQuery[key] = result.data;
165
+ } else {
166
+ delete sanitizedQuery[key];
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ if (strictParams) {
173
+ const allowedKeys = [
174
+ ...ALLOWED_QUERY_PARAM_KEYS,
175
+ ...extraQueryKeys
176
+ ];
177
+ return pick(allowedKeys, sanitizedQuery);
178
+ }
109
179
  return sanitizedQuery;
110
180
  };
111
181
  const sanitizeFilters = (filters, schema, { auth } = {})=>{
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../src/sanitize/index.ts"],"sourcesContent":["import { CurriedFunction1 } from 'lodash';\nimport { isArray, cloneDeep, omit } from 'lodash/fp';\n\nimport { constants, getNonWritableAttributes } from '../content-types';\nimport { pipe as pipeAsync } from '../async';\n\nimport * as visitors from './visitors';\nimport * as sanitizers from './sanitizers';\nimport traverseEntity from '../traverse-entity';\n\nimport { traverseQueryFilters, traverseQuerySort, traverseQueryPopulate } from '../traverse';\nimport type { Model, Data } from '../types';\n\nexport interface Options {\n auth?: unknown;\n}\n\nexport interface Sanitizer {\n (schema: Model): CurriedFunction1<Data, Promise<Data>>;\n}\nexport interface SanitizeFunc {\n (data: unknown, schema: Model, options?: Options): Promise<unknown>;\n}\n\nexport interface APIOptions {\n sanitizers?: Sanitizers;\n getModel: (model: string) => Model;\n}\n\nexport interface Sanitizers {\n input?: Sanitizer[];\n output?: Sanitizer[];\n}\n\nconst createAPISanitizers = (opts: APIOptions) => {\n const { getModel } = opts;\n\n const sanitizeInput: SanitizeFunc = (data: unknown, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeInput');\n }\n if (isArray(data)) {\n return Promise.all(data.map((entry) => sanitizeInput(entry, schema, { auth })));\n }\n\n const nonWritableAttributes = getNonWritableAttributes(schema);\n\n const transforms = [\n // Remove first level ID in inputs\n omit(constants.ID_ATTRIBUTE),\n omit(constants.DOC_ID_ATTRIBUTE),\n // Remove non-writable attributes\n traverseEntity(visitors.removeRestrictedFields(nonWritableAttributes), { schema, getModel }),\n ];\n\n if (auth) {\n // Remove restricted relations\n transforms.push(\n traverseEntity(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n // Apply sanitizers from registry if exists\n opts?.sanitizers?.input?.forEach((sanitizer: Sanitizer) => transforms.push(sanitizer(schema)));\n\n return pipeAsync(...transforms)(data as Data);\n };\n\n const sanitizeOutput: SanitizeFunc = async (data, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeOutput');\n }\n if (isArray(data)) {\n const res = new Array(data.length);\n for (let i = 0; i < data.length; i += 1) {\n res[i] = await sanitizeOutput(data[i], schema, { auth });\n }\n return res;\n }\n\n const transforms = [\n (data: Data) => sanitizers.defaultSanitizeOutput({ schema, getModel }, data),\n ];\n\n if (auth) {\n transforms.push(\n traverseEntity(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n // Apply sanitizers from registry if exists\n opts?.sanitizers?.output?.forEach((sanitizer: Sanitizer) => transforms.push(sanitizer(schema)));\n\n return pipeAsync(...transforms)(data as Data);\n };\n\n const sanitizeQuery = async (\n query: Record<string, unknown>,\n schema: Model,\n { auth }: Options = {}\n ) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeQuery');\n }\n const { filters, sort, fields, populate } = query;\n\n const sanitizedQuery = cloneDeep(query);\n\n if (filters) {\n Object.assign(sanitizedQuery, { filters: await sanitizeFilters(filters, schema, { auth }) });\n }\n\n if (sort) {\n Object.assign(sanitizedQuery, { sort: await sanitizeSort(sort, schema, { auth }) });\n }\n\n if (fields) {\n Object.assign(sanitizedQuery, { fields: await sanitizeFields(fields, schema) });\n }\n\n if (populate) {\n Object.assign(sanitizedQuery, { populate: await sanitizePopulate(populate, schema) });\n }\n\n return sanitizedQuery;\n };\n\n const sanitizeFilters: SanitizeFunc = (filters, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeFilters');\n }\n if (isArray(filters)) {\n return Promise.all(filters.map((filter) => sanitizeFilters(filter, schema, { auth })));\n }\n\n const transforms = [sanitizers.defaultSanitizeFilters({ schema, getModel })];\n\n if (auth) {\n transforms.push(\n traverseQueryFilters(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n return pipeAsync(...transforms)(filters);\n };\n\n const sanitizeSort: SanitizeFunc = (sort, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeSort');\n }\n const transforms = [sanitizers.defaultSanitizeSort({ schema, getModel })];\n\n if (auth) {\n transforms.push(\n traverseQuerySort(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n return pipeAsync(...transforms)(sort);\n };\n\n const sanitizeFields: SanitizeFunc = (fields, schema: Model) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeFields');\n }\n const transforms = [sanitizers.defaultSanitizeFields({ schema, getModel })];\n\n return pipeAsync(...transforms)(fields);\n };\n\n const sanitizePopulate: SanitizeFunc = (populate, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizePopulate');\n }\n const transforms = [sanitizers.defaultSanitizePopulate({ schema, getModel })];\n\n if (auth) {\n transforms.push(\n traverseQueryPopulate(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n return pipeAsync(...transforms)(populate);\n };\n\n return {\n input: sanitizeInput,\n output: sanitizeOutput,\n query: sanitizeQuery,\n filters: sanitizeFilters,\n sort: sanitizeSort,\n fields: sanitizeFields,\n populate: sanitizePopulate,\n };\n};\n\nexport { createAPISanitizers, sanitizers, visitors };\n\nexport type APISanitiers = ReturnType<typeof createAPISanitizers>;\n"],"names":["createAPISanitizers","opts","getModel","sanitizeInput","data","schema","auth","Error","isArray","Promise","all","map","entry","nonWritableAttributes","getNonWritableAttributes","transforms","omit","constants","ID_ATTRIBUTE","DOC_ID_ATTRIBUTE","traverseEntity","visitors","push","sanitizers","input","forEach","sanitizer","pipeAsync","sanitizeOutput","res","Array","length","i","output","sanitizeQuery","query","filters","sort","fields","populate","sanitizedQuery","cloneDeep","Object","assign","sanitizeFilters","sanitizeSort","sanitizeFields","sanitizePopulate","filter","traverseQueryFilters","traverseQuerySort","traverseQueryPopulate"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../../src/sanitize/index.ts"],"sourcesContent":["import { CurriedFunction1 } from 'lodash';\nimport { isArray, cloneDeep, omit, pick } from 'lodash/fp';\nimport type { z } from 'zod/v4';\n\nimport { constants, getNonWritableAttributes } from '../content-types';\nimport { ALLOWED_QUERY_PARAM_KEYS } from '../content-api-constants';\nimport {\n type RouteLike,\n getExtraQueryKeysFromRoute,\n getExtraRootKeysFromRouteBody,\n} from '../content-api-route-params';\nimport { pipe as pipeAsync } from '../async';\n\nimport * as visitors from './visitors';\nimport * as sanitizers from './sanitizers';\nimport traverseEntity from '../traverse-entity';\n\nimport { traverseQueryFilters, traverseQuerySort, traverseQueryPopulate } from '../traverse';\nimport type { Model, Data } from '../types';\n\nexport interface Options {\n auth?: unknown;\n /**\n * If true, removes fields that are not declared in the schema (input) or keeps only allowed query param keys (query).\n * Defaults to false for backward compatibility.\n * TODO: In Strapi 6, strictParams will default to true (and may be removed as an option)\n */\n strictParams?: boolean;\n /**\n * When set, extra query/input params are derived from the route's request schema (and validated/sanitized with Zod).\n * When absent, no extra params are allowed in strict mode.\n */\n route?: RouteLike;\n}\n\nexport interface Sanitizer {\n (schema: Model): CurriedFunction1<Data, Promise<Data>>;\n}\nexport interface SanitizeFunc {\n (data: unknown, schema: Model, options?: Options): Promise<unknown>;\n}\n\nexport type SanitizeQueryParamHandler = (\n value: unknown,\n schema: Model,\n options?: Options\n) => Promise<unknown>;\n\nexport type SanitizeBodyParamHandler = (\n value: unknown,\n schema: Model,\n options?: Options\n) => Promise<unknown>;\n\nexport interface APIOptions {\n sanitizers?: Sanitizers;\n getModel: (model: string) => Model;\n}\n\nexport interface Sanitizers {\n input?: Sanitizer[];\n output?: Sanitizer[];\n}\n\nconst createAPISanitizers = (opts: APIOptions) => {\n const { getModel } = opts;\n\n const sanitizeInput: SanitizeFunc = (\n data: unknown,\n schema: Model,\n { auth, strictParams = false, route } = {}\n ) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeInput');\n }\n if (isArray(data)) {\n return Promise.all(\n data.map((entry) => sanitizeInput(entry, schema, { auth, strictParams, route }))\n );\n }\n\n const allowedExtraRootKeys = getExtraRootKeysFromRouteBody(route);\n\n const nonWritableAttributes = getNonWritableAttributes(schema);\n\n const transforms = [\n // Remove first level ID in inputs\n omit(constants.ID_ATTRIBUTE),\n omit(constants.DOC_ID_ATTRIBUTE),\n // Remove non-writable attributes\n traverseEntity(visitors.removeRestrictedFields(nonWritableAttributes), { schema, getModel }),\n ];\n\n if (strictParams) {\n // Remove unrecognized fields (allowedExtraRootKeys = registered input param keys)\n transforms.push(\n traverseEntity(visitors.removeUnrecognizedFields, {\n schema,\n getModel,\n allowedExtraRootKeys,\n })\n );\n }\n\n if (auth) {\n // Remove restricted relations\n transforms.push(\n traverseEntity(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n // Apply sanitizers from registry if exists\n opts?.sanitizers?.input?.forEach((sanitizer: Sanitizer) => transforms.push(sanitizer(schema)));\n\n /**\n * For each extra root key from the route's body schema present in data, run Zod safeParse.\n * If parsing fails, the key is removed from the output.\n *\n * Content-api sends the document payload as body.data; the controller calls sanitizeInput(body.data, ctx),\n * so the input we receive here is the inner payload (keys like \"relatedMedia\", \"name\"), not the full body.\n * The route's body schema is z.object({ data: ... }), so its shape includes \"data\". We skip \"data\" because\n * the main document payload is already sanitized above by traverseEntity (removeUnrecognizedFields, etc.);\n * relation ops (connect/disconnect/set) are handled there, not by the route's Zod schema. We only run\n * Zod here for truly extra root keys added via addInputParams (e.g. clientMutationId).\n */\n const routeBodySanitizeTransform = async (data: Data): Promise<Data> => {\n if (!data || typeof data !== 'object' || Array.isArray(data)) return data;\n const obj = data as Record<string, unknown>;\n const bodySchema = route?.request?.body?.['application/json'];\n if (bodySchema && typeof bodySchema === 'object' && 'shape' in bodySchema) {\n const shape = (bodySchema as { shape: Record<string, z.ZodTypeAny> }).shape;\n for (const key of Object.keys(shape)) {\n if (key === 'data' || !(key in obj)) continue;\n const zodSchema = shape[key];\n if (zodSchema && typeof (zodSchema as z.ZodTypeAny).safeParse === 'function') {\n const result = (zodSchema as z.ZodTypeAny).safeParse(obj[key]);\n if (result.success) {\n obj[key] = result.data;\n } else {\n delete obj[key];\n }\n }\n }\n }\n return data;\n };\n (transforms as Array<(data: Data) => Data | Promise<Data>>).push(routeBodySanitizeTransform);\n\n return pipeAsync(...transforms)(data as Data);\n };\n\n const sanitizeOutput: SanitizeFunc = async (data, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeOutput');\n }\n if (isArray(data)) {\n const res = new Array(data.length);\n for (let i = 0; i < data.length; i += 1) {\n res[i] = await sanitizeOutput(data[i], schema, { auth });\n }\n return res;\n }\n\n const transforms = [\n (data: Data) => sanitizers.defaultSanitizeOutput({ schema, getModel }, data),\n ];\n\n if (auth) {\n transforms.push(\n traverseEntity(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n // Apply sanitizers from registry if exists\n opts?.sanitizers?.output?.forEach((sanitizer: Sanitizer) => transforms.push(sanitizer(schema)));\n\n return pipeAsync(...transforms)(data as Data);\n };\n\n const sanitizeQuery = async (\n query: Record<string, unknown>,\n schema: Model,\n { auth, strictParams = false, route }: Options = {}\n ) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeQuery');\n }\n const { filters, sort, fields, populate } = query;\n\n const sanitizedQuery = cloneDeep(query);\n\n if (filters) {\n Object.assign(sanitizedQuery, { filters: await sanitizeFilters(filters, schema, { auth }) });\n }\n\n if (sort) {\n Object.assign(sanitizedQuery, { sort: await sanitizeSort(sort, schema, { auth }) });\n }\n\n if (fields) {\n Object.assign(sanitizedQuery, { fields: await sanitizeFields(fields, schema) });\n }\n\n if (populate) {\n Object.assign(sanitizedQuery, { populate: await sanitizePopulate(populate, schema) });\n }\n\n const extraQueryKeys = getExtraQueryKeysFromRoute(route);\n const routeQuerySchema = route?.request?.query;\n if (routeQuerySchema) {\n for (const key of extraQueryKeys) {\n if (key in query) {\n const zodSchema = routeQuerySchema[key];\n if (zodSchema && typeof (zodSchema as z.ZodTypeAny).safeParse === 'function') {\n const result = (zodSchema as z.ZodTypeAny).safeParse(query[key]);\n if (result.success) {\n sanitizedQuery[key] = result.data;\n } else {\n delete sanitizedQuery[key];\n }\n }\n }\n }\n }\n\n if (strictParams) {\n const allowedKeys = [...ALLOWED_QUERY_PARAM_KEYS, ...extraQueryKeys];\n return pick(allowedKeys, sanitizedQuery) as Record<string, unknown>;\n }\n\n return sanitizedQuery;\n };\n\n const sanitizeFilters: SanitizeFunc = (filters, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeFilters');\n }\n if (isArray(filters)) {\n return Promise.all(filters.map((filter) => sanitizeFilters(filter, schema, { auth })));\n }\n\n const transforms = [sanitizers.defaultSanitizeFilters({ schema, getModel })];\n\n if (auth) {\n transforms.push(\n traverseQueryFilters(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n return pipeAsync(...transforms)(filters);\n };\n\n const sanitizeSort: SanitizeFunc = (sort, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeSort');\n }\n const transforms = [sanitizers.defaultSanitizeSort({ schema, getModel })];\n\n if (auth) {\n transforms.push(\n traverseQuerySort(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n return pipeAsync(...transforms)(sort);\n };\n\n const sanitizeFields: SanitizeFunc = (fields, schema: Model) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizeFields');\n }\n const transforms = [sanitizers.defaultSanitizeFields({ schema, getModel })];\n\n return pipeAsync(...transforms)(fields);\n };\n\n const sanitizePopulate: SanitizeFunc = (populate, schema: Model, { auth } = {}) => {\n if (!schema) {\n throw new Error('Missing schema in sanitizePopulate');\n }\n const transforms = [sanitizers.defaultSanitizePopulate({ schema, getModel })];\n\n if (auth) {\n transforms.push(\n traverseQueryPopulate(visitors.removeRestrictedRelations(auth), { schema, getModel })\n );\n }\n\n return pipeAsync(...transforms)(populate);\n };\n\n return {\n input: sanitizeInput,\n output: sanitizeOutput,\n query: sanitizeQuery,\n filters: sanitizeFilters,\n sort: sanitizeSort,\n fields: sanitizeFields,\n populate: sanitizePopulate,\n };\n};\n\nexport { createAPISanitizers, sanitizers, visitors };\n\nexport type APISanitiers = ReturnType<typeof createAPISanitizers>;\n"],"names":["createAPISanitizers","opts","getModel","sanitizeInput","data","schema","auth","strictParams","route","Error","isArray","Promise","all","map","entry","allowedExtraRootKeys","getExtraRootKeysFromRouteBody","nonWritableAttributes","getNonWritableAttributes","transforms","omit","constants","ID_ATTRIBUTE","DOC_ID_ATTRIBUTE","traverseEntity","visitors","push","sanitizers","input","forEach","sanitizer","routeBodySanitizeTransform","Array","obj","bodySchema","request","body","shape","key","Object","keys","zodSchema","safeParse","result","success","pipeAsync","sanitizeOutput","res","length","i","output","sanitizeQuery","query","filters","sort","fields","populate","sanitizedQuery","cloneDeep","assign","sanitizeFilters","sanitizeSort","sanitizeFields","sanitizePopulate","extraQueryKeys","getExtraQueryKeysFromRoute","routeQuerySchema","allowedKeys","ALLOWED_QUERY_PARAM_KEYS","pick","filter","traverseQueryFilters","traverseQuerySort","traverseQueryPopulate"],"mappings":"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"}
@@ -5,5 +5,6 @@ export { default as removeMorphToRelations } from './remove-morph-to-relations';
5
5
  export { default as removeDynamicZones } from './remove-dynamic-zones';
6
6
  export { default as removeDisallowedFields } from './remove-disallowed-fields';
7
7
  export { default as removeRestrictedFields } from './remove-restricted-fields';
8
+ export { default as removeUnrecognizedFields } from './remove-unrecognized-fields';
8
9
  export { default as expandWildcardPopulate } from './expand-wildcard-populate';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sanitize/visitors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AACrF,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAChF,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sanitize/visitors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AACrF,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAChF,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,4BAA4B,CAAC"}
@@ -7,6 +7,7 @@ var removeMorphToRelations = require('./remove-morph-to-relations.js');
7
7
  var removeDynamicZones = require('./remove-dynamic-zones.js');
8
8
  var removeDisallowedFields = require('./remove-disallowed-fields.js');
9
9
  var removeRestrictedFields = require('./remove-restricted-fields.js');
10
+ var removeUnrecognizedFields = require('./remove-unrecognized-fields.js');
10
11
  var expandWildcardPopulate = require('./expand-wildcard-populate.js');
11
12
 
12
13
 
@@ -18,5 +19,6 @@ exports.removeMorphToRelations = removeMorphToRelations;
18
19
  exports.removeDynamicZones = removeDynamicZones;
19
20
  exports.removeDisallowedFields = removeDisallowedFields;
20
21
  exports.removeRestrictedFields = removeRestrictedFields;
22
+ exports.removeUnrecognizedFields = removeUnrecognizedFields;
21
23
  exports.expandWildcardPopulate = expandWildcardPopulate;
22
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;"}
@@ -5,5 +5,6 @@ export { default as removeMorphToRelations } from './remove-morph-to-relations.m
5
5
  export { default as removeDynamicZones } from './remove-dynamic-zones.mjs';
6
6
  export { default as removeDisallowedFields } from './remove-disallowed-fields.mjs';
7
7
  export { default as removeRestrictedFields } from './remove-restricted-fields.mjs';
8
+ export { default as removeUnrecognizedFields } from './remove-unrecognized-fields.mjs';
8
9
  export { default as expandWildcardPopulate } from './expand-wildcard-populate.mjs';
9
10
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
@@ -0,0 +1,4 @@
1
+ import type { Visitor } from '../../traverse-entity';
2
+ declare const removeUnrecognizedFields: Visitor;
3
+ export default removeUnrecognizedFields;
4
+ //# sourceMappingURL=remove-unrecognized-fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove-unrecognized-fields.d.ts","sourceRoot":"","sources":["../../../src/sanitize/visitors/remove-unrecognized-fields.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,QAAA,MAAM,wBAAwB,EAAE,OAuD/B,CAAC;AAEF,eAAe,wBAAwB,CAAC"}
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ var contentTypes = require('../../content-types.js');
4
+
5
+ const removeUnrecognizedFields = ({ key, attribute, path, schema, parent, allowedExtraRootKeys }, { remove })=>{
6
+ // We only look at properties that are not attributes
7
+ if (attribute) {
8
+ return;
9
+ }
10
+ // At root level (path.attribute === null), only accept id-like fields
11
+ if (path.attribute === null) {
12
+ if (contentTypes.ID_FIELDS.includes(key)) {
13
+ return;
14
+ }
15
+ if (allowedExtraRootKeys?.includes(key)) {
16
+ return;
17
+ }
18
+ remove(key);
19
+ return;
20
+ }
21
+ // allow special morphTo keys
22
+ if (contentTypes.isMorphToRelationalAttribute(parent?.attribute) && contentTypes.MORPH_TO_KEYS.includes(key)) {
23
+ return;
24
+ }
25
+ // allow special dz keys
26
+ if (contentTypes.isComponentSchema(schema) && contentTypes.isDynamicZoneAttribute(parent?.attribute) && contentTypes.DYNAMIC_ZONE_KEYS.includes(key)) {
27
+ return;
28
+ }
29
+ // allow relation operation keys (connect, disconnect, set, options) for relations and media
30
+ if ((contentTypes.isRelationalAttribute(parent?.attribute) || contentTypes.isMediaAttribute(parent?.attribute)) && contentTypes.RELATION_OPERATION_KEYS.includes(key)) {
31
+ return;
32
+ }
33
+ // allow id fields for relations, media, and components
34
+ const canUseID = contentTypes.isRelationalAttribute(parent?.attribute) || contentTypes.isMediaAttribute(parent?.attribute) || contentTypes.isComponentAttribute(parent?.attribute);
35
+ if (canUseID && contentTypes.ID_FIELDS.includes(key)) {
36
+ return;
37
+ }
38
+ // if we couldn't find any reason for it to be here, remove it
39
+ remove(key);
40
+ };
41
+
42
+ module.exports = removeUnrecognizedFields;
43
+ //# sourceMappingURL=remove-unrecognized-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove-unrecognized-fields.js","sources":["../../../src/sanitize/visitors/remove-unrecognized-fields.ts"],"sourcesContent":["import {\n isDynamicZoneAttribute,\n isMorphToRelationalAttribute,\n isRelationalAttribute,\n isComponentSchema,\n isMediaAttribute,\n isComponentAttribute,\n DYNAMIC_ZONE_KEYS,\n ID_FIELDS,\n MORPH_TO_KEYS,\n RELATION_OPERATION_KEYS,\n} from '../../content-types';\nimport type { Visitor } from '../../traverse-entity';\n\nconst removeUnrecognizedFields: Visitor = (\n { key, attribute, path, schema, parent, allowedExtraRootKeys },\n { remove }\n) => {\n // We only look at properties that are not attributes\n if (attribute) {\n return;\n }\n\n // At root level (path.attribute === null), only accept id-like fields\n if (path.attribute === null) {\n if (ID_FIELDS.includes(key)) {\n return;\n }\n if (allowedExtraRootKeys?.includes(key)) {\n return;\n }\n\n remove(key);\n return;\n }\n\n // allow special morphTo keys\n if (isMorphToRelationalAttribute(parent?.attribute) && MORPH_TO_KEYS.includes(key)) {\n return;\n }\n\n // allow special dz keys\n if (\n isComponentSchema(schema) &&\n isDynamicZoneAttribute(parent?.attribute) &&\n DYNAMIC_ZONE_KEYS.includes(key)\n ) {\n return;\n }\n\n // allow relation operation keys (connect, disconnect, set, options) for relations and media\n if (\n (isRelationalAttribute(parent?.attribute) || isMediaAttribute(parent?.attribute)) &&\n RELATION_OPERATION_KEYS.includes(key)\n ) {\n return;\n }\n\n // allow id fields for relations, media, and components\n const canUseID =\n isRelationalAttribute(parent?.attribute) ||\n isMediaAttribute(parent?.attribute) ||\n isComponentAttribute(parent?.attribute);\n if (canUseID && ID_FIELDS.includes(key)) {\n return;\n }\n\n // if we couldn't find any reason for it to be here, remove it\n remove(key);\n};\n\nexport default removeUnrecognizedFields;\n"],"names":["removeUnrecognizedFields","key","attribute","path","schema","parent","allowedExtraRootKeys","remove","ID_FIELDS","includes","isMorphToRelationalAttribute","MORPH_TO_KEYS","isComponentSchema","isDynamicZoneAttribute","DYNAMIC_ZONE_KEYS","isRelationalAttribute","isMediaAttribute","RELATION_OPERATION_KEYS","canUseID","isComponentAttribute"],"mappings":";;;;AAcA,MAAMA,2BAAoC,CACxC,EAAEC,GAAG,EAAEC,SAAS,EAAEC,IAAI,EAAEC,MAAM,EAAEC,MAAM,EAAEC,oBAAoB,EAAE,EAC9D,EAAEC,MAAM,EAAE,GAAA;;AAGV,IAAA,IAAIL,SAAW,EAAA;AACb,QAAA;AACF;;IAGA,IAAIC,IAAAA,CAAKD,SAAS,KAAK,IAAM,EAAA;QAC3B,IAAIM,sBAAAA,CAAUC,QAAQ,CAACR,GAAM,CAAA,EAAA;AAC3B,YAAA;AACF;QACA,IAAIK,oBAAAA,EAAsBG,SAASR,GAAM,CAAA,EAAA;AACvC,YAAA;AACF;QAEAM,MAAON,CAAAA,GAAAA,CAAAA;AACP,QAAA;AACF;;AAGA,IAAA,IAAIS,0CAA6BL,MAAQH,EAAAA,SAAAA,CAAAA,IAAcS,0BAAcF,CAAAA,QAAQ,CAACR,GAAM,CAAA,EAAA;AAClF,QAAA;AACF;;IAGA,IACEW,8BAAAA,CAAkBR,WAClBS,mCAAuBR,CAAAA,MAAAA,EAAQH,cAC/BY,8BAAkBL,CAAAA,QAAQ,CAACR,GAC3B,CAAA,EAAA;AACA,QAAA;AACF;;AAGA,IAAA,IACE,CAACc,kCAAsBV,CAAAA,MAAAA,EAAQH,SAAcc,CAAAA,IAAAA,6BAAAA,CAAiBX,MAAQH,EAAAA,SAAAA,CAAS,KAC/Ee,oCAAAA,CAAwBR,QAAQ,CAACR,GACjC,CAAA,EAAA;AACA,QAAA;AACF;;IAGA,MAAMiB,QAAAA,GACJH,mCAAsBV,MAAQH,EAAAA,SAAAA,CAAAA,IAC9Bc,8BAAiBX,MAAQH,EAAAA,SAAAA,CAAAA,IACzBiB,kCAAqBd,MAAQH,EAAAA,SAAAA,CAAAA;AAC/B,IAAA,IAAIgB,QAAYV,IAAAA,sBAAAA,CAAUC,QAAQ,CAACR,GAAM,CAAA,EAAA;AACvC,QAAA;AACF;;IAGAM,MAAON,CAAAA,GAAAA,CAAAA;AACT;;;;"}
@@ -0,0 +1,41 @@
1
+ import { ID_FIELDS, isMorphToRelationalAttribute, MORPH_TO_KEYS, isComponentSchema, isDynamicZoneAttribute, DYNAMIC_ZONE_KEYS, isRelationalAttribute, isMediaAttribute, RELATION_OPERATION_KEYS, isComponentAttribute } from '../../content-types.mjs';
2
+
3
+ const removeUnrecognizedFields = ({ key, attribute, path, schema, parent, allowedExtraRootKeys }, { remove })=>{
4
+ // We only look at properties that are not attributes
5
+ if (attribute) {
6
+ return;
7
+ }
8
+ // At root level (path.attribute === null), only accept id-like fields
9
+ if (path.attribute === null) {
10
+ if (ID_FIELDS.includes(key)) {
11
+ return;
12
+ }
13
+ if (allowedExtraRootKeys?.includes(key)) {
14
+ return;
15
+ }
16
+ remove(key);
17
+ return;
18
+ }
19
+ // allow special morphTo keys
20
+ if (isMorphToRelationalAttribute(parent?.attribute) && MORPH_TO_KEYS.includes(key)) {
21
+ return;
22
+ }
23
+ // allow special dz keys
24
+ if (isComponentSchema(schema) && isDynamicZoneAttribute(parent?.attribute) && DYNAMIC_ZONE_KEYS.includes(key)) {
25
+ return;
26
+ }
27
+ // allow relation operation keys (connect, disconnect, set, options) for relations and media
28
+ if ((isRelationalAttribute(parent?.attribute) || isMediaAttribute(parent?.attribute)) && RELATION_OPERATION_KEYS.includes(key)) {
29
+ return;
30
+ }
31
+ // allow id fields for relations, media, and components
32
+ const canUseID = isRelationalAttribute(parent?.attribute) || isMediaAttribute(parent?.attribute) || isComponentAttribute(parent?.attribute);
33
+ if (canUseID && ID_FIELDS.includes(key)) {
34
+ return;
35
+ }
36
+ // if we couldn't find any reason for it to be here, remove it
37
+ remove(key);
38
+ };
39
+
40
+ export { removeUnrecognizedFields as default };
41
+ //# sourceMappingURL=remove-unrecognized-fields.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove-unrecognized-fields.mjs","sources":["../../../src/sanitize/visitors/remove-unrecognized-fields.ts"],"sourcesContent":["import {\n isDynamicZoneAttribute,\n isMorphToRelationalAttribute,\n isRelationalAttribute,\n isComponentSchema,\n isMediaAttribute,\n isComponentAttribute,\n DYNAMIC_ZONE_KEYS,\n ID_FIELDS,\n MORPH_TO_KEYS,\n RELATION_OPERATION_KEYS,\n} from '../../content-types';\nimport type { Visitor } from '../../traverse-entity';\n\nconst removeUnrecognizedFields: Visitor = (\n { key, attribute, path, schema, parent, allowedExtraRootKeys },\n { remove }\n) => {\n // We only look at properties that are not attributes\n if (attribute) {\n return;\n }\n\n // At root level (path.attribute === null), only accept id-like fields\n if (path.attribute === null) {\n if (ID_FIELDS.includes(key)) {\n return;\n }\n if (allowedExtraRootKeys?.includes(key)) {\n return;\n }\n\n remove(key);\n return;\n }\n\n // allow special morphTo keys\n if (isMorphToRelationalAttribute(parent?.attribute) && MORPH_TO_KEYS.includes(key)) {\n return;\n }\n\n // allow special dz keys\n if (\n isComponentSchema(schema) &&\n isDynamicZoneAttribute(parent?.attribute) &&\n DYNAMIC_ZONE_KEYS.includes(key)\n ) {\n return;\n }\n\n // allow relation operation keys (connect, disconnect, set, options) for relations and media\n if (\n (isRelationalAttribute(parent?.attribute) || isMediaAttribute(parent?.attribute)) &&\n RELATION_OPERATION_KEYS.includes(key)\n ) {\n return;\n }\n\n // allow id fields for relations, media, and components\n const canUseID =\n isRelationalAttribute(parent?.attribute) ||\n isMediaAttribute(parent?.attribute) ||\n isComponentAttribute(parent?.attribute);\n if (canUseID && ID_FIELDS.includes(key)) {\n return;\n }\n\n // if we couldn't find any reason for it to be here, remove it\n remove(key);\n};\n\nexport default removeUnrecognizedFields;\n"],"names":["removeUnrecognizedFields","key","attribute","path","schema","parent","allowedExtraRootKeys","remove","ID_FIELDS","includes","isMorphToRelationalAttribute","MORPH_TO_KEYS","isComponentSchema","isDynamicZoneAttribute","DYNAMIC_ZONE_KEYS","isRelationalAttribute","isMediaAttribute","RELATION_OPERATION_KEYS","canUseID","isComponentAttribute"],"mappings":";;AAcA,MAAMA,2BAAoC,CACxC,EAAEC,GAAG,EAAEC,SAAS,EAAEC,IAAI,EAAEC,MAAM,EAAEC,MAAM,EAAEC,oBAAoB,EAAE,EAC9D,EAAEC,MAAM,EAAE,GAAA;;AAGV,IAAA,IAAIL,SAAW,EAAA;AACb,QAAA;AACF;;IAGA,IAAIC,IAAAA,CAAKD,SAAS,KAAK,IAAM,EAAA;QAC3B,IAAIM,SAAAA,CAAUC,QAAQ,CAACR,GAAM,CAAA,EAAA;AAC3B,YAAA;AACF;QACA,IAAIK,oBAAAA,EAAsBG,SAASR,GAAM,CAAA,EAAA;AACvC,YAAA;AACF;QAEAM,MAAON,CAAAA,GAAAA,CAAAA;AACP,QAAA;AACF;;AAGA,IAAA,IAAIS,6BAA6BL,MAAQH,EAAAA,SAAAA,CAAAA,IAAcS,aAAcF,CAAAA,QAAQ,CAACR,GAAM,CAAA,EAAA;AAClF,QAAA;AACF;;IAGA,IACEW,iBAAAA,CAAkBR,WAClBS,sBAAuBR,CAAAA,MAAAA,EAAQH,cAC/BY,iBAAkBL,CAAAA,QAAQ,CAACR,GAC3B,CAAA,EAAA;AACA,QAAA;AACF;;AAGA,IAAA,IACE,CAACc,qBAAsBV,CAAAA,MAAAA,EAAQH,SAAcc,CAAAA,IAAAA,gBAAAA,CAAiBX,MAAQH,EAAAA,SAAAA,CAAS,KAC/Ee,uBAAAA,CAAwBR,QAAQ,CAACR,GACjC,CAAA,EAAA;AACA,QAAA;AACF;;IAGA,MAAMiB,QAAAA,GACJH,sBAAsBV,MAAQH,EAAAA,SAAAA,CAAAA,IAC9Bc,iBAAiBX,MAAQH,EAAAA,SAAAA,CAAAA,IACzBiB,qBAAqBd,MAAQH,EAAAA,SAAAA,CAAAA;AAC/B,IAAA,IAAIgB,QAAYV,IAAAA,SAAAA,CAAUC,QAAQ,CAACR,GAAM,CAAA,EAAA;AACvC,QAAA;AACF;;IAGAM,MAAON,CAAAA,GAAAA,CAAAA;AACT;;;;"}
@@ -10,6 +10,8 @@ export interface VisitorOptions {
10
10
  path: Path;
11
11
  getModel(uid: string): Model;
12
12
  parent?: Parent;
13
+ /** Extra root-level keys allowed (e.g. registered input params). Only used when path.attribute === null. */
14
+ allowedExtraRootKeys?: string[];
13
15
  }
14
16
  export type Visitor = (visitorOptions: VisitorOptions, visitorUtils: VisitorUtils) => void;
15
17
  export interface Path {
@@ -22,6 +24,8 @@ export interface TraverseOptions {
22
24
  path?: Path;
23
25
  parent?: Parent;
24
26
  getModel(uid: string): Model;
27
+ /** Extra root-level keys allowed (e.g. registered input params). Only used when path.attribute === null. */
28
+ allowedExtraRootKeys?: string[];
25
29
  }
26
30
  export interface Parent {
27
31
  attribute?: Attribute;
@@ -1 +1 @@
1
- {"version":3,"file":"traverse-entity.d.ts","sourceRoot":"","sources":["../src/traverse-entity.ts"],"names":[],"mappings":";AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAoBpE,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,KAAK,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;IACxB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,KAAK,IAAI,CAAC;AAE3F,MAAM,WAAW,IAAI;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,KAAK,CAAC;IACd,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC;CAC9B;AAED,MAAM,WAAW,MAAM;IACrB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,KAAK,CAAC;CACf;AA6LD,QAAA,MAAM,kBAAkB,aAAc;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE;gBACtC,MAAM;aAIT,MAAM,SAAS,IAAI;CAG5B,CAAC;;AAEH,wBAAqC"}
1
+ {"version":3,"file":"traverse-entity.d.ts","sourceRoot":"","sources":["../src/traverse-entity.ts"],"names":[],"mappings":";AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAoBpE,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,KAAK,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;IACxB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4GAA4G;IAC5G,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,KAAK,IAAI,CAAC;AAE3F,MAAM,WAAW,IAAI;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,KAAK,CAAC;IACd,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC;IAC7B,4GAA4G;IAC5G,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,MAAM;IACrB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,KAAK,CAAC;CACf;AAiOD,QAAA,MAAM,kBAAkB,aAAc;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE;gBACtC,MAAM;aAIT,MAAM,SAAS,IAAI;CAG5B,CAAC;;AAEH,wBAAqC"}