@ditojs/server 1.28.0 → 1.30.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 +16 -16
- package/src/app/Application.js +7 -6
- package/src/controllers/AdminController.js +2 -7
- package/src/mixins/AssetMixin.js +1 -1
- package/src/models/Model.js +4 -1
- package/src/storage/S3Storage.js +3 -25
- package/src/storage/Storage.js +28 -13
- package/src/utils/function.test.js +4 -4
- package/types/index.d.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.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.
|
|
26
|
-
"@ditojs/build": "^1.
|
|
27
|
-
"@ditojs/router": "^1.
|
|
28
|
-
"@ditojs/utils": "^1.
|
|
25
|
+
"@ditojs/admin": "^1.30.0",
|
|
26
|
+
"@ditojs/build": "^1.30.0",
|
|
27
|
+
"@ditojs/router": "^1.30.0",
|
|
28
|
+
"@ditojs/utils": "^1.30.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.
|
|
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": "^
|
|
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": "^
|
|
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
|
|
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.
|
|
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": "^
|
|
83
|
+
"@types/koa-session": "^6.4.0",
|
|
84
84
|
"@types/koa-static": "^4.0.2",
|
|
85
|
-
"@types/koa__cors": "^
|
|
86
|
-
"@types/node": "^18.
|
|
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": "^
|
|
90
|
+
"typescript": "^5.0.2"
|
|
91
91
|
},
|
|
92
92
|
"types": "types",
|
|
93
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "5d09dc24ecb77b511f3c25643275ea1b9514fb52",
|
|
94
94
|
"scripts": {
|
|
95
95
|
"types": "tsc --noEmit ./src/index.d.ts"
|
|
96
96
|
},
|
package/src/app/Application.js
CHANGED
|
@@ -819,12 +819,13 @@ export class Application extends Koa {
|
|
|
819
819
|
addedFiles.length > 0 ||
|
|
820
820
|
removedFiles.length > 0
|
|
821
821
|
) {
|
|
822
|
-
const changeCount = (files, increment) =>
|
|
823
|
-
files.length > 0
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
822
|
+
const changeCount = async (files, increment) => {
|
|
823
|
+
if (files.length > 0) {
|
|
824
|
+
await AssetModel.query(trx)
|
|
825
|
+
.whereIn('key', files.map(file => file.key))
|
|
826
|
+
.increment('count', increment)
|
|
827
|
+
}
|
|
828
|
+
}
|
|
828
829
|
await Promise.all([
|
|
829
830
|
changeCount(addedFiles, 1),
|
|
830
831
|
changeCount(removedFiles, -1)
|
|
@@ -6,11 +6,7 @@ import { createVuePlugin } from 'vite-plugin-vue2'
|
|
|
6
6
|
import {
|
|
7
7
|
viteCommonjs as createCommonJsPlugin
|
|
8
8
|
} from '@originjs/vite-plugin-commonjs'
|
|
9
|
-
import {
|
|
10
|
-
createRollupImportsResolver,
|
|
11
|
-
testModuleIdentifier,
|
|
12
|
-
getPostCssConfig
|
|
13
|
-
} from '@ditojs/build'
|
|
9
|
+
import { testModuleIdentifier, getPostCssConfig } from '@ditojs/build'
|
|
14
10
|
import { merge } from '@ditojs/utils'
|
|
15
11
|
import { Controller } from './Controller.js'
|
|
16
12
|
import { handleConnectMiddleware } from '../middleware/index.js'
|
|
@@ -237,8 +233,7 @@ export class AdminController extends Controller {
|
|
|
237
233
|
{
|
|
238
234
|
find: '@',
|
|
239
235
|
replacement: root
|
|
240
|
-
}
|
|
241
|
-
createRollupImportsResolver({ cwd: root })
|
|
236
|
+
}
|
|
242
237
|
]
|
|
243
238
|
}
|
|
244
239
|
}, config))
|
package/src/mixins/AssetMixin.js
CHANGED
|
@@ -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.
|
|
43
|
+
// These are only used when the storage defines `config.readDimensions`:
|
|
44
44
|
width: {
|
|
45
45
|
type: 'integer'
|
|
46
46
|
},
|
package/src/models/Model.js
CHANGED
|
@@ -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
|
|
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
|
package/src/storage/S3Storage.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
}
|
package/src/storage/Storage.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
165
|
-
|
|
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
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1671
|
+
// The height of the image if the storage defines `config.readDimensions`
|
|
1672
1672
|
height: number
|
|
1673
1673
|
}
|
|
1674
1674
|
|