@ditojs/server 1.14.2 → 1.14.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/server",
3
- "version": "1.14.2",
3
+ "version": "1.14.3",
4
4
  "type": "module",
5
5
  "description": "Dito.js Server – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/master/packages/server",
@@ -39,7 +39,6 @@
39
39
  "data-uri-to-buffer": "^4.0.0",
40
40
  "eventemitter2": "^6.4.9",
41
41
  "file-type": "^18.0.0",
42
- "fs-extra": "^10.1.0",
43
42
  "image-size": "^1.0.2",
44
43
  "is-svg": "^4.3.2",
45
44
  "koa": "^2.13.4",
@@ -95,5 +94,5 @@
95
94
  "typescript": "^4.9.3"
96
95
  },
97
96
  "types": "types",
98
- "gitHead": "3165af9ce8e5c8143fd5940ff9d2ae7c3fa45d8e"
97
+ "gitHead": "cedf7eb2a28bb051ef194fc9b29205681e390ef9"
99
98
  }
@@ -1,7 +1,7 @@
1
1
  import path from 'path'
2
2
  import util from 'util'
3
3
  import zlib from 'zlib'
4
- import fs from 'fs-extra'
4
+ import fs from 'fs/promises'
5
5
  import Koa from 'koa'
6
6
  import Knex from 'knex'
7
7
  import pico from 'picocolors'
