@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,324 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Relation,
|
|
3
|
+
BelongsToOneRelation,
|
|
4
|
+
HasOneRelation,
|
|
5
|
+
HasOneThroughRelation,
|
|
6
|
+
HasManyRelation,
|
|
7
|
+
ManyToManyRelation
|
|
8
|
+
} from 'objection'
|
|
9
|
+
import {
|
|
10
|
+
isObject,
|
|
11
|
+
isArray,
|
|
12
|
+
isString,
|
|
13
|
+
isFunction,
|
|
14
|
+
asArray,
|
|
15
|
+
capitalize,
|
|
16
|
+
camelize
|
|
17
|
+
} from '@ditojs/utils'
|
|
18
|
+
import { RelationError } from '../errors/index.js'
|
|
19
|
+
|
|
20
|
+
const relationLookup = {
|
|
21
|
+
// one:
|
|
22
|
+
belongsTo: BelongsToOneRelation,
|
|
23
|
+
hasOne: HasOneRelation,
|
|
24
|
+
hasOneThrough: HasOneThroughRelation,
|
|
25
|
+
// many:
|
|
26
|
+
hasMany: HasManyRelation,
|
|
27
|
+
manyToMany: ManyToManyRelation
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const relationClasses = {
|
|
31
|
+
BelongsToOneRelation,
|
|
32
|
+
HasOneRelation,
|
|
33
|
+
HasOneThroughRelation,
|
|
34
|
+
HasManyRelation,
|
|
35
|
+
ManyToManyRelation
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const throughRelationClasses = {
|
|
39
|
+
ManyToManyRelation,
|
|
40
|
+
HasOneThroughRelation
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class ModelReference {
|
|
44
|
+
constructor(reference, models, allowUnknown = false) {
|
|
45
|
+
this.modelName = null
|
|
46
|
+
this.modelClass = null
|
|
47
|
+
this.propertyNames = []
|
|
48
|
+
// Parse and validate model key references
|
|
49
|
+
for (const ref of asArray(reference)) {
|
|
50
|
+
const [modelName, propertyName] = ref?.split('.') || []
|
|
51
|
+
const modelClass = models[modelName]
|
|
52
|
+
if (!modelClass && !allowUnknown) {
|
|
53
|
+
throw new RelationError(
|
|
54
|
+
`Unknown model reference: ${ref}`
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
if (!this.modelName) {
|
|
58
|
+
this.modelName = modelName
|
|
59
|
+
this.modelClass = modelClass
|
|
60
|
+
} else if (this.modelName !== modelName) {
|
|
61
|
+
throw new RelationError(
|
|
62
|
+
`Composite keys need to be defined on the same table: ${ref}`
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
this.propertyNames.push(propertyName)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
asValue(properties) {
|
|
70
|
+
// Unpack the reference array if it doesn't contain multiple properties.
|
|
71
|
+
return properties.length > 1 ? properties : properties[0]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
toValue() {
|
|
75
|
+
return this.asValue(
|
|
76
|
+
this.propertyNames.map(
|
|
77
|
+
propName => `${this.modelName}.${propName}`
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
toString() {
|
|
83
|
+
const value = this.toValue()
|
|
84
|
+
return isArray(value) ? `[${value.join(', ')}]` : value
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
buildThrough(toRef, inverse) {
|
|
88
|
+
// Auto-generate the `through` setting based on simple conventions:
|
|
89
|
+
// - The camelized through table is called: `${fromName}${toName}`
|
|
90
|
+
// - The `through.from` property is called:
|
|
91
|
+
// `${fromModelName)${capitalize(fromProp)}`
|
|
92
|
+
// - The `through.to` property is called:
|
|
93
|
+
// `${toModelName)${capitalize(toProp)}`
|
|
94
|
+
// NOTE: Due to the use of knexSnakeCaseMappers(), there is no need to
|
|
95
|
+
// generate actual table-names here.
|
|
96
|
+
const fromName = this.modelName
|
|
97
|
+
const fromProperties = this.propertyNames
|
|
98
|
+
const toName = toRef.modelName
|
|
99
|
+
const toProperties = toRef.propertyNames
|
|
100
|
+
if (fromProperties.length !== toProperties.length) {
|
|
101
|
+
throw new RelationError(
|
|
102
|
+
'Unable to create through join for ' +
|
|
103
|
+
`composite keys from '${this}' to '${toRef}'`
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
const from = []
|
|
107
|
+
const to = []
|
|
108
|
+
for (let i = 0; i < fromProperties.length; i++) {
|
|
109
|
+
const fromProperty = fromProperties[i]
|
|
110
|
+
const toProperty = toProperties[i]
|
|
111
|
+
if (fromProperty && toProperty) {
|
|
112
|
+
const throughName = `${fromName}${toName}`
|
|
113
|
+
const throughFrom = this.getThroughProperty(fromName, fromProperty)
|
|
114
|
+
const throughTo = this.getThroughProperty(toName, toProperty)
|
|
115
|
+
from.push(`${throughName}.${throughFrom}`)
|
|
116
|
+
to.push(`${throughName}.${throughTo}`)
|
|
117
|
+
} else {
|
|
118
|
+
throw new RelationError(
|
|
119
|
+
`Unable to create through join from '${this}' to '${to}'`
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
from: this.asValue(inverse ? to : from),
|
|
125
|
+
to: this.asValue(inverse ? from : to)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
getThroughProperty(name, property) {
|
|
130
|
+
return `${name[0].toLowerCase()}${name.slice(1)}${capitalize(property)}`
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function getRelationClass(relation) {
|
|
135
|
+
return isString(relation)
|
|
136
|
+
? relationLookup[camelize(relation)] || relationClasses[relation]
|
|
137
|
+
: Relation.isPrototypeOf(relation)
|
|
138
|
+
? relation
|
|
139
|
+
: null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function isThroughRelationClass(relationClass) {
|
|
143
|
+
return throughRelationClasses[relationClass.name]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function convertRelation(schema, models) {
|
|
147
|
+
let {
|
|
148
|
+
relation,
|
|
149
|
+
// Dito.js-style relation description:
|
|
150
|
+
modelClass,
|
|
151
|
+
from,
|
|
152
|
+
to,
|
|
153
|
+
through,
|
|
154
|
+
inverse,
|
|
155
|
+
modify,
|
|
156
|
+
scope,
|
|
157
|
+
filter,
|
|
158
|
+
// Objection.js-style relation description (`modify` is shared)
|
|
159
|
+
join,
|
|
160
|
+
// Pluck Dito.js-related properties that should not end up in `rest`:
|
|
161
|
+
nullable,
|
|
162
|
+
owner,
|
|
163
|
+
...rest
|
|
164
|
+
} = schema || {}
|
|
165
|
+
const relationClass = getRelationClass(relation)
|
|
166
|
+
if (!relationClass) {
|
|
167
|
+
throw new RelationError(`Unrecognized relation: ${relation}`)
|
|
168
|
+
} else if (join && !isString(relation)) {
|
|
169
|
+
// Original Objection.js-style relation, just pass through
|
|
170
|
+
return schema
|
|
171
|
+
} else {
|
|
172
|
+
// Dito.js-style relation, e.g.:
|
|
173
|
+
// {
|
|
174
|
+
// relation: 'hasMany',
|
|
175
|
+
// from: 'FromModel.primaryKeyPropertyName',
|
|
176
|
+
// to: 'ToModel.foreignKeyPropertyName',
|
|
177
|
+
// scope: 'latest'
|
|
178
|
+
// }
|
|
179
|
+
from = new ModelReference(from, models, false)
|
|
180
|
+
to = new ModelReference(to, models, false)
|
|
181
|
+
if (isThroughRelationClass(relationClass)) {
|
|
182
|
+
if (!through) {
|
|
183
|
+
// Auto-generate the through settings, see buildThrough():
|
|
184
|
+
through = inverse
|
|
185
|
+
? to.buildThrough(from, true)
|
|
186
|
+
: from.buildThrough(to, false)
|
|
187
|
+
} else if (through.from && through.to) {
|
|
188
|
+
// `through.from` and `through.to` need special processing, where they
|
|
189
|
+
// can either be model references or table references, and if they
|
|
190
|
+
// reference models, they should be converted to table references and
|
|
191
|
+
// the model should be extracted automatically.
|
|
192
|
+
const throughFrom = new ModelReference(through.from, models, true)
|
|
193
|
+
const throughTo = new ModelReference(through.to, models, true)
|
|
194
|
+
if (throughFrom.modelClass || throughTo.modelClass) {
|
|
195
|
+
if (throughFrom.modelClass === throughTo.modelClass) {
|
|
196
|
+
through.modelClass = throughTo.modelClass
|
|
197
|
+
through.from = throughFrom.toValue()
|
|
198
|
+
through.to = throughTo.toValue()
|
|
199
|
+
} else {
|
|
200
|
+
throw new RelationError(
|
|
201
|
+
'Both sides of the `through` definition ' +
|
|
202
|
+
'need to be on the same join model'
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
// Assume the references are to a join table, and use the settings
|
|
207
|
+
// unmodified.
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
throw new RelationError(
|
|
211
|
+
'The relation needs a `through.from` and ' + '`through.to` definition'
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
} else if (through) {
|
|
215
|
+
throw new RelationError('Unsupported through join definition')
|
|
216
|
+
}
|
|
217
|
+
// Combine `modify` and `filter` when `filter` is a function (Objection.js
|
|
218
|
+
// backward-compatible style). Otherwise, handle `filter` as a Dito.js-style
|
|
219
|
+
// filter name/array below.
|
|
220
|
+
if (isFunction(filter)) {
|
|
221
|
+
modify ||= filter
|
|
222
|
+
filter = null
|
|
223
|
+
}
|
|
224
|
+
if (isObject(modify)) {
|
|
225
|
+
// Convert a find-filter object to a filter function, same as in the
|
|
226
|
+
// handling of definition.scopes, see Model.js
|
|
227
|
+
modify = query => query.find(modify)
|
|
228
|
+
}
|
|
229
|
+
if (scope) {
|
|
230
|
+
// Create a new modify function that merges scope and modify:
|
|
231
|
+
const origModify = modify
|
|
232
|
+
modify = query => {
|
|
233
|
+
query.applyScope(
|
|
234
|
+
...asArray(scope),
|
|
235
|
+
// Pass `false` for `checkAllowedScopes`, to always allow scopes
|
|
236
|
+
// that are defined on relations to be applied.
|
|
237
|
+
false
|
|
238
|
+
)
|
|
239
|
+
if (origModify) {
|
|
240
|
+
query.modify(origModify)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (filter) {
|
|
245
|
+
// Create a new modify function that merges filter and modify:
|
|
246
|
+
const origModify = modify
|
|
247
|
+
modify = query => {
|
|
248
|
+
query.applyFilter(filter)
|
|
249
|
+
if (origModify) {
|
|
250
|
+
query.modify(origModify)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (isString(modelClass)) {
|
|
255
|
+
const modelName = modelClass
|
|
256
|
+
modelClass = models[modelName]
|
|
257
|
+
if (!modelClass) {
|
|
258
|
+
throw new RelationError(
|
|
259
|
+
`Unknown model class: ${modelName}`
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
relation: relationClass,
|
|
265
|
+
modelClass: modelClass ?? to.modelClass,
|
|
266
|
+
join: {
|
|
267
|
+
from: from.toValue(),
|
|
268
|
+
to: to.toValue(),
|
|
269
|
+
...(through && { through })
|
|
270
|
+
},
|
|
271
|
+
...(modify && { modify }),
|
|
272
|
+
...rest
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function convertRelations(ownerModelClass, relations, models) {
|
|
278
|
+
const converted = {}
|
|
279
|
+
for (const [name, relation] of Object.entries(relations)) {
|
|
280
|
+
try {
|
|
281
|
+
converted[name] = convertRelation(relation, models)
|
|
282
|
+
} catch (err) {
|
|
283
|
+
throw new RelationError(
|
|
284
|
+
`${ownerModelClass.name}.relations.${name}: ${err.message || err}`
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return converted
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Adds JSON schema properties for each of the modelClass' relations.
|
|
293
|
+
*/
|
|
294
|
+
export function addRelationSchemas(modelClass, properties) {
|
|
295
|
+
for (const relationInstance of Object.values(modelClass.getRelations())) {
|
|
296
|
+
const { name, ownerModelClass, relatedModelClass } = relationInstance
|
|
297
|
+
const relationDefinition = ownerModelClass.definition.relations[name]
|
|
298
|
+
const isOneToOne = relationInstance.isOneToOne()
|
|
299
|
+
const { owner } = relationDefinition
|
|
300
|
+
const $ref = relatedModelClass.name
|
|
301
|
+
const anyOf = []
|
|
302
|
+
if (isOneToOne) {
|
|
303
|
+
// Allow null-value on one-to-one relations
|
|
304
|
+
anyOf.push({ type: 'null' })
|
|
305
|
+
}
|
|
306
|
+
if (!owner) {
|
|
307
|
+
// Allow reference objects for relations that don't own their data.
|
|
308
|
+
anyOf.push({ relate: $ref })
|
|
309
|
+
}
|
|
310
|
+
// Finally the model itself
|
|
311
|
+
anyOf.push({ $ref })
|
|
312
|
+
const items =
|
|
313
|
+
anyOf.length > 1
|
|
314
|
+
? { anyOf }
|
|
315
|
+
: anyOf[0]
|
|
316
|
+
properties[name] = isOneToOne
|
|
317
|
+
? items
|
|
318
|
+
: {
|
|
319
|
+
type: 'array',
|
|
320
|
+
items
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return properties
|
|
324
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BelongsToOneRelation,
|
|
3
|
+
HasOneRelation,
|
|
4
|
+
HasOneThroughRelation,
|
|
5
|
+
HasManyRelation,
|
|
6
|
+
ManyToManyRelation
|
|
7
|
+
} from 'objection'
|
|
8
|
+
import { Model } from '../models/index.js'
|
|
9
|
+
import {
|
|
10
|
+
getRelationClass,
|
|
11
|
+
convertRelation,
|
|
12
|
+
addRelationSchemas
|
|
13
|
+
} from './relations.js'
|
|
14
|
+
|
|
15
|
+
describe('getRelationClass()', () => {
|
|
16
|
+
it('returns the corresponding relation classes', () => {
|
|
17
|
+
expect(getRelationClass('belongs-to')).toBe(BelongsToOneRelation)
|
|
18
|
+
expect(getRelationClass('has-one')).toBe(HasOneRelation)
|
|
19
|
+
expect(getRelationClass('has-one-through')).toBe(HasOneThroughRelation)
|
|
20
|
+
expect(getRelationClass('has-many')).toBe(HasManyRelation)
|
|
21
|
+
expect(getRelationClass('many-to-many')).toBe(ManyToManyRelation)
|
|
22
|
+
|
|
23
|
+
expect(getRelationClass('belongsTo')).toBe(BelongsToOneRelation)
|
|
24
|
+
expect(getRelationClass('hasOne')).toBe(HasOneRelation)
|
|
25
|
+
expect(getRelationClass('hasOneThrough')).toBe(HasOneThroughRelation)
|
|
26
|
+
expect(getRelationClass('hasMany')).toBe(HasManyRelation)
|
|
27
|
+
expect(getRelationClass('manyToMany')).toBe(ManyToManyRelation)
|
|
28
|
+
|
|
29
|
+
expect(getRelationClass('BelongsToOneRelation')).toBe(BelongsToOneRelation)
|
|
30
|
+
expect(getRelationClass('HasOneRelation')).toBe(HasOneRelation)
|
|
31
|
+
expect(getRelationClass('HasOneThroughRelation'))
|
|
32
|
+
.toBe(HasOneThroughRelation)
|
|
33
|
+
expect(getRelationClass('HasManyRelation')).toBe(HasManyRelation)
|
|
34
|
+
expect(getRelationClass('ManyToManyRelation')).toBe(ManyToManyRelation)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('convertRelation()', () => {
|
|
39
|
+
class ModelOne extends Model {}
|
|
40
|
+
class ModelTwo extends Model {}
|
|
41
|
+
|
|
42
|
+
const models = { ModelOne, ModelTwo }
|
|
43
|
+
|
|
44
|
+
it('converts a belongs-to relation to Objection.js format', () => {
|
|
45
|
+
expect(
|
|
46
|
+
convertRelation(
|
|
47
|
+
{
|
|
48
|
+
relation: 'belongs-to',
|
|
49
|
+
from: 'ModelOne.modelTwoId',
|
|
50
|
+
to: 'ModelTwo.id',
|
|
51
|
+
modify: 'myScope'
|
|
52
|
+
},
|
|
53
|
+
models
|
|
54
|
+
)
|
|
55
|
+
).toEqual({
|
|
56
|
+
relation: BelongsToOneRelation,
|
|
57
|
+
modelClass: ModelTwo,
|
|
58
|
+
join: {
|
|
59
|
+
from: 'ModelOne.modelTwoId',
|
|
60
|
+
to: 'ModelTwo.id'
|
|
61
|
+
},
|
|
62
|
+
modify: 'myScope'
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('converts a many-to-many relation to Objection.js format', () => {
|
|
67
|
+
expect(
|
|
68
|
+
convertRelation(
|
|
69
|
+
{
|
|
70
|
+
relation: 'many-to-many',
|
|
71
|
+
from: 'ModelOne.id',
|
|
72
|
+
to: 'ModelTwo.id',
|
|
73
|
+
inverse: false
|
|
74
|
+
},
|
|
75
|
+
models
|
|
76
|
+
)
|
|
77
|
+
).toEqual({
|
|
78
|
+
relation: ManyToManyRelation,
|
|
79
|
+
modelClass: ModelTwo,
|
|
80
|
+
join: {
|
|
81
|
+
from: 'ModelOne.id',
|
|
82
|
+
through: {
|
|
83
|
+
from: 'ModelOneModelTwo.modelOneId',
|
|
84
|
+
to: 'ModelOneModelTwo.modelTwoId'
|
|
85
|
+
},
|
|
86
|
+
to: 'ModelTwo.id'
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('converts an inverse many-to-many relation to Objection.js format', () => {
|
|
92
|
+
expect(
|
|
93
|
+
convertRelation(
|
|
94
|
+
{
|
|
95
|
+
relation: 'many-to-many',
|
|
96
|
+
from: 'ModelTwo.id',
|
|
97
|
+
to: 'ModelOne.id',
|
|
98
|
+
inverse: true
|
|
99
|
+
},
|
|
100
|
+
models
|
|
101
|
+
)
|
|
102
|
+
).toEqual({
|
|
103
|
+
relation: ManyToManyRelation,
|
|
104
|
+
modelClass: ModelOne,
|
|
105
|
+
join: {
|
|
106
|
+
from: 'ModelTwo.id',
|
|
107
|
+
through: {
|
|
108
|
+
from: 'ModelOneModelTwo.modelTwoId',
|
|
109
|
+
to: 'ModelOneModelTwo.modelOneId'
|
|
110
|
+
},
|
|
111
|
+
to: 'ModelOne.id'
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('preserves Objection.js relation format', () => {
|
|
117
|
+
const relation = {
|
|
118
|
+
relation: BelongsToOneRelation,
|
|
119
|
+
modelClass: ModelTwo,
|
|
120
|
+
join: {
|
|
121
|
+
from: 'ModelOne.modelTwoId',
|
|
122
|
+
to: 'ModelTwo.id'
|
|
123
|
+
},
|
|
124
|
+
modify: 'myScope'
|
|
125
|
+
}
|
|
126
|
+
expect(convertRelation(relation, models)).toEqual(relation)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
describe('addRelationSchemas()', () => {
|
|
131
|
+
class ModelOne extends Model {
|
|
132
|
+
static relations = {
|
|
133
|
+
modelTwo: {
|
|
134
|
+
relation: 'belongs-to',
|
|
135
|
+
from: 'ModelOne.modelTwoId',
|
|
136
|
+
to: 'ModelTwo.id'
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
class ModelTwo extends Model {
|
|
142
|
+
static relations = {
|
|
143
|
+
modelOnes: {
|
|
144
|
+
relation: 'has-many',
|
|
145
|
+
from: 'ModelTwo.id',
|
|
146
|
+
to: 'ModelOne.modelTwoId',
|
|
147
|
+
owner: true
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Add a mock app to both, to expose models:
|
|
153
|
+
ModelOne.app = ModelTwo.app = {
|
|
154
|
+
models: { ModelOne, ModelTwo }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
it('adds correct property schema for a belongs-to relation', () => {
|
|
158
|
+
expect(addRelationSchemas(ModelOne, {})).toEqual({
|
|
159
|
+
modelTwo: {
|
|
160
|
+
anyOf: [
|
|
161
|
+
{ type: 'null' },
|
|
162
|
+
{ relate: 'ModelTwo' },
|
|
163
|
+
{ $ref: 'ModelTwo' }
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('adds correct property schema for a has-many owner relation', () => {
|
|
170
|
+
expect(addRelationSchemas(ModelTwo, {})).toEqual({
|
|
171
|
+
modelOnes: {
|
|
172
|
+
type: 'array',
|
|
173
|
+
items: { $ref: 'ModelOne' }
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
})
|