@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 +8 -7
- package/src/controllers/Controller.js +13 -2
- package/src/controllers/ControllerAction.js +44 -29
- package/src/models/Model.js +17 -14
- package/src/models/definitions/filters.js +17 -4
- package/src/schema/index.js +1 -1
- package/src/schema/keywords/index.js +0 -1
- package/src/schema/relations.js +2 -6
- package/src/schema/{properties.test.js → schema.test.js} +1 -1
- package/src/storage/Storage.js +2 -1
- package/src/utils/koa.js +12 -0
- package/types/index.d.ts +4 -4
- package/src/schema/keywords/_extend.js +0 -31
- package/src/utils/decorator.js +0 -10
- /package/src/schema/{properties.js → schema.js} +0 -0
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/server",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
29
|
-
"@ditojs/build": "^2.
|
|
30
|
-
"@ditojs/router": "^2.
|
|
31
|
-
"@ditojs/utils": "^2.
|
|
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
|
-
"
|
|
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, '
|
|
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.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
113
|
-
return this.
|
|
114
|
-
await this.controller.emitHook(`after:${identifier}`, true, ctx,
|
|
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
|
|
219
|
-
if (this.
|
|
220
|
-
const
|
|
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(
|
|
223
|
-
const
|
|
224
|
-
|
|
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 = () => (
|
|
236
|
+
const getResult = () => (responseWrapped ? wrapped : response)
|
|
230
237
|
try {
|
|
231
|
-
await this.
|
|
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:
|
|
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
|
|
257
|
+
return response
|
|
243
258
|
}
|
|
244
259
|
|
|
245
260
|
async collectArguments(ctx, params) {
|
package/src/models/Model.js
CHANGED
|
@@ -6,8 +6,9 @@ import {
|
|
|
6
6
|
isFunction,
|
|
7
7
|
isPromise,
|
|
8
8
|
asArray,
|
|
9
|
-
|
|
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
|
-
|
|
187
|
+
|
|
188
|
+
const shallow = !options.graph
|
|
192
189
|
if (shallow) {
|
|
193
|
-
|
|
194
|
-
|
|
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 = {
|
|
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
|
|
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 {
|
|
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, '
|
|
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 `
|
|
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
|
package/src/schema/index.js
CHANGED
package/src/schema/relations.js
CHANGED
|
@@ -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 })
|
package/src/storage/Storage.js
CHANGED
|
@@ -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
|
-
|
|
107
|
+
this.convertAssetFile(file)
|
|
108
|
+
return file
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
async removeFile(file) {
|
package/src/utils/koa.js
ADDED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
1786
|
-
|
|
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
|
-
}
|
package/src/utils/decorator.js
DELETED
|
@@ -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
|