@rvoh/psychic 3.2.0 → 3.2.1

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,4 +1,4 @@
1
- import { Ajv } from 'ajv';
1
+ import { Ajv2020 } from 'ajv/dist/2020.js';
2
2
  import addFormats from 'ajv-formats';
3
3
  /**
4
4
  * @internal
@@ -99,7 +99,7 @@ export function createValidator(schema, options = {}) {
99
99
  ...options,
100
100
  init: undefined,
101
101
  };
102
- const ajv = new Ajv({
102
+ const ajv = new Ajv2020({
103
103
  removeAdditional: false,
104
104
  useDefaults: true,
105
105
  strict: false,
@@ -51,11 +51,15 @@ export default class SerializerOpenapiRenderer {
51
51
  alreadyExtractedDescendantSerializers[this.serializer.globalName] = true;
52
52
  const referencedSerializersAndOpenapiSchemaBodyShorthand = this._renderedOpenapi(alreadyExtractedDescendantSerializers);
53
53
  if (this.allOfSiblings.length) {
54
- const openapi = referencedSerializersAndOpenapiSchemaBodyShorthand.openapi;
54
+ // Property-level locks live only at the `allOf` wrapper (never on the
55
+ // inline branch or the `$ref`'d siblings) so that all branches'
56
+ // properties are visible to `unevaluatedProperties` for the union check.
55
57
  return {
56
58
  ...referencedSerializersAndOpenapiSchemaBodyShorthand,
57
59
  openapi: {
58
- allOf: [openapi, ...this.allOfSiblings],
60
+ type: 'object',
61
+ allOf: [referencedSerializersAndOpenapiSchemaBodyShorthand.openapi, ...this.allOfSiblings],
62
+ unevaluatedProperties: false,
59
63
  },
60
64
  };
61
65
  }
@@ -98,7 +102,13 @@ export default class SerializerOpenapiRenderer {
98
102
  type: 'object',
99
103
  required: sort(uniq(requiredProperties.map(property => this.setCase(property)))),
100
104
  properties: sortObjectByKey(referencedSerializersAndAttributes.attributes),
101
- additionalProperties: false,
105
+ // Property-level locks (`additionalProperties` / `unevaluatedProperties`)
106
+ // are not emitted on leaf schemas: when a leaf is composed via `$ref`
107
+ // inside an `allOf`, neither keyword sees properties contributed by
108
+ // sibling branches, so a per-leaf lock incorrectly rejects flattened
109
+ // properties. Strictness is enforced at the `allOf`-wrapper level
110
+ // (`unevaluatedProperties: false`) when flattening occurs, and at the
111
+ // validation-pipeline level for top-level schemas.
102
112
  },
103
113
  };
104
114
  }
@@ -4,7 +4,10 @@ import CannotInferControllerFromTopLevelRouteError from '../error/router/cannot-
4
4
  import pascalizeFileName from '../helpers/pascalizeFileName.js';
5
5
  import PsychicApp from '../psychic-app/index.js';
6
6
  export function routePath(routePath) {
7
- return `/${routePath.replace(/^\//, '')}`;
7
+ const normalized = `/${routePath.replace(/^\//, '')}`;
8
+ if (normalized === '/')
9
+ return normalized;
10
+ return normalized.replace(/\/+$/, '');
8
11
  }
9
12
  export function resourcePath(routePath) {
10
13
  return `/${routePath}/:id`;
@@ -72,7 +72,8 @@ export default class PsychicRouter {
72
72
  prefixPathWithNamespaces(str) {
73
73
  if (!this.currentNamespaces.length)
74
74
  return str;
75
- return '/' + this.currentNamespacePaths.join('/') + '/' + str;
75
+ const prefix = '/' + this.currentNamespacePaths.join('/');
76
+ return str ? `${prefix}/${str}` : prefix;
76
77
  }
77
78
  crud(httpMethod, path, controllerOrMiddleware, action) {
78
79
  this.checkPathForInvalidChars(path);
@@ -1,4 +1,4 @@
1
- import { Ajv } from 'ajv';
1
+ import { Ajv2020 } from 'ajv/dist/2020.js';
2
2
  import addFormats from 'ajv-formats';
3
3
  /**
4
4
  * @internal
@@ -99,7 +99,7 @@ export function createValidator(schema, options = {}) {
99
99
  ...options,
100
100
  init: undefined,
101
101
  };
102
- const ajv = new Ajv({
102
+ const ajv = new Ajv2020({
103
103
  removeAdditional: false,
104
104
  useDefaults: true,
105
105
  strict: false,
@@ -51,11 +51,15 @@ export default class SerializerOpenapiRenderer {
51
51
  alreadyExtractedDescendantSerializers[this.serializer.globalName] = true;
52
52
  const referencedSerializersAndOpenapiSchemaBodyShorthand = this._renderedOpenapi(alreadyExtractedDescendantSerializers);
53
53
  if (this.allOfSiblings.length) {
54
- const openapi = referencedSerializersAndOpenapiSchemaBodyShorthand.openapi;
54
+ // Property-level locks live only at the `allOf` wrapper (never on the
55
+ // inline branch or the `$ref`'d siblings) so that all branches'
56
+ // properties are visible to `unevaluatedProperties` for the union check.
55
57
  return {
56
58
  ...referencedSerializersAndOpenapiSchemaBodyShorthand,
57
59
  openapi: {
58
- allOf: [openapi, ...this.allOfSiblings],
60
+ type: 'object',
61
+ allOf: [referencedSerializersAndOpenapiSchemaBodyShorthand.openapi, ...this.allOfSiblings],
62
+ unevaluatedProperties: false,
59
63
  },
60
64
  };
61
65
  }
@@ -98,7 +102,13 @@ export default class SerializerOpenapiRenderer {
98
102
  type: 'object',
99
103
  required: sort(uniq(requiredProperties.map(property => this.setCase(property)))),
100
104
  properties: sortObjectByKey(referencedSerializersAndAttributes.attributes),
101
- additionalProperties: false,
105
+ // Property-level locks (`additionalProperties` / `unevaluatedProperties`)
106
+ // are not emitted on leaf schemas: when a leaf is composed via `$ref`
107
+ // inside an `allOf`, neither keyword sees properties contributed by
108
+ // sibling branches, so a per-leaf lock incorrectly rejects flattened
109
+ // properties. Strictness is enforced at the `allOf`-wrapper level
110
+ // (`unevaluatedProperties: false`) when flattening occurs, and at the
111
+ // validation-pipeline level for top-level schemas.
102
112
  },
103
113
  };
104
114
  }
@@ -4,7 +4,10 @@ import CannotInferControllerFromTopLevelRouteError from '../error/router/cannot-
4
4
  import pascalizeFileName from '../helpers/pascalizeFileName.js';
5
5
  import PsychicApp from '../psychic-app/index.js';
6
6
  export function routePath(routePath) {
7
- return `/${routePath.replace(/^\//, '')}`;
7
+ const normalized = `/${routePath.replace(/^\//, '')}`;
8
+ if (normalized === '/')
9
+ return normalized;
10
+ return normalized.replace(/\/+$/, '');
8
11
  }
9
12
  export function resourcePath(routePath) {
10
13
  return `/${routePath}/:id`;
@@ -72,7 +72,8 @@ export default class PsychicRouter {
72
72
  prefixPathWithNamespaces(str) {
73
73
  if (!this.currentNamespaces.length)
74
74
  return str;
75
- return '/' + this.currentNamespacePaths.join('/') + '/' + str;
75
+ const prefix = '/' + this.currentNamespacePaths.join('/');
76
+ return str ? `${prefix}/${str}` : prefix;
76
77
  }
77
78
  crud(httpMethod, path, controllerOrMiddleware, action) {
78
79
  this.checkPathForInvalidChars(path);
@@ -1,4 +1,5 @@
1
- import { Ajv, type ErrorObject, type JSONSchemaType, type ValidateFunction } from 'ajv';
1
+ import { type ErrorObject, type JSONSchemaType, type ValidateFunction } from 'ajv';
2
+ import { Ajv2020 } from 'ajv/dist/2020.js';
2
3
  /**
3
4
  * @internal
4
5
  *
@@ -72,8 +73,8 @@ export interface ValidationError {
72
73
  message: string;
73
74
  params?: Record<string, unknown>;
74
75
  }
75
- export type AjvValidationOpts = ConstructorParameters<typeof Ajv>[0];
76
+ export type AjvValidationOpts = ConstructorParameters<typeof Ajv2020>[0];
76
77
  export type ValidateOpenapiSchemaOptions = AjvValidationOpts & CustomOpenapiValidationOptions;
77
78
  export interface CustomOpenapiValidationOptions {
78
- init?: (ajv: Ajv) => void;
79
+ init?: (ajv: Ajv2020) => void;
79
80
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "name": "@rvoh/psychic",
4
4
  "description": "Typescript web framework",
5
- "version": "3.2.0",
5
+ "version": "3.2.1",
6
6
  "author": "RVOHealth",
7
7
  "repository": {
8
8
  "type": "git",