@ditojs/server 2.0.4 → 2.1.0

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 (54) hide show
  1. package/package.json +11 -13
  2. package/src/app/Application.js +228 -212
  3. package/src/app/Validator.js +53 -43
  4. package/src/cli/console.js +6 -4
  5. package/src/cli/db/createMigration.js +59 -30
  6. package/src/cli/db/migrate.js +6 -4
  7. package/src/cli/db/reset.js +8 -5
  8. package/src/cli/db/rollback.js +6 -4
  9. package/src/cli/db/seed.js +2 -1
  10. package/src/cli/index.js +1 -1
  11. package/src/controllers/AdminController.js +100 -84
  12. package/src/controllers/CollectionController.js +37 -30
  13. package/src/controllers/Controller.js +83 -43
  14. package/src/controllers/ControllerAction.js +27 -15
  15. package/src/controllers/ModelController.js +4 -1
  16. package/src/controllers/RelationController.js +19 -21
  17. package/src/controllers/UsersController.js +3 -4
  18. package/src/decorators/parameters.js +3 -1
  19. package/src/decorators/scope.js +1 -1
  20. package/src/errors/ControllerError.js +2 -1
  21. package/src/errors/DatabaseError.js +20 -11
  22. package/src/graph/DitoGraphProcessor.js +48 -40
  23. package/src/graph/expression.js +6 -8
  24. package/src/graph/graph.js +20 -11
  25. package/src/lib/EventEmitter.js +12 -12
  26. package/src/middleware/handleConnectMiddleware.js +16 -10
  27. package/src/middleware/handleError.js +6 -5
  28. package/src/middleware/handleSession.js +78 -0
  29. package/src/middleware/handleUser.js +2 -2
  30. package/src/middleware/index.js +2 -0
  31. package/src/middleware/logRequests.js +3 -3
  32. package/src/middleware/setupRequestStorage.js +14 -0
  33. package/src/mixins/AssetMixin.js +62 -58
  34. package/src/mixins/SessionMixin.js +13 -10
  35. package/src/mixins/TimeStampedMixin.js +33 -29
  36. package/src/mixins/UserMixin.js +130 -116
  37. package/src/models/Model.js +245 -194
  38. package/src/models/definitions/filters.js +14 -13
  39. package/src/query/QueryBuilder.js +252 -195
  40. package/src/query/QueryFilters.js +3 -3
  41. package/src/query/QueryParameters.js +2 -2
  42. package/src/query/Registry.js +8 -10
  43. package/src/schema/keywords/_validate.js +10 -8
  44. package/src/schema/properties.test.js +247 -206
  45. package/src/schema/relations.js +42 -20
  46. package/src/schema/relations.test.js +36 -19
  47. package/src/services/Service.js +8 -14
  48. package/src/storage/S3Storage.js +5 -3
  49. package/src/storage/Storage.js +16 -14
  50. package/src/utils/function.js +7 -4
  51. package/src/utils/function.test.js +30 -6
  52. package/src/utils/object.test.js +5 -1
  53. package/types/index.d.ts +244 -257
  54. package/src/app/SessionStore.js +0 -31
@@ -7,7 +7,12 @@ import {
7
7
  ManyToManyRelation
8
8
  } from 'objection'
9
9
  import {
10
- isObject, isArray, isString, asArray, capitalize, camelize
10
+ isObject,
11
+ isArray,
12
+ isString,
13
+ asArray,
14
+ capitalize,
15
+ camelize
11
16
  } from '@ditojs/utils'
12
17
  import { RelationError } from '../errors/index.js'
13
18
 
@@ -45,14 +50,16 @@ class ModelReference {
45
50
  const modelClass = models[modelName]
46
51
  if (!modelClass && !allowUnknown) {
47
52
  throw new RelationError(
48
- `Unknown model reference: ${ref}`)
53
+ `Unknown model reference: ${ref}`
54
+ )
49
55
  }
50
56
  if (!this.modelName) {
51
57
  this.modelName = modelName
52
58
  this.modelClass = modelClass
53
59
  } else if (this.modelName !== modelName) {
54
60
  throw new RelationError(
55
- `Composite keys need to be defined on the same table: ${ref}`)
61
+ `Composite keys need to be defined on the same table: ${ref}`
62
+ )
56
63
  }
57
64
  this.propertyNames.push(propertyName)
