@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.
Files changed (137) hide show
  1. package/README.md +6 -0
  2. package/package.json +95 -0
  3. package/src/app/Application.js +1186 -0
  4. package/src/app/Validator.js +405 -0
  5. package/src/app/index.js +2 -0
  6. package/src/cli/console.js +152 -0
  7. package/src/cli/db/createMigration.js +241 -0
  8. package/src/cli/db/index.js +7 -0
  9. package/src/cli/db/listAssetConfig.js +10 -0
  10. package/src/cli/db/migrate.js +12 -0
  11. package/src/cli/db/reset.js +23 -0
  12. package/src/cli/db/rollback.js +12 -0
  13. package/src/cli/db/seed.js +80 -0
  14. package/src/cli/db/unlock.js +9 -0
  15. package/src/cli/index.js +72 -0
  16. package/src/controllers/AdminController.js +322 -0
  17. package/src/controllers/CollectionController.js +274 -0
  18. package/src/controllers/Controller.js +657 -0
  19. package/src/controllers/ControllerAction.js +370 -0
  20. package/src/controllers/MemberAction.js +27 -0
  21. package/src/controllers/ModelController.js +63 -0
  22. package/src/controllers/RelationController.js +93 -0
  23. package/src/controllers/UsersController.js +64 -0
  24. package/src/controllers/index.js +5 -0
  25. package/src/errors/AssetError.js +7 -0
  26. package/src/errors/AuthenticationError.js +7 -0
  27. package/src/errors/AuthorizationError.js +7 -0
  28. package/src/errors/ControllerError.js +14 -0
  29. package/src/errors/DatabaseError.js +37 -0
  30. package/src/errors/GraphError.js +7 -0
  31. package/src/errors/ModelError.js +12 -0
  32. package/src/errors/NotFoundError.js +7 -0
  33. package/src/errors/NotImplementedError.js +7 -0
  34. package/src/errors/QueryBuilderError.js +7 -0
  35. package/src/errors/RelationError.js +21 -0
  36. package/src/errors/ResponseError.js +56 -0
  37. package/src/errors/ValidationError.js +7 -0
  38. package/src/errors/index.js +13 -0
  39. package/src/graph/DitoGraphProcessor.js +213 -0
  40. package/src/graph/expression.js +53 -0
  41. package/src/graph/graph.js +258 -0
  42. package/src/graph/index.js +3 -0
  43. package/src/index.js +9 -0
  44. package/src/lib/EventEmitter.js +66 -0
  45. package/src/lib/KnexHelper.js +30 -0
  46. package/src/lib/index.js +2 -0
  47. package/src/middleware/attachLogger.js +8 -0
  48. package/src/middleware/createTransaction.js +33 -0
  49. package/src/middleware/extendContext.js +10 -0
  50. package/src/middleware/findRoute.js +20 -0
  51. package/src/middleware/handleConnectMiddleware.js +99 -0
  52. package/src/middleware/handleError.js +29 -0
  53. package/src/middleware/handleRoute.js +23 -0
  54. package/src/middleware/handleSession.js +77 -0
  55. package/src/middleware/handleUser.js +31 -0
  56. package/src/middleware/index.js +11 -0
  57. package/src/middleware/logRequests.js +125 -0
  58. package/src/middleware/setupRequestStorage.js +14 -0
  59. package/src/mixins/AssetMixin.js +78 -0
  60. package/src/mixins/SessionMixin.js +17 -0
  61. package/src/mixins/TimeStampedMixin.js +41 -0
  62. package/src/mixins/UserMixin.js +171 -0
  63. package/src/mixins/index.js +4 -0
  64. package/src/models/AssetModel.js +4 -0
  65. package/src/models/Model.js +1205 -0
  66. package/src/models/RelationAccessor.js +41 -0
  67. package/src/models/SessionModel.js +4 -0
  68. package/src/models/TimeStampedModel.js +4 -0
  69. package/src/models/UserModel.js +4 -0
  70. package/src/models/definitions/assets.js +5 -0
  71. package/src/models/definitions/filters.js +121 -0
  72. package/src/models/definitions/hooks.js +8 -0
  73. package/src/models/definitions/index.js +22 -0
  74. package/src/models/definitions/modifiers.js +5 -0
  75. package/src/models/definitions/options.js +5 -0
  76. package/src/models/definitions/properties.js +73 -0
  77. package/src/models/definitions/relations.js +5 -0
  78. package/src/models/definitions/schema.js +5 -0
  79. package/src/models/definitions/scopes.js +36 -0
  80. package/src/models/index.js +5 -0
  81. package/src/query/QueryBuilder.js +1077 -0
  82. package/src/query/QueryFilters.js +66 -0
  83. package/src/query/QueryParameters.js +79 -0
  84. package/src/query/Registry.js +29 -0
  85. package/src/query/index.js +3 -0
  86. package/src/schema/formats/_empty.js +4 -0
  87. package/src/schema/formats/_required.js +4 -0
  88. package/src/schema/formats/index.js +2 -0
  89. package/src/schema/index.js +5 -0
  90. package/src/schema/keywords/_computed.js +7 -0
  91. package/src/schema/keywords/_foreign.js +7 -0
  92. package/src/schema/keywords/_hidden.js +7 -0
  93. package/src/schema/keywords/_index.js +7 -0
  94. package/src/schema/keywords/_instanceof.js +45 -0
  95. package/src/schema/keywords/_primary.js +7 -0
  96. package/src/schema/keywords/_range.js +18 -0
  97. package/src/schema/keywords/_relate.js +13 -0
  98. package/src/schema/keywords/_specificType.js +7 -0
  99. package/src/schema/keywords/_unique.js +7 -0
  100. package/src/schema/keywords/_unsigned.js +7 -0
  101. package/src/schema/keywords/_validate.js +73 -0
  102. package/src/schema/keywords/index.js +12 -0
  103. package/src/schema/relations.js +324 -0
  104. package/src/schema/relations.test.js +177 -0
  105. package/src/schema/schema.js +289 -0
  106. package/src/schema/schema.test.js +720 -0
  107. package/src/schema/types/_asset.js +31 -0
  108. package/src/schema/types/_color.js +4 -0
  109. package/src/schema/types/index.js +2 -0
  110. package/src/services/Service.js +35 -0
  111. package/src/services/index.js +1 -0
  112. package/src/storage/AssetFile.js +81 -0
  113. package/src/storage/DiskStorage.js +114 -0
  114. package/src/storage/S3Storage.js +169 -0
  115. package/src/storage/Storage.js +231 -0
  116. package/src/storage/index.js +9 -0
  117. package/src/utils/duration.js +15 -0
  118. package/src/utils/emitter.js +8 -0
  119. package/src/utils/fs.js +10 -0
  120. package/src/utils/function.js +17 -0
  121. package/src/utils/function.test.js +77 -0
  122. package/src/utils/handler.js +17 -0
  123. package/src/utils/json.js +3 -0
  124. package/src/utils/model.js +35 -0
  125. package/src/utils/net.js +17 -0
  126. package/src/utils/object.js +82 -0
  127. package/src/utils/object.test.js +86 -0
  128. package/src/utils/scope.js +7 -0
  129. package/types/index.d.ts +3547 -0
  130. package/types/tests/application.test-d.ts +26 -0
  131. package/types/tests/controller.test-d.ts +113 -0
  132. package/types/tests/errors.test-d.ts +53 -0
  133. package/types/tests/fixtures.ts +19 -0
  134. package/types/tests/model.test-d.ts +193 -0
  135. package/types/tests/query-builder.test-d.ts +106 -0
  136. package/types/tests/relation.test-d.ts +83 -0
  137. 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,7 @@
1
+ import { ResponseError } from './ResponseError.js'
2
+
3
+ export class GraphError extends ResponseError {
4
+ constructor(error) {
5
+ super(error, { message: 'Graph error', status: 400 })
6
+ }
7
+ }
@@ -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,7 @@
1
+ import { ResponseError } from './ResponseError.js'
2
+
3
+ export class NotFoundError extends ResponseError {
4
+ constructor(error) {
5
+ super(error, { message: 'Not-found error', status: 404 })
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ import { ResponseError } from './ResponseError.js'
2
+
3
+ export class NotImplementedError extends ResponseError {
4
+ constructor(error) {
5
+ super(error, { message: 'Method not implemented', status: 404 })
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ import { ResponseError } from './ResponseError.js'
2
+
3
+ export class QueryBuilderError extends ResponseError {
4
+ constructor(error) {
5
+ super(error, { message: 'Query-builder error', status: 400 })
6
+ }
7
+ }
@@ -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,7 @@
1
+ import { ResponseError } from './ResponseError.js'
2
+
3
+ export class ValidationError extends ResponseError {
4
+ constructor(error) {
5
+ super(error, { message: 'Validation error', status: 400 })
6
+ }
7
+ }
@@ -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
+ }