@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,289 @@
|
|
|
1
|
+
import { isObject, isArray, isString, equals } from '@ditojs/utils'
|
|
2
|
+
|
|
3
|
+
const schemaCaches = {}
|
|
4
|
+
|
|
5
|
+
function getSchemaCache(options) {
|
|
6
|
+
const key = Object.entries(options || {})
|
|
7
|
+
.toSorted()
|
|
8
|
+
.map(([key, value]) => `${key}:${value}`)
|
|
9
|
+
.join(',') || 'default'
|
|
10
|
+
return (schemaCaches[key] ||= new WeakMap())
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function convertSchema(
|
|
14
|
+
schema,
|
|
15
|
+
options = {},
|
|
16
|
+
parentEntry = null
|
|
17
|
+
) {
|
|
18
|
+
const original = schema
|
|
19
|
+
const isRoot = parentEntry === null
|
|
20
|
+
|
|
21
|
+
const schemaCache = getSchemaCache(options)
|
|
22
|
+
if (schemaCache.has(original)) {
|
|
23
|
+
const { schema, definitions, parentEntries } = schemaCache.get(original)
|
|
24
|
+
parentEntries.push(parentEntry)
|
|
25
|
+
if (definitions) {
|
|
26
|
+
if (isRoot) {
|
|
27
|
+
return { ...schema, definitions }
|
|
28
|
+
} else {
|
|
29
|
+
mergeDefinitions(parentEntry.definitions, definitions)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// When the schema is null, a circular reference is being processed.
|
|
33
|
+
// Break the chain by cloning the original schema and converting it again:
|
|
34
|
+
return schema === null
|
|
35
|
+
? convertSchema({ ...original }, options, parentEntry)
|
|
36
|
+
: schema
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const entry = {
|
|
40
|
+
schema: null,
|
|
41
|
+
definitions: {},
|
|
42
|
+
parentEntries: parentEntry ? [parentEntry] : []
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// To prevent circular references, cache the entry before all conversion work.
|
|
46
|
+
schemaCache.set(original, entry)
|
|
47
|
+
|
|
48
|
+
let definitions = null
|
|
49
|
+
if (isArray(schema)) {
|
|
50
|
+
// Needed for allOf, anyOf, oneOf, not, items, see below:
|
|
51
|
+
schema = schema.map(item => convertSchema(item, options, entry))
|
|
52
|
+
} else if (isObject(schema)) {
|
|
53
|
+
// Create a shallow clone so we can modify and return:
|
|
54
|
+
// Also collect and propagate the definitions up to the root schema through
|
|
55
|
+
// `options.definitions`, as passed from `Model static get jsonSchema()`:
|
|
56
|
+
const { definitions: defs, ...rest } = schema
|
|
57
|
+
definitions = defs
|
|
58
|
+
schema = rest
|
|
59
|
+
const { $ref, type } = schema
|
|
60
|
+
const jsonType = jsonTypes[type]
|
|
61
|
+
|
|
62
|
+
if (schema.required === true) {
|
|
63
|
+
// Our 'required' is not the same as JSON Schema's: Use the 'required'
|
|
64
|
+
// format instead that only validates if the required value is not empty,
|
|
65
|
+
// meaning neither nullish nor an empty string. The JSON schema `required`
|
|
66
|
+
// array is generated separately below through `convertProperties()`.
|
|
67
|
+
delete schema.required
|
|
68
|
+
schema = addFormat(schema, 'required')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Convert array items
|
|
72
|
+
schema.prefixItems &&= convertSchema(schema.prefixItems, options, entry)
|
|
73
|
+
schema.items &&= convertSchema(schema.items, options, entry)
|
|
74
|
+
|
|
75
|
+
// Handle nested allOf, anyOf, oneOf & co. fields
|
|
76
|
+
for (const key of ['allOf', 'anyOf', 'oneOf', 'not', '$extend']) {
|
|
77
|
+
if (key in schema) {
|
|
78
|
+
schema[key] = convertSchema(schema[key], options, entry)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isString($ref)) {
|
|
83
|
+
// If the $ref is a nested Dito.js definition, convert it to a JSON schema
|
|
84
|
+
// reference. If it is a full URL, use it as is.
|
|
85
|
+
schema.$ref = $ref.startsWith('#')
|
|
86
|
+
? `#/definitions/${$ref}`
|
|
87
|
+
: $ref
|
|
88
|
+
} else if (isString(type)) {
|
|
89
|
+
if (jsonType) {
|
|
90
|
+
schema.type = jsonType
|
|
91
|
+
} else if (['date', 'datetime', 'timestamp'].includes(type)) {
|
|
92
|
+
// Date properties can be submitted both as a string or a Date object.
|
|
93
|
+
// Provide validation through date-time format, which in Ajv appears
|
|
94
|
+
// to handle both types correctly.
|
|
95
|
+
schema.type = ['string', 'object']
|
|
96
|
+
schema = addFormat(schema, 'date-time')
|
|
97
|
+
} else if (type !== 'null') {
|
|
98
|
+
// A reference to another model as nested JSON data, use $ref or
|
|
99
|
+
// instanceof instead of type, based on the passed option:
|
|
100
|
+
if (options.useInstanceOf) {
|
|
101
|
+
schema.type = 'object'
|
|
102
|
+
schema.instanceof = type
|
|
103
|
+
} else {
|
|
104
|
+
// Move `type` to `$ref`, but still keep other properties for now,
|
|
105
|
+
// to support `nullable`.
|
|
106
|
+
delete schema.type
|
|
107
|
+
// TODO: Consider moving to `model` keyword instead that would support
|
|
108
|
+
// model validation and still could be combined with other keywords.
|
|
109
|
+
schema.$ref = type
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (excludeDefaults[schema.default]) {
|
|
114
|
+
delete schema.default
|
|
115
|
+
}
|
|
116
|
+
if (schema.nullable) {
|
|
117
|
+
if (schema.$ref) {
|
|
118
|
+
// `$ref` doesn't play with `nullable`, so convert to `oneOf`
|
|
119
|
+
delete schema.nullable
|
|
120
|
+
schema = {
|
|
121
|
+
oneOf: [
|
|
122
|
+
{ type: 'null' },
|
|
123
|
+
schema
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
} else if (schema.enum && !schema.enum.includes(null)) {
|
|
127
|
+
// Make nullable work with enum, see the issue for more details:
|
|
128
|
+
// https://github.com/ajv-validator/ajv/issues/1471
|
|
129
|
+
schema.enum.push(null)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Convert properties last. This is needed for circular references
|
|
134
|
+
// to work correctly, as the properties may reference the same schema
|
|
135
|
+
// that is being converted right now.
|
|
136
|
+
let hasConvertedProperties = false
|
|
137
|
+
if (schema.properties) {
|
|
138
|
+
const { properties, required } = convertProperties(
|
|
139
|
+
schema.properties,
|
|
140
|
+
options,
|
|
141
|
+
entry
|
|
142
|
+
)
|
|
143
|
+
schema.properties = properties
|
|
144
|
+
if (required.length > 0) {
|
|
145
|
+
schema.required = required
|
|
146
|
+
}
|
|
147
|
+
hasConvertedProperties = true
|
|
148
|
+
}
|
|
149
|
+
for (const key of ['additionalProperties', 'patternProperties']) {
|
|
150
|
+
if (isObject(schema[key])) {
|
|
151
|
+
// TODO: Don't we need to handle required here too?
|
|
152
|
+
const { properties } = convertProperties(
|
|
153
|
+
schema[key],
|
|
154
|
+
options,
|
|
155
|
+
entry
|
|
156
|
+
)
|
|
157
|
+
schema[key] = properties
|
|
158
|
+
hasConvertedProperties = true
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (
|
|
162
|
+
jsonType &&
|
|
163
|
+
(hasConvertedProperties || schema.discriminator) &&
|
|
164
|
+
!('unevaluatedProperties' in schema)
|
|
165
|
+
) {
|
|
166
|
+
// Invert the logic of `unevaluatedProperties` so that it needs to be
|
|
167
|
+
// explicitly set to `true`:
|
|
168
|
+
schema.unevaluatedProperties = false
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
entry.schema = schema
|
|
173
|
+
|
|
174
|
+
// Only convert definitions once `entry.schema` is set, so that it works as
|
|
175
|
+
// expected with circular references.
|
|
176
|
+
if (definitions) {
|
|
177
|
+
mergeDefinitions(
|
|
178
|
+
entry.definitions,
|
|
179
|
+
convertDefinitions(definitions, options, entry)
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (Object.keys(entry.definitions).length > 0) {
|
|
184
|
+
// Propagate the definitions up the parent entry chains, that due to
|
|
185
|
+
// circular references may not be up to date yet.
|
|
186
|
+
mergeDefinitionsRecursively(entry, entry.definitions)
|
|
187
|
+
if (isRoot) {
|
|
188
|
+
schema.definitions = entry.definitions
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return schema
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function convertProperties(schemaProperties, options, entry) {
|
|
196
|
+
const properties = {}
|
|
197
|
+
const required = []
|
|
198
|
+
for (const [key, property] of Object.entries(schemaProperties)) {
|
|
199
|
+
properties[key] = convertSchema(property, options, entry)
|
|
200
|
+
if (property?.required) {
|
|
201
|
+
required.push(key)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { properties, required }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function convertDefinitions(definitions, options, entry) {
|
|
208
|
+
let converted = null
|
|
209
|
+
for (const [key, schema] of Object.entries(definitions)) {
|
|
210
|
+
if (!key.startsWith('#')) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Invalid definition '${
|
|
213
|
+
key
|
|
214
|
+
}', the name of nested Dito.js definitions must start with '#': ${
|
|
215
|
+
JSON.stringify(schema)
|
|
216
|
+
}`
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
converted ??= {}
|
|
220
|
+
converted[key] = convertSchema(schema, options, entry)
|
|
221
|
+
}
|
|
222
|
+
return converted
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function mergeDefinitions(definitions, defs) {
|
|
226
|
+
for (const [key, def] of Object.entries(defs)) {
|
|
227
|
+
const definition = definitions[key]
|
|
228
|
+
if (definition && !equals(definition, def)) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
`Duplicate nested definition for '${key}' with different schema: ${
|
|
231
|
+
JSON.stringify(def, null, 2)
|
|
232
|
+
}, ${
|
|
233
|
+
JSON.stringify(definition, null, 2)
|
|
234
|
+
}`
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
definitions[key] ??= def
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function mergeDefinitionsRecursively(
|
|
242
|
+
entry,
|
|
243
|
+
definitions,
|
|
244
|
+
visited = new WeakSet()
|
|
245
|
+
) {
|
|
246
|
+
if (!visited.has(entry)) {
|
|
247
|
+
visited.add(entry)
|
|
248
|
+
|
|
249
|
+
if (definitions !== entry.definitions) {
|
|
250
|
+
mergeDefinitions(entry.definitions, definitions)
|
|
251
|
+
}
|
|
252
|
+
for (const parentEntry of entry.parentEntries) {
|
|
253
|
+
mergeDefinitionsRecursively(parentEntry, definitions, visited)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function addFormat(schema, newFormat) {
|
|
259
|
+
// Support multiple `format` keywords through `allOf`:
|
|
260
|
+
const { allOf, format, ...rest } = schema
|
|
261
|
+
if (format || allOf) {
|
|
262
|
+
if (!allOf?.find(({ format }) => format === newFormat)) {
|
|
263
|
+
schema = {
|
|
264
|
+
...rest,
|
|
265
|
+
allOf: [...(allOf ?? []), { format }, { format: newFormat }]
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
schema.format = newFormat
|
|
270
|
+
}
|
|
271
|
+
return schema
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Table to translate schema types to JSON schema types. Other types are allowed
|
|
275
|
+
// also, e.g. 'date', 'datetime', 'timestamp', but they need special treatment.
|
|
276
|
+
// Anything not recognized as a type is used as a $ref instead.
|
|
277
|
+
const jsonTypes = {
|
|
278
|
+
string: 'string',
|
|
279
|
+
text: 'string',
|
|
280
|
+
number: 'number',
|
|
281
|
+
integer: 'integer',
|
|
282
|
+
boolean: 'boolean',
|
|
283
|
+
object: 'object',
|
|
284
|
+
array: 'array'
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const excludeDefaults = {
|
|
288
|
+
'now()': true
|
|
289
|
+
}
|