@ditojs/server 0.270.0 → 0.273.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 (70) hide show
  1. package/lib/app/Application.js +59 -22
  2. package/lib/app/SessionStore.js +3 -1
  3. package/lib/app/Validator.js +16 -2
  4. package/lib/app/index.js +7 -1
  5. package/lib/cli/console.js +11 -1
  6. package/lib/cli/db/createMigration.js +13 -1
  7. package/lib/cli/db/index.js +7 -1
  8. package/lib/cli/db/listAssetConfig.js +4 -2
  9. package/lib/cli/db/seed.js +10 -2
  10. package/lib/cli/index.js +6 -2
  11. package/lib/controllers/AdminController.js +18 -6
  12. package/lib/controllers/CollectionController.js +15 -1
  13. package/lib/controllers/Controller.js +25 -1
  14. package/lib/controllers/ControllerAction.js +18 -6
  15. package/lib/controllers/RelationController.js +7 -1
  16. package/lib/controllers/UserController.js +15 -1
  17. package/lib/controllers/index.js +7 -1
  18. package/lib/decorators/index.js +7 -1
  19. package/lib/decorators/parameters.js +4 -2
  20. package/lib/decorators/returns.js +4 -2
  21. package/lib/errors/DatabaseError.js +5 -19
  22. package/lib/errors/ResponseError.js +4 -14
  23. package/lib/errors/index.js +7 -1
  24. package/lib/graph/DitoGraphProcessor.js +5 -1
  25. package/lib/graph/expression.js +5 -1
  26. package/lib/graph/graph.js +18 -2
  27. package/lib/graph/index.js +7 -1
  28. package/lib/index.js +7 -1
  29. package/lib/lib/index.js +7 -1
  30. package/lib/middleware/findRoute.js +7 -1
  31. package/lib/middleware/index.js +7 -1
  32. package/lib/mixins/AssetMixin.js +4 -4
  33. package/lib/mixins/SessionMixin.js +4 -4
  34. package/lib/mixins/TimeStampedMixin.js +4 -4
  35. package/lib/mixins/UserMixin.js +10 -4
  36. package/lib/mixins/index.js +7 -1
  37. package/lib/models/Model.js +41 -22
  38. package/lib/models/definitions/filters.js +9 -1
  39. package/lib/models/definitions/properties.js +7 -1
  40. package/lib/models/definitions/scopes.js +9 -1
  41. package/lib/models/index.js +7 -1
  42. package/lib/query/QueryBuilder.js +11 -1
  43. package/lib/query/QueryFilters.js +11 -1
  44. package/lib/query/index.js +7 -1
  45. package/lib/schema/formats/index.js +7 -1
  46. package/lib/schema/index.js +10 -2
  47. package/lib/schema/keywords/index.js +7 -1
  48. package/lib/schema/properties.js +11 -1
  49. package/lib/schema/relations.js +18 -4
  50. package/lib/services/index.js +7 -1
  51. package/lib/storage/DiskStorage.js +7 -1
  52. package/lib/storage/Storage.js +5 -1
  53. package/lib/storage/index.js +7 -1
  54. package/lib/utils/emitter.js +5 -1
  55. package/lib/utils/index.js +15 -1
  56. package/lib/utils/json.js +9 -0
  57. package/lib/utils/object.js +9 -3
  58. package/package.json +29 -29
  59. package/src/app/Application.js +28 -5
  60. package/src/app/Validator.js +2 -1
  61. package/src/cli/db/listAssetConfig.js +3 -1
  62. package/src/controllers/AdminController.js +3 -2
  63. package/src/controllers/ControllerAction.js +7 -5
  64. package/src/decorators/parameters.js +2 -2
  65. package/src/decorators/returns.js +2 -2
  66. package/src/errors/DatabaseError.js +2 -23
  67. package/src/errors/ResponseError.js +1 -8
  68. package/src/models/Model.js +13 -8
  69. package/src/utils/index.js +1 -0
  70. package/src/utils/json.js +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/server",
3
- "version": "0.270.0",
3
+ "version": "0.273.0",
4
4
  "description": "Dito.js Server – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
5
5
  "main": "lib/index.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/master/packages/server",
@@ -25,34 +25,34 @@
25
25
  "yarn": ">= 1.0.0"
26
26
  },
27
27
  "browserslist": [
28
- "node 14"
28
+ "node >= 14"
29
29
  ],
