@ditojs/server 1.28.0 → 1.29.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/server",
3
- "version": "1.28.0",
3
+ "version": "1.29.0",
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",
@@ -22,10 +22,10 @@
22
22
  "node >= 18"
23
23
  ],
24
24
  "dependencies": {
25
- "@ditojs/admin": "^1.28.0",
26
- "@ditojs/build": "^1.28.0",
27
- "@ditojs/router": "^1.28.0",
28
- "@ditojs/utils": "^1.28.0",
25
+ "@ditojs/admin": "^1.29.0",
26
+ "@ditojs/build": "^1.29.0",
27
+ "@ditojs/router": "^1.29.0",
28
+ "@ditojs/utils": "^1.29.0",
29
29
  "@koa/cors": "^4.0.0",
30
30
  "@koa/multer": "^3.0.2",
31
31
  "@originjs/vite-plugin-commonjs": "^1.0.3",
@@ -36,19 +36,19 @@
36
36
  "data-uri-to-buffer": "^4.0.1",
37
37
  "eventemitter2": "^6.4.9",
38
38
  "file-type": "^18.2.1",
39
- "image-size": "^1.0.2",
40
39
  "koa": "^2.14.1",
41
- "koa-bodyparser": "^4.3.0",
40
+ "koa-bodyparser": "^4.4.0",
42
41
  "koa-compose": "^4.1.0",
43
42
  "koa-compress": "^5.1.0",
44
43
  "koa-conditional-get": "^3.0.0",
45
44
  "koa-etag": "^4.0.0",
46
- "koa-helmet": "^6.1.0",
45
+ "koa-helmet": "^7.0.1",
47
46
  "koa-mount": "^4.0.0",
48
47
  "koa-passport": "^6.0.0",
49
48
  "koa-response-time": "^2.1.0",
50
49
  "koa-session": "^6.4.0",
51
50
  "koa-static": "^5.0.0",
51
+ "leather": "^2.1.4",
52
52
  "mime-types": "^2.1.35",
53
53
  "multer": "^1.4.5-lts.1",
54
54
  "multer-s3": "https://github.com/ditojs/multer-s3#dito",
@@ -59,11 +59,11 @@
59
59
  "picocolors": "^1.0.0",
60
60
  "picomatch": "^2.3.1",
61
61
  "pino": "^8.11.0",
62
- "pino-pretty": "^9.4.0",
62
+ "pino-pretty": "^10.0.0",
63
63
  "pluralize": "^8.0.0",
64
64
  "repl": "^0.1.3",
65
65
  "uuid": "^9.0.0",
66
- "vite": "^4.1.4",
66
+ "vite": "^4.2.1",
67
67
  "vite-plugin-vue2": "^2.0.3",
68
68
  "vue": "^2.7.14",
69
69
  "vue-template-compiler": "^2.7.14"
@@ -74,23 +74,23 @@
74
74
  "objection": "^3.0.1"
75
75
  },
76
76
  "devDependencies": {
77
- "@aws-sdk/client-s3": "^3.282.0",
77
+ "@aws-sdk/client-s3": "^3.295.0",
78
78
  "@types/koa-bodyparser": "^4.3.10",
79
79
  "@types/koa-compress": "^4.0.3",
80
80
  "@types/koa-logger": "^3.1.2",
81
81
  "@types/koa-pino-logger": "^3.0.1",
82
82
  "@types/koa-response-time": "^2.1.2",
83
- "@types/koa-session": "^5.10.6",
83
+ "@types/koa-session": "^6.4.0",
84
84
  "@types/koa-static": "^4.0.2",
85
- "@types/koa__cors": "^3.3.1",
86
- "@types/node": "^18.14.6",
85
+ "@types/koa__cors": "^4.0.0",
86
+ "@types/node": "^18.15.5",
87
87
  "knex": "^2.4.2",
88
88
  "objection": "^3.0.1",
89
89
  "type-fest": "^3.6.1",
90
- "typescript": "^4.9.5"
90
+ "typescript": "^5.0.2"
91
91
  },
92
92
  "types": "types",
93
- "gitHead": "9e43f6a450f6c38a5147502863d1f44738fcecef",
93
+ "gitHead": "6e93fe59126eab3d5ee1961d9e03f0fbdc297fba",
94
94
  "scripts": {
95
95
  "types": "tsc --noEmit ./src/index.d.ts"
96
96
  },
