@ditojs/server 1.13.1 → 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 +52 -48
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.1",
|
|
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
|
|
@@ -15,36 +15,35 @@ import {
|
|
|
15
15
|
} from '@ditojs/utils'
|
|
16
16
|
|
|
17
17
|
export class Controller {
|
|
18
|
+
name = null
|
|
19
|
+
path = null
|
|
20
|
+
url = null
|
|
21
|
+
actions = null
|
|
22
|
+
assets = null
|
|
23
|
+
transacted = false
|
|
24
|
+
initialized = false
|
|
25
|
+
|
|
18
26
|
constructor(app, namespace) {
|
|
19
27
|
this.app = app
|
|
20
|
-
this.namespace =
|
|
28
|
+
this.namespace = namespace
|
|
21
29
|
this.logging = this.app.config.log.routes
|
|
22
30
|
this.level = 0
|
|
23
31
|
}
|
|
24
32
|
|
|
33
|
+
// `configure()` is called right after the constructor, but before `setup()`
|
|
34
|
+
// which sets up the actions and routes, and the custom `async initialize()`.
|
|
25
35
|
// @overridable
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// @return {Application|Function} [app or function]
|
|
30
|
-
compose() {
|
|
31
|
-
// To be overridden in sub-classes, if the controller needs to install
|
|
32
|
-
// middleware. For normal routes, use `this.app.addRoute()` instead.
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
setup(isRoot = true, setupActionsObject = true) {
|
|
36
|
-
this._setupEmitter(this.hooks, {
|
|
36
|
+
configure() {
|
|
37
|
+
this._configureEmitter(this.hooks, {
|
|
37
38
|
// Support wildcard hooks only on controllers:
|
|
38
39
|
wildcard: true
|
|
39
40
|
})
|
|
40
41
|
// If the class name ends in 'Controller', remove it from controller name.
|
|
41
|
-
this.name
|
|
42
|
-
|
|
43
|
-
if (this.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.transacted = !!this.transacted
|
|
47
|
-
if (isRoot) {
|
|
42
|
+
this.name ||= this.constructor.name.match(/^(.*?)(?:Controller|)$/)[1]
|
|
43
|
+
this.path ??= this.app.normalizePath(this.name)
|
|
44
|
+
if (!this.url) {
|
|
45
|
+
// NOTE: `RelationController.configure()` sets the `url` before calling
|
|
46
|
+
// `super.configure()` and has its own handling of logging.
|
|
48
47
|
const { path, namespace } = this
|
|
49
48
|
// TODO: The distinction between `url` and `path` is a bit tricky, since
|
|
50
49
|
// what we call `url` here is called `path` in Router, and may contain
|
|
@@ -61,16 +60,30 @@ export class Controller {
|
|
|
61
60
|
}`,
|
|
62
61
|
this.level
|
|
63
62
|
)
|
|
64
|
-
if (setupActionsObject) {
|
|
65
|
-
this.actions = this.actions || this.reflectActionsObject()
|
|
66
|
-
// Now that the instance fields are reflected in the `controller` object
|
|
67
|
-
// we can use the normal inheritance mechanism through `setupActions()`:
|
|
68
|
-
this.actions = this.setupActions('actions')
|
|
69
|
-
}
|
|
70
|
-
this.assets = this.setupAssets()
|
|
71
63
|
}
|
|
72
64
|
}
|
|
73
65
|
|
|
66
|
+
// @overridable
|
|
67
|
+
setup() {
|
|
68
|
+
this.actions ||= this.reflectActionsObject()
|
|
69
|
+
// Now that the instance fields are reflected in the `controller` object
|
|
70
|
+
// we can use the normal inheritance mechanism through `setupActions()`:
|
|
71
|
+
this.actions = this.setupActions('actions')
|
|
72
|
+
this.assets = this.setupAssets()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// @overridable
|
|
76
|
+
async initialize() {
|
|
77
|
+
// To be overridden in sub-classes, if the controller needs to initialize.
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// @return {Application|Function} [app or function]
|
|
81
|
+
// @overridable
|
|
82
|
+
compose() {
|
|
83
|
+
// To be overridden in sub-classes, if the controller needs to install
|
|
84
|
+
// middleware. For normal routes, use `this.app.addRoute()` instead.
|
|
85
|
+
}
|
|
86
|
+
|
|
74
87
|
reflectActionsObject() {
|
|
75
88
|
// On base controllers, the actions can be defined directly in the class
|
|
76
89
|
// instead of inside an actions object, as is done with model and relation
|
|
@@ -78,7 +91,7 @@ export class Controller {
|
|
|
78
91
|
// these other controllers, we reflect these instance fields in a separate
|
|
79
92
|
// `actions` object.
|
|
80
93
|
const { allow } = this
|
|
81
|
-
const
|
|
94
|
+
const actions = allow ? { allow } : {}
|
|
82
95
|
|
|
83
96
|
const addAction = key => {
|
|
84
97
|
const value = this[key]
|
|
@@ -86,7 +99,7 @@ export class Controller {
|
|
|
86
99
|
// in turn sets the `method` property on the method, as well as action
|
|
87
100
|
// objects which provide the `method` property:
|
|
88
101
|
if (value?.method) {
|
|
89
|
-
|
|
102
|
+
actions[key] = value
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
// Use `Object.getOwnPropertyNames()` to get the fields, in order to
|
|
@@ -95,7 +108,7 @@ export class Controller {
|
|
|
95
108
|
const proto = Object.getPrototypeOf(this)
|
|
96
109
|
Object.getOwnPropertyNames(proto).forEach(addAction)
|
|
97
110
|
Object.getOwnPropertyNames(this).forEach(addAction)
|
|
98
|
-
return
|
|
111
|
+
return actions
|
|
99
112
|
}
|
|
100
113
|
|
|
101
114
|
setupRoute(method, url, transacted, authorize, action, middlewares) {
|
|
@@ -6,10 +6,14 @@ import { ControllerError } from '../errors/index.js'
|
|
|
6
6
|
import { setupPropertyInheritance } from '../utils/index.js'
|
|
7
7
|
|
|
8
8
|
export class ModelController extends CollectionController {
|
|
9
|
-
|
|
10
|
-
super.
|
|
11
|
-
this.modelClass
|
|
9
|
+
configure() {
|
|
10
|
+
super.configure()
|
|
11
|
+
this.modelClass ||=
|
|
12
12
|
this.app.models[camelize(pluralize.singular(this.name), true)]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setup() {
|
|
16
|
+
super.setup()
|
|
13
17
|
this.relations = this.setupRelations()
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -36,9 +40,14 @@ export class ModelController extends CollectionController {
|
|
|
36
40
|
if (!relationInstance || !relationDefinition) {
|
|
37
41
|
throw new ControllerError(this, `Relation '${name}' not found.`)
|
|
38
42
|
}
|
|
39
|
-
|
|
43
|
+
const relation = new RelationController(
|
|
40
44
|
this, object, relationInstance, relationDefinition
|
|
41
45
|
)
|
|
46
|
+
// RelationController instances are not registered with the app, but are
|
|
47
|
+
// manged by their parent controller instead.
|
|
48
|
+
relation.configure()
|
|
49
|
+
relation.setup()
|
|
50
|
+
return relation
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
// @override
|
|
@@ -29,10 +29,6 @@ export class RelationController extends CollectionController {
|
|
|
29
29
|
// relations:
|
|
30
30
|
this.scope = asArray(parent.scope).filter(scope => getScope(scope).graph)
|
|
31
31
|
}
|
|
32
|
-
// Initialize:
|
|
33
|
-
this.path = this.app.normalizePath(this.name)
|
|
34
|
-
this.url = `${this.parent.url}/${this.parent.getPath('member', this.path)}`
|
|
35
|
-
this.log(`${pico.blue(this.path)}${pico.white(':')}`, this.level)
|
|
36
32
|
// Copy over all fields in the relation object except the ones that are
|
|
37
33
|
// going to be inherited in `setup()` (relation, member, allow), for
|
|
38
34
|
// settings like scope, etc.
|
|
@@ -41,7 +37,15 @@ export class RelationController extends CollectionController {
|
|
|
41
37
|
this[key] = this.object[key]
|
|
42
38
|
}
|
|
43
39
|
}
|
|
44
|
-
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// @override
|
|
43
|
+
configure() {
|
|
44
|
+
// Setup the `url` before calling `super.configure()` to override its
|
|
45
|
+
// default behavior for `RelationController`:
|
|
46
|
+
this.url = `${this.parent.url}/${this.parent.getPath('member', this.path)}`
|
|
47
|
+
this.log(`${pico.blue(this.path)}${pico.white(':')}`, this.level)
|
|
48
|
+
super.configure()
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
// @override
|
package/src/lib/EventEmitter.js
CHANGED
|
@@ -3,7 +3,7 @@ import { isPlainObject, isString, isArray, asArray } from '@ditojs/utils'
|
|
|
3
3
|
|
|
4
4
|
export class EventEmitter extends EventEmitter2 {
|
|
5
5
|
// Method for classes that use `EventEmitter.mixin()` to setup the emitter.
|
|
6
|
-
|
|
6
|
+
_configureEmitter(events, options) {
|
|
7
7
|
EventEmitter2.call(this, {
|
|
8
8
|
delimiter: ':',
|
|
9
9
|
maxListeners: 0,
|
package/src/mixins/UserMixin.js
CHANGED
package/src/models/Model.js
CHANGED
|
@@ -23,6 +23,10 @@ import RelationAccessor from './RelationAccessor.js'
|
|
|
23
23
|
import definitions from './definitions/index.js'
|
|
24
24
|
|
|
25
25
|
export class Model extends objection.Model {
|
|
26
|
+
static app = null // Set by `Application.addModel()`
|
|
27
|
+
static initialized = false
|
|
28
|
+
static referenceValidator = null
|
|
29
|
+
|
|
26
30
|
// Define a default constructor to allow new Model(json) as a short-cut to
|
|
27
31
|
// `Model.fromJson(json, { skipValidation: true })`
|
|
28
32
|
constructor(json) {
|
|
@@ -32,19 +36,24 @@ export class Model extends objection.Model {
|
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
static
|
|
36
|
-
this.
|
|
39
|
+
static configure(app) {
|
|
40
|
+
this.app = app
|
|
41
|
+
this.knex(app.knex)
|
|
42
|
+
const { hooks, assets } = this.definition
|
|
43
|
+
this._configureEmitter(hooks)
|
|
44
|
+
if (assets) {
|
|
45
|
+
this._configureAssetsEvents(assets)
|
|
46
|
+
}
|
|
37
47
|
try {
|
|
38
48
|
for (const relation of Object.values(this.getRelations())) {
|
|
39
|
-
this.
|
|
49
|
+
this._configureRelation(relation)
|
|
40
50
|
}
|
|
41
51
|
} catch (error) {
|
|
42
52
|
throw error instanceof RelationError ? error : new RelationError(error)
|
|
43
53
|
}
|
|
44
|
-
this.referenceValidator = null
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
static
|
|
56
|
+
static _configureRelation(relation) {
|
|
48
57
|
// Add this relation to the related model's relatedRelations, so it can
|
|
49
58
|
// register all required foreign keys in its properties.
|
|
50
59
|
relation.relatedModelClass.getRelatedRelations().push(relation)
|
|
@@ -87,13 +96,12 @@ export class Model extends objection.Model {
|
|
|
87
96
|
defineAccessor(this.prototype, false)
|
|
88
97
|
}
|
|
89
98
|
|
|
99
|
+
// @overridable
|
|
100
|
+
static setup() {
|
|
101
|
+
}
|
|
102
|
+
|
|
90
103
|
// @overridable
|
|
91
104
|
static initialize() {
|
|
92
|
-
const { hooks, assets } = this.definition
|
|
93
|
-
this._setupEmitter(hooks)
|
|
94
|
-
if (assets) {
|
|
95
|
-
this._setupAssetsEvents(assets)
|
|
96
|
-
}
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
// @overridable
|
|
@@ -900,7 +908,7 @@ export class Model extends objection.Model {
|
|
|
900
908
|
|
|
901
909
|
// Assets handling
|
|
902
910
|
|
|
903
|
-
static
|
|
911
|
+
static _configureAssetsEvents(assets) {
|
|
904
912
|
const assetDataPaths = Object.keys(assets)
|
|
905
913
|
|
|
906
914
|
this.on([
|
|
@@ -167,7 +167,7 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
allowScope(...scopes) {
|
|
170
|
-
this._allowScopes
|
|
170
|
+
this._allowScopes ||= {
|
|
171
171
|
default: true // The default scope is always allowed.
|
|
172
172
|
}
|
|
173
173
|
for (const expr of scopes) {
|
|
@@ -279,7 +279,7 @@ export class QueryBuilder extends objection.QueryBuilder {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
allowFilter(...filters) {
|
|
282
|
-
this._allowFilters
|
|
282
|
+
this._allowFilters ||= {}
|
|
283
283
|
for (const filter of filters) {
|
|
284
284
|
this._allowFilters[filter] = true
|
|
285
285
|
}
|
package/src/services/Service.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { camelize } from '@ditojs/utils'
|
|
2
2
|
|
|
3
3
|
export class Service {
|
|
4
|
+
initialized = false
|
|
5
|
+
|
|
4
6
|
constructor(app, name) {
|
|
5
7
|
this.app = app
|
|
6
8
|
this.name = camelize(
|
|
@@ -16,7 +18,7 @@ export class Service {
|
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
// @overridable
|
|
19
|
-
initialize() {
|
|
21
|
+
async initialize() {
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
// @overridable
|
|
@@ -34,7 +36,7 @@ export class Service {
|
|
|
34
36
|
|
|
35
37
|
get logger() {
|
|
36
38
|
const value = this.getLogger()
|
|
37
|
-
Object.
|
|
39
|
+
Object.defineProperty(this, 'logger', { value })
|
|
38
40
|
return value
|
|
39
41
|
}
|
|
40
42
|
}
|
|
@@ -6,12 +6,10 @@ import { Storage } from './Storage.js'
|
|
|
6
6
|
export class DiskStorage extends Storage {
|
|
7
7
|
static type = 'disk'
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
super(app, config)
|
|
9
|
+
setup() {
|
|
11
10
|
if (!this.path) {
|
|
12
11
|
throw new Error(`Missing configuration (path) for storage ${this.name}`)
|
|
13
12
|
}
|
|
14
|
-
|
|
15
13
|
this.storage = multer.diskStorage({
|
|
16
14
|
destination: (req, storageFile, cb) => {
|
|
17
15
|
// Add `storageFile.key` property to internal storage file object.
|
package/src/storage/S3Storage.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { S3 } from '@aws-sdk/client-s3'
|
|
2
1
|
import multerS3 from 'multer-s3'
|
|
3
2
|
import { fileTypeFromBuffer } from 'file-type'
|
|
4
3
|
import isSvg from 'is-svg'
|
|
@@ -9,15 +8,22 @@ import consumers from 'stream/consumers'
|
|
|
9
8
|
export class S3Storage extends Storage {
|
|
10
9
|
static type = 's3'
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
s3 = null
|
|
12
|
+
acl = null
|
|
13
|
+
bucket = null
|
|
14
|
+
|
|
15
|
+
async setup() {
|
|
14
16
|
const {
|
|
15
17
|
name,
|
|
16
18
|
s3,
|
|
17
19
|
acl,
|
|
18
20
|
bucket,
|
|
19
21
|
...options
|
|
20
|
-
} = config
|
|
22
|
+
} = this.config
|
|
23
|
+
|
|
24
|
+
// "@aws-sdk/client-s3" is a peer-dependency, and importing it costly,
|
|
25
|
+
// so we do it lazily.
|
|
26
|
+
const { S3 } = await import('@aws-sdk/client-s3')
|
|
21
27
|
this.s3 = new S3(s3)
|
|
22
28
|
this.acl = acl
|
|
23
29
|
this.bucket = bucket
|
package/src/storage/Storage.js
CHANGED
|
@@ -10,6 +10,8 @@ import { AssetFile } from './AssetFile.js'
|
|
|
10
10
|
const storageClasses = {}
|
|
11
11
|
|
|
12
12
|
export class Storage {
|
|
13
|
+
initialized = false
|
|
14
|
+
|
|
13
15
|
constructor(app, config) {
|
|
14
16
|
this.app = app
|
|
15
17
|
this.config = config
|
|
@@ -20,6 +22,14 @@ export class Storage {
|
|
|
20
22
|
this.storage = null
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
// @overridable
|
|
26
|
+
async setup() {
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// @overridable
|
|
30
|
+
async initialize() {
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
static register(storageClass) {
|
|
24
34
|
const type = (
|
|
25
35
|
storageClass.type ||
|
package/types/index.d.ts
CHANGED
|
@@ -27,11 +27,13 @@ import {
|
|
|
27
27
|
Class,
|
|
28
28
|
ConditionalExcept,
|
|
29
29
|
ConditionalKeys,
|
|
30
|
-
Constructor,
|
|
30
|
+
Constructor,
|
|
31
|
+
SetOptional,
|
|
32
|
+
SetReturnType
|
|
31
33
|
} from 'type-fest'
|
|
32
34
|
import { UserConfig } from 'vite'
|
|
33
35
|
|
|
34
|
-
export type Page<$Model extends Model> = {
|
|
36
|
+
export type Page<$Model extends Model = Model> = {
|
|
35
37
|
total: number
|
|
36
38
|
results: $Model[]
|
|
37
39
|
}
|
|
@@ -357,14 +359,14 @@ export interface ApiConfig {
|
|
|
357
359
|
|
|
358
360
|
export interface ApplicationControllers {
|
|
359
361
|
[k: string]:
|
|
360
|
-
| Class<ModelController
|
|
362
|
+
| Class<ModelController>
|
|
361
363
|
| Class<Controller>
|
|
362
364
|
| ApplicationControllers
|
|
363
365
|
}
|
|
364
366
|
|
|
365
367
|
export type Models = Record<string, Class<Model>>
|
|
366
368
|
|
|
367
|
-
export class Application<$Models extends Models> {
|
|
369
|
+
export class Application<$Models extends Models = Models> {
|
|
368
370
|
constructor(options: {
|
|
369
371
|
config?: ApplicationConfig
|
|
370
372
|
validator?: Validator
|
|
@@ -383,17 +385,22 @@ export class Application<$Models extends Models> {
|
|
|
383
385
|
})
|
|
384
386
|
|
|
385
387
|
models: $Models
|
|
388
|
+
setup(): Promise<void>
|
|
389
|
+
execute(): Promise<void>
|
|
386
390
|
start(): Promise<void>
|
|
387
391
|
stop(timeout?: number): Promise<void>
|
|
388
|
-
|
|
389
|
-
|
|
392
|
+
addStorage(storage: StorageConfig): void
|
|
393
|
+
addStorages(storages: StorageConfigs): void
|
|
394
|
+
setupStorages(): Promise<void>
|
|
390
395
|
addService(service: Service): void
|
|
396
|
+
addServices(services: Services): void
|
|
397
|
+
setupServices(): Promise<void>
|
|
398
|
+
addModel(model: Class<Model>): void
|
|
399
|
+
addModels(models: Models): void
|
|
400
|
+
setupModels(): Promise<void>
|
|
391
401
|
addController(controllers: Controller, namespace?: string): void
|
|
392
402
|
addControllers(controllers: ApplicationControllers, namespace?: string): void
|
|
393
|
-
|
|
394
|
-
addStorage(storage: StorageConfig): void
|
|
395
|
-
addModels(models: Models): void
|
|
396
|
-
addModel(model: Class<Model>): void
|
|
403
|
+
setupControllers(): Promise<void>
|
|
397
404
|
getAdminViteConfig(config?: UserConfig): UserConfig
|
|
398
405
|
}
|
|
399
406
|
export interface Application
|
|
@@ -564,23 +571,23 @@ export type ModelProperty<T = any> = Schema<T> & {
|
|
|
564
571
|
hidden?: boolean
|
|
565
572
|
}
|
|
566
573
|
|
|
567
|
-
export type ModelScope<$Model extends Model> = (
|
|
574
|
+
export type ModelScope<$Model extends Model = Model> = (
|
|
568
575
|
this: $Model,
|
|
569
576
|
query: QueryBuilder<$Model>,
|
|
570
577
|
applyParentScope: (query: QueryBuilder<$Model>) => QueryBuilder<$Model>
|
|
571
578
|
) => QueryBuilder<$Model, any> | void
|
|
572
579
|
|
|
573
|
-
export type ModelScopes<$Model extends Model> = Record<
|
|
580
|
+
export type ModelScopes<$Model extends Model = Model> = Record<
|
|
574
581
|
string,
|
|
575
582
|
ModelScope<$Model>
|
|
576
583
|
>
|
|
577
584
|
|
|
578
|
-
export type ModelFilterFunction<$Model extends Model> = (
|
|
585
|
+
export type ModelFilterFunction<$Model extends Model = Model> = (
|
|
579
586
|
queryBuilder: QueryBuilder<$Model>,
|
|
580
587
|
...args: any[]
|
|
581
588
|
) => void
|
|
582
589
|
|
|
583
|
-
export type ModelFilter<$Model extends Model> =
|
|
590
|
+
export type ModelFilter<$Model extends Model = Model> =
|
|
584
591
|
| {
|
|
585
592
|
filter: 'text' | 'date-range'
|
|
586
593
|
properties?: string[]
|
|
@@ -593,7 +600,7 @@ export type ModelFilter<$Model extends Model> =
|
|
|
593
600
|
}
|
|
594
601
|
| ModelFilterFunction<$Model>
|
|
595
602
|
|
|
596
|
-
export type ModelFilters<$Model extends Model> = Record<
|
|
603
|
+
export type ModelFilters<$Model extends Model = Model> = Record<
|
|
597
604
|
string,
|
|
598
605
|
ModelFilter<$Model>
|
|
599
606
|
>
|
|
@@ -614,7 +621,8 @@ export interface ModelOptions extends objection.ModelOptions {
|
|
|
614
621
|
type ModelHookFunction<$Model extends Model> = (
|
|
615
622
|
args: objection.StaticHookArguments<$Model>
|
|
616
623
|
) => void
|
|
617
|
-
|
|
624
|
+
|
|
625
|
+
export type ModelHooks<$Model extends Model = Model> = {
|
|
618
626
|
[key in `${'before' | 'after'}:${
|
|
619
627
|
| 'find'
|
|
620
628
|
| 'insert'
|
|
@@ -823,7 +831,7 @@ export type ModelRelations = Record<string, ModelRelation>
|
|
|
823
831
|
|
|
824
832
|
export type ModelProperties = Record<string, ModelProperty>
|
|
825
833
|
|
|
826
|
-
export type ControllerAction<$Controller extends Controller> =
|
|
834
|
+
export type ControllerAction<$Controller extends Controller = Controller> =
|
|
827
835
|
| ControllerActionOptions<$Controller>
|
|
828
836
|
| ControllerActionHandler<$Controller>
|
|
829
837
|
|
|
@@ -860,11 +868,12 @@ export class Controller {
|
|
|
860
868
|
authorize?: Authorize
|
|
861
869
|
actions?: ControllerActions<this>
|
|
862
870
|
|
|
871
|
+
configure(): void
|
|
872
|
+
setup(): void
|
|
863
873
|
initialize(): void
|
|
864
|
-
setup(isRoot: boolean, setupActionsObject: boolean): void
|
|
865
874
|
// TODO: type reflectActionsObject
|
|
866
875
|
reflectActionsObject(): any
|
|
867
|
-
setupRoute<$ControllerAction extends ControllerAction
|
|
876
|
+
setupRoute<$ControllerAction extends ControllerAction = ControllerAction>(
|
|
868
877
|
method: HTTPMethod,
|
|
869
878
|
url: string,
|
|
870
879
|
transacted: boolean,
|
|
@@ -915,14 +924,12 @@ export class Controller {
|
|
|
915
924
|
export type ActionParameter = Schema & { name: string }
|
|
916
925
|
|
|
917
926
|
export type ModelControllerActionHandler<
|
|
918
|
-
$ModelController extends ModelController
|
|
927
|
+
$ModelController extends ModelController = ModelController
|
|
919
928
|
> = (this: $ModelController, ctx: KoaContext, ...args: any[]) => any
|
|
920
929
|
|
|
921
|
-
export type ControllerActionHandler
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
...args: any[]
|
|
925
|
-
) => any
|
|
930
|
+
export type ControllerActionHandler<
|
|
931
|
+
$Controller extends Controller = Controller
|
|
932
|
+
> = (this: $Controller, ctx: KoaContext, ...args: any[]) => any
|
|
926
933
|
|
|
927
934
|
export type ExtractModelProperties<$Model> = {
|
|
928
935
|
[$Key in SelectModelPropertyKeys<$Model>]: $Model[$Key] extends Model
|
|
@@ -1005,19 +1012,20 @@ export type BaseControllerActionOptions = {
|
|
|
1005
1012
|
transacted?: boolean
|
|
1006
1013
|
}
|
|
1007
1014
|
|
|
1008
|
-
export type ControllerActionOptions
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1015
|
+
export type ControllerActionOptions<
|
|
1016
|
+
$Controller extends Controller = Controller
|
|
1017
|
+
> = BaseControllerActionOptions & {
|
|
1018
|
+
handler: ControllerActionHandler<$Controller>
|
|
1019
|
+
}
|
|
1012
1020
|
|
|
1013
1021
|
export type ModelControllerActionOptions<
|
|
1014
|
-
$ModelController extends ModelController
|
|
1022
|
+
$ModelController extends ModelController = ModelController
|
|
1015
1023
|
> = BaseControllerActionOptions & {
|
|
1016
1024
|
/** The function to be called when the action route is requested. */
|
|
1017
1025
|
handler: ModelControllerActionHandler<$ModelController>
|
|
1018
1026
|
}
|
|
1019
1027
|
|
|
1020
|
-
export type MemberActionParameter
|
|
1028
|
+
export type MemberActionParameter<$Model extends Model = Model> =
|
|
1021
1029
|
| Schema
|
|
1022
1030
|
| {
|
|
1023
1031
|
member: true
|
|
@@ -1037,17 +1045,17 @@ export type MemberActionParameter<M extends Model> =
|
|
|
1037
1045
|
*/
|
|
1038
1046
|
forUpdate?: boolean
|
|
1039
1047
|
/** Modify the member query. */
|
|
1040
|
-
modify?: (query: QueryBuilder
|
|
1048
|
+
modify?: (query: QueryBuilder<$Model>) => QueryBuilder<$Model>
|
|
1041
1049
|
}
|
|
1042
1050
|
|
|
1043
1051
|
export type ModelControllerAction<
|
|
1044
|
-
$ModelController extends ModelController
|
|
1052
|
+
$ModelController extends ModelController = ModelController
|
|
1045
1053
|
> =
|
|
1046
1054
|
| ModelControllerActionOptions<$ModelController>
|
|
1047
1055
|
| ModelControllerActionHandler<$ModelController>
|
|
1048
1056
|
|
|
1049
1057
|
export type ModelControllerActions<
|
|
1050
|
-
$ModelController extends ModelController
|
|
1058
|
+
$ModelController extends ModelController = ModelController
|
|
1051
1059
|
> = {
|
|
1052
1060
|
[name: ControllerActionName]: ModelControllerAction<$ModelController>
|
|
1053
1061
|
allow?: OrReadOnly<ControllerActionName[]>
|
|
@@ -1055,19 +1063,19 @@ export type ModelControllerActions<
|
|
|
1055
1063
|
}
|
|
1056
1064
|
|
|
1057
1065
|
type ModelControllerMemberAction<
|
|
1058
|
-
$ModelController extends ModelController
|
|
1066
|
+
$ModelController extends ModelController = ModelController
|
|
1059
1067
|
> =
|
|
1060
1068
|
| (Omit<ModelControllerActionOptions<$ModelController>, 'parameters'> & {
|
|
1061
1069
|
parameters?: {
|
|
1062
1070
|
[key: string]: MemberActionParameter<
|
|
1063
|
-
|
|
1071
|
+
ModelFromModelController<$ModelController>
|
|
1064
1072
|
>
|
|
1065
1073
|
}
|
|
1066
1074
|
})
|
|
1067
1075
|
| ModelControllerActionHandler<$ModelController>
|
|
1068
1076
|
|
|
1069
1077
|
export type ModelControllerMemberActions<
|
|
1070
|
-
$ModelController extends ModelController
|
|
1078
|
+
$ModelController extends ModelController = ModelController
|
|
1071
1079
|
> = {
|
|
1072
1080
|
[name: ControllerActionName]: ModelControllerMemberAction<$ModelController>
|
|
1073
1081
|
allow?: OrReadOnly<ControllerActionName[]>
|
|
@@ -1076,7 +1084,7 @@ export type ModelControllerMemberActions<
|
|
|
1076
1084
|
|
|
1077
1085
|
export type ControllerActionName = `${HTTPMethod}${string}`
|
|
1078
1086
|
|
|
1079
|
-
export type ControllerActions<$Controller extends Controller> = {
|
|
1087
|
+
export type ControllerActions<$Controller extends Controller = Controller> = {
|
|
1080
1088
|
[name: ControllerActionName]: ControllerAction<$Controller>
|
|
1081
1089
|
allow?: OrReadOnly<ControllerActionName[]>
|
|
1082
1090
|
authorize?: Authorize
|
|
@@ -1110,15 +1118,15 @@ type ModelControllerHookKeys<
|
|
|
1110
1118
|
| ControllerActionName
|
|
1111
1119
|
| '*'}`
|
|
1112
1120
|
type ModelControllerHook<
|
|
1113
|
-
$ModelController extends ModelController
|
|
1121
|
+
$ModelController extends ModelController = ModelController
|
|
1114
1122
|
> = (
|
|
1115
1123
|
ctx: KoaContext,
|
|
1116
|
-
result: objection.Page<
|
|
1124
|
+
result: objection.Page<ModelFromModelController<$ModelController>>
|
|
1117
1125
|
) => any
|
|
1118
1126
|
|
|
1119
1127
|
type HookHandler = () => void
|
|
1120
1128
|
|
|
1121
|
-
type HookKeysFromController<$ModelController extends ModelController
|
|
1129
|
+
type HookKeysFromController<$ModelController extends ModelController> =
|
|
1122
1130
|
| ModelControllerHookKeys<
|
|
1123
1131
|
Exclude<
|
|
1124
1132
|
keyof Exclude<$ModelController['collection'], undefined>,
|
|
@@ -1135,7 +1143,7 @@ type HookKeysFromController<$ModelController extends ModelController<Model>> =
|
|
|
1135
1143
|
>
|
|
1136
1144
|
|
|
1137
1145
|
type HandlerFromHookKey<
|
|
1138
|
-
$ModelController extends ModelController
|
|
1146
|
+
$ModelController extends ModelController,
|
|
1139
1147
|
$Key extends HookKeysFromController<$ModelController>
|
|
1140
1148
|
> = $Key extends `${'before' | 'after' | '*'}:${
|
|
1141
1149
|
| 'collection'
|
|
@@ -1145,7 +1153,7 @@ type HandlerFromHookKey<
|
|
|
1145
1153
|
: never
|
|
1146
1154
|
|
|
1147
1155
|
type ModelControllerHooks<
|
|
1148
|
-
$ModelController extends ModelController
|
|
1156
|
+
$ModelController extends ModelController = ModelController
|
|
1149
1157
|
> = {
|
|
1150
1158
|
[$Key in HookKeysFromController<$ModelController>]?: HandlerFromHookKey<
|
|
1151
1159
|
$ModelController,
|
|
@@ -1428,13 +1436,9 @@ export type QueryParameterOptionKey = keyof QueryParameterOptions
|
|
|
1428
1436
|
|
|
1429
1437
|
export class Service {
|
|
1430
1438
|
constructor(app: Application<Models>, name?: string)
|
|
1431
|
-
|
|
1432
1439
|
setup(config: any): void
|
|
1433
|
-
|
|
1434
1440
|
initialize(): void
|
|
1435
|
-
|
|
1436
1441
|
start(): Promise<void>
|
|
1437
|
-
|
|
1438
1442
|
stop(): Promise<void>
|
|
1439
1443
|
}
|
|
1440
1444
|
export type Services = Record<string, Class<Service> | Service>
|
|
@@ -1853,7 +1857,7 @@ type OrReadOnly<T> = Readonly<T> | T
|
|
|
1853
1857
|
|
|
1854
1858
|
type OrPromiseOf<T> = Promise<T> | T
|
|
1855
1859
|
|
|
1856
|
-
type
|
|
1860
|
+
type ModelFromModelController<$ModelController extends ModelController> =
|
|
1857
1861
|
InstanceType<Exclude<$ModelController['modelClass'], undefined>>
|
|
1858
1862
|
|
|
1859
1863
|
export type SelectModelProperties<T> = {
|