@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,41 @@
1
+ import { QueryBuilder } from '../query/index.js'
2
+
3
+ export default class RelationAccessor {
4
+ constructor(relation, modelClass, model) {
5
+ this.relation = relation
6
+ this.name = relation.name
7
+ this.modelClass = modelClass
8
+ this.model = model
9
+ }
10
+
11
+ query(trx) {
12
+ return this.modelClass
13
+ ? this.modelClass.relatedQuery(this.name, trx)
14
+ : this.model.$relatedQuery(this.name, trx)
15
+ }
16
+
17
+ load(arg0, ...args) {
18
+ return this.modelClass
19
+ ? this.modelClass.loadRelated(arg0, this.name, ...args)
20
+ : this.model.$loadRelated(this.name, arg0, ...args)
21
+ }
22
+
23
+ /**
24
+ * Accessor to provide simplified access to the implicitly generated join
25
+ * model class, handling the passing on of the knex instance, as well as
26
+ * the application of `QueryBuilder.mixin()`.
27
+ */
28
+ get joinModelClass() {
29
+ const { joinModelClass } = this.relation
30
+ // Result is already cached per knex by `this.relation.joinModelClass()`,
31
+ // so all that's left to do is apply `QueryBuilder.mixin()`,
32
+ // if there is no `joinModelClass.where()` yet:
33
+ if (joinModelClass && !('where' in joinModelClass)) {
34
+ QueryBuilder.mixin(joinModelClass)
35
+ }
36
+ return joinModelClass
37
+ }
38
+ }
39
+
40
+ // Expose a selection of QueryBuilder methods as instance methods on relations.
41
+ QueryBuilder.mixin(RelationAccessor.prototype)
@@ -0,0 +1,4 @@
1
+ import { SessionMixin } from '../mixins/index.js'
2
+ import { Model } from './Model.js'
3
+
4
+ export const SessionModel = SessionMixin(Model)
@@ -0,0 +1,4 @@
1
+ import { TimeStampedMixin } from '../mixins/index.js'
2
+ import { Model } from './Model.js'
3
+
4
+ export const TimeStampedModel = TimeStampedMixin(Model)
@@ -0,0 +1,4 @@
1
+ import { UserMixin } from '../mixins/index.js'
2
+ import { Model } from './Model.js'
3
+
4
+ export const UserModel = UserMixin(Model)
@@ -0,0 +1,5 @@
1
+ import { mergeReversedOrNull } from '../../utils/object.js'
2
+
3
+ export default function assets(values) {
4
+ return mergeReversedOrNull(values)
5
+ }
@@ -0,0 +1,121 @@
1
+ import { isObject, isFunction, deprecate } from '@ditojs/utils'
2
+ import { processHandlerParameters } from '../../utils/handler.js'
3
+ import { mergeReversed } from '../../utils/object.js'
4
+ import { QueryFilters } from '../../query/index.js'
5
+
6
+ export default function filters(values) {
7
+ const filters = {}
8
+ for (const [name, definition] of Object.entries(mergeReversed(values))) {
9
+ const filter = isFunction(definition)
10
+ ? definition
11
+ : isObject(definition)
12
+ ? convertFilterObject(name, definition)
13
+ : null
14
+ if (!filter) {
15
+ throw new Error(
16
+ `Invalid filter '${name}': Unrecognized definition: ${definition}.`
17
+ )
18
+ }
19
+ filters[name] = wrapWithValidation(filter, name, this.app)
20
+ }
21
+ return filters
22
+ }
23
+
24
+ function convertFilterObject(name, object) {
25
+ const addHandlerSettings = (handler, definition) => {
26
+ // Copy over parameters, returns and their validation options settings.
27
+ const {
28
+ parameters,
29
+ // TODO: `returns` was deprecated in May 2025 in favour of `response`.
30
+ // Remove this in 2026.
31
+ returns,
32
+ response = returns,
33
+ ...rest
34
+ } = definition
35
+ if (returns) {
36
+ deprecate(
37
+ 'The `returns` property is deprecated in favour of `response`. ' +
38
+ 'Update your handler definition to use `response` instead.'
39
+ )
40
+ }
41
+ processHandlerParameters(handler, 'parameters', parameters)
42
+ processHandlerParameters(handler, 'response', response)
43
+ return Object.assign(handler, rest)
44
+ }
45
+
46
+ const { handler, filter, properties } = object
47
+ if (handler) {
48
+ return addHandlerSettings(handler, object)
49
+ } else if (filter) {
50
+ // Convert QueryFilter to normal filter function.
51
+ const queryFilter = QueryFilters.get(filter)
52
+ if (!queryFilter) {
53
+ throw new Error(
54
+ `Invalid filter '${name}': Unknown filter type '${filter}'.`
55
+ )
56
+ }
57
+ // Support both object and function definitions.
58
+ const queryHandler = isObject(queryFilter)
59
+ ? queryFilter.handler
60
+ : queryFilter
61
+ const func = properties
62
+ ? (query, ...args) => {
63
+ // When the filter provides multiple properties, match them
64
+ // all, but combine the expressions with OR.
65
+ for (const property of properties) {
66
+ query.orWhere(query => queryHandler(query, property, ...args))
67
+ }
68
+ }
69
+ : (query, ...args) => {
70
+ queryHandler(query, name, ...args)
71
+ }
72
+ return addHandlerSettings(func, queryFilter)
73
+ }
74
+ }
75
+
76
+ function wrapWithValidation(filter, name, app) {
77
+ if (filter) {
78
+ // TODO: Implement `response` validation for filters too.
79
+ // TODO: Share additional coercion handling with
80
+ // `ControllerAction#coerceValue()`
81
+ const { parameters, options = {} } = filter
82
+ // If parameters are defined, wrap the function in a closure that
83
+ // performs parameter validation...
84
+ const dataName = 'query'
85
+ const validator = app.compileParametersValidator(parameters, {
86
+ ...options.parameters,
87
+ dataName
88
+ })
89
+ if (validator?.validate) {
90
+ return (query, ...args) => {
91
+ // Convert args to object for validation:
92
+ const object = {}
93
+ let index = 0
94
+ for (const { name } of validator.list) {
95
+ // Use dataName if no name is given, see:
96
+ // Application.compileParametersValidator()
97
+ object[name || dataName] = args[index++]
98
+ }
99
+ try {
100
+ validator.validate(object)
101
+ } catch (error) {
102
+ throw app.createValidationError({
103
+ type: 'FilterValidation',
104
+ message: `The provided data for query filter '${
105
+ name
106
+ }' is not valid`,
107
+ errors: app.validator.prefixInstancePaths(
108
+ error.errors,
109
+ `.${name}`
110
+ )
111
+ })
112
+ }
113
+ return validator.asObject
114
+ ? filter(query, object)
115
+ : filter(query, ...args)
116
+ }
117
+ }
118
+ }
119
+ // ...otherwise use the defined filter function unmodified.
120
+ return filter
121
+ }
@@ -0,0 +1,8 @@
1
+ import { mergeAsReversedArrays } from '../../utils/object.js'
2
+
3
+ export default function hooks(values) {
4
+ // Use `mergeAsReversedArrays()` so that for each event there is an array
5
+ // of hooks in sequence from base class to sub class, so that the hooks
6
+ // from the base class are run first by `_emitStaticHook()`.
7
+ return mergeAsReversedArrays(values)
8
+ }
@@ -0,0 +1,22 @@
1
+ import options from './options.js'
2
+ import properties from './properties.js'
3
+ import relations from './relations.js'
4
+ import schema from './schema.js'
5
+ import scopes from './scopes.js'
6
+ import filters from './filters.js'
7
+ import modifiers from './modifiers.js'
8
+ import assets from './assets.js'
9
+ import hooks from './hooks.js'
10
+
11
+ export default {
12
+ // Export options first, as other definitions may rely on them, e.g. UserMixin
13
+ options,
14
+ properties,
15
+ relations,
16
+ schema,
17
+ scopes,
18
+ filters,
19
+ modifiers,
20
+ assets,
21
+ hooks
22
+ }
@@ -0,0 +1,5 @@
1
+ import { mergeReversed } from '../../utils/object.js'
2
+
3
+ export default function modifiers(values) {
4
+ return mergeReversed(values)
5
+ }
@@ -0,0 +1,5 @@
1
+ import { mergeReversed } from '../../utils/object.js'
2
+
3
+ export default function options(values) {
4
+ return mergeReversed(values)
5
+ }
@@ -0,0 +1,73 @@
1
+ import { mergeReversed } from '../../utils/object.js'
2
+
3
+ export default function properties(values) {
4
+ const properties = mergeReversed(values)
5
+ // Include auto-generated 'id' properties for models and relations.
6
+ const addIdProperty = (name, schema) => {
7
+ if (!(name in properties)) {
8
+ properties[name] = {
9
+ type: 'integer',
10
+ ...schema
11
+ }
12
+ }
13
+ }
14
+
15
+ for (const name of this.getIdPropertyArray()) {
16
+ addIdProperty(name, {
17
+ primary: true
18
+ })
19
+ }
20
+
21
+ const addRelationProperties = (relation, propName) => {
22
+ const modelClass = relation.ownerModelClass
23
+ const { nullable } = modelClass.definition.relations[relation.name]
24
+ for (const property of relation[propName].props) {
25
+ addIdProperty(property, {
26
+ unsigned: true,
27
+ foreign: true,
28
+ index: true,
29
+ ...(nullable && { nullable })
30
+ })
31
+ }
32
+ }
33
+
34
+ for (const relation of Object.values(this.getRelations())) {
35
+ addRelationProperties(relation, 'ownerProp')
36
+ }
37
+
38
+ for (const relation of this.getRelatedRelations()) {
39
+ addRelationProperties(relation, 'relatedProp')
40
+ }
41
+
42
+ // Support Objection's #id and #ref references on all models:
43
+ properties[this.uidProp] = {
44
+ type: 'string'
45
+ }
46
+ properties[this.uidRefProp] = {
47
+ type: 'string'
48
+ }
49
+
50
+ // Convert root-level short-forms, for easier properties handling in
51
+ // attributes and idColumn() & co:
52
+ // - `name: type` to `name: { type }`
53
+ // - `name: [...items]` to `name: { type: 'array', items }
54
+ // NOTE: Substitutions on all other levels happen in convertSchema()
55
+ const ids = []
56
+ const rest = []
57
+ for (const [name, property] of Object.entries(properties)) {
58
+ // Also sort properties by kind: primary id > foreign id > rest:
59
+ const entry = [name, property]
60
+ if (property.primary) {
61
+ ids.unshift(entry)
62
+ } else if (property.foreign) {
63
+ ids.push(entry)
64
+ } else {
65
+ rest.push(entry)
66
+ }
67
+ }
68
+ // Finally recompile a new properties object out of the sorted properties.
69
+ return [...ids, ...rest].reduce((merged, [name, property]) => {
70
+ merged[name] = property
71
+ return merged
72
+ }, {})
73
+ }
@@ -0,0 +1,5 @@
1
+ import { mergeReversed } from '../../utils/object.js'
2
+
3
+ export default function relations(values) {
4
+ return mergeReversed(values)
5
+ }
@@ -0,0 +1,5 @@
1
+ import { mergeReversed } from '../../utils/object.js'
2
+
3
+ export default function schema(values) {
4
+ return mergeReversed(values)
5
+ }
@@ -0,0 +1,36 @@
1
+ import { isObject, isFunction } from '@ditojs/utils'
2
+ import { ModelError } from '../../errors/index.js'
3
+ import { mergeReversed } from '../../utils/object.js'
4
+
5
+ export default function scopes(values) {
6
+ const scopes = {}
7
+ // Use mergeAsReversedArrays() to keep lists of filters to be inherited per
8
+ // scope, so they can be called in sequence.
9
+ for (const [name, scope] of Object.entries(mergeReversed(values))) {
10
+ const func = isFunction(scope)
11
+ ? scope
12
+ : isObject(scope)
13
+ ? query => query.find(scope)
14
+ : null
15
+ if (!func) {
16
+ throw new Error(`Invalid scope '${name}': ${scope}.`)
17
+ }
18
+
19
+ const parentModelClass = Object.getPrototypeOf(this)
20
+
21
+ const applyParentScope = query => {
22
+ const parentScope = parentModelClass.getScope(name)
23
+ if (!parentScope) {
24
+ throw new ModelError(this, `Undefined parent scope: '${name}'`)
25
+ }
26
+ parentScope(query)
27
+ return query
28
+ }
29
+
30
+ scopes[name] = query => {
31
+ func(query, applyParentScope)
32
+ return query
33
+ }
34
+ }
35
+ return scopes
36
+ }
@@ -0,0 +1,5 @@
1
+ export * from './Model.js'
2
+ export * from './AssetModel.js'
3
+ export * from './SessionModel.js'
4
+ export * from './TimeStampedModel.js'
5
+ export * from './UserModel.js'