@@ -40,7 +40,7 @@ export const AssetMixin = mixin(Model => class extends TimeStampedMixin(Model) {
40
40
  url: {
41
41
  type: 'string'
42
42
  },
43
- // These are only used when the storage defines `config.readImageSize`:
43
+ // These are only used when the storage defines `config.readDimensions`:
44
44
  width: {
45
45
  type: 'integer'
46
46
  },
@@ -958,9 +958,12 @@ export class Model extends objection.Model {
958
958
  const beforeItems = isInsert
959
959
  ? []
960
960
  : isDelete
961
+ // When deleting it's ok to load all columns when data-paths contain
962
+ // wildcards unfiltered, since `afterItems` will be empty anyway.
961
963
  ? await loadAssetDataPaths(asFindQuery(), dataPaths)
962
964
  : await asFindQuery().select(
963
- // Select all columns that are present in the data and not computed.
965
+ // Select only the properties that are present in the data,
966
+ // and which aren't the result of computed properties.
964
967
  Object.keys(inputItems[0]).filter(key => {
965
968
  const property = this.definition.properties[key]
966
969
  return property && !property.computed
@@ -3,7 +3,7 @@ import { fileTypeFromBuffer } from 'file-type'
3
3
  import { Storage } from './Storage.js'
4
4
  import { PassThrough } from 'stream'
5
5
  import consumers from 'stream/consumers'
6
- import imageSize from 'image-size'
6
+ import { attributes as readMediaAttributes } from 'leather'
7
7
 
8
8
  export class S3Storage extends Storage {
9
9
  static type = 's3'
@@ -159,31 +159,9 @@ export class S3Storage extends Storage {
159
159
  }
160
160
 
161
161
  function getFileTypeFromBuffer(buffer) {
162
- const type = fileTypeFromBuffer(buffer)
163
- if (type) {
164
- return type.mime
165
- }
166
162
  try {
167
- const { type } = imageSize(buffer)
168
- return {
169
- jpg: 'image/jpeg',
170
- png: 'image/png',
171
- gif: 'image/gif',
172
- svg: 'image/svg+xml',
173
- webp: 'image/webp',
174
- tiff: 'image/tiff',
175
- j2c: 'image/jp2',
176
- jp2: 'image/jp2',
177
- ktx: 'image/ktx',
178
- bmp: 'image/bmp',
179
- tga: 'image/x-targa',
180
- cur: 'image/x-win-bitmap',
181
- icns: 'image/x-icon',
182
- ico: 'image/x-icon',
183
- pnm: 'image/x-portable-anymap',
184
- dds: 'image/vnd-ms.dds',
185
- psd: 'image/vnd.adobe.photoshop'
186
- }[type]
163
+ // Use leather as fall-back for better media file mime type detection.
164
+ return fileTypeFromBuffer(buffer)?.mime || readMediaAttributes(buffer)?.mime
187
165
  } catch (err) {}
188
166
  return null
189
167
  }
@@ -2,10 +2,11 @@ import path from 'path'
2
2
  import { URL } from 'url'
3
3
  import multer from '@koa/multer'
4
4
  import picomatch from 'picomatch'
5
- import imageSize from 'image-size'
6
5
  import { PassThrough } from 'stream'
6
+ import { attributes as readMediaAttributes } from 'leather'
7
7
  import { hyphenate, toPromiseCallback } from '@ditojs/utils'
8
8
  import { AssetFile } from './AssetFile.js'
9
+ import { deprecate } from '../utils/deprecate.js'
9
10
 
10
11
  const storageClasses = {}
11
12
 
@@ -87,7 +88,7 @@ export class Storage {
87
88
  type: storageFile.mimetype,
88
89
  size: storageFile.size,
89
90
  url: this._getFileUrl(storageFile),
90
- // In case `config.readImageSize` is set:
91
+ // In case `config.readDimensions` is set:
91
92
  width: storageFile.width,
92
93
  height: storageFile.height
93
94
  }
@@ -101,7 +102,7 @@ export class Storage {
101
102
  await this._addFile(file, data)
102
103
  file.size = Buffer.byteLength(data)
103
104
  file.url = this._getFileUrl(file)
104
- // TODO: Support `config.readImageSize`, but this can only be done once
105
+ // TODO: Support `config.readDimensions`, but this can only be done once
105
106
  // there are separate storage instances per model assets config!
106
107
  return this.convertAssetFile(file)
107
108
  }
@@ -126,10 +127,6 @@ export class Storage {
126
127
  return this._getFileUrl(file)
127
128
  }
128
129
 
129
- isImageFile(file) {
130
- return file.mimetype.startsWith('image/')
131
- }
132
-
133
130
  _getUrl(...parts) {
134
131
  return this.url
135
132
  ? new URL(path.posix.join(...parts), this.url).toString()
@@ -161,8 +158,20 @@ export class Storage {
161
158
  async _listKeys() {}
162
159
 
163
160
  async _handleUpload(req, file, config) {
164
- if (config.readImageSize && this.isImageFile(file)) {
165
- return this._handleImageFile(req, file)
161
+ if (config.readImageSize) {
162
+ deprecate(
163
+ `config.readImageSize is deprecated in favour of config.readDimensions`
164
+ )
165
+ }
166
+ if (
167
+ (
168
+ config.readDimensions ||
169
+ // TODO: `config.readImageSize` was deprecated in favour of
170
+ // `config.readDimensions` in March 2023. Remove in 1 year.
171
+ config.readImageSize
172
+ ) && /^(image|video)\//.test(file.mimetype)
173
+ ) {
174
+ return this._handleMediaFile(req, file)
166
175
  } else {
167
176
  return this._handleFile(req, file)
168
177
  }
@@ -183,7 +192,7 @@ export class Storage {
183
192
  })
184
193
  }
185
194
 
186
- async _handleImageFile(req, file) {
195
+ async _handleMediaFile(req, file) {
187
196
  const { size, stream } = await new Promise(resolve => {
188
197
  let data = null
189
198
 
@@ -204,9 +213,15 @@ export class Storage {
204
213
 
205
214
  const onData = chunk => {
206
215
  data = data ? Buffer.concat([data, chunk]) : chunk
207
- const size = imageSize(data)
208
- if (size) {
209
- done(size)
216
+ try {
217
+ const size = readMediaAttributes(data)
218
+ // On partial data, sometimes we get results back from leather without
219
+ // actual dimensions, so check for that.
220
+ if (size.mime && (size.width > 0 || size.height > 0)) {
221
+ done(size)
222
+ }
223
+ } catch {
224
+ // Ignore errors in `readMediaAttributes()` on partial data.
210
225
  }
211
226
  }
212
227
 
@@ -31,22 +31,22 @@ describe('describeFunction()', () => {
31
31
  .toBe('async function (a, b, c) { ... }')
32
32
  })
33
33
 
34
- it('describes lambdas with one param and a body', () => {
34
+ it('describes async lambdas with one param and a body', () => {
35
35
  expect(describeFunction(async a => { return a }))
36
36
  .toBe('async a => { ... }')
37
37
  })
38
38
 
39
- it('describes lambdas with one param and no body', () => {
39
+ it('describes async lambdas with one param and no body', () => {
40
40
  expect(describeFunction(async a => a))
41
41
  .toBe('async a => ...')
42
42
  })
43
43
 
44
- it('describes lambdas with multiple params and a body', () => {
44
+ it('describes async lambdas with multiple params and a body', () => {
45
45
  expect(describeFunction(async (a, b, c) => { return a + b + c }))
46
46
  .toBe('async (a, b, c) => { ... }')
47
47
  })
48
48
 
49
- it('describes lambdas with multiple params and no body', () => {
49
+ it('describes async lambdas with multiple params and no body', () => {
50
50
  expect(describeFunction(async (a, b, c) => a + b + c))
51
51
  .toBe('async (a, b, c) => ...')
52
52
  })
package/types/index.d.ts CHANGED
@@ -588,7 +588,7 @@ export type ModelFilters<$Model extends Model = Model> = Record<
588
588
 
589
589
  export interface ModelAsset {
590
590
  storage: string
591
- readImageSize?: boolean
591
+ readDimensions?: boolean
592
592
  }
593
593
 
594
594
  export type ModelAssets = Record<string, ModelAsset>
@@ -1666,9 +1666,9 @@ type AssetFileObject = {
1666
1666
  size: number
1667
1667
  // The public url of the file
1668
1668
  url: string
1669
- // The width of the image if the storage defines `config.readImageSize`
1669
+ // The width of the image if the storage defines `config.readDimensions`
1670
1670
  width: number
1671
- // The height of the image if the storage defines `config.readImageSize`
1671
+ // The height of the image if the storage defines `config.readDimensions`
1672
1672
  height: number
1673
1673
  }
1674
1674