@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
|
@@ -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 ||
|