@tsed/schema 8.44.5 → 8.44.6

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,5 +1,5 @@
1
1
  import "../components/index.js";
2
- import { DecoratorTypes, getValue, isClass, isPlainObject, nameOf } from "@tsed/core";
2
+ import { DecoratorTypes, getValue, isArray, isClass, isPlainObject, nameOf } from "@tsed/core";
3
3
  import { JsonSchema, SpecTypes } from "../domain/index.js";
4
4
  import { execMapper } from "../registries/JsonSchemaMapperContainer.js";
5
5
  import { getJsonEntityStore } from "../registries/JsonEntitiesContainer.js";
@@ -7,11 +7,49 @@ import { inlineRefs } from "../utils/inlineRefs.js";
7
7
  /**
8
8
  * @ignore
9
9
  */
10
- const CACHES = new Map();
10
+ const CACHES = new WeakMap();
11
+ /**
12
+ * Inline typeof on purpose: this runs for each option on every compile() call, and the
13
+ * `isPrimitive()`/`isNil()` helpers of @tsed/core made the cache hit ~50% slower.
14
+ * @ignore
15
+ */
16
+ function isPrimitiveValue(value) {
17
+ return value === null || (typeof value !== "object" && typeof value !== "function");
18
+ }
19
+ /**
20
+ * Options holding only primitives and arrays of primitives (the usual `groups`, `customKeys`, `specType`...)
21
+ * give the same key with the native `JSON.stringify`, because the replacer below returns them unchanged.
22
+ * @ignore
23
+ */
24
+ function hasOnlyPrimitiveValues(options) {
25
+ for (const key in options) {
26
+ // always set by compile() to an empty object
27
+ if (key === "components") {
28
+ continue;
29
+ }
30
+ const value = options[key];
31
+ if (isPrimitiveValue(value)) {
32
+ continue;
33
+ }
34
+ // an array with its own toJSON can return a class, that only the replacer turns into a name
35
+ if (!isArray(value) || value.toJSON !== undefined) {
36
+ return false;
37
+ }
38
+ for (let i = 0; i < value.length; i++) {
39
+ if (!isPrimitiveValue(value[i])) {
40
+ return false;
41
+ }
42
+ }
43
+ }
44
+ return true;
45
+ }
11
46
  /**
12
47
  * @ignore
13
48
  */
