@ditojs/server 1.13.0 → 1.14.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 +16 -15
- package/src/app/Application.js +198 -186
- package/src/controllers/CollectionController.js +18 -14
- package/src/controllers/Controller.js +42 -29
- package/src/controllers/ModelController.js +13 -4
- package/src/controllers/RelationController.js +9 -5
- package/src/lib/EventEmitter.js +1 -1
- package/src/mixins/UserMixin.js +1 -2
- package/src/models/Model.js +19 -11
- package/src/query/QueryBuilder.js +2 -2
- package/src/services/Service.js +4 -2
- package/src/storage/DiskStorage.js +1 -3
- package/src/storage/S3Storage.js +10 -4
- package/src/storage/Storage.js +10 -0
- package/types/index.d.ts +599 -543
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.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",
|
|
@@ -25,15 +25,14 @@
|
|
|
25
25
|
"node >= 18"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"@ditojs/
|
|
30
|
-
"@ditojs/
|
|
31
|
-
"@ditojs/
|
|
32
|
-
"@ditojs/utils": "^1.13.0",
|
|
28
|
+
"@ditojs/admin": "^1.14.0",
|
|
29
|
+
"@ditojs/build": "^1.14.0",
|
|
30
|
+
"@ditojs/router": "^1.14.0",
|
|
31
|
+
"@ditojs/utils": "^1.14.0",
|
|
33
32
|
"@koa/cors": "^4.0.0",
|
|
34
|
-
"@koa/multer": "^3.0.
|
|
33
|
+
"@koa/multer": "^3.0.2",
|
|
35
34
|
"@originjs/vite-plugin-commonjs": "^1.0.3",
|
|
36
|
-
"ajv": "^8.11.
|
|
35
|
+
"ajv": "^8.11.2",
|
|
37
36
|
"ajv-formats": "^2.1.1",
|
|
38
37
|
"bcryptjs": "^2.4.3",
|
|
39
38
|
"bytes": "^3.1.2",
|
|
@@ -69,17 +68,19 @@
|
|
|
69
68
|
"pluralize": "^8.0.0",
|
|
70
69
|
"repl": "^0.1.3",
|
|
71
70
|
"uuid": "^9.0.0",
|
|
72
|
-
"vite": "^3.2.
|
|
71
|
+
"vite": "^3.2.4",
|
|
73
72
|
"vite-plugin-vue2": "^2.0.2",
|
|
74
|
-
"vue": "^2.7.
|
|
75
|
-
"vue-template-compiler": "^2.7.
|
|
73
|
+
"vue": "^2.7.14",
|
|
74
|
+
"vue-template-compiler": "^2.7.14"
|
|
76
75
|
},
|
|
77
76
|
"peerDependencies": {
|
|
77
|
+
"@aws-sdk/client-s3": "^3.0.0",
|
|
78
78
|
"knex": ">=2.0.5",
|
|
79
79
|
"objection": "^3.0.1"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
|
-
"@
|
|
82
|
+
"@aws-sdk/client-s3": "^3.200.0",
|
|
83
|
+
"@types/koa-bodyparser": "^4.3.10",
|
|
83
84
|
"@types/koa-compress": "^4.0.3",
|
|
84
85
|
"@types/koa-logger": "^3.1.2",
|
|
85
86
|
"@types/koa-pino-logger": "^3.0.1",
|
|
@@ -90,9 +91,9 @@
|
|
|
90
91
|
"@types/node": "^18.11.9",
|
|
91
92
|
"knex": "^2.3.0",
|
|
92
93
|
"objection": "^3.0.1",
|
|
93
|
-
"type-fest": "^3.
|
|
94
|
-
"typescript": "^4.
|
|
94
|
+
"type-fest": "^3.2.0",
|
|
95
|
+
"typescript": "^4.9.3"
|
|
95
96
|
},
|
|
96
97
|
"types": "types",
|
|
97
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "ba197ae5254deb657b2c3b5dab7a851f488e022a"
|
|
98
99
|
}
|
package/src/app/Application.js
CHANGED
|
@@ -19,6 +19,7 @@ import session from 'koa-session'
|
|
|
19
19
|
import etag from 'koa-etag'
|
|
20
20
|
import helmet from 'koa-helmet'
|
|
21
21
|
import responseTime from 'koa-response-time'
|
|
22
|
+
import { Model, knexSnakeCaseMappers, ref } from 'objection'
|
|
22
23
|
import Router from '@ditojs/router'
|
|
23
24
|
import {
|
|
24
25
|
isArray, isObject, isString, asArray, isPlainObject, isModule,
|
|
@@ -31,7 +32,7 @@ import { Controller, AdminController } from '../controllers/index.js'
|
|
|
31
32
|
import { Service } from '../services/index.js'
|
|
32
33
|
import { Storage } from '../storage/index.js'
|
|
33
34
|
import { convertSchema } from '../schema/index.js'
|
|
34
|
-
import { formatJson } from '../utils/index.js'
|
|
35
|
+
import { deprecate, formatJson } from '../utils/index.js'
|
|
35
36
|
import {
|
|
36
37
|
ResponseError,
|
|
37
38
|
ValidationError,
|
|
@@ -47,12 +48,6 @@ import {
|
|
|
47
48
|
handleUser,
|
|
48
49
|
logRequests
|
|
49
50
|
} from '../middleware/index.js'
|
|
50
|
-
import {
|
|
51
|
-
Model,
|
|
52
|
-
BelongsToOneRelation,
|
|
53
|
-
knexSnakeCaseMappers,
|
|
54
|
-
ref
|
|
55
|
-
} from 'objection'
|
|
56
51
|
|
|
57
52
|
export class Application extends Koa {
|
|
58
53
|
constructor({
|
|
@@ -61,12 +56,12 @@ export class Application extends Koa {
|
|
|
61
56
|
router,
|
|
62
57
|
events,
|
|
63
58
|
middleware,
|
|
64
|
-
models,
|
|
65
59
|
services,
|
|
60
|
+
models,
|
|
66
61
|
controllers
|
|
67
62
|
} = {}) {
|
|
68
63
|
super()
|
|
69
|
-
this.
|
|
64
|
+
this._configureEmitter(events)
|
|
70
65
|
const {
|
|
71
66
|
// Pluck keys out of `config.app` to keep them secret
|
|
72
67
|
app: { keys, ...app } = {},
|
|
@@ -84,12 +79,13 @@ export class Application extends Koa {
|
|
|
84
79
|
this.router = router || new Router()
|
|
85
80
|
this.validator.app = this
|
|
86
81
|
this.storages = Object.create(null)
|
|
87
|
-
this.models = Object.create(null)
|
|
88
82
|
this.services = Object.create(null)
|
|
83
|
+
this.models = Object.create(null)
|
|
89
84
|
this.controllers = Object.create(null)
|
|
90
85
|
this.server = null
|
|
91
86
|
this.isRunning = false
|
|
92
87
|
|
|
88
|
+
// TODO: Rename setup to configure?
|
|
93
89
|
this.setupLogger()
|
|
94
90
|
this.setupKnex()
|
|
95
91
|
this.setupMiddleware(middleware)
|
|
@@ -97,17 +93,24 @@ export class Application extends Koa {
|
|
|
97
93
|
if (config.storages) {
|
|
98
94
|
this.addStorages(config.storages)
|
|
99
95
|
}
|
|
100
|
-
if (models) {
|
|
101
|
-
this.addModels(models)
|
|
102
|
-
}
|
|
103
96
|
if (services) {
|
|
104
97
|
this.addServices(services)
|
|
105
98
|
}
|
|
99
|
+
if (models) {
|
|
100
|
+
this.addModels(models)
|
|
101
|
+
}
|
|
106
102
|
if (controllers) {
|
|
107
103
|
this.addControllers(controllers)
|
|
108
104
|
}
|
|
109
105
|
}
|
|
110
106
|
|
|
107
|
+
async setup() {
|
|
108
|
+
await this.setupStorages()
|
|
109
|
+
await this.setupServices()
|
|
110
|
+
await this.setupModels()
|
|
111
|
+
await this.setupControllers()
|
|
112
|
+
}
|
|
113
|
+
|
|
111
114
|
addRoute(
|
|
112
115
|
method, path, transacted, middlewares, controller = null, action = null
|
|
113
116
|
) {
|
|
@@ -133,121 +136,56 @@ export class Application extends Koa {
|
|
|
133
136
|
this.router[method](path, route)
|
|
134
137
|
}
|
|
135
138
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
modelClass => models[modelClass.name] === modelClass
|
|
147
|
-
)
|
|
148
|
-
// Initialize the added models in correct sorted sequence, so that for every
|
|
149
|
-
// model, getRelatedRelations() returns the full list of relating relations.
|
|
150
|
-
for (const modelClass of sortedModels) {
|
|
151
|
-
if (models[modelClass.name] === modelClass) {
|
|
152
|
-
modelClass.setup(this.knex)
|
|
153
|
-
// Now that the modelClass is set up, call `initialize()`, which can be
|
|
154
|
-
// overridden by sub-classes,without having to call `super.initialize()`
|
|
155
|
-
modelClass.initialize()
|
|
156
|
-
this.validator.addSchema(modelClass.getJsonSchema())
|
|
139
|
+
getStorage(name) {
|
|
140
|
+
return this.storages[name] || null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
addStorage(config, name) {
|
|
144
|
+
let storage = null
|
|
145
|
+
if (isPlainObject(config)) {
|
|
146
|
+
const storageClass = Storage.get(config.type)
|
|
147
|
+
if (!storageClass) {
|
|
148
|
+
throw new Error(`Unsupported storage: ${config}`)
|
|
157
149
|
}
|
|
150
|
+
// eslint-disable-next-line new-cap
|
|
151
|
+
storage = new storageClass(this, config)
|
|
152
|
+
} else if (config instanceof Storage) {
|
|
153
|
+
storage = config
|
|
158
154
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const shouldLog = option => (
|
|
163
|
-
option === true ||
|
|
164
|
-
asArray(option).includes(modelClass.name)
|
|
165
|
-
)
|
|
166
|
-
const data = {}
|
|
167
|
-
if (shouldLog(log.schema)) {
|
|
168
|
-
data.schema = modelClass.getJsonSchema()
|
|
169
|
-
}
|
|
170
|
-
if (shouldLog(log.relations)) {
|
|
171
|
-
data.relations = clone(modelClass.relationMappings, value =>
|
|
172
|
-
Model.isPrototypeOf(value) ? `[Model: ${value.name}]` : value
|
|
173
|
-
)
|
|
174
|
-
}
|
|
175
|
-
if (Object.keys(data).length > 0) {
|
|
176
|
-
console.info(
|
|
177
|
-
pico.yellow(pico.bold(`\n${modelClass.name}:\n`)),
|
|
178
|
-
util.inspect(data, {
|
|
179
|
-
colors: true,
|
|
180
|
-
depth: null,
|
|
181
|
-
maxArrayLength: null
|
|
182
|
-
})
|
|
183
|
-
)
|
|
184
|
-
}
|
|
155
|
+
if (storage) {
|
|
156
|
+
if (name) {
|
|
157
|
+
storage.name = name
|
|
185
158
|
}
|
|
159
|
+
this.storages[storage.name] = storage
|
|
186
160
|
}
|
|
161
|
+
return storage
|
|
187
162
|
}
|
|
188
163
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
} else {
|
|
194
|
-
throw new Error(`Invalid model class: ${modelClass}`)
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
sortModels(models) {
|
|
199
|
-
const sortByRelations = (list, collected = {}, excluded = {}) => {
|
|
200
|
-
for (const modelClass of list) {
|
|
201
|
-
const { name } = modelClass
|
|
202
|
-
if (!collected[name] && !excluded[name]) {
|
|
203
|
-
for (const relation of Object.values(modelClass.getRelations())) {
|
|
204
|
-
if (!(relation instanceof BelongsToOneRelation)) {
|
|
205
|
-
const { relatedModelClass, joinTableModelClass } = relation
|
|
206
|
-
for (const related of [joinTableModelClass, relatedModelClass]) {
|
|
207
|
-
// Exclude self-references and generated join models:
|
|
208
|
-
if (related && related !== modelClass && models[related.name]) {
|
|
209
|
-
sortByRelations([related], collected, {
|
|
210
|
-
// Exclude modelClass to prevent endless recursions:
|
|
211
|
-
[name]: modelClass,
|
|
212
|
-
...excluded
|
|
213
|
-
})
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
collected[name] = modelClass
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return Object.values(collected)
|
|
222
|
-
}
|
|
223
|
-
// Return a new object with the sorted models as its key/value pairs.
|
|
224
|
-
// NOTE: We need to reverse for the above algorithm to sort properly,
|
|
225
|
-
// and then reverse the result back.
|
|
226
|
-
return sortByRelations(Object.values(models).reverse()).reverse().reduce(
|
|
227
|
-
(models, modelClass) => {
|
|
228
|
-
models[modelClass.name] = modelClass
|
|
229
|
-
return models
|
|
230
|
-
},
|
|
231
|
-
Object.create(null)
|
|
232
|
-
)
|
|
164
|
+
addStorages(storages) {
|
|
165
|
+
for (const [name, config] of Object.entries(storages)) {
|
|
166
|
+
this.addStorage(config, name)
|
|
167
|
+
}
|
|
233
168
|
}
|
|
234
169
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
170
|
+
async setupStorages() {
|
|
171
|
+
await Promise.all(Object.values(this.storages)
|
|
172
|
+
.filter(storage => !storage.initialized)
|
|
173
|
+
.map(async storage => {
|
|
174
|
+
// Different from models, services and controllers, storages can have
|
|
175
|
+
// async `setup()` methods, as used by `S3Storage`.
|
|
176
|
+
await storage.setup()
|
|
177
|
+
await storage.initialize()
|
|
178
|
+
storage.initialized = true
|
|
179
|
+
})
|
|
240
180
|
)
|
|
241
181
|
}
|
|
242
182
|
|
|
243
|
-
|
|
244
|
-
return
|
|
183
|
+
getService(name) {
|
|
184
|
+
return this.services[name] || null
|
|
245
185
|
}
|
|
246
186
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
this.addService(service, name)
|
|
250
|
-
}
|
|
187
|
+
findService(callback) {
|
|
188
|
+
return Object.values(this.services).find(callback) || null
|
|
251
189
|
}
|
|
252
190
|
|
|
253
191
|
addService(service, name) {
|
|
@@ -259,40 +197,126 @@ export class Application extends Koa {
|
|
|
259
197
|
if (!(service instanceof Service)) {
|
|
260
198
|
throw new Error(`Invalid service: ${service}`)
|
|
261
199
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
// As a convention, the configuration of a service can be set to `false`
|
|
270
|
-
// in order to entirely deactivate the service.
|
|
271
|
-
if (config !== false) {
|
|
272
|
-
service.setup(config)
|
|
273
|
-
this.services[name] = service
|
|
274
|
-
// Now that the service is set up, call `initialize()` which can be
|
|
275
|
-
// overridden by services.
|
|
276
|
-
service.initialize()
|
|
200
|
+
this.services[service.name] = service
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
addServices(services) {
|
|
204
|
+
for (const [name, service] of Object.entries(services)) {
|
|
205
|
+
this.addService(service, name)
|
|
277
206
|
}
|
|
278
207
|
}
|
|
279
208
|
|
|
280
|
-
|
|
281
|
-
|
|
209
|
+
async setupServices() {
|
|
210
|
+
await Promise.all(Object.values(this.services)
|
|
211
|
+
.filter(service => !service.initialized)
|
|
212
|
+
.map(async service => {
|
|
213
|
+
const { name } = service
|
|
214
|
+
const config = this.config.services[name]
|
|
215
|
+
if (config === undefined) {
|
|
216
|
+
throw new Error(`Configuration missing for service '${name}'`)
|
|
217
|
+
}
|
|
218
|
+
// As a convention, the configuration of a service can be set to `false`
|
|
219
|
+
// in order to entirely deactivate the service.
|
|
220
|
+
if (config === false) {
|
|
221
|
+
delete this.services[name]
|
|
222
|
+
} else {
|
|
223
|
+
service.setup(config)
|
|
224
|
+
await service.initialize()
|
|
225
|
+
service.initialized = true
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
)
|
|
282
229
|
}
|
|
283
230
|
|
|
284
|
-
|
|
285
|
-
return
|
|
231
|
+
getModel(name) {
|
|
232
|
+
return (
|
|
233
|
+
this.models[name] ||
|
|
234
|
+
!name.endsWith('Model') && this.models[`${name}Model`] ||
|
|
235
|
+
null
|
|
236
|
+
)
|
|
286
237
|
}
|
|
287
238
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
239
|
+
findModel(callback) {
|
|
240
|
+
return Object.values(this.models).find(callback) || null
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
addModel(modelClass) {
|
|
244
|
+
this.addModels([modelClass])
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
addModels(models) {
|
|
248
|
+
models = Object.values(models)
|
|
249
|
+
// First, add all models to the application, so that they can be referenced
|
|
250
|
+
// by other models, e.g. in `jsonSchema` and `relationMappings`:
|
|
251
|
+
for (const modelClass of models) {
|
|
252
|
+
if (Model.isPrototypeOf(modelClass)) {
|
|
253
|
+
this.models[modelClass.name] = modelClass
|
|
292
254
|
} else {
|
|
293
|
-
|
|
255
|
+
throw new Error(`Invalid model class: ${modelClass}`)
|
|
294
256
|
}
|
|
295
257
|
}
|
|
258
|
+
// Then, configure all models and add their schemas to the validator:
|
|
259
|
+
for (const modelClass of models) {
|
|
260
|
+
modelClass.configure(this)
|
|
261
|
+
this.validator.addSchema(modelClass.getJsonSchema())
|
|
262
|
+
}
|
|
263
|
+
this.logModels(models)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async setupModels() {
|
|
267
|
+
await Promise.all(Object.values(this.models)
|
|
268
|
+
.filter(modelClass => !modelClass.initialized)
|
|
269
|
+
.map(async modelClass => {
|
|
270
|
+
// While `setup()` is used for internal dito things, `initialize()` is
|
|
271
|
+
// called async and meant to be used by the user, without the need to
|
|
272
|
+
// call `super.initialize()`.
|
|
273
|
+
modelClass.setup()
|
|
274
|
+
await modelClass.initialize()
|
|
275
|
+
modelClass.initialized = true
|
|
276
|
+
})
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
logModels(models) {
|
|
281
|
+
const { log } = this.config
|
|
282
|
+
if (log.schema || log.relations) {
|
|
283
|
+
for (const modelClass of models) {
|
|
284
|
+
const shouldLog = option => (
|
|
285
|
+
option === true ||
|
|
286
|
+
asArray(option).includes(modelClass.name)
|
|
287
|
+
)
|
|
288
|
+
const data = {}
|
|
289
|
+
if (shouldLog(log.schema)) {
|
|
290
|
+
data.schema = modelClass.getJsonSchema()
|
|
291
|
+
}
|
|
292
|
+
if (shouldLog(log.relations)) {
|
|
293
|
+
data.relations = clone(
|
|
294
|
+
modelClass.getRelationMappings(),
|
|
295
|
+
value => Model.isPrototypeOf(value)
|
|
296
|
+
? `[Model: ${value.name}]`
|
|
297
|
+
: value
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
if (Object.keys(data).length > 0) {
|
|
301
|
+
console.info(
|
|
302
|
+
pico.yellow(pico.bold(`\n${modelClass.name}:\n`)),
|
|
303
|
+
util.inspect(data, {
|
|
304
|
+
colors: true,
|
|
305
|
+
depth: null,
|
|
306
|
+
maxArrayLength: null
|
|
307
|
+
})
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
getController(url) {
|
|
315
|
+
return this.controllers[url] || null
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
findController(callback) {
|
|
319
|
+
return Object.values(this.controllers).find(callback) || null
|
|
296
320
|
}
|
|
297
321
|
|
|
298
322
|
addController(controller, namespace) {
|
|
@@ -305,26 +329,36 @@ export class Application extends Koa {
|
|
|
305
329
|
throw new Error(`Invalid controller: ${controller}`)
|
|
306
330
|
}
|
|
307
331
|
// Inheritance of action methods cannot happen in the constructor itself,
|
|
308
|
-
// so call separate `
|
|
309
|
-
controller.
|
|
332
|
+
// so call separate `configure()` method after in order to take care of it.
|
|
333
|
+
controller.configure()
|
|
310
334
|
this.controllers[controller.url] = controller
|
|
311
|
-
// Now that the controller is set up, call `initialize()` which can be
|
|
312
|
-
// overridden by controllers.
|
|
313
|
-
controller.initialize()
|
|
314
|
-
// Each controller can also compose their own middleware (or app), e.g. as
|
|
315
|
-
// used in `AdminController`:
|
|
316
|
-
const composed = controller.compose()
|
|
317
|
-
if (composed) {
|
|
318
|
-
this.use(mount(controller.url, composed))
|
|
319
|
-
}
|
|
320
335
|
}
|
|
321
336
|
|
|
322
|
-
|
|
323
|
-
|
|
337
|
+
addControllers(controllers, namespace) {
|
|
338
|
+
for (const [key, value] of Object.entries(controllers)) {
|
|
339
|
+
if (isModule(value) || isPlainObject(value)) {
|
|
340
|
+
this.addControllers(value, namespace ? `${namespace}/${key}` : key)
|
|
341
|
+
} else {
|
|
342
|
+
this.addController(value, namespace)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
324
345
|
}
|
|
325
346
|
|
|
326
|
-
|
|
327
|
-
|
|
347
|
+
async setupControllers() {
|
|
348
|
+
await Promise.all(Object.values(this.controllers)
|
|
349
|
+
.filter(controller => !controller.initialized)
|
|
350
|
+
.map(async controller => {
|
|
351
|
+
controller.setup()
|
|
352
|
+
await controller.initialize()
|
|
353
|
+
// Each controller can also compose their own middleware (or app), e.g.
|
|
354
|
+
// as used in `AdminController`:
|
|
355
|
+
const composed = controller.compose()
|
|
356
|
+
if (composed) {
|
|
357
|
+
this.use(mount(controller.url, composed))
|
|
358
|
+
}
|
|
359
|
+
controller.initialized = true
|
|
360
|
+
})
|
|
361
|
+
)
|
|
328
362
|
}
|
|
329
363
|
|
|
330
364
|
getAdminController() {
|
|
@@ -377,37 +411,6 @@ export class Application extends Koa {
|
|
|
377
411
|
return assetConfig
|
|
378
412
|
}
|
|
379
413
|
|
|
380
|
-
addStorages(storages) {
|
|
381
|
-
for (const [name, config] of Object.entries(storages)) {
|
|
382
|
-
this.addStorage(config, name)
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
addStorage(config, name) {
|
|
387
|
-
let storage = null
|
|
388
|
-
if (isPlainObject(config)) {
|
|
389
|
-
const storageClass = Storage.get(config.type)
|
|
390
|
-
if (!storageClass) {
|
|
391
|
-
throw new Error(`Unsupported storage: ${config}`)
|
|
392
|
-
}
|
|
393
|
-
// eslint-disable-next-line new-cap
|
|
394
|
-
storage = new storageClass(this, config)
|
|
395
|
-
} else if (config instanceof Storage) {
|
|
396
|
-
storage = config
|
|
397
|
-
}
|
|
398
|
-
if (storage) {
|
|
399
|
-
if (name) {
|
|
400
|
-
storage.name = name
|
|
401
|
-
}
|
|
402
|
-
this.storages[storage.name] = storage
|
|
403
|
-
}
|
|
404
|
-
return storage
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
getStorage(name) {
|
|
408
|
-
return this.storages[name] || null
|
|
409
|
-
}
|
|
410
|
-
|
|
411
414
|
compileValidator(jsonSchema, options) {
|
|
412
415
|
return jsonSchema
|
|
413
416
|
? this.validator.compile(jsonSchema, options)
|
|
@@ -715,6 +718,10 @@ export class Application extends Koa {
|
|
|
715
718
|
if (this.config.log.errors !== false) {
|
|
716
719
|
this.on('error', this.logError)
|
|
717
720
|
}
|
|
721
|
+
// It's ok to call this multiple times, because only the entries in the
|
|
722
|
+
// registres (storages, services, models, controllers) that weren't
|
|
723
|
+
// initialized yet will be initialized.
|
|
724
|
+
await this.setup()
|
|
718
725
|
await this.emit('before:start')
|
|
719
726
|
this.server = await new Promise(resolve => {
|
|
720
727
|
const server = this.listen(this.config.server, () => {
|
|
@@ -771,7 +778,7 @@ export class Application extends Koa {
|
|
|
771
778
|
}
|
|
772
779
|
}
|
|
773
780
|
|
|
774
|
-
async
|
|
781
|
+
async execute() {
|
|
775
782
|
try {
|
|
776
783
|
await this.start()
|
|
777
784
|
} catch (err) {
|
|
@@ -780,6 +787,11 @@ export class Application extends Koa {
|
|
|
780
787
|
}
|
|
781
788
|
}
|
|
782
789
|
|
|
790
|
+
startOrExit() {
|
|
791
|
+
deprecate(`app.startOrExit() is deprecated. Call app.execute() instead.`)
|
|
792
|
+
return this.execute()
|
|
793
|
+
}
|
|
794
|
+
|
|
783
795
|
// Assets handling
|
|
784
796
|
|
|
785
797
|
async createAssets(storage, files, count = 0, trx = null) {
|
|
@@ -4,27 +4,31 @@ import { ControllerError } from '../errors/index.js'
|
|
|
4
4
|
|
|
5
5
|
// Abstract base class for ModelController and RelationController
|
|
6
6
|
export class CollectionController extends Controller {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
graph = false
|
|
8
|
+
scope = null
|
|
9
|
+
relate = false
|
|
10
|
+
unrelate = false
|
|
11
|
+
modelClass = null // To be defined by sub-classes
|
|
12
|
+
isOneToOne = false
|
|
13
|
+
idParam = null
|
|
14
|
+
idValidator = null
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
// @override
|
|
17
|
+
configure() {
|
|
18
|
+
super.configure()
|
|
17
19
|
this.idParam = this.level ? `id${this.level}` : 'id'
|
|
18
|
-
this.graph = !!this.graph
|
|
19
|
-
this.transacted = !!this.transacted
|
|
20
|
-
this.scope = this.scope || null
|
|
21
|
-
this.collection = this.setupActions('collection')
|
|
22
|
-
this.member = this.isOneToOne ? {} : this.setupActions('member')
|
|
23
20
|
// Create a dummy model instance to validate the requested id against.
|
|
24
21
|
// eslint-disable-next-line new-cap
|
|
25
22
|
this.idValidator = new this.modelClass()
|
|
26
23
|
}
|
|
27
24
|
|
|
25
|
+
// @override
|
|
26
|
+
setup() {
|
|
27
|
+
this.collection = this.setupActions('collection')
|
|
28
|
+
this.member = this.isOneToOne ? {} : this.setupActions('member')
|
|
29
|
+
this.assets = this.setupAssets()
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
// @override
|
|
29
33
|
setupAssets() {
|
|
30
34
|
const { modelClass } = this
|