58
65
  }
@@ -90,8 +97,10 @@ class ModelReference {
90
97
  const toName = toRef.modelName
91
98
  const toProperties = toRef.propertyNames
92
99
  if (fromProperties.length !== toProperties.length) {
93
- throw new RelationError('Unable to create through join for ' +
94
- `composite keys from '${this}' to '${toRef}'`)
100
+ throw new RelationError(
101
+ 'Unable to create through join for ' +
102
+ `composite keys from '${this}' to '${toRef}'`
103
+ )
95
104
  }
96
105
  const from = []
97
106
  const to = []
@@ -106,7 +115,8 @@ class ModelReference {
106
115
  to.push(`${throughName}.${throughTo}`)
107
116
  } else {
108
117
  throw new RelationError(
109
- `Unable to create through join from '${this}' to '${to}'`)
118
+ `Unable to create through join from '${this}' to '${to}'`
119
+ )
110
120
  }
111
121
  }
112
122
  return {
@@ -136,11 +146,18 @@ export function convertRelation(schema, models) {
136
146
  let {
137
147
  relation,
138
148
  // Dito.js-style relation description:
139
- from, to, through, inverse, modify, scope,
149
+ from,
150
+ to,
151
+ through,
152
+ inverse,
153
+ modify,
154
+ scope,
140
155
  // Objection.js-style relation description (`modify` is shared)
141
- join, filter,
156
+ join,
157
+ filter,
142
158
  // Pluck Dito.js-related properties that should not end up in `rest`:
143
- nullable, owner,
159
+ nullable,
160
+ owner,
144
161
  ...rest
145
162
  } = schema || {}
146
163
  const relationClass = getRelationClass(relation)
@@ -178,16 +195,19 @@ export function convertRelation(schema, models) {
178
195
  through.from = throughFrom.toValue()
179
196
  through.to = throughTo.toValue()
180
197
  } else {
181
- throw new RelationError('Both sides of the `through` definition ' +
182
- 'need to be on the same join model')
198
+ throw new RelationError(
199
+ 'Both sides of the `through` definition ' +
200
+ 'need to be on the same join model'
201
+ )
183
202
  }
184
203
  } else {
185
204
  // Assume the references are to a join table, and use the settings
186
205
  // unmodified.
187
206
  }
188
207
  } else {
189
- throw new RelationError('The relation needs a `through.from` and ' +
190
- '`through.to` definition')
208
+ throw new RelationError(
209
+ 'The relation needs a `through.from` and ' + '`through.to` definition'
210
+ )
191
211
  }
192
212
  } else if (through) {
193
213
  throw new RelationError('Unsupported through join definition')
@@ -230,7 +250,8 @@ export function convertRelations(ownerModelClass, relations, models) {
230
250
  converted[name] = convertRelation(relation, models)
231
251
  } catch (err) {
232
252
  throw new RelationError(
233
- `${ownerModelClass.name}.relations.${name}: ${(err.message || err)}`)
253
+ `${ownerModelClass.name}.relations.${name}: ${err.message || err}`
254
+ )
234
255
  }
235
256
  }
236
257
  return converted
@@ -261,15 +282,16 @@ export function addRelationSchemas(modelClass, properties) {
261
282
  }
262
283
  // Finally the model itself
263
284
  anyOf.push({ $ref })
264
- const items = anyOf.length > 1
265
- ? { anyOf }
266
- : anyOf[0]
285
+ const items =
286
+ anyOf.length > 1
287
+ ? { anyOf }
288
+ : anyOf[0]
267
289
  properties[name] = isOneToOne
268
290
  ? items
269
291
  : {
270
- type: 'array',
271
- items
272
- }
292
+ type: 'array',
293
+ items
294
+ }
273
295
  }
274
296
  return properties
275
297
  }
@@ -7,7 +7,9 @@ import {
7
7
  } from 'objection'
8
8
  import { Model } from '../models/index.js'
9
9
  import {
10
- getRelationClass, convertRelation, addRelationSchemas
10
+ getRelationClass,
11
+ convertRelation,
12
+ addRelationSchemas
11
13
  } from './relations.js'
12
14
 