@@ -594,7 +594,7 @@ export class Application extends Koa {
594
594
  prettyPrint: {
595
595
  colorize: true,
596
596
  // List of keys to ignore in pretty mode.
597
- ignore: 'req,res,durationMs,user,requestId,err.message',
597
+ ignore: 'req,res,durationMs,user,requestId',
598
598
  // SYS to use system time and not UTC.
599
599
  translateTime: 'SYS:HH:MM:ss.l'
600
600
  },
@@ -689,20 +689,40 @@ export class Application extends Koa {
689
689
  return this.config.app.normalizePaths ? hyphenate(path) : path
690
690
  }
691
691
 
692
- logError(err, ctx) {
693
- if (!err.expose && !this.silent) {
692
+ formatError(error) {
693
+ // Clone the error to be able to delete hidden properties.
694
+ const copy = clone(error)
695
+ // Remove headers added by the CORS middleware.
696
+ delete copy.headers
697
+ if (this.config.log.errors?.stack === false) {
698
+ delete copy.stack
699
+ delete copy.cause
700
+ } else {
701
+ // These aren't enumerable and thus couldn't be cloned above.
702
+ if (error.stack !== undefined) {
703
+ copy.stack = error.stack
704
+ }
705
+ if (error.cause !== undefined) {
706
+ copy.cause = error.cause
707
+ }
708
+ }
709
+ // Use `util.inspect()` instead of Pino's internal error logging for better
710
+ // stack traces and logging of error data.
711
+ return util.inspect(copy, {
712
+ compact: false,
713
+ depth: null,
714
+ maxArrayLength: null
715
+ })
716
+ }
717
+
718
+ logError(error, ctx) {
719
+ if (!error.expose && !this.silent) {
694
720
  try {
695
- const clone = structuredClone(err)
696
- // Remove headers added by the CORS middleware.
697
- delete clone.headers
698
- if (this.config.log.errors?.stack === false) {
699
- delete clone.stack
700
- delete clone.cause
701
- }
702
- const level =
703
- err instanceof ResponseError && err.status < 500 ? 'info' : 'error'
704
721
  const logger = ctx?.logger || this.logger
705
- logger[level](clone)
722
+ const level = error instanceof ResponseError && error.status < 500
723
+ ? 'info'
724
+ : 'error'
725
+ logger[level](this.formatError(error))
706
726
  } catch (e) {
707
727
  console.error('Could not log error', e)
708
728
  }
@@ -895,7 +915,8 @@ export class Application extends Koa {
895
915
  data = await fs.readFile(filepath)
896
916
  } else {
897
917
  const response = await fetch(url)
898
- data = await response.arrayBuffer()
918
+ const buffer = await response.arrayBuffer()
919
+ data = new DataView(buffer)
899
920
  }
900
921
  }
901
922
  const importedFile = await storage.addFile(file, data)
@@ -1,6 +1,6 @@
1
1
  import repl from 'repl'
2
2
  import path from 'path'
3
- import fs from 'fs-extra'
3
+ import fs from 'fs/promises'
4
4
  import pico from 'picocolors'
5
5
  import objection from 'objection'
6
6
  import { isFunction, deindent } from '@ditojs/utils'
@@ -1,5 +1,5 @@
1
1
  import path from 'path'
2
- import fs from 'fs-extra'
2
+ import fs from 'fs/promises'
3
3
  import pico from 'picocolors'
4
4
  import { getRelationClass, isThroughRelationClass } from '@ditojs/server'
5
5
  import {
@@ -50,7 +50,7 @@ export async function createMigration(app, name, ...modelNames) {
50
50
  : ''
51
51
  const filename = `${getTimestamp()}_${name}.js`
52
52
  const file = path.join(migrationDir, filename)
53
- if (await fs.exists(file)) {
53
+ if (await exists(file)) {
54
54
  // This should never happen, but let's be on the safe side here:
55
55
  console.info(pico.red(`Migration '${filename}' already exists.`))
56
56
  return false
@@ -204,3 +204,12 @@ function getTimestamp() {
204
204
  padDate(d.getMinutes()) +
205
205
  padDate(d.getSeconds())
206
206
  }
207
+
208
+ async function exists(path) {
209
+ try {
210
+ await fs.access(path)
211
+ return true
212
+ } catch {
213
+ return false
214
+ }
215
+ }
@@ -1,5 +1,5 @@
1
1
  import path from 'path'
2
- import fs from 'fs-extra'
2
+ import fs from 'fs/promises'
3
3
  import pico from 'picocolors'
4
4
  import util from 'util'
5
5
  import pluralize from 'pluralize'
@@ -3,7 +3,7 @@ import { EventEmitter } from '../lib/index.js'
3
3
  import ControllerAction from './ControllerAction.js'
4
4
  import MemberAction from './MemberAction.js'
5
5
  import {
6
- ResponseError, WrappedError, ControllerError, AuthorizationError
6
+ ResponseError, ControllerError, AuthorizationError
7
7
  } from '../errors/index.js'
8
8
  import {
9
9
  getOwnProperty, getOwnKeys, getAllKeys, processHandlerParameters,
@@ -185,7 +185,7 @@ export class Controller {
185
185
  ctx.body = res
186
186
  }
187
187
  } catch (err) {
188
- throw err instanceof ResponseError ? err : new WrappedError(err)
188
+ throw err instanceof ResponseError ? err : new ResponseError(err)
189
189
  }
190
190
  }
191
191
  ])
@@ -1,4 +1,4 @@
1
- import { WrappedError } from './WrappedError.js'
1
+ import { ResponseError } from './ResponseError.js'
2
2
  // TODO: Import directly once we can move to Objection 3 and this is fixed:
3
3
  // import {
4
4
  // DBError,
@@ -16,16 +16,21 @@ const {
16
16
  ConstraintViolationError
17
17
  } = objection
18
18
 
19
- export class DatabaseError extends WrappedError {
19
+ export class DatabaseError extends ResponseError {
20
20
  constructor(error, overrides) {
21
- const status =
22
- error instanceof CheckViolationError ? 400
23
- : error instanceof NotNullViolationError ? 400
24
- : error instanceof ConstraintViolationError ? 409
25
- : error instanceof DataError ? 400
26
- : error instanceof DBError ? 500
27
- : 400
28
- overrides = { type: error.constructor.name, status, ...overrides }
29
- super(error, overrides, { message: 'Database error', status })
21
+ super(error, {
22
+ type: error.constructor.name,
23
+ message: 'Database error',
24
+ status: getStatus(error)
25
+ }, overrides)
30
26
  }
31
27
  }
28
+
29
+ function getStatus(error) {
30
+ return error instanceof CheckViolationError ? 400
31
+ : error instanceof NotNullViolationError ? 400
32
+ : error instanceof ConstraintViolationError ? 409
33
+ : error instanceof DataError ? 400
34
+ : error instanceof DBError ? 500
35
+ : 400
36
+ }
@@ -1,18 +1,21 @@
1
- import { WrappedError } from './WrappedError.js'
2
- import { isObject } from '@ditojs/utils'
1
+ import { ResponseError } from './ResponseError.js'
3
2
 
4
- export class RelationError extends WrappedError {
3
+ export class RelationError extends ResponseError {
5
4
  constructor(error) {
6
- let overrides
7
- if (isObject(error)) {
8
- // Adjust Objection.js error messages to point to the right property.
9
- const parse = str => str?.replace(/\brelationMappings\b/g, 'relations')
10
- const { message, stack } = error
11
- overrides = {
12
- message: parse(message),
13
- stack: parse(stack)
14
- }
15
- }
16
- super(error, overrides, { message: 'Relation error', status: 400 })
5
+ super(
6
+ error,
7
+ { message: 'Relation error', status: 400 },
8
+ error instanceof Error ? getParsedOverrides(error) : null
9
+ )
10
+ }
11
+ }
12
+
13
+ function getParsedOverrides(error) {
14
+ // Adjust Objection.js error messages to point to the right property.
15
+ const parse = str => str?.replace(/\brelationMappings\b/g, 'relations')
16
+ const { message, stack } = error
17
+ return {
18
+ message: parse(message),
19
+ stack: parse(stack)
17
20
  }
18
21
  }
@@ -1,33 +1,52 @@
1
1
  import { isPlainObject, isString } from '@ditojs/utils'
2
2
 
3
3
  export class ResponseError extends Error {
4
- constructor(error, defaults = { message: 'Response error', status: 400 }) {
4
+ constructor(
5
+ error,
6
+ defaults = { message: 'Response error', status: 500 },
7
+ overrides
8
+ ) {
5
9
  const object = isPlainObject(error)
6
10
  ? error
7
11
  : error instanceof Error
8
- ? {
9
- // Copy error into object so they can be merged with defaults after.
10
- // First copy everything that is enumerable, unless the error is from
11
- // axios, in which case we don't want to leak config information.
12
- ...(error.isAxiosError ? null : error),
13
- // Also explicitly copy message, status and code.
14
- message: error.message,
15
- status: error.status,
16
- code: error.code
17
- }
12
+ ? getErrorObject(error)
18
13
  : isString(error)
19
14
  ? { message: error }
20
15
  : error || {}
21
- const { status, cause, ...data } = { ...defaults, ...object }
22
- const { message, code } = data
23
- super(message, { cause })
24
- this.name = this.constructor.name
16
+ const { message, status, stack, cause, ...data } = {
17
+ ...defaults,
18
+ ...object,
19
+ ...overrides
20
+ }
21
+ super(message, cause ? { cause } : {})
25
22
  this.status = status
26
- this.code = code
27
23
  this.data = data
24
+ if (stack != null) {
25
+ this.stack = stack
26
+ }
28
27
  }
29
28
 
30
29
  toJSON() {
31
- return this.data
30
+ return {
31
+ // Include the message in the JSON data sent back.
32
+ message: this.message,
33
+ ...this.data
34
+ }
32
35
  }
33
36
  }
37
+
38
+ function getErrorObject(error) {
39
+ // For generic errors, explicitly copy message.
40
+ const object = error.toJSON?.() ?? { message: error.message }
41
+ // Additionally copy status and code if present.
42
+ if (error.status != null) {
43
+ object.status = error.status
44
+ }
45
+ if (error.code != null) {
46
+ object.code = error.code
47
+ }
48
+ // Preserve the cause if already set in the original error, and set it to the
49
+ // error itself otherwise.
50
+ object.cause = error.cause ?? error
51
+ return object
52
+ }
@@ -11,4 +11,3 @@ export * from './NotImplementedError.js'
11
11
  export * from './QueryBuilderError.js'
12
12
  export * from './RelationError.js'
13
13
  export * from './ValidationError.js'
14
- export * from './WrappedError.js'
@@ -16,8 +16,7 @@ import {
16
16
  ResponseError,
17
17
  GraphError, ModelError,
18
18
  NotFoundError,
19
- RelationError,
20
- WrappedError
19
+ RelationError
21
20
  } from '../errors/index.js'
22
21
  import RelationAccessor from './RelationAccessor.js'
23
22
  import definitions from './definitions/index.js'
@@ -239,7 +238,7 @@ export class Model extends objection.Model {
239
238
  // TODO: Shouldn't this wrapping happen on the Controller level?
240
239
  err = err instanceof ResponseError ? err
241
240
  : err instanceof objection.DBError ? this.app.createDatabaseError(err)
242
- : new WrappedError(err)
241
+ : new ResponseError(err)
243
242
  return Promise.reject(err)
244
243
  })
245
244
  }
@@ -1,4 +1,4 @@
1
- import fs from 'fs-extra'
1
+ import fs from 'fs/promises'
2
2
  import path from 'path'
3
3
  import multer from '@koa/multer'
4
4
  import { Storage } from './Storage.js'
@@ -15,7 +15,7 @@ export class DiskStorage extends Storage {
15
15
  // Add `storageFile.key` property to internal storage file object.
16
16
  storageFile.key = this.getUniqueKey(storageFile.originalname)
17
17
  const dir = this._getPath(this._getNestedFolder(storageFile.key))
18
- fs.ensureDir(dir)
18
+ fs.mkdir(dir, { recursive: true })
19
19
  .then(() => cb(null, dir))
20
20
  .catch(cb)
21
21
  },
@@ -41,7 +41,7 @@ export class DiskStorage extends Storage {
41
41
  async _addFile(file, buffer) {
42
42
  const filePath = this._getFilePath(file)
43
43
  const dir = path.dirname(filePath)
44
- await fs.ensureDir(dir)
44
+ await fs.mkdir(dir, { recursive: true })
45
45
  await fs.writeFile(filePath, buffer)
46
46
  return file
47
47
  }
package/types/index.d.ts CHANGED
@@ -1617,8 +1617,7 @@ export class ResponseError extends Error {
1617
1617
  export class AssetError extends ResponseError {}
1618
1618
  export class AuthenticationError extends ResponseError {}
1619
1619
  export class AuthorizationError extends ResponseError {}
1620
- export class WrappedError extends ResponseError {}
1621
- export class DatabaseError extends WrappedError {
1620
+ export class DatabaseError extends ResponseError {
1622
1621
  constructor(
1623
1622
  error:
1624
1623
  | dbErrors.CheckViolationError
@@ -1,18 +0,0 @@
1
- import { ResponseError } from './ResponseError.js'
2
-
3
- export class WrappedError extends ResponseError {
4
- constructor(error, overrides, defaults = {
5
- message: 'Wrapped error',
6
- status: 400
7
- }) {
8
- super(
9
- overrides
10
- ? Object.setPrototypeOf({ ...overrides }, error)
11
- : error,
12
- defaults
13
- )
14
- if (error?.stack) {
15
- this.stack = error.stack
16
- }
17
- }
18
- }