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