@ditojs/server 2.49.0 → 2.50.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.
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@ditojs/server",
3
- "version": "2.49.0",
3
+ "version": "2.50.0",
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",
7
7
  "author": "Jürg Lehni <juerg@scratchdisk.com> (http://scratchdisk.com)",
8
8
  "license": "MIT",
9
9
  "main": "./src/index.js",
10
+ "types": "./types/index.d.ts",
10
11
  "files": [
11
12
  "src/",
12
13
  "types/"
@@ -25,10 +26,10 @@
25
26
  "node >= 18"
26
27
  ],
27
28
  "dependencies": {
28
- "@ditojs/admin": "^2.49.0",
29
- "@ditojs/build": "^2.49.0",
30
- "@ditojs/router": "^2.49.0",
31
- "@ditojs/utils": "^2.49.0",
29
+ "@ditojs/admin": "^2.50.0",
30
+ "@ditojs/build": "^2.50.0",
31
+ "@ditojs/router": "^2.50.0",
32
+ "@ditojs/utils": "^2.50.0",
32
33
  "@koa/cors": "^5.0.0",
33
34
  "@koa/multer": "^3.1.0",
34
35
  "@originjs/vite-plugin-commonjs": "^1.0.3",
@@ -40,6 +41,7 @@
40
41
  "eventemitter2": "^6.4.9",
41
42
  "file-type": "^20.5.0",
42
43
  "helmet": "^8.1.0",
44
+ "is-stream": "^4.0.1",
43
45
  "koa": "^3.0.0",
44
46
  "koa-bodyparser": "^4.4.1",
45
47
  "koa-compose": "^4.1.0",
@@ -89,6 +91,5 @@
89
91
  "objection": "^3.1.5",
90
92
  "typescript": "^5.8.3"
91
93
  },
92
- "types": "types",
93
- "gitHead": "8276ff1db64a82c03e1569e2403f378b0201ea3c"
94
+ "gitHead": "24ab390e75addc046571ffa7161c75569b11e4f5"
94
95
  }
@@ -25,7 +25,8 @@ import {
25
25
  asArray,
26
26
  equals,
27
27
  parseDataPath,
28
- normalizeDataPath
28
+ normalizeDataPath,
29
+ deprecate
29
30
  } from '@ditojs/utils'
30
31
 