14
49
  function getKey(options) {
50
+ if (hasOnlyPrimitiveValues(options)) {
51
+ return JSON.stringify(options);
52
+ }
15
53
  return JSON.stringify(options, (key, value) => {
16
54
  if (value && !isPlainObject(value) && isClass(value)) {
17
55
  return nameOf(value);
@@ -23,32 +61,39 @@ function getKey(options) {
23
61
  * @ignore
24
62
  */
25
63
  function get(model, options) {
26
- const cache = CACHES.get(model) || new Map();
27
- CACHES.set(model, cache);
64
+ let cache = CACHES.get(model);
28
65
  const key = getKey(options);
29
- if (!cache.has(key)) {
30
- let schema;
31
- if (model instanceof JsonSchema) {
32
- schema = model.toJSON(options);
33
- }
34
- else {
35
- const entity = getJsonEntityStore(model);
36
- let mapper = "schema";
37
- if (entity.is(DecoratorTypes.PARAM)) {
38
- options = {
39
- ...options,
40
- root: true,
41
- groups: entity.schema.getGroups()
42
- };
43
- mapper = "item";
44
- }
45
- schema = execMapper(mapper, [entity.schema], options);
46
- }
47
- if (Object.keys(getValue(options, "components.schemas", {})).length) {
48
- schema.definitions = options.components.schemas;
66
+ // compiled schemas are objects, never undefined
67
+ const cached = cache?.get(key);
68
+ if (cached !== undefined) {
69
+ return cached;
70
+ }
71
+ let schema;
72
+ if (model instanceof JsonSchema) {
73
+ schema = model.toJSON(options);
74
+ }
75
+ else {
76
+ const entity = getJsonEntityStore(model);
77
+ let mapper = "schema";
78
+ if (entity.is(DecoratorTypes.PARAM)) {
79
+ options = {
80
+ ...options,
81
+ root: true,
82
+ groups: entity.schema.getGroups()
83
+ };
84
+ mapper = "item";
49
85
  }
50
- cache.set(key, options.inlineRefs ? inlineRefs(schema) : schema);
86
+ schema = execMapper(mapper, [entity.schema], options);
87
+ }
88
+ if (Object.keys(getValue(options, "components.schemas", {})).length) {
89
+ schema.definitions = options.components.schemas;
90
+ }
91
+ if (!cache) {
92
+ // a nested compile() of the same model may have created it in the meantime
93
+ cache = CACHES.get(model) ?? new Map();
94
+ CACHES.set(model, cache);
51
95
  }
96
+ cache.set(key, options.inlineRefs ? inlineRefs(schema) : schema);
52
97
  return cache.get(key);
53
98
  }
54
99
  /**
@@ -1,4 +1,26 @@
1
1
  import picomatch from "picomatch";
2
+ /**
3
+ * Compiled matchers per pattern. Patterns can come from the request (`includes`), so the cache is bounded
4
+ * in number of entries, and oversized patterns are compiled on each call instead of being kept.
5
+ * @ignore
6
+ */
7
+ const MATCHERS = new Map();
8
+ const MAX_MATCHERS = 1000;
9
+ const MAX_CACHED_PATTERN_LENGTH = 256;
10
+ function getMatcher(pattern) {
11
+ if (pattern.length > MAX_CACHED_PATTERN_LENGTH) {
12
+ return picomatch(pattern, {}, true);
13
+ }
14
+ let isMatch = MATCHERS.get(pattern);
15
+ if (!isMatch) {
16
+ isMatch = picomatch(pattern, {}, true);
17
+ if (MATCHERS.size >= MAX_MATCHERS) {
18
+ MATCHERS.clear();
19
+ }
20
+ MATCHERS.set(pattern, isMatch);
21
+ }
22
+ return isMatch;
23
+ }
2
24
  function micromatch(list, patterns) {
3
25
  patterns = [].concat(patterns);
4
26
  list = [].concat(list);
@@ -6,17 +28,16 @@ function micromatch(list, patterns) {
6
28
  let keep = new Set();
7
29
  let items = new Set();
8
30
  let negatives = 0;
9
- let onResult = (state) => {
10
- items.add(state.output);
11
- };
12
31
  for (let i = 0; i < patterns.length; i++) {
13
- let isMatch = picomatch(String(patterns[i]), { onResult }, true);
32
+ const isMatch = getMatcher(String(patterns[i]));
14
33
  let negated = isMatch.state.negated || isMatch.state.negatedExtglob;
15
34
  if (negated) {
16
35
  negatives++;
17
36
  }
18
37
  for (let item of list) {
19
38
  let matched = isMatch(item, true);
39
+ // same as the picomatch onResult option: collect the output of every tested item
40
+ items.add(matched.output);
20
41
  let match = negated ? !matched.isMatch : matched.isMatch;
21
42
  if (!match)
22
43
  continue;
@@ -44,7 +65,7 @@ export function matchGroups(groups, compareWith = []) {
44
65
  }
45
66
  }
46
67
  if (groupsIncludes.length) {
47
- return !micromatch(groups.filter((group) => !group.startsWith("!")), compareWith);
68
+ return !micromatch(groupsIncludes, compareWith);
48
69
  }
49
70
  return false;
50
71
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsed/schema",
3
- "version": "8.44.5",
3
+ "version": "8.44.6",
4
4
  "description": "JsonSchema module for Ts.ED Framework",
5
5
  "keywords": [
6
6
  "JsonSchema",
@@ -34,7 +34,7 @@
34
34
  "test:ci": "vitest run --coverage.thresholds.autoUpdate=true"
35
35
  },
36
36
  "dependencies": {
37
- "@tsed/openspec": "8.44.5",
37
+ "@tsed/openspec": "8.44.6",
38
38
  "change-case": "^5.4.4",
39
39
  "json-schema": "0.4.0",
40
40
  "picomatch": "4.0.4",
@@ -43,11 +43,11 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@apidevtools/swagger-parser": "10.1.0",
46
- "@tsed/barrels": "8.44.5",
47
- "@tsed/core": "8.44.5",
48
- "@tsed/hooks": "8.44.5",
49
- "@tsed/openspec": "8.44.5",
50
- "@tsed/typescript": "8.44.5",
46
+ "@tsed/barrels": "8.44.6",
47
+ "@tsed/core": "8.44.6",
48
+ "@tsed/hooks": "8.44.6",
49
+ "@tsed/openspec": "8.44.6",
50
+ "@tsed/typescript": "8.44.6",
51
51
  "@types/fs-extra": "11.0.4",
52
52
  "@types/json-schema": "7.0.15",
53
53
  "@types/picomatch": "3.0.2",