30
30
  "dependencies": {
31
- "@babel/core": "^7.15.5",
32
- "@babel/runtime": "^7.15.4",
33
- "@ditojs/admin": "^0.270.0",
34
- "@ditojs/router": "^0.270.0",
35
- "@ditojs/utils": "^0.270.0",
31
+ "@babel/core": "^7.17.5",
32
+ "@babel/runtime": "^7.17.2",
33
+ "@ditojs/admin": "^0.273.0",
34
+ "@ditojs/router": "^0.273.0",
35
+ "@ditojs/utils": "^0.273.0",
36
36
  "@koa/cors": "^3.1.0",
37
37
  "@koa/multer": "^3.0.0",
38
- "@vue/cli-service": "^4.5.13",
38
+ "@vue/cli-service": "^4.5.15",
39
39
  "ajv": "^7.2.4",
40
40
  "ajv-formats": "^1.6.1",
41
- "aws-sdk": "^2.999.0",
42
- "axios": "^0.22.0",
41
+ "aws-sdk": "^2.1085.0",
42
+ "axios": "^0.26.0",
43
43
  "babel-loader": "^8.2.2",
44
44
  "bcryptjs": "^2.4.3",
45
- "bytes": "^3.1.0",
45
+ "bytes": "^3.1.2",
46
46
  "chalk": "^4.1.2",
47
- "core-js": "^3.18.1",
47
+ "core-js": "^3.21.1",
48
48
  "data-uri-to-buffer": "^3.0.1",
49
- "eventemitter2": "^6.4.4",
49
+ "eventemitter2": "^6.4.5",
50
50
  "file-type": "^16.5.3",
51
- "fs-extra": "^10.0.0",
51
+ "fs-extra": "^10.0.1",
52
52
  "html-webpack-tags-plugin": "^2.0.17",
53
- "image-size": "^1.0.0",
54
- "is-svg": "^4.3.1",
55
- "koa": "^2.13.3",
53
+ "image-size": "^1.0.1",
54
+ "is-svg": "^4.3.2",
55
+ "koa": "^2.13.4",
56
56
  "koa-bodyparser": "^4.3.0",
57
57
  "koa-compose": "^4.1.0",
58
58
  "koa-compress": "^5.1.0",
@@ -66,14 +66,14 @@
66
66
  "koa-session": "^6.2.0",
67
67
  "koa-static": "^5.0.0",
68
68
  "koa-webpack": "^6.0.0",
69
- "mime-types": "^2.1.33",
70
- "multer": "^1.4.3",
71
- "multer-s3": "^2.9.0",
72
- "nanoid": "^3.1.28",
73
- "parse-duration": "^1.0.0",
69
+ "mime-types": "^2.1.34",
70
+ "multer": "^1.4.4",
71
+ "multer-s3": "^2.10.0",
72
+ "nanoid": "^3.3.1",
73
+ "parse-duration": "^1.0.2",
74
74
  "passport-local": "^1.0.0",
75
75
  "passthrough-counter": "^1.0.0",
76
- "pino": "^6.13.3",
76
+ "pino": "^6.14.0",
77
77
  "pino-pretty": "^6.0.0",
78
78
  "pluralize": "^8.0.0",
79
79
  "repl": "^0.1.3",
@@ -89,16 +89,16 @@
89
89
  "objection": "^2.2.0"
90
90
  },
91
91
  "devDependencies": {
92
- "@babel/cli": "^7.15.7",
93
- "@babel/preset-env": "^7.15.6",
94
- "@ditojs/babel-preset": "^0.270.0",
92
+ "@babel/cli": "^7.17.6",
93
+ "@babel/preset-env": "^7.16.11",
94
+ "@ditojs/babel-preset": "^0.273.0",
95
95
  "babel-plugin-dynamic-import-node": "^2.3.3",
96
96
  "babel-plugin-module-resolver": "^4.1.0",
97
97
  "knex": "^0.21.21",
98
- "objection": "^2.2.16",
99
- "pg": "^8.7.1",
98
+ "objection": "^2.2.18",
99
+ "pg": "^8.7.3",
100
100
  "rimraf": "^3.0.2",
101
101
  "sqlite3": "^5.0.2"
102
102
  },
103
- "gitHead": "99b1d3c89aa4a4aed19a0741d6a0683654a13196"
103
+ "gitHead": "ecdd9eee04947c3362c449623b8a685b3e02e0ab"
104
104
  }