31
32
  export class Controller {
@@ -602,10 +603,20 @@ function convertActionObject(name, object, actions) {
602
603
  transacted,
603
604
  scope,
604
605
  parameters,
606
+ // TODO: `returns` was deprecated in May 2025 in favour of `response`.
607
+ // Remove this in 2026.
605
608
  returns,
609
+ response = returns,
606
610
  ...rest
607
611
  } = object
608
612
 
613
+ if (returns) {
614
+ deprecate(
615
+ 'The `returns` property is deprecated in favour of `response`. ' +
616
+ 'Update your handler definition to use `response` instead.'
617
+ )
618
+ }
619
+
609
620
  // In order to support `super` calls in the `handler` function in object
610
621
  // notation, deploy this crazy JS sorcery:
611
622
  Object.setPrototypeOf(object, Object.getPrototypeOf(actions))
@@ -621,7 +632,7 @@ function convertActionObject(name, object, actions) {
621
632
  handler.scope = scope ? asArray(scope) : null
622
633
 
623
634
  processHandlerParameters(handler, 'parameters', parameters)
624
- processHandlerParameters(handler, 'returns', returns)
635
+ processHandlerParameters(handler, 'response', response)
625
636
 
626
637
  return Object.assign(handler, rest)
627
638
  }
@@ -1,4 +1,4 @@
1
- import { isString, isObject, asArray, clone } from '@ditojs/utils'
1
+ import { isString, isObject, asArray, clone, deprecate } from '@ditojs/utils'
2
2
 
3
3
  export default class ControllerAction {
4
4
  constructor(
@@ -17,11 +17,21 @@ export default class ControllerAction {
17
17
  authorize,
18
18
  transacted,
19
19
  parameters,
20
+ // TODO: `returns` was deprecated in May 2025 in favour of `response`.
21
+ // Remove this in 2026.
20
22
  returns,
23
+ response = returns,
21
24
  options = {},
22
25
  ...additional
23
26
  } = handler
24
27
 
28
+ if (returns) {
29
+ deprecate(
30
+ 'The `returns` property is deprecated in favour of `response`. ' +
31
+ 'Update your handler definition to use `response` instead.'
32
+ )
33
+ }
34
+
25
35
  this.app = controller.app
26
36
  this.controller = controller
27
37
  this.actions = actions
@@ -57,19 +67,17 @@ export default class ControllerAction {
57
67
  ...options.parameters,
58
68
  dataName: this.paramsName
59
69
  })
60
- this.returns = this.app.compileParametersValidator(
61
- // TODO: Shouldn't we set `this.returns` to null instead?
62
- returns ? [returns] : [],
63
- {
64
- async: true,
65
- // Use instanceof checks instead of $ref to check returned values.
66
- // TODO: That doesn't guarantee the validity though...
67
- // This should always be $ref checks, I think?
68
- useInstanceOf: true,
69
- ...options.returns, // See @returns() decorator
70
- dataName: 'returns'
71
- }
72
- )
70
+ this.response = this.app.compileParametersValidator(asArray(response), {
71
+ async: true,
72
+ // Use patch validation for response, as we often don't return the
73
+ // full model with all properties, but only a subset of them.
74
+ patch: true,
75
+ // TODO: `returns` was deprecated in May 2025 in favour of `response`.
76
+ // Remove this in 2026.
77
+ ...options.returns,
78
+ ...options.response,
79
+ dataName: 'response'
80
+ })
73
81
  // Copy over the additional properties, e.g. `cached` so application
74
82
  // middleware can implement caching mechanisms:
75
83
  Object.assign(this, additional)
@@ -109,9 +117,9 @@ export default class ControllerAction {
109
117
  await this.controller.handleAuthorization(this.authorization, ctx, member)
110
118
  const { identifier } = this
111
119
  await this.controller.emitHook(`before:${identifier}`, false, ctx, ...args)
112
- const result = await this.callHandler(ctx, ...args)
113
- return this.validateResult(
114
- await this.controller.emitHook(`after:${identifier}`, true, ctx, result)
120
+ const response = await this.callHandler(ctx, ...args)
121
+ return this.validateResponse(
122
+ await this.controller.emitHook(`after:${identifier}`, true, ctx, response)
115
123
  )
116
124
  }
117
125
 
@@ -215,31 +223,38 @@ export default class ControllerAction {
215
223
  }
216
224
  }
217
225
 
218
- async validateResult(result) {
219
- if (this.returns.validate) {
220
- const returnsName = this.handler.returns.name
226
+ async validateResponse(response) {
227
+ if (this.response.validate) {
228
+ const responseName = this.handler.response.name
229
+ const responseWrapped = !!responseName
221
230
  // Use dataName if no name is given, see:
222
- // Application.compileParametersValidator(returns, { dataName })
223
- const data = {
224
- [returnsName || this.returns.dataName]: result
225
- }
226
-
231
+ // Application.compileParametersValidator(response, { dataName })
232
+ const dataName = responseName || this.response.dataName
233
+ const wrapped = { [dataName]: response }
227
234
  // If a named result is defined, return the data wrapped,
228
235
  // otherwise return the original unwrapped result object.
229
- const getResult = () => (returnsName ? data : result)
236
+ const getResult = () => (responseWrapped ? wrapped : response)
230
237
  try {
231
- await this.returns.validate(data)
238
+ await this.response.validate(wrapped)
232
239
  return getResult()
233
240
  } catch (error) {
241
+ // If the error contains errors, add them to the validation error:
242
+ const { errors } = error
243
+ const regexp = new RegExp(`^/${dataName}`)
234
244
  throw this.createValidationError({
235
245
  type: 'ResultValidation',
236
246
  message: 'The returned action result is not valid',
237
- errors: error.errors,
247
+ errors: responseWrapped
248
+ ? errors
249
+ : errors.map(error => ({
250
+ ...error,
251
+ instancePath: error.instancePath.replace(regexp, '')
252
+ })),
238
253
  json: getResult()
239
254
  })
240
255
  }
241
256
  }
242
- return result
257
+ return response
243
258
  }
244
259
 
245
260
  async collectArguments(ctx, params) {
@@ -6,8 +6,9 @@ import {
6
6
  isFunction,
7
7
  isPromise,
8
8
  asArray,
9
- flatten,
9
+ clone,
10
10
  equals,
11
+ flatten,
11
12
  parseDataPath,
12
13
  normalizeDataPath,
13
14
  getValueAtDataPath,
@@ -181,19 +182,21 @@ export class Model extends objection.Model {
181
182
  if (options.skipValidation) {
182
183
  return json
183
184
  }
184
- if (!options.graph && !options.async) {
185
- // Fall back to Objection's $validate() if we don't need any of our
186
- // extensions (async and graph for now):
187
- return super.$validate(json, options)
188
- }
189
185
  json ||= this
190
186
  const inputJson = json
191
- const shallow = json.$isObjectionModel && !options.graph
187
+
188
+ const shallow = !options.graph
192
189
  if (shallow) {
193
- // Strip away relations and other internal stuff.
194
- json = json.$clone({ shallow: true })
190
+ json = clone(json, { shallow: true })
191
+ // Strip away relations.
192
+ for (const key of this.constructor.getRelationNames()) {
193
+ delete json[key]
194
+ }
195
195
  // We can mutate `json` now that we took a copy of it.
196
- options = { ...options, mutable: true }
196
+ options = {
197
+ ...options,
198
+ mutable: true
199
+ }
197
200
  }
198
201
 
199
202
  const validator = this.constructor.getValidator()
@@ -209,7 +212,7 @@ export class Model extends objection.Model {
209
212
  const handleResult = result => {
210
213
  validator.afterValidate(args)
211
214
  // If `json` was shallow-cloned, copy over the possible default values.
212
- return shallow ? inputJson.$set(result) : result
215
+ return shallow ? Object.assign(inputJson, result) : result
213
216
  }
214
217
  // Handle both async and sync validation here:
215
218
  return isPromise(result)
@@ -781,13 +784,13 @@ export class Model extends objection.Model {
781
784
  if (deprecatedPrefixes[prefix]) {
782
785
  prefix = deprecatedPrefixes[prefix]
783
786
  deprecate(
784
- `The ${
787
+ `The '${
785
788
  modifier
786
- } modifier is deprecated, use the ${
789
+ }' modifier is deprecated, use the '${
787
790
  prefix
788
791
  }${
789
792
  modifier.slice(1)
790
- } modifier instead.`
793
+ }' modifier instead.`
791
794
  )
792
795
  }
793
796
 
@@ -1,4 +1,4 @@
1
- import { isObject, isFunction } from '@ditojs/utils'
1
+ import { isObject, isFunction, deprecate } from '@ditojs/utils'
2
2
  import { processHandlerParameters } from '../../utils/handler.js'
3
3
  import { mergeReversed } from '../../utils/object.js'
4
4
  import { QueryFilters } from '../../query/index.js'
@@ -24,9 +24,22 @@ export default function filters(values) {
24
24
  function convertFilterObject(name, object) {
25
25
  const addHandlerSettings = (handler, definition) => {
26
26
  // Copy over parameters, returns and their validation options settings.
27
- const { parameters, returns, ...rest } = definition
27
+ const {
28
+ parameters,
29
+ // TODO: `returns` was deprecated in May 2025 in favour of `response`.
30
+ // Remove this in 2026.
31
+ returns,
32
+ response = returns,
33
+ ...rest
34
+ } = definition
35
+ if (returns) {
36
+ deprecate(
37
+ 'The `returns` property is deprecated in favour of `response`. ' +
38
+ 'Update your handler definition to use `response` instead.'
39
+ )
40
+ }
28
41
  processHandlerParameters(handler, 'parameters', parameters)
29
- processHandlerParameters(handler, 'returns', returns)
42
+ processHandlerParameters(handler, 'response', response)
30
43
  return Object.assign(handler, rest)
31
44
  }
32
45
 
@@ -62,7 +75,7 @@ function convertFilterObject(name, object) {
62
75
 
63
76
  function wrapWithValidation(filter, name, app) {
64
77
  if (filter) {
65
- // TODO: Implement `returns` validation for filters too.
78
+ // TODO: Implement `response` validation for filters too.
66
79
  // TODO: Share additional coercion handling with
67
80
  // `ControllerAction#coerceValue()`
68
81
  const { parameters, options = {} } = filter
@@ -1,5 +1,5 @@
1
1
  export * as keywords from './keywords/index.js'
2
2
  export * as formats from './formats/index.js'
3
3
  export * as types from './types/index.js'
4
- export * from './properties.js'
4
+ export * from './schema.js'
5
5
  export * from './relations.js'
@@ -10,4 +10,3 @@ export * from './_instanceof.js'
10
10
  export * from './_validate.js'
11
11
  export * from './_relate.js'
12
12
  export * from './_range.js'
13
- export * from './_extend.js'
@@ -275,15 +275,11 @@ export function addRelationSchemas(modelClass, properties) {
275
275
  const anyOf = []
276
276
  if (isOneToOne) {
277
277
  // Allow null-value on one-to-one relations
278
- anyOf.push({
279
- type: 'null'
280
- })
278
+ anyOf.push({ type: 'null' })
281
279
  }
282
280
  if (!owner) {
283
281
  // Allow reference objects for relations that don't own their data.
284
- anyOf.push({
285
- relate: $ref
286
- })
282
+ anyOf.push({ relate: $ref })
287
283
  }
288
284
  // Finally the model itself
289
285
  anyOf.push({ $ref })
@@ -1,4 +1,4 @@
1
- import { convertSchema } from './properties.js'
1
+ import { convertSchema } from './schema.js'
2
2
 
3
3
  describe('convertSchema()', () => {
4
4
  it('expands objects with properties to full JSON schemas', () => {
@@ -104,7 +104,8 @@ export class Storage {
104
104
  file.url = this._getFileUrl(file)
105
105
  // TODO: Support `config.readDimensions`, but this can only be done once
106
106
  // there are separate storage instances per model assets config!
107
- return this.convertAssetFile(file)
107
+ this.convertAssetFile(file)
108
+ return file
108
109
  }
109
110
 
110
111
  async removeFile(file) {
@@ -0,0 +1,12 @@
1
+ import { isString } from '@ditojs/utils'
2
+ import { isStream } from 'is-stream'
3
+
4
+ export function isSupportedKoaBody(body) {
5
+ return (
6
+ Buffer.isBuffer(body) ||
7
+ isString(body) ||
8
+ body instanceof Blob ||
9
+ body instanceof Response ||
10
+ isStream(body)
11
+ )
12
+ }
package/types/index.d.ts CHANGED
@@ -980,7 +980,7 @@ export type BaseControllerActionOptions = {
980
980
  *
981
981
  * @see {@link https://github.com/ditojs/dito/blob/master/docs/model-properties.md Model Properties}
982
982
  */
983
- returns?: Schema & { name?: string }
983
+ response?: Schema & { name?: string }
984
984
  /**
985
985
  * The scope(s) to be applied to every query executed through the action.
986
986
  *
@@ -1778,12 +1778,12 @@ export const authorize: (
1778
1778
  export const parameters: (params: { [key: string]: Schema }) => Mixin
1779
1779
 
1780
1780
  /**
1781
- * Apply the returns mixin to a controller action, in order to provide a schema
1781
+ * Apply the response mixin to a controller action, in order to provide a schema
1782
1782
  * for the value returned from the action handler and optionally map the value
1783
1783
  * to a key inside a returned object when it contains a `name` property.
1784
1784
  */
1785
- export const returns: (
1786
- returns: Schema & { name?: string },
1785
+ export const response: (
1786
+ response: Schema & { name?: string },
1787
1787
  options: any
1788
1788
  ) => Mixin
1789
1789
 
@@ -1,31 +0,0 @@
1
- import { resolve } from 'url'
2
- import { MissingRefError } from 'ajv'
3
- import { clone, mergeDeeply } from '@ditojs/utils'
4
-
5
- export const $extend = {
6
- macro(schemas, parentSchema, ctx) {
7
- const [source, ...patch] = schemas.map(schema => {
8
- const { $ref } = schema
9
- if ($ref) {
10
- const { baseId, self } = ctx
11
- const id =
12
- baseId && baseId !== '#'
13
- ? resolve(baseId, $ref)
14
- : $ref
15
- const validate = self.getSchema(id)
16
- if (!validate) {
17
- throw new MissingRefError(baseId, $ref)
18
- }
19
- schema = validate.schema
20
- }
21
- return schema
22
- })
23
- return mergeDeeply(clone(source), ...patch)
24
- },
25
-
26
- metaSchema: {
27
- type: 'array',
28
- items: { type: 'object' },
29
- minItems: 2
30
- }
31
- }
@@ -1,10 +0,0 @@
1
- export function createDecorator(handler) {
2
- return (target, key, descriptor) => {
3
- // Convert `descriptor.initializer()` to `descriptor.value`:
4
- if (descriptor.initializer) {
5
- descriptor.value = descriptor.initializer()
6
- delete descriptor.initializer
7
- }
8
- handler(descriptor.value)
9
- }
10
- }
File without changes