@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,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
+ })