@radio-garden/ditojs-server 2.85.2-0.5067ad799
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/README.md +6 -0
- package/package.json +95 -0
- package/src/app/Application.js +1186 -0
- package/src/app/Validator.js +405 -0
- package/src/app/index.js +2 -0
- package/src/cli/console.js +152 -0
- package/src/cli/db/createMigration.js +241 -0
- package/src/cli/db/index.js +7 -0
- package/src/cli/db/listAssetConfig.js +10 -0
- package/src/cli/db/migrate.js +12 -0
- package/src/cli/db/reset.js +23 -0
- package/src/cli/db/rollback.js +12 -0
- package/src/cli/db/seed.js +80 -0
- package/src/cli/db/unlock.js +9 -0
- package/src/cli/index.js +72 -0
- package/src/controllers/AdminController.js +322 -0
- package/src/controllers/CollectionController.js +274 -0
- package/src/controllers/Controller.js +657 -0
- package/src/controllers/ControllerAction.js +370 -0
- package/src/controllers/MemberAction.js +27 -0
- package/src/controllers/ModelController.js +63 -0
- package/src/controllers/RelationController.js +93 -0
- package/src/controllers/UsersController.js +64 -0
- package/src/controllers/index.js +5 -0
- package/src/errors/AssetError.js +7 -0
- package/src/errors/AuthenticationError.js +7 -0
- package/src/errors/AuthorizationError.js +7 -0
- package/src/errors/ControllerError.js +14 -0
- package/src/errors/DatabaseError.js +37 -0
- package/src/errors/GraphError.js +7 -0
- package/src/errors/ModelError.js +12 -0
- package/src/errors/NotFoundError.js +7 -0
- package/src/errors/NotImplementedError.js +7 -0
- package/src/errors/QueryBuilderError.js +7 -0
- package/src/errors/RelationError.js +21 -0
- package/src/errors/ResponseError.js +56 -0
- package/src/errors/ValidationError.js +7 -0
- package/src/errors/index.js +13 -0
- package/src/graph/DitoGraphProcessor.js +213 -0
- package/src/graph/expression.js +53 -0
- package/src/graph/graph.js +258 -0
- package/src/graph/index.js +3 -0
- package/src/index.js +9 -0
- package/src/lib/EventEmitter.js +66 -0
- package/src/lib/KnexHelper.js +30 -0
- package/src/lib/index.js +2 -0
- package/src/middleware/attachLogger.js +8 -0
- package/src/middleware/createTransaction.js +33 -0
- package/src/middleware/extendContext.js +10 -0
- package/src/middleware/findRoute.js +20 -0
- package/src/middleware/handleConnectMiddleware.js +99 -0
- package/src/middleware/handleError.js +29 -0
- package/src/middleware/handleRoute.js +23 -0
- package/src/middleware/handleSession.js +77 -0
- package/src/middleware/handleUser.js +31 -0
- package/src/middleware/index.js +11 -0
- package/src/middleware/logRequests.js +125 -0
- package/src/middleware/setupRequestStorage.js +14 -0
- package/src/mixins/AssetMixin.js +78 -0
- package/src/mixins/SessionMixin.js +17 -0
- package/src/mixins/TimeStampedMixin.js +41 -0
- package/src/mixins/UserMixin.js +171 -0
- package/src/mixins/index.js +4 -0
- package/src/models/AssetModel.js +4 -0
- package/src/models/Model.js +1205 -0
- package/src/models/RelationAccessor.js +41 -0
- package/src/models/SessionModel.js +4 -0
- package/src/models/TimeStampedModel.js +4 -0
- package/src/models/UserModel.js +4 -0
- package/src/models/definitions/assets.js +5 -0
- package/src/models/definitions/filters.js +121 -0
- package/src/models/definitions/hooks.js +8 -0
- package/src/models/definitions/index.js +22 -0
- package/src/models/definitions/modifiers.js +5 -0
- package/src/models/definitions/options.js +5 -0
- package/src/models/definitions/properties.js +73 -0
- package/src/models/definitions/relations.js +5 -0
- package/src/models/definitions/schema.js +5 -0
- package/src/models/definitions/scopes.js +36 -0
- package/src/models/index.js +5 -0
- package/src/query/QueryBuilder.js +1077 -0
- package/src/query/QueryFilters.js +66 -0
- package/src/query/QueryParameters.js +79 -0
- package/src/query/Registry.js +29 -0
- package/src/query/index.js +3 -0
- package/src/schema/formats/_empty.js +4 -0
- package/src/schema/formats/_required.js +4 -0
- package/src/schema/formats/index.js +2 -0
- package/src/schema/index.js +5 -0
- package/src/schema/keywords/_computed.js +7 -0
- package/src/schema/keywords/_foreign.js +7 -0
- package/src/schema/keywords/_hidden.js +7 -0
- package/src/schema/keywords/_index.js +7 -0
- package/src/schema/keywords/_instanceof.js +45 -0
- package/src/schema/keywords/_primary.js +7 -0
- package/src/schema/keywords/_range.js +18 -0
- package/src/schema/keywords/_relate.js +13 -0
- package/src/schema/keywords/_specificType.js +7 -0
- package/src/schema/keywords/_unique.js +7 -0
- package/src/schema/keywords/_unsigned.js +7 -0
- package/src/schema/keywords/_validate.js +73 -0
- package/src/schema/keywords/index.js +12 -0
- package/src/schema/relations.js +324 -0
- package/src/schema/relations.test.js +177 -0
- package/src/schema/schema.js +289 -0
- package/src/schema/schema.test.js +720 -0
- package/src/schema/types/_asset.js +31 -0
- package/src/schema/types/_color.js +4 -0
- package/src/schema/types/index.js +2 -0
- package/src/services/Service.js +35 -0
- package/src/services/index.js +1 -0
- package/src/storage/AssetFile.js +81 -0
- package/src/storage/DiskStorage.js +114 -0
- package/src/storage/S3Storage.js +169 -0
- package/src/storage/Storage.js +231 -0
- package/src/storage/index.js +9 -0
- package/src/utils/duration.js +15 -0
- package/src/utils/emitter.js +8 -0
- package/src/utils/fs.js +10 -0
- package/src/utils/function.js +17 -0
- package/src/utils/function.test.js +77 -0
- package/src/utils/handler.js +17 -0
- package/src/utils/json.js +3 -0
- package/src/utils/model.js +35 -0
- package/src/utils/net.js +17 -0
- package/src/utils/object.js +82 -0
- package/src/utils/object.test.js +86 -0
- package/src/utils/scope.js +7 -0
- package/types/index.d.ts +3547 -0
- package/types/tests/application.test-d.ts +26 -0
- package/types/tests/controller.test-d.ts +113 -0
- package/types/tests/errors.test-d.ts +53 -0
- package/types/tests/fixtures.ts +19 -0
- package/types/tests/model.test-d.ts +193 -0
- package/types/tests/query-builder.test-d.ts +106 -0
- package/types/tests/relation.test-d.ts +83 -0
- package/types/tests/storage.test-d.ts +113 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ResponseError } from './ResponseError.js'
|
|
2
|
+
import {
|
|
3
|
+
DBError,
|
|
4
|
+
DataError,
|
|
5
|
+
CheckViolationError,
|
|
6
|
+
NotNullViolationError,
|
|
7
|
+
ConstraintViolationError
|
|
8
|
+
} from 'objection'
|
|
9
|
+
|
|
10
|
+
export class DatabaseError extends ResponseError {
|
|
11
|
+
constructor(error, overrides) {
|
|
12
|
+
super(
|
|
13
|
+
error,
|
|
14
|
+
{
|
|
15
|
+
type: error.constructor.name,
|
|
16
|
+
message: 'Database error',
|
|
17
|
+
status: getStatus(error),
|
|
18
|
+
cause: error.nativeError
|
|
19
|
+
},
|
|
20
|
+
overrides
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getStatus(error) {
|
|
26
|
+
return error instanceof CheckViolationError
|
|
27
|
+
? 400
|
|
28
|
+
: error instanceof NotNullViolationError
|
|
29
|
+
? 400
|
|
30
|
+
: error instanceof ConstraintViolationError
|
|
31
|
+
? 409
|
|
32
|
+
: error instanceof DataError
|
|
33
|
+
? 400
|
|
34
|
+
: error instanceof DBError
|
|
35
|
+
? 500
|
|
36
|
+
: 400
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { isFunction } from '@ditojs/utils'
|
|
2
|
+
import { ResponseError } from './ResponseError.js'
|
|
3
|
+
|
|
4
|
+
export class ModelError extends ResponseError {
|
|
5
|
+
constructor(model, error) {
|
|
6
|
+
const { name } = isFunction(model) ? model : model.constructor
|
|
7
|
+
super(`Model '${name}': ${error}`, {
|
|
8
|
+
message: `Model '${name}': Model error`,
|
|
9
|
+
status: 400
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ResponseError } from './ResponseError.js'
|
|
2
|
+
|
|
3
|
+
export class RelationError extends ResponseError {
|
|
4
|
+
constructor(error) {
|
|
5
|
+
super(
|
|
6
|
+
error,
|
|
7
|
+
{ message: 'Relation error', status: 400 },
|
|
8
|
+
error instanceof Error ? getFormattedOverrides(error) : null
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getFormattedOverrides(error) {
|
|
14
|
+
// Adjust Objection.js error messages to point to the right property.
|
|
15
|
+
const format = str => str?.replace(/\brelationMappings\b/g, 'relations')
|
|
16
|
+
const { message, stack } = error
|
|
17
|
+
return {
|
|
18
|
+
message: format(message),
|
|
19
|
+
stack: format(stack)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isPlainObject, isString } from '@ditojs/utils'
|
|
2
|
+
|
|
3
|
+
export class ResponseError extends Error {
|
|
4
|
+
constructor(
|
|
5
|
+
error,
|
|
6
|
+
defaults = { message: 'Response error', status: 500 },
|
|
7
|
+
overrides
|
|
8
|
+
) {
|
|
9
|
+
const object = isPlainObject(error)
|
|
10
|
+
? error
|
|
11
|
+
: error instanceof Error
|
|
12
|
+
? getErrorObject(error)
|
|
13
|
+
: isString(error)
|
|
14
|
+
? { message: error }
|
|
15
|
+
: error || {}
|
|
16
|
+
const { message, status, stack, cause, ...data } = {
|
|
17
|
+
...defaults,
|
|
18
|
+
...object,
|
|
19
|
+
...overrides
|
|
20
|
+
}
|
|
21
|
+
super(message, cause ? { cause } : {})
|
|
22
|
+
this.status = status
|
|
23
|
+
if (Object.keys(data).length > 0) {
|
|
24
|
+
this.data = data
|
|
25
|
+
}
|
|
26
|
+
// Allow `stack` overrides, e.g. for `RelationError`.
|
|
27
|
+
if (stack != null) {
|
|
28
|
+
this.stack = stack
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
toJSON() {
|
|
33
|
+
return {
|
|
34
|
+
// Include the message in the JSON data sent back.
|
|
35
|
+
message: this.message,
|
|
36
|
+
...this.data
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getErrorObject(error) {
|
|
42
|
+
const object = {
|
|
43
|
+
// For generic errors, explicitly copy message.
|
|
44
|
+
message: error.message,
|
|
45
|
+
...error.toJSON?.()
|
|
46
|
+
}
|
|
47
|
+
// Additionally copy status and code if present.
|
|
48
|
+
if (error.status != null) {
|
|
49
|
+
object.status = error.status
|
|
50
|
+
}
|
|
51
|
+
if (error.code != null) {
|
|
52
|
+
object.code = error.code
|
|
53
|
+
}
|
|
54
|
+
object.cause = error
|
|
55
|
+
return object
|
|
56
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './AssetError.js'
|
|
2
|
+
export * from './ResponseError.js'
|
|
3
|
+
export * from './AuthenticationError.js'
|
|
4
|
+
export * from './AuthorizationError.js'
|
|
5
|
+
export * from './ControllerError.js'
|
|
6
|
+
export * from './DatabaseError.js'
|
|
7
|
+
export * from './GraphError.js'
|
|
8
|
+
export * from './ModelError.js'
|
|
9
|
+
export * from './NotFoundError.js'
|
|
10
|
+
export * from './NotImplementedError.js'
|
|
11
|
+
export * from './QueryBuilderError.js'
|
|
12
|
+
export * from './RelationError.js'
|
|
13
|
+
export * from './ValidationError.js'
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { isArray } from '@ditojs/utils'
|
|
2
|
+
import { ensureModelArray } from './graph.js'
|
|
3
|
+
import { modelGraphToExpression } from './expression.js'
|
|
4
|
+
|
|
5
|
+
export class DitoGraphProcessor {
|
|
6
|
+
constructor(rootModelClass, data, options = {}, settings = {}) {
|
|
7
|
+
this.rootModelClass = rootModelClass
|
|
8
|
+
this.data = ensureModelArray(rootModelClass, data, {
|
|
9
|
+
skipValidation: true
|
|
10
|
+
})
|
|
11
|
+
this.isArray = isArray(data)
|
|
12
|
+
this.options = options
|
|
13
|
+
this.settings = settings
|
|
14
|
+
this.overrides = {}
|
|
15
|
+
this.extras = {}
|
|
16
|
+
this.numOptions = Object.keys(options).length
|
|
17
|
+
this.numOverrides = 0
|
|
18
|
+
if (settings.processOverrides) {
|
|
19
|
+
this.collectOverrides()
|
|
20
|
+
if (this.numOverrides > 0) {
|
|
21
|
+
this.processOverrides()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getOptions() {
|
|
27
|
+
return {
|
|
28
|
+
...this.options,
|
|
29
|
+
...this.overrides
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getData() {
|
|
34
|
+
// If setting.processRelates is used, call processRelate() to filter out
|
|
35
|
+
// nested relations of models that are used for relates.
|
|
36
|
+
const data = this.settings.processRelates
|
|
37
|
+
? this.processRelates(this.data)
|
|
38
|
+
: this.data
|
|
39
|
+
return this.isArray ? data : data[0]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getGraphOptions(relation) {
|
|
43
|
+
// When a relation is owner of its data, then a fall-back for `graphOptions`
|
|
44
|
+
// is provided where both `relate` and `unrelate` is disabled, resulting in
|
|
45
|
+
// inserts and deletes instead.
|
|
46
|
+
const ownerOptions = {
|
|
47
|
+
relate: false,
|
|
48
|
+
unrelate: false
|
|
49
|
+
}
|
|
50
|
+
// Determine the `graphOptions` to be used for this relation.
|
|
51
|
+
return relation.graphOptions || relation.owner && ownerOptions || {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Loops through all nested relations and finds the ones that define local
|
|
56
|
+
* overrides of the global options, then collects empty override arrays for
|
|
57
|
+
* each setting, so processOverrides() can fill them if any overrides exist.
|
|
58
|
+
*/
|
|
59
|
+
collectOverrides() {
|
|
60
|
+
// TODO: we may want optimize this code to only collect the overrides for
|
|
61
|
+
// the relations that are actually used in the graph, e.g. through
|
|
62
|
+
// `modelGraphToExpression(data)`. Should we ever switch to our own
|
|
63
|
+
// implementation of *AndFetch() methods, we already have to call this.
|
|
64
|
+
const processed = {}
|
|
65
|
+
const processModelClass = modelClass => {
|
|
66
|
+
const { name } = modelClass
|
|
67
|
+
// Only process each modelClass once, to avoid circular reference loops.
|
|
68
|
+
if (!processed[name]) {
|
|
69
|
+
processed[name] = true
|
|
70
|
+
const { relations } = modelClass.definition
|
|
71
|
+
const relationInstances = modelClass.getRelations()
|
|
72
|
+
for (const [name, relation] of Object.entries(relations)) {
|
|
73
|
+
const graphOptions = this.getGraphOptions(relation)
|
|
74
|
+
if (graphOptions) {
|
|
75
|
+
// Loop through `this.options` and only look for overrides of them,
|
|
76
|
+
// since `relation.graphOptions` is across insert / upsert & co.,
|
|
77
|
+
// but not all of them use all options (insert defines less).
|
|
78
|
+
for (const key in this.options) {
|
|
79
|
+
if (
|
|
80
|
+
key in graphOptions &&
|
|
81
|
+
graphOptions[key] !== this.options[key] &&
|
|
82
|
+
!this.overrides[key]
|
|
83
|
+
) {
|
|
84
|
+
this.numOverrides++
|
|
85
|
+
this.overrides[key] = []
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Keep scanning until we're done or found that all options have
|
|
89
|
+
// overrides.
|
|
90
|
+
if (this.numOverrides < this.numOptions) {
|
|
91
|
+
processModelClass(relationInstances[name].relatedModelClass)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
processModelClass(this.rootModelClass)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Fills the empty override arrays collected by collectOverrides() by walking
|
|
103
|
+
* through the actual graph and finding relations that have overrides, and
|
|
104
|
+
* building relation paths for them.
|
|
105
|
+
*/
|
|
106
|
+
processOverrides() {
|
|
107
|
+
const expr = modelGraphToExpression(this.data)
|
|
108
|
+
|
|
109
|
+
const processExpression = (
|
|
110
|
+
expr,
|
|
111
|
+
modelClass,
|
|
112
|
+
relation,
|
|
113
|
+
relationPath = ''
|
|
114
|
+
) => {
|
|
115
|
+
if (relation) {
|
|
116
|
+
const graphOptions = this.getGraphOptions(relation)
|
|
117
|
+
// Loop through all override options, figure out their settings for
|
|
118
|
+
// the current relation and build relation expression arrays for each
|
|
119
|
+
// override reflecting their nested settings in arrays of expressions.
|
|
120
|
+
for (const key in this.overrides) {
|
|
121
|
+
const option = graphOptions[key] ?? this.options[key]
|
|
122
|
+
if (option) {
|
|
123
|
+
this.overrides[key].push(relationPath)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Also collect any many-to-many pivot table extra properties.
|
|
128
|
+
const extra = relation.through?.extra
|
|
129
|
+
if (extra?.length > 0) {
|
|
130
|
+
this.extras[relationPath] = extra
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const { relations } = modelClass.definition
|
|
135
|
+
const relationInstances = modelClass.getRelations()
|
|
136
|
+
for (const key in expr) {
|
|
137
|
+
const childExpr = expr[key]
|
|
138
|
+
const { relatedModelClass } = relationInstances[key]
|
|
139
|
+
processExpression(
|
|
140
|
+
childExpr,
|
|
141
|
+
relatedModelClass,
|
|
142
|
+
relations[key],
|
|
143
|
+
appendPath(relationPath, '.', key)
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
processExpression(expr, this.rootModelClass)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
shouldRelate(relationPath) {
|
|
152
|
+
// Root objects (relationPath === '') should never relate.
|
|
153
|
+
if (relationPath !== '') {
|
|
154
|
+
const { relate } = this.overrides
|
|
155
|
+
return relate
|
|
156
|
+
? // See if the relate overrides contain this particular relation-Path
|
|
157
|
+
// and only remove and restore relation data if relate is to be used
|
|
158
|
+
relate.includes(relationPath)
|
|
159
|
+
: this.options.relate
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Handles relate option by detecting Objection instances in the graph and
|
|
165
|
+
* converting them to shallow id links.
|
|
166
|
+
*
|
|
167
|
+
* For details, see:
|
|
168
|
+
* https://gitter.im/Vincit/objection.js?at=5a4246eeba39a53f1aa3a3b1
|
|
169
|
+
*/
|
|
170
|
+
processRelates(data, relationPath = '', dataPath = '') {
|
|
171
|
+
if (data) {
|
|
172
|
+
if (data.$isObjectionModel) {
|
|
173
|
+
const { constructor } = data
|
|
174
|
+
let copy
|
|
175
|
+
if (this.shouldRelate(relationPath)) {
|
|
176
|
+
// For relates, start with a reference model that only contains the
|
|
177
|
+
// id / #ref fields, and any many-to-many pivot table extra values:
|
|
178
|
+
copy = constructor.getReference(data, this.extras[relationPath])
|
|
179
|
+
} else {
|
|
180
|
+
// This isn't a relate, so create a proper shallow clone:
|
|
181
|
+
// NOTE: This also copies `$$queryProps`, which is crucial for more
|
|
182
|
+
// advanced Objection.js features to work, e.g. LiteralBuilder:
|
|
183
|
+
copy = data.$clone({ shallow: true })
|
|
184
|
+
// Follow all relations and keep processing:
|
|
185
|
+
for (const { name } of Object.values(constructor.getRelations())) {
|
|
186
|
+
if (name in data) {
|
|
187
|
+
copy[name] = this.processRelates(
|
|
188
|
+
data[name],
|
|
189
|
+
appendPath(relationPath, '.', name),
|
|
190
|
+
appendPath(dataPath, '/', name)
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return copy
|
|
196
|
+
} else if (isArray(data)) {
|
|
197
|
+
// Potentially a has-many relation, so keep processing relates:
|
|
198
|
+
return data.map((entry, index) =>
|
|
199
|
+
this.processRelates(
|
|
200
|
+
entry,
|
|
201
|
+
relationPath,
|
|
202
|
+
appendPath(dataPath, '/', index)
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return data
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function appendPath(path, separator, token) {
|
|
212
|
+
return path !== '' ? `${path}${separator}${token}` : token
|
|
213
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { isObject, asArray } from '@ditojs/utils'
|
|
2
|
+
|
|
3
|
+
export function collectExpressionPaths(expr) {
|
|
4
|
+
const paths = []
|
|
5
|
+
for (const key of Object.keys(expr)) {
|
|
6
|
+
const child = expr[key]
|
|
7
|
+
if (isObject(child)) {
|
|
8
|
+
const relation = child.$relation || key
|
|
9
|
+
const alias = relation !== key ? key : undefined
|
|
10
|
+
const modify = child.$modify
|
|
11
|
+
const entry = { relation, alias, modify }
|
|
12
|
+
const subPaths = collectExpressionPaths(child)
|
|
13
|
+
if (subPaths.length > 0) {
|
|
14
|
+
// The child has itself children.
|
|
15
|
+
for (const subPath of subPaths) {
|
|
16
|
+
paths.push([entry, ...subPath])
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
// The child is a leaf.
|
|
20
|
+
paths.push([entry])
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return paths
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function expressionPathToString(path, start = 0) {
|
|
28
|
+
return (start ? path.slice(start) : path)
|
|
29
|
+
.map(({ relation, alias, modify }) => {
|
|
30
|
+
const expr = alias ? `${relation} as ${alias}` : relation
|
|
31
|
+
return modify.length > 0
|
|
32
|
+
? `${expr}(${modify.join(', ')})`
|
|
33
|
+
: expr
|
|
34
|
+
})
|
|
35
|
+
.join('.')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function modelGraphToExpression(modelGraph, expr) {
|
|
39
|
+
if (modelGraph) {
|
|
40
|
+
expr ||= {}
|
|
41
|
+
for (const model of asArray(modelGraph)) {
|
|
42
|
+
if (model) {
|
|
43
|
+
const relations = model.constructor.getRelations()
|
|
44
|
+
for (const { name } of Object.values(relations)) {
|
|
45
|
+
if (model.hasOwnProperty(name)) {
|
|
46
|
+
expr[name] = modelGraphToExpression(model[name], expr[name])
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return expr
|
|
53
|
+
}
|