13
15
  describe('getRelationClass()', () => {
@@ -40,12 +42,17 @@ describe('convertRelation()', () => {
40
42
  const models = { ModelOne, ModelTwo }
41
43
 
42
44
  it('converts a belongs-to relation to Objection.js format', () => {
43
- expect(convertRelation({
44
- relation: 'belongs-to',
45
- from: 'ModelOne.modelTwoId',
46
- to: 'ModelTwo.id',
47
- modify: 'myScope'
48
- }, models)).toEqual({
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({
49
56
  relation: BelongsToOneRelation,
50
57
  modelClass: ModelTwo,
51
58
  join: {
@@ -57,12 +64,17 @@ describe('convertRelation()', () => {
57
64
  })
58
65
 
59
66
  it('converts a many-to-many relation to Objection.js format', () => {
60
- expect(convertRelation({
61
- relation: 'many-to-many',
62
- from: 'ModelOne.id',
63
- to: 'ModelTwo.id',
64
- inverse: false
65
- }, models)).toEqual({
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({
66
78
  relation: ManyToManyRelation,
67
79
  modelClass: ModelTwo,
68
80
  join: {
@@ -77,12 +89,17 @@ describe('convertRelation()', () => {
77
89
  })
78
90
 
79
91
  it('converts an inverse many-to-many relation to Objection.js format', () => {
80
- expect(convertRelation({
81
- relation: 'many-to-many',
82
- from: 'ModelTwo.id',
83
- to: 'ModelOne.id',
84
- inverse: true
85
- }, models)).toEqual({
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({
86
103
  relation: ManyToManyRelation,
87
104
  modelClass: ModelOne,
88
105
  join: {
@@ -20,27 +20,21 @@ export class Service {
20
20
  }
21
21
 
22
22
  // @overridable
23
- async initialize() {
24
- }
23
+ async initialize() {}
25
24
 
26
25
  // @overridable
27
- async start() {
28
- }
26
+ async start() {}
29
27
 
30
28
  // @overridable
31
- async stop() {
32
- }
29
+ async stop() {}
33
30
 
34
- // Only use this method to get a logger instance that is bound to the context,
35
- // otherwise use the cached getter.
36
- getLogger(ctx) {
37
- const logger = ctx?.logger ?? this.app.logger
38
- return logger.child({ name: this.#loggerName })
31
+ /** @deprecated Use `instance.logger` instead. */
32
+ getLogger() {
33
+ return this.logger
39
34
  }
40
35
 
41
36
  get logger() {
42
- const value = this.getLogger()
43
- Object.defineProperty(this, 'logger', { value })
44
- return value
37
+ const logger = this.app.requestLocals.logger ?? this.app.logger
38
+ return logger.child({ name: this.#loggerName })
45
39
  }
46
40
  }
@@ -63,9 +63,11 @@ export class S3Storage extends Storage {
63
63
  } else {
64
64
  // 3. If that fails, keep collecting all chunks and determine
65
65
  // the mimetype using the full data.
66
- stream.once('end', () => done(
67
- getFileTypeFromBuffer(data) || 'application/octet-stream'
68
- ))
66
+ stream.once('end', () =>
67
+ done(
68
+ getFileTypeFromBuffer(data) || 'application/octet-stream'
69
+ )
70
+ )
69
71
  }
70
72
  }
71
73
  data = data ? Buffer.concat([data, chunk]) : chunk
@@ -27,12 +27,10 @@ export class Storage {
27
27
  }
28
28
 
29
29
  // @overridable
30
- async setup() {
31
- }
30
+ async setup() {}
32
31
 
33
32
  // @overridable
34
- async initialize() {
35
- }
33
+ async initialize() {}
36
34
 
37
35
  static register(storageClass) {
38
36
  const type = (
@@ -51,15 +49,18 @@ export class Storage {
51
49
  // Returns a storage that inherits from this.storage but overrides
52
50
  // _handleFile to pass on `config` to the call of `handleUpload()`
53
51
  return this.storage
54
- ? Object.setPrototypeOf({
55
- _handleFile: async (req, file, callback) => {
56
- try {
57
- callback(null, await this._handleUpload(req, file, config))
58
- } catch (err) {
59
- callback(err)
60
- }
61
- }
62
- }, this.storage)
52
+ ? Object.setPrototypeOf(
53
+ {
54
+ _handleFile: async (req, file, callback) => {
55
+ try {
56
+ callback(null, await this._handleUpload(req, file, config))
57
+ } catch (err) {
58
+ callback(err)
59
+ }
60
+ }
61
+ },
62
+ this.storage
63
+ )
63
64
  : null
64
65
  }
65
66
 
@@ -169,7 +170,8 @@ export class Storage {
169
170
  // TODO: `config.readImageSize` was deprecated in favour of
170
171
  // `config.readDimensions` in March 2023. Remove in 1 year.
171
172
  config.readImageSize
172
- ) && /^(image|video)\//.test(file.mimetype)
173
+ ) &&
174
+ /^(image|video)\//.test(file.mimetype)
173
175
  ) {
174
176
  return this._handleMediaFile(req, file)
175
177
  } else {
@@ -6,9 +6,12 @@ export function describeFunction(func) {
6
6
  )
7
7
  if (match) {
8
8
  const body = match[5] === '{' ? '{ ... }' : '...'
9
- return match[2] !== undefined ? `${match[1]}function (${match[2]}) ${body}`
10
- : match[3] !== undefined ? `${match[1]}(${match[3]}) => ${body}`
11
- : match[4] !== undefined ? `${match[1]}${match[4]} => ${body}`
12
- : ''
9
+ return match[2] !== undefined
10
+ ? `${match[1]}function (${match[2]}) ${body}`
11
+ : match[3] !== undefined
12
+ ? `${match[1]}(${match[3]}) => ${body}`
13
+ : match[4] !== undefined
14
+ ? `${match[1]}${match[4]} => ${body}`
15
+ : ''
13
16
  }
14
17
  }
@@ -2,12 +2,20 @@ import { describeFunction } from './function.js'
2
2
 
3
3
  describe('describeFunction()', () => {
4
4
  it('describes normal functions', () => {
5
- expect(describeFunction(function(a, b, c) { return a + b + c }))
5
+ expect(
6
+ describeFunction(function (a, b, c) {
7
+ return a + b + c
8
+ })
9
+ )
6
10
  .toBe('function (a, b, c) { ... }')
7
11
  })
8
12
 
9
13
  it('describes lambdas with one param and a body', () => {
10
- expect(describeFunction(a => { return a }))
14
+ expect(
15
+ describeFunction(a => {
16
+ return a
17
+ })
18
+ )
11
19
  .toBe('a => { ... }')
12
20
  })
13
21
 
@@ -17,7 +25,11 @@ describe('describeFunction()', () => {
17
25
  })
18
26
 
19
27
  it('describes lambdas with multiple params and a body', () => {
20
- expect(describeFunction((a, b, c) => { return a + b + c }))
28
+ expect(
29
+ describeFunction((a, b, c) => {
30
+ return a + b + c
31
+ })
32
+ )
21
33
  .toBe('(a, b, c) => { ... }')
22
34
  })
23
35
 
@@ -27,12 +39,20 @@ describe('describeFunction()', () => {
27
39
  })
28
40
 
29
41
  it('describes async functions', () => {
30
- expect(describeFunction(async function(a, b, c) { return a + b + c }))
42
+ expect(
43
+ describeFunction(async function (a, b, c) {
44
+ return a + b + c
45
+ })
46
+ )
31
47
  .toBe('async function (a, b, c) { ... }')
32
48
  })
33
49
 
34
50
  it('describes async lambdas with one param and a body', () => {
35
- expect(describeFunction(async a => { return a }))
51
+ expect(
52
+ describeFunction(async a => {
53
+ return a
54
+ })
55
+ )
36
56
  .toBe('async a => { ... }')
37
57
  })
38
58
 
@@ -42,7 +62,11 @@ describe('describeFunction()', () => {
42
62
  })
43
63
 
44
64
  it('describes async lambdas with multiple params and a body', () => {
45
- expect(describeFunction(async (a, b, c) => { return a + b + c }))
65
+ expect(
66
+ describeFunction(async (a, b, c) => {
67
+ return a + b + c
68
+ })
69
+ )
46
70
  .toBe('async (a, b, c) => { ... }')
47
71
  })
48
72
 
@@ -1,5 +1,9 @@
1
1
  import {
2
- getAllKeys, getOwnProperty, createLookup, mergeReversed, mergeAsReversedArrays
2
+ getAllKeys,
3
+ getOwnProperty,
4
+ createLookup,
5
+ mergeReversed,
6
+ mergeAsReversedArrays
3
7
  } from './object.js'
4
8
 
5
9
  const object = Object.create({ a: 1 })