@ditojs/server 2.53.0 → 2.53.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/server",
3
- "version": "2.53.0",
3
+ "version": "2.53.1",
4
4
  "type": "module",
5
5
  "description": "Dito.js Server – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/master/packages/server",
@@ -90,5 +90,5 @@
90
90
  "objection": "^3.1.5",
91
91
  "typescript": "^5.8.3"
92
92
  },
93
- "gitHead": "7f36fa29f11708527b85e37233b83ecf6bf40497"
93
+ "gitHead": "542865f8e49144518b8c9ea1bc4521c0aa5456df"
94
94
  }
@@ -10,24 +10,42 @@ function getSchemaCache(options) {
10
10
  return (schemaCaches[key] ||= new WeakMap())
11
11
  }
12
12
 
13
- export function convertSchema(schema, options = {}, rootDefinitions = null) {
13
+ export function convertSchema(
14
+ schema,
15
+ options = {},
16
+ parentEntry = null
17
+ ) {
14
18
  const original = schema
19
+ const isRoot = parentEntry === null
15
20
 
16
21
  const schemaCache = getSchemaCache(options)
17
22
  if (schemaCache.has(original)) {
18
- const { schema, definitions } = schemaCache.get(original)
19
- mergeDefinitions(rootDefinitions, definitions)
23
+ const { schema, definitions, parentEntries } = schemaCache.get(original)
24
+ parentEntries.push(parentEntry)
25
+ if (definitions) {
26
+ if (isRoot) {
27
+ return { ...schema, definitions }
28
+ } else {
29
+ mergeDefinitions(parentEntry.definitions, definitions)
30
+ }
31
+ }
20
32
  return schema
21
33
  }
22
34
 
35
+ const entry = {
36
+ schema: null,
37
+ definitions: {},
38
+ parentEntries: parentEntry ? [parentEntry] : []
39
+ }
40
+
41
+ // To prevent circular references, cache the entry before all conversion work.
42
+ schemaCache.set(original, entry)
43
+
23
44
  let definitions = null
24
45
  if (isArray(schema)) {
25
46
  // Needed for allOf, anyOf, oneOf, not, items, see below:
26
- schema = schema.map(entry => convertSchema(entry, options, rootDefinitions))
47
+ schema = schema.map(item => convertSchema(item, options, entry))
27
48
  } else if (isObject(schema)) {
28
- const isRoot = rootDefinitions === null
29
- rootDefinitions ??= {}
30
-
31
49
  // Create a shallow clone so we can modify and return:
32
50
  // Also collect and propagate the definitions up to the root schema through
33
51
  // `options.definitions`, as passed from `Model static get jsonSchema()`:
@@ -47,21 +65,13 @@ export function convertSchema(schema, options = {}, rootDefinitions = null) {
47
65
  }
48
66
 
49
67
  // Convert array items
50
- schema.prefixItems &&= convertSchema(
51
- schema.prefixItems,
52
- options,
53
- rootDefinitions
54
- )
55
- schema.items &&= convertSchema(
56
- schema.items,
57
- options,
58
- rootDefinitions
59
- )
68
+ schema.prefixItems &&= convertSchema(schema.prefixItems, options, entry)
69
+ schema.items &&= convertSchema(schema.items, options, entry)
60
70
 
61
71
  // Handle nested allOf, anyOf, oneOf & co. fields
62
72
  for (const key of ['allOf', 'anyOf', 'oneOf', 'not', '$extend']) {
63
73
  if (key in schema) {
64
- schema[key] = convertSchema(schema[key], options, rootDefinitions)
74
+ schema[key] = convertSchema(schema[key], options, entry)
65
75
  }
66
76
  }
67
77
 
@@ -115,14 +125,15 @@ export function convertSchema(schema, options = {}, rootDefinitions = null) {
115
125
  schema.enum.push(null)
116
126
  }
117
127
 
118
- // Only convert properties after the schema is remembered, to avoid endless
119
- // recursion in circular schema definitions.
128
+ // Convert properties last. This is needed for circular references
129
+ // to work correctly, as the properties may reference the same schema
130
+ // that is being converted right now.
120
131
  let hasConvertedProperties = false
121
132
  if (schema.properties) {
122
133
  const { properties, required } = convertProperties(
123
134
  schema.properties,
124
135
  options,
125
- rootDefinitions
136
+ entry
126
137
  )
127
138
  schema.properties = properties
128
139
  if (required.length > 0) {
@@ -135,7 +146,7 @@ export function convertSchema(schema, options = {}, rootDefinitions = null) {
135
146
  const { properties } = convertProperties(
136
147
  schema.patternProperties,
137
148
  options,
138
- rootDefinitions
149
+ entry
139
150
  )
140
151
  schema.patternProperties = properties
141
152
  hasConvertedProperties = true
@@ -149,44 +160,45 @@ export function convertSchema(schema, options = {}, rootDefinitions = null) {
149
160
  // explicitly set to `true`:
150
161
  schema.unevaluatedProperties = false
151
162
  }
152
-
153
- if (isRoot && hasDefinitions(rootDefinitions)) {
154
- schema.definitions = rootDefinitions
155
- }
156
163
  }
157
164
 
158
- const entry = { schema, definitions: null }
159
- schemaCache.set(original, entry)
165
+ entry.schema = schema
160
166
 
161
- // To prevent circular references, we need to convert the definitions
162
- // after the schema entry is cached.
167
+ // Only convert definitions once `entry.schema` is set, so that it works as
168
+ // expected with circular references.
163
169
  if (definitions) {
164
- definitions = convertDefinitions(definitions, options)
165
- mergeDefinitions(rootDefinitions, definitions)
166
- entry.definitions = definitions
170
+ mergeDefinitions(
171
+ entry.definitions,
172
+ convertDefinitions(definitions, options, entry)
173
+ )
174
+ }
175
+
176
+ if (Object.keys(entry.definitions).length > 0) {
177
+ // Propagate the definitions up the parent entry chains, that due to
178
+ // circular references may not be up to date yet.
179
+ mergeDefinitionsRecursively(entry, entry.definitions)
180
+ if (isRoot) {
181
+ schema.definitions = entry.definitions
182
+ }
167
183
  }
168
184
 
169
185
  return schema
170
186
  }
171
187
 
172
- function convertProperties(schemaProperties, options, definitions = {}) {
188
+ function convertProperties(schemaProperties, options, entry) {
173
189
  const properties = {}
174
190
  const required = []
175
191
  for (const [key, property] of Object.entries(schemaProperties)) {
176
- properties[key] = convertSchema(property, options, definitions)
192
+ properties[key] = convertSchema(property, options, entry)
177
193
  if (property?.required) {
178
194
  required.push(key)
179
195
  }
180
196
  }
181
- return { properties, required, definitions }
182
- }
183
-
184
- function hasDefinitions(definitions) {
185
- return definitions && Object.keys(definitions).length > 0
197
+ return { properties, required }
186
198
  }
187
199
 
188
- function convertDefinitions(definitions, options) {
189
- const converted = {}
200
+ function convertDefinitions(definitions, options, entry) {
201
+ let converted = null
190
202
  for (const [key, schema] of Object.entries(definitions)) {
191
203
  if (!key.startsWith('#')) {
192
204
  throw new Error(
@@ -197,25 +209,41 @@ function convertDefinitions(definitions, options) {
197
209
  }`
198
210
  )
199
211
  }
200
- converted[key] = convertSchema(schema, options, converted)
212
+ converted ??= {}
213
+ converted[key] = convertSchema(schema, options, entry)
201
214
  }
202
- return hasDefinitions(converted) ? converted : null
215
+ return converted
203
216
  }
204
217
 
205
218
  function mergeDefinitions(definitions, defs) {
206
- if (definitions && defs) {
207
- for (const [key, def] of Object.entries(defs)) {
208
- const definition = definitions[key]
209
- if (definition && !equals(definition, def)) {
210
- throw new Error(
211
- `Duplicate nested definition for '${key}' with different schema: ${
212
- JSON.stringify(def, null, 2)
213
- }, ${
214
- JSON.stringify(definition, null, 2)
215
- }`
216
- )
217
- }
218
- definitions[key] = def
219
+ for (const [key, def] of Object.entries(defs)) {
220
+ const definition = definitions[key]
221
+ if (definition && !equals(definition, def)) {
222
+ throw new Error(
223
+ `Duplicate nested definition for '${key}' with different schema: ${
224
+ JSON.stringify(def, null, 2)
225
+ }, ${
226
+ JSON.stringify(definition, null, 2)
227
+ }`
228
+ )
229
+ }
230
+ definitions[key] ??= def
231
+ }
232
+ }
233
+
234
+ function mergeDefinitionsRecursively(
235
+ entry,
236
+ definitions,
237
+ visited = new WeakSet()
238
+ ) {
239
+ if (!visited.has(entry)) {
240
+ visited.add(entry)
241
+
242
+ if (definitions !== entry.definitions) {
243
+ mergeDefinitions(entry.definitions, definitions)
244
+ }
245
+ for (const parentEntry of entry.parentEntries) {
246
+ mergeDefinitionsRecursively(parentEntry, definitions, visited)
219
247
  }
220
248
  }
221
249
  }