@ditojs/server 1.25.0 → 1.25.1

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.25.0",
3
+ "version": "1.25.1",
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",
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "@ditojs/admin": "^1.25.0",
26
26
  "@ditojs/build": "^1.25.0",
27
- "@ditojs/router": "^1.25.0",
27
+ "@ditojs/router": "^1.25.1",
28
28
  "@ditojs/utils": "^1.25.0",
29
29
  "@koa/cors": "^4.0.0",
30
30
  "@koa/multer": "^3.0.2",
@@ -90,7 +90,7 @@
90
90
  "typescript": "^4.9.5"
91
91
  },
92
92
  "types": "types",
93
- "gitHead": "29e7f8d8c613c48a856d90d1ee876e85c8d84c08",
93
+ "gitHead": "bebbf44ddf15a565e50ed8b2cbe0bf8a463c276d",
94
94
  "scripts": {
95
95
  "types": "tsc --noEmit ./src/index.d.ts"
96
96
  },
@@ -6,7 +6,6 @@ import Koa from 'koa'
6
6
  import Knex from 'knex'
7
7
  import pico from 'picocolors'
8
8
  import pino from 'pino'
9
- import parseDuration from 'parse-duration'
10
9
  import bodyParser from 'koa-bodyparser'
11
10
  import cors from '@koa/cors'
12
11
  import compose from 'koa-compose'
@@ -21,9 +20,9 @@ import responseTime from 'koa-response-time'
21
20
  import { Model, knexSnakeCaseMappers, ref } from 'objection'
22
21
  import Router from '@ditojs/router'
23
22
  import {
24
- isArray, isObject, isString, asArray, isPlainObject, isModule,
25
- hyphenate, clone, merge, parseDataPath, normalizeDataPath, toPromiseCallback,
26
- mapConcurrently
23
+ isArray, isObject, asArray, isPlainObject, isModule,
24
+ hyphenate, clone, merge, parseDataPath, normalizeDataPath,
25
+ toPromiseCallback, mapConcurrently
27
26
  } from '@ditojs/utils'
28
27
  import SessionStore from './SessionStore.js'
29
28
  import { Validator } from './Validator.js'
@@ -32,7 +31,7 @@ import { Controller, AdminController } from '../controllers/index.js'
32
31
  import { Service } from '../services/index.js'
33
32
  import { Storage } from '../storage/index.js'
34
33
  import { convertSchema } from '../schema/index.js'
35
- import { deprecate } from '../utils/index.js'
34
+ import { getDuration, subtractDuration, deprecate } from '../utils/index.js'
36
35
  import {
37
36
  ResponseError,
38
37
  ValidationError,
@@ -66,6 +65,7 @@ export class Application extends Koa {
66
65
  // Pluck keys out of `config.app` to keep them secret
67
66
  app: { keys, ...app } = {},
68
67
  log,
68
+ assets,
69
69
  logger,
70
70
  ...rest
71
71
  } = config
@@ -74,6 +74,7 @@ export class Application extends Koa {
74
74
  log: log === false || log?.silent || process.env.DITO_SILENT
75
75
  ? {}
76
76
  : getOptions(log),
77
+ assets: merge(defaultAssetOptions, getOptions(assets)),
77
78
  logger: merge(defaultLoggerOptions, getOptions(logger)),
78
79
  ...rest
79
80
  }
@@ -805,16 +806,6 @@ export class Application extends Koa {
805
806
  removedFiles,
806
807
  trx = null
807
808
  ) {
808
- const {
809
- assets: {
810
- cleanupTimeThreshold = 0
811
- } = {}
812
- } = this.config
813
- // Only remove unused assets that haven't seen changes for given time frame.
814
- const timeThreshold = isString(cleanupTimeThreshold)
815
- ? parseDuration(cleanupTimeThreshold)
816
- : cleanupTimeThreshold
817
-
818
809
  let importedFiles = []
819
810
  const AssetModel = this.getModel('Asset')
820
811
  if (AssetModel) {
@@ -837,18 +828,20 @@ export class Application extends Koa {
837
828
  changeCount(addedFiles, 1),
838
829
  changeCount(removedFiles, -1)
839
830
  ])
840
- if (timeThreshold > 0) {
831
+ const cleanupTimeThreshold =
832
+ getDuration(this.config.assets.cleanupTimeThreshold)
833
+ if (cleanupTimeThreshold > 0) {
841
834
  setTimeout(
842
835
  // Don't pass `trx` here, as we want this delayed execution to
843
836
  // create its own transaction.
844
- () => this.releaseUnusedAssets(timeThreshold),
845
- timeThreshold
837
+ () => this.releaseUnusedAssets(),
838
+ cleanupTimeThreshold
846
839
  )
847
840
  }
848
841
  }
849
842
  // Also execute releaseUnusedAssets() immediately in the same
850
843
  // transaction, to potentially clean up other pending assets.
851
- await this.releaseUnusedAssets(timeThreshold, trx)
844
+ await this.releaseUnusedAssets(null, trx)
852
845
  return importedFiles
853
846
  }
854
847
  }