@@ -23,7 +23,13 @@ import { Controller, AdminController } from '@/controllers'
23
23
  import { Service } from '@/services'
24
24
  import { Storage } from '@/storage'
25
25
  import { convertSchema } from '@/schema'
26
- import { ResponseError, ValidationError, AssetError } from '@/errors'
26
+ import { formatJson } from '@/utils'
27
+ import {
28
+ ResponseError,
29
+ ValidationError,
30
+ DatabaseError,
31
+ AssetError
32
+ } from '@/errors'
27
33
  import SessionStore from './SessionStore'
28
34
  import { Validator } from './Validator'
29
35
  import {
@@ -472,11 +478,26 @@ export class Application extends Koa {
472
478
  }
473
479
  }
474
480
 
475
- createValidationError({ type, message, errors, options }) {
481
+ createValidationError({ type, message, errors, options, json }) {
476
482
  return new ValidationError({
477
483
  type,
478
484
  message,
479
- errors: this.validator.parseErrors(errors, options)
485
+ errors: this.validator.parseErrors(errors, options),
486
+ // Only include the JSON data in the error if `log.errors.json`is set.
487
+ json: this.config.log.errors?.json ? json : undefined
488
+ })
489
+ }
490
+
491
+ createDatabaseError(error) {
492
+ // Remove knex SQL query and move to separate `sql` property.
493
+ // TODO: Fix this properly in Knex / Objection instead, see:
494
+ // https://gitter.im/Vincit/objection.js?at=5a68728f5a9ebe4f75ca40b0
495
+ const [, sql, message] = error.message.match(/^([\s\S]*) - ([\s\S]*?)$/) ||
496
+ [null, null, error.message]
497
+ return new DatabaseError(error, {
498
+ message,
499
+ // Only include the SQL query in the error if `log.errors.sql`is set.
500
+ sql: this.config.log.errors?.sql ? sql : undefined
480
501
  })
481
502
  }
482
503
 
@@ -485,13 +506,15 @@ export class Application extends Koa {
485
506
 
486
507
  this.use(attachLogger(this.logger))
487
508
 
488
- this.use(handleError())
489
509
  if (app.responseTime !== false) {
490
510
  this.use(responseTime(getOptions(app.responseTime)))
491
511
  }
492
512
  if (log.requests) {
493
513
  this.use(logRequests())
494
514
  }
515
+ // Needs to be positioned after the request logger to log the correct
516
+ // response status.
517
+ this.use(handleError())
495
518
  if (app.helmet !== false) {
496
519
  this.use(helmet(getOptions(app.helmet)))
497
520
  }
@@ -655,7 +678,7 @@ export class Application extends Koa {
655
678
 
656
679
  formatError(err) {
657
680
  const message = err.toJSON
658
- ? JSON.stringify(err.toJSON(), null, 2)
681
+ ? formatJson(err.toJSON())
659
682
  : err.message || err
660
683
  const str = `${err.name}: ${message}`
661
684
  return err.stack && this.config.log.errors?.stack !== false
@@ -2,6 +2,7 @@ import objection from 'objection'
2
2
  import Ajv from 'ajv'
3
3
  import addFormats from 'ajv-formats'
4
4
  import { isArray, isObject, clone, isAsync, isPromise } from '@ditojs/utils'
5
+ import { formatJson } from '@/utils'
5
6
  import * as schema from '@/schema'
6
7
 
7
8
  // Dito does not rely on objection.AjvValidator but instead implements its own
@@ -83,7 +84,7 @@ export class Validator extends objection.Validator {
83
84
  }
84
85
  return opts
85
86
  }, {})
86
- const cacheKey = JSON.stringify(opts)
87
+ const cacheKey = formatJson(opts, false)
87
88
  const { ajv } = this.ajvCache[cacheKey] || (this.ajvCache[cacheKey] = {
88
89
  ajv: this.createAjv(opts),
89
90
  options
@@ -1,8 +1,10 @@
1
+ import { formatJson } from '@/utils'
2
+
1
3
  export async function listAssetConfig(app, ...args) {
2
4
  const assetConfig = app.getAssetConfig({
3
5
  models: args.length > 0 ? args : Object.keys(app.models),
4
6
  normalizeDbNames: true
5
7
  })
6
- console.info(JSON.stringify(assetConfig, null, 2))
8
+ console.info(formatJson(assetConfig))
7
9
  return true
8
10
  }
@@ -6,8 +6,9 @@ import koaWebpack from 'koa-webpack'
6
6
  import historyApiFallback from 'koa-connect-history-api-fallback'
7
7
  import VueService from '@vue/cli-service'
8
8
  import HtmlWebpackTagsPlugin from 'html-webpack-tags-plugin'
9
- import { ControllerError } from '@/errors'
10
9
  import { Controller } from './Controller'
10
+ import { ControllerError } from '@/errors'
11
+ import { formatJson } from '@/utils'
11
12
  import { isString, isObject } from '@ditojs/utils'
12
13
 
13
14
  export class AdminController extends Controller {
@@ -59,7 +60,7 @@ export class AdminController extends Controller {
59
60
  sendDitoObject(ctx) {
60
61
  // Send back the global dito object as JavaScript code.
61
62
  ctx.type = 'text/javascript'
62
- ctx.body = `window.dito = ${JSON.stringify(this.getDitoObject())}`
63
+ ctx.body = `window.dito = ${formatJson(this.getDitoObject())}`
63
64
  }
64
65
 
65
66
  middleware() {
@@ -165,8 +165,9 @@ export default class ControllerAction {
165
165
  if (errors.length > 0) {
166
166
  throw this.createValidationError({
167
167
  type: 'ParameterValidation',
168
- message: `The provided data is not valid: ${JSON.stringify(getData())}`,
169
- errors
168
+ message: 'The provided action parameters are not valid',
169
+ errors,
170
+ json: getData()
170
171
  })
171
172
  }
172
173
  }
@@ -189,8 +190,9 @@ export default class ControllerAction {
189
190
  } catch (error) {
190
191
  throw this.createValidationError({
191
192
  type: 'ResultValidation',
192
- message: `Invalid result of action: ${JSON.stringify(getResult())}`,
193
- errors: error.errors
193
+ message: 'The returned action result is not valid',
194
+ errors: error.errors,
195
+ json: getResult()
194
196
  })
195
197
  }
196
198
  }
@@ -229,7 +231,7 @@ export default class ControllerAction {
229
231
 
230
232
  coerceValue(type, value, modelOptions) {
231
233
  // See if param needs additional coercion:
232
- if (['date', 'datetime', 'timestamp'].includes(type)) {
234
+ if (value && ['date', 'datetime', 'timestamp'].includes(type)) {
233
235
  value = new Date(value)
234
236
  } else {
235
237
  // See if the defined type(s) require coercion to objects:
@@ -1,5 +1,5 @@
1
1
  import { isArray, isObject } from '@ditojs/utils'
2
- import { createDecorator, deprecate } from '@/utils'
2
+ import { createDecorator, deprecate, formatJson } from '@/utils'
3
3
 
4
4
  export function parameters(parameters, options) {
5
5
  if (isObject(parameters)) {
@@ -7,7 +7,7 @@ export function parameters(parameters, options) {
7
7
  if (!isObject(first)) {
8
8
  deprecate(
9
9
  `@parameters(${
10
- JSON.stringify(parameters)
10
+ formatJson(parameters, false)
11
11
  }) with parameter schema object is deprecated: Schema object should be passed nested inside an array or object definition.`
12
12
  )
13
13
  parameters = [...arguments]
@@ -1,11 +1,11 @@
1
1
  import { isObject } from '@ditojs/utils'
2
- import { createDecorator } from '@/utils'
2
+ import { createDecorator, formatJson } from '@/utils'
3
3
 
4
4
  export function returns(returns, options) {
5
5
  if (!isObject(returns)) {
6
6
  throw new Error(
7
7
  `@returns(${
8
- JSON.stringify(returns)
8
+ formatJson(returns, false)
9
9
  }) needs to be defined using an object parameter definition`
10
10
  )
11
11
  }
@@ -8,7 +8,7 @@ import {
8
8
  } from 'objection'
9
9
 
10
10
  export class DatabaseError extends WrappedError {
11
- constructor(error) {
11
+ constructor(error, overrides) {
12
12
  const status =
13
13
  error instanceof CheckViolationError ? 400
14
14
  : error instanceof NotNullViolationError ? 400
@@ -16,28 +16,7 @@ export class DatabaseError extends WrappedError {
16
16
  : error instanceof DataError ? 400
17
17
  : error instanceof DBError ? 500
18
18
  : 400
19
- // Remove knex SQL query and move to separate `query` property.
20
- // TODO: Fix this properly in Knex / Objection instead, see:
21
- // https://gitter.im/Vincit/objection.js?at=5a68728f5a9ebe4f75ca40b0
22
- const [, sql, message] = error.message.match(/^([\s\S]*) - ([\s\S]*?)$/) ||
23
- [null, null, error.message]
24
- const overrides = {
25
- type: error.constructor.name,
26
- message,
27
- sql,
28
- status
29
- }
19
+ overrides = { type: error.constructor.name, status, ...overrides }
30
20
  super(error, overrides, { message: 'Database error', status })
31
21
  }
32
-
33
- toJSON() {
34
- // Remove SQL query from displayed data in front-end when not in development
35
- // or test.
36
- if (process.env.NODE_ENV !== 'development' ||
37
- process.env.NODE_ENV !== 'test') {
38
- const { sql, ...data } = this.data
39
- return data
40
- }
41
- return this.data
42
- }
43
22
  }
@@ -21,14 +21,7 @@ export class ResponseError extends Error {
21
21
  ? { message: error }
22
22
  : error || {}
23
23
  const { status, ...data } = { ...defaults, ...object }
24
- let { message, code } = data
25
- if (process.env.NODE_ENV === 'test' && error === object) {
26
- // Include full JSON error in message during tests, for better reporting.
27
- const { message: _, ...rest } = data
28
- if (Object.keys(rest).length > 0) {
29
- message = `${message}\nError Data:\n${JSON.stringify(rest, null, 2)}`
30
- }
31
- }
24
+ const { message, code } = data
32
25
  super(message)
33
26
  this.name = this.constructor.name
34
27
  this.status = status
@@ -3,9 +3,13 @@ import { QueryBuilder } from '@/query'
3
3
  import { EventEmitter, KnexHelper } from '@/lib'
4
4
  import { convertSchema, addRelationSchemas, convertRelations } from '@/schema'
5
5
  import { populateGraph, filterGraph } from '@/graph'
6
+ import { formatJson } from '@/utils'
6
7
  import {
7
- ResponseError, DatabaseError, GraphError, ModelError, NotFoundError,
8
- RelationError, WrappedError
8
+ ResponseError,
9
+ GraphError, ModelError,
10
+ NotFoundError,
11
+ RelationError,
12
+ WrappedError
9
13
  } from '@/errors'
10
14
  import {
11
15
  isString, isObject, isArray, isFunction, isPromise, asArray, merge, flatten,
@@ -222,7 +226,7 @@ export class Model extends objection.Model {
222
226
  return super.query(trx).onError(err => {
223
227
  // TODO: Shouldn't this wrapping happen on the Controller level?
224
228
  err = err instanceof ResponseError ? err
225
- : err instanceof objection.DBError ? new DatabaseError(err)
229
+ : err instanceof objection.DBError ? this.app.createDatabaseError(err)
226
230
  : new WrappedError(err)
227
231
  return Promise.reject(err)
228
232
  })
@@ -279,9 +283,9 @@ export class Model extends objection.Model {
279
283
  throw new ModelError(
280
284
  this,
281
285
  `Invalid amount of id values provided for reference: Unable to map ${
282
- JSON.stringify(modelOrId)
286
+ formatJson(modelOrId, false)
283
287
  } to ${
284
- JSON.stringify(idProperties)
288
+ formatJson(idProperties, false)
285
289
  }.`
286
290
  )
287
291
  }
@@ -729,10 +733,11 @@ export class Model extends objection.Model {
729
733
  case 'ModelValidation':
730
734
  return this.app.createValidationError({
731
735
  type,
732
- message: message ||
733
- `The provided data for the ${this.name} model is not valid: ${JSON.stringify(json)}`,
736
+ message:
737
+ message || `The provided data for the ${this.name} model is not valid`,
734
738
  errors,
735
- options
739
+ options,
740
+ json
736
741
  })
737
742
  case 'RelationExpression':
738
743
  case 'UnallowedRelation':
@@ -2,5 +2,6 @@ export * from './decorator'
2
2
  export * from './deprecate'
3
3
  export * from './emitter'
4
4
  export * from './function'
5
+ export * from './json'
5
6
  export * from './object'
6
7
  export * from './scope'
@@ -0,0 +1,3 @@
1
+ export function formatJson(json, indented = true) {
2
+ return JSON.stringify(json, null, indented ? 2 : 0)
3
+ }