@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,66 @@
1
+ import Registry from './Registry.js'
2
+ export const QueryFilters = new Registry()
3
+
4
+ QueryFilters.register({
5
+ 'text': {
6
+ parameters: {
7
+ operator: {
8
+ type: 'string'
9
+ },
10
+ text: {
11
+ type: 'string'
12
+ }
13
+ },
14
+
15
+ handler(query, property, { operator, text }) {
16
+ if (text === undefined) {
17
+ text = operator
18
+ operator = 'contains'
19
+ }
20
+ const templates = {
21
+ 'equals': text => text,
22
+ 'contains': text => `%${text}%`,
23
+ 'starts-with': text => `${text}%`,
24
+ 'ends-with': text => `%${text}`
25
+ }
26
+ if (text) {
27
+ const operand = templates[operator]?.(text)
28
+ if (operand) {
29
+ if (query.isPostgreSQL()) {
30
+ query.where(property, 'ILIKE', operand)
31
+ } else {
32
+ query.whereRaw(
33
+ `LOWER(??) LIKE ?`,
34
+ [property, operand.toLowerCase()]
35
+ )
36
+ }
37
+ }
38
+ }
39
+ }
40
+ },
41
+
42
+ 'date-range': {
43
+ parameters: {
44
+ from: {
45
+ type: 'datetime',
46
+ nullable: true
47
+ },
48
+ to: {
49
+ type: 'datetime',
50
+ nullable: true
51
+ }
52
+ },
53
+
54
+ handler(query, property, { from, to }) {
55
+ if (from && to) {
56
+ query.whereBetween(property, [new Date(from), new Date(to)])
57
+ } else if (from) {
58
+ query.where(property, '>=', new Date(from))
59
+ } else if (to) {
60
+ query.where(property, '<=', new Date(to))
61
+ } else {
62
+ // TODO: Can we get validation to catch the case where both are empty?
63
+ }
64
+ }
65
+ }
66
+ })
@@ -0,0 +1,79 @@
1
+ import { isString, asArray } from '@ditojs/utils'
2
+ import Registry from './Registry.js'
3
+ import { ResponseError, QueryBuilderError } from '../errors/index.js'
4
+
5
+ export const QueryParameters = new Registry()
6
+
7
+ QueryParameters.register({
8
+ scope(query, key, value) {
9
+ // Use `applyScope()` instead of `withScope()`, so the scope applied here
10
+ // can clear earlier `withScope()` statements before `execute()` is run.
11
+ query.applyScope(...asArray(value))
12
+ },
13
+
14
+ filter(query, key, value) {
15
+ try {
16
+ query.applyFilter(
17
+ Object.fromEntries(
18
+ asArray(value).map(filter => {
19
+ const [, name, json] = filter.match(/^(\w+):(.*)$/)
20
+ return [name, JSON.parse(`[${json}]`)]
21
+ })
22
+ )
23
+ )
24
+ } catch (error) {
25
+ throw error instanceof ResponseError
26
+ ? error
27
+ : new QueryBuilderError(
28
+ `Invalid Query filter parameters: ${error.message}.`
29
+ )
30
+ }
31
+ },
32
+
33
+ range(query, key, value) {
34
+ if (value) {
35
+ const [from, to] = isString(value) ? value.split(/\s*,s*/) : value
36
+ const start = +from
37
+ const end = +to
38
+ if (isNaN(start) || isNaN(end) || end < start) {
39
+ throw new QueryBuilderError(`Invalid range: [${start}, ${end}].`)
40
+ }
41
+ query.range(start, end)
42
+ }
43
+ },
44
+
45
+ limit(query, key, value) {
46
+ query.limit(value)
47
+ },
48
+
49
+ offset(query, key, value) {
50
+ query.offset(value)
51
+ },
52
+
53
+ order(query, key, value) {
54
+ if (value) {
55
+ for (const entry of asArray(value)) {
56
+ const [propertyName, direction, nulls] = entry.trim().split(/\s+/)
57
+ if (direction && !['asc', 'desc'].includes(direction)) {
58
+ throw new QueryBuilderError(
59
+ `Invalid order direction: '${direction}'.`
60
+ )
61
+ }
62
+ if (nulls && !['first', 'last'].includes(nulls)) {
63
+ throw new QueryBuilderError(
64
+ `Invalid nulls order: '${null}'.`
65
+ )
66
+ }
67
+ const tableRef = query.tableRefFor(query.modelClass())
68
+ const columnName = `${tableRef}.${propertyName}`
69
+ if (nulls) {
70
+ query.orderBy(columnName, direction, nulls)
71
+ } else if (direction) {
72
+ query.orderBy(columnName, direction)
73
+ } else {
74
+ query.orderBy(columnName)
75
+ }
76
+ }
77
+ }
78
+ }
79
+ })
@@ -0,0 +1,29 @@
1
+ import { isObject } from '@ditojs/utils'
2
+
3
+ export default class Registry {
4
+ #registry = Object.create(null)
5
+ #allowed = Object.create(null)
6
+
7
+ get(name) {
8
+ return this.#registry[name]
9
+ }
10
+
11
+ has(name) {
12
+ return name in this.#registry
13
+ }
14
+
15
+ register(name, handler) {
16
+ if (isObject(name)) {
17
+ for (const key in name) {
18
+ this.register(key, name[key])
19
+ }
20
+ } else {
21
+ this.#registry[name] = handler
22
+ this.#allowed[name] = true
23
+ }
24
+ }
25
+
26
+ getAllowed() {
27
+ return this.#allowed
28
+ }
29
+ }
@@ -0,0 +1,3 @@
1
+ export * from './QueryBuilder.js'
2
+ export * from './QueryFilters.js'
3
+ export * from './QueryParameters.js'
@@ -0,0 +1,4 @@
1
+ export const empty = {
2
+ validate: value => !value,
3
+ message: 'needs to be empty'
4
+ }
@@ -0,0 +1,4 @@
1
+ export const required = {
2
+ validate: value => value != null && value !== '',
3
+ message: 'is required'
4
+ }
@@ -0,0 +1,2 @@
1
+ export * from './_empty.js'
2
+ export * from './_required.js'
@@ -0,0 +1,5 @@
1
+ export * as keywords from './keywords/index.js'
2
+ export * as formats from './formats/index.js'
3
+ export * as types from './types/index.js'
4
+ export * from './schema.js'
5
+ export * from './relations.js'
@@ -0,0 +1,7 @@
1
+ // This keyword is there for meta-information and migration generation only.
2
+ // It doesn't do any actual validation.
3
+ export const computed = {
4
+ metaSchema: {
5
+ type: 'boolean'
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ // This keyword is there for meta-information and migration generation only.
2
+ // It doesn't do any actual validation.
3
+ export const foreign = {
4
+ metaSchema: {
5
+ type: 'boolean'
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ // This keyword is there for meta-information and migration generation only.
2
+ // It doesn't do any actual validation.
3
+ export const hidden = {
4
+ metaSchema: {
5
+ type: 'boolean'
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ // This keyword is there for meta-information and migration generation only.
2
+ // It doesn't do any actual validation.
3
+ export const index = {
4
+ metaSchema: {
5
+ type: 'boolean'
6
+ }
7
+ }
@@ -0,0 +1,45 @@
1
+ import { isString, isFunction, asArray } from '@ditojs/utils'
2
+
3
+ export const _instanceof = {
4
+ metaSchema: {
5
+ anyOf: [
6
+ { type: 'string' },
7
+ {
8
+ type: 'array',
9
+ items: {
10
+ type: ['string', 'object']
11
+ }
12
+ }
13
+ ]
14
+ },
15
+
16
+ validate(schema, data) {
17
+ // Support instanceof for basic JS types and Dito.js models. If `this` is
18
+ // the validator's ctx (see passContext), then we can access the models and
19
+ // check.
20
+ const models = this?.app?.models
21
+ for (const type of asArray(schema)) {
22
+ const ctor = isString(type)
23
+ ? constructors[type] || models?.[type]
24
+ : isFunction(type)
25
+ ? type
26
+ : null
27
+ if (ctor && data instanceof ctor) {
28
+ return true
29
+ }
30
+ }
31
+ return false
32
+ }
33
+ }
34
+
35
+ const constructors = {
36
+ Object,
37
+ Array,
38
+ Function,
39
+ String,
40
+ Number,
41
+ Boolean,
42
+ Date,
43
+ RegExp,
44
+ Buffer
45
+ }
@@ -0,0 +1,7 @@
1
+ // This keyword is there for meta-information and migration generation only.
2
+ // It doesn't do any actual validation.
3
+ export const primary = {
4
+ metaSchema: {
5
+ type: 'boolean'
6
+ }
7
+ }
@@ -0,0 +1,18 @@
1
+ export const range = {
2
+ type: ['number', 'integer'],
3
+ metaSchema: {
4
+ type: 'array',
5
+ prefixItems: [
6
+ { type: 'number' },
7
+ { type: 'number' }
8
+ ],
9
+ minItems: 2,
10
+ items: false
11
+ },
12
+ macro(config) {
13
+ return {
14
+ minimum: config[0],
15
+ maximum: config[1]
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,13 @@
1
+ // This keyword is used to validate reference-only data in relations: { id: 1 }
2
+ // See ../schema/relation.js for more details
3
+ export const relate = {
4
+ silent: true,
5
+
6
+ metaSchema: {
7
+ type: 'string'
8
+ },
9
+
10
+ validate(schema, data) {
11
+ return this.app?.models[schema]?.isReference(data) || false
12
+ }
13
+ }
@@ -0,0 +1,7 @@
1
+ // This keyword is there for meta-information and migration generation only.
2
+ // It doesn't do any actual validation.
3
+ export const specificType = {
4
+ metaSchema: {
5
+ type: 'string'
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ // This keyword is there for meta-information and migration generation only.
2
+ // It doesn't do any actual validation.
3
+ export const unique = {
4
+ metaSchema: {
5
+ type: ['boolean', 'string']
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ // This keyword is there for meta-information and migration generation only.
2
+ // It doesn't do any actual validation.
3
+ export const unsigned = {
4
+ metaSchema: {
5
+ type: 'boolean'
6
+ }
7
+ }
@@ -0,0 +1,73 @@
1
+ import Ajv from 'ajv/dist/2020.js'
2
+ import { isNumber, isArray } from '@ditojs/utils'
3
+
4
+ export const validate = {
5
+ metaSchema: {
6
+ instanceof: 'Function'
7
+ },
8
+ errors: 'full',
9
+
10
+ validate: function validate(func, data, parentSchema, dataCtx) {
11
+ // The validator's `ctx` as passed to Ajv with passContext as `this`:
12
+ const params = getParams(this, data, parentSchema, dataCtx)
13
+ try {
14
+ return func(params) ?? true
15
+ } catch (error) {
16
+ // In sync validation, we have to pass the errors back to Ajv through
17
+ // `validate.errors`.
18
+ validate.errors = getErrors(error, params)
19
+ return false
20
+ }
21
+ }
22
+ }
23
+
24
+ // This is the async version of the above, to handle async `validate()`
25
+ // functions in schemas. See `Validator.processSchema()` for details.
26
+ export const validateAsync = {
27
+ ...validate,
28
+ async: true,
29
+
30
+ async validate(func, data, parentSchema, dataCtx) {
31
+ // The validator's `ctx` as passed to Ajv with passContext as `this`:
32
+ const params = getParams(this, data, parentSchema, dataCtx)
33
+ try {
34
+ return (await func(params)) ?? true
35
+ } catch (error) {
36
+ // Async validate methods need to throw their errors.
37
+ throw new Ajv.ValidationError(getErrors(error, params))
38
+ }
39
+ }
40
+ }
41
+
42
+ function getParams(ctx, data, parentSchema, dataCtx) {
43
+ const { instancePath, parentData, parentDataProperty, rootData } = dataCtx
44
+ return {
45
+ data,
46
+ parentData,
47
+ rootData,
48
+ instancePath,
49
+ // NOTE: We rename parentDataProperty to parentKey / parentIndex:
50
+ [isNumber(parentDataProperty) ? 'parentIndex' : 'parentKey']:
51
+ parentDataProperty,
52
+ ctx,
53
+ app: ctx.app,
54
+ validator: ctx.validator,
55
+ options: ctx.options
56
+ }
57
+ }
58
+
59
+ function getErrors(error, { validator, instancePath }) {
60
+ const errors = isArray(error.errors)
61
+ ? // Ajv errors array:
62
+ error.errors
63
+ : // Convert string error message to errors array:
64
+ [
65
+ {
66
+ keyword: 'validate',
67
+ message: error.message || error.toString(),
68
+ params: {}
69
+ }
70
+ ]
71
+ // Return errors prefixed with the current instancePath:
72
+ return validator.prefixInstancePaths(errors, instancePath)
73
+ }
@@ -0,0 +1,12 @@
1
+ export * from './_specificType.js'
2
+ export * from './_primary.js'
3
+ export * from './_foreign.js'
4
+ export * from './_unique.js'
5
+ export * from './_index.js'
6
+ export * from './_computed.js'
7
+ export * from './_hidden.js'
8
+ export * from './_unsigned.js'
9
+ export * from './_instanceof.js'
10
+ export * from './_validate.js'
11
+ export * from './_relate.js'
12
+ export * from './_range.js'