@@ -951,21 +944,34 @@ export class Application extends Koa {
951
944
  return modifiedFiles
952
945
  }
953
946
 
954
- async releaseUnusedAssets(timeThreshold = 0, trx = null) {
947
+ async releaseUnusedAssets(timeThreshold = null, trx = null) {
955
948
  const AssetModel = this.getModel('Asset')
956
949
  if (AssetModel) {
950
+ const { assets } = this.config
951
+ const cleanupTimeThreshold =
952
+ getDuration(timeThreshold ?? assets.cleanupTimeThreshold)
953
+ const danglingTimeThreshold =
954
+ getDuration(timeThreshold ?? assets.danglingTimeThreshold)
957
955
  return AssetModel.transaction(trx, async trx => {
958
- // Determine the time threshold in JS instead of SQL, as there is no
959
- // easy cross-SQL way to do `now() - interval X hours`:
960
- const date = new Date()
961
- date.setMilliseconds(date.getMilliseconds() - timeThreshold)
956
+ // Calculate the date math in JS instead of SQL, as there is no easy
957
+ // cross-SQL way to do `now() - interval X hours`:
958
+ const now = new Date()
959
+ const cleanupDate = subtractDuration(now, cleanupTimeThreshold)
960
+ const danglingDate = subtractDuration(now, danglingTimeThreshold)
962
961
  const orphanedAssets = await AssetModel
963
962
  .query(trx)
964
963
  .where('count', 0)
965
- .andWhere('updatedAt', '<=', date)
966
- // Protect freshly created assets from being deleted again right away,
967
- // .e.g. when `config.assets.cleanupTimeThreshold = 0`
968
- .andWhere('updatedAt', '>', ref('createdAt'))
964
+ .andWhere(
965
+ query => query
966
+ .where('updatedAt', '<=', cleanupDate)
967
+ .orWhere(
968
+ // Protect freshly created assets from being deleted again right
969
+ // away, .e.g. when `config.assets.cleanupTimeThreshold = 0`
970
+ query => query
971
+ .where('updatedAt', '=', ref('createdAt'))
972
+ .andWhere('updatedAt', '<=', danglingDate)
973
+ )
974
+ )
969
975
  if (orphanedAssets.length > 0) {
970
976
  const orphanedKeys = await mapConcurrently(
971
977
  orphanedAssets,
@@ -998,6 +1004,17 @@ function getOptions(options) {
998
1004
  return isObject(options) ? options : {}
999
1005
  }
1000
1006
 
1007
+ const defaultAssetOptions = {
1008
+ // Only remove unused or dangling assets that haven't seen changes for
1009
+ // these given time frames. Set to `0` to clean up instantly.
1010
+ cleanupTimeThreshold: '24h',
1011
+ // Dangling assets are those that got uploaded but never actually persisted in
1012
+ // the model. This can happen when the admin uploads a file but doesn't store
1013
+ // the associated form. This cannot be set to 0 or else the the file would be
1014
+ // deleted immediately after upload.
1015
+ danglingTimeThreshold: '24h'
1016
+ }
1017
+
1001
1018
  const { err, req, res } = pino.stdSerializers
1002
1019
  const defaultLoggerOptions = {
1003
1020
  level: 'info',
@@ -0,0 +1,15 @@
1
+ import { isNumber } from '@ditojs/utils'
2
+ import parseDuration from 'parse-duration'
3
+
4
+ export function getDuration(duration) {
5
+ return isNumber(duration) ? duration : parseDuration(duration)
6
+ }
7
+
8
+ export function addDuration(date, duration) {
9
+ date.setMilliseconds(date.getMilliseconds() + getDuration(duration))
10
+ return date
11
+ }
12
+
13
+ export function subtractDuration(date, duration) {
14
+ return addDuration(date, -getDuration(duration))
15
+ }
@@ -1,3 +1,4 @@
1
+ export * from './date.js'
1
2
  export * from './decorator.js'
2
3
  export * from './deprecate.js'
3
4
  export * from './emitter.js'