@anjianshi/utils 2.0.1 → 2.0.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.
Files changed (43) hide show
  1. package/lang/string.d.ts +4 -0
  2. package/lang/string.js +11 -0
  3. package/package.json +5 -6
  4. package/url.js +1 -1
  5. package/.eslintrc.cjs +0 -6
  6. package/README.md +0 -1
  7. package/build-cleanup.cjs +0 -10
  8. package/src/.eslintrc.cjs +0 -4
  9. package/src/env-browser/.eslintrc.cjs +0 -6
  10. package/src/env-browser/device.ts +0 -60
  11. package/src/env-browser/global.ts +0 -21
  12. package/src/env-browser/load-script.ts +0 -13
  13. package/src/env-browser/logging.ts +0 -58
  14. package/src/env-browser/manage-vconsole.ts +0 -54
  15. package/src/env-node/.eslintrc.cjs +0 -4
  16. package/src/env-node/env-reader.ts +0 -33
  17. package/src/env-node/index.ts +0 -46
  18. package/src/env-node/logging/handlers.ts +0 -182
  19. package/src/env-node/logging/index.ts +0 -16
  20. package/src/env-node/typeorm/adapt-logging.ts +0 -54
  21. package/src/env-node/typeorm/index.ts +0 -62
  22. package/src/index.ts +0 -3
  23. package/src/init-dayjs.ts +0 -8
  24. package/src/lang/async.ts +0 -47
  25. package/src/lang/index.ts +0 -6
  26. package/src/lang/may-success.ts +0 -57
  27. package/src/lang/object.ts +0 -28
  28. package/src/lang/string.ts +0 -83
  29. package/src/lang/time.ts +0 -19
  30. package/src/lang/types.ts +0 -88
  31. package/src/logging/adapt.ts +0 -40
  32. package/src/logging/formatters.ts +0 -23
  33. package/src/logging/index.ts +0 -106
  34. package/src/md5.ts +0 -319
  35. package/src/url.ts +0 -185
  36. package/src/validators/array.ts +0 -62
  37. package/src/validators/base.ts +0 -49
  38. package/src/validators/boolean.ts +0 -24
  39. package/src/validators/factories.ts +0 -47
  40. package/src/validators/index.ts +0 -9
  41. package/src/validators/number.ts +0 -43
  42. package/src/validators/object.ts +0 -70
  43. package/src/validators/string.ts +0 -55
package/lang/string.d.ts CHANGED
@@ -23,3 +23,7 @@ export declare function safeParseFloat(value: string | number, fallback: number)
23
23
  * dp: 保留几位小数
24
24
  */
25
25
  export declare function readableSize(bytes: number, si?: boolean, dp?: number): string;
26
+ /**
27
+ * 解析 JSON,失败时不抛出异常,而是返回 undefined
28
+ */
29
+ export declare function safeParseJSON<T>(json: string): T | undefined;
package/lang/string.js CHANGED
@@ -78,3 +78,14 @@ export function readableSize(bytes, si = false, dp = 1) {
78
78
  } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
79
79
  return `${bytes.toFixed(dp)} ${units[u]}`;
80
80
  }
81
+ /**
82
+ * 解析 JSON,失败时不抛出异常,而是返回 undefined
83
+ */
84
+ export function safeParseJSON(json) {
85
+ try {
86
+ return JSON.parse(json);
87
+ }
88
+ catch (e) {
89
+ return undefined;
90
+ }
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anjianshi/utils",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "Common JavaScript Utils",
5
5
  "homepage": "https://github.com/anjianshi/js-packages/utils",
6
6
  "bugs": {
@@ -23,17 +23,16 @@
23
23
  "devDependencies": {
24
24
  "@types/debug": "^4.1.9",
25
25
  "@types/lodash": "^4.14.199",
26
- "@types/node": "^20.8.6",
26
+ "@types/node": "^20.12.7",
27
27
  "chalk": "^5.3.0",
28
28
  "dotenv": "^16.4.5",
29
29
  "typeorm": "^0.3.20",
30
30
  "vconsole": "^3.15.1",
31
- "typescript": "^5.4.3",
32
- "@anjianshi/presets-eslint-base": "4.0.1",
31
+ "typescript": "^5.4.4",
33
32
  "@anjianshi/presets-eslint-typescript": "4.0.1",
34
- "@anjianshi/presets-eslint-node": "4.0.1",
35
33
  "@anjianshi/presets-prettier": "3.0.0",
36
- "@anjianshi/presets-typescript": "3.0.0"
34
+ "@anjianshi/presets-typescript": "3.0.0",
35
+ "@anjianshi/presets-eslint-node": "4.0.1"
37
36
  },
38
37
  "eslintIgnore": [],
39
38
  "prettier": "@anjianshi/presets-prettier/prettierrc",
package/url.js CHANGED
@@ -10,7 +10,7 @@
10
10
  import isPlainObject from 'lodash/isPlainObject.js';
11
11
  function parseQuery(url, options) {
12
12
  const { array = false, loose = false, decode = true } = options ?? {};
13
- // 正常状态下,将仅剩 a=1&b=1(即不会再有 ? 和 #);loose 模型下,可能为 a=1&b=2#c=3?d=4
13
+ // 正常状态下,将仅剩 a=1&b=1(即不会再有 ? 和 #);loose 模式下,可能为 a=1&b=2#c=3?d=4
14
14
  const queryString = (loose ? /(\?|#)(.+)/ : /(\?)(.+?)(#|$)/).exec(url)?.[2] ?? '';
15
15
  if (!queryString)
16
16
  return {};
package/.eslintrc.cjs DELETED
@@ -1,6 +0,0 @@
1
- /* eslint-env node */
2
- module.exports = {
3
- root: true,
4
- extends: [require.resolve('@anjianshi/presets-eslint-base')],
5
- rules: {},
6
- }
package/README.md DELETED
@@ -1 +0,0 @@
1
- # Common JavaScript Utils
package/build-cleanup.cjs DELETED
@@ -1,10 +0,0 @@
1
- /* eslint-env node */
2
- const child_process = require('child_process')
3
- const fs = require('fs')
4
- const path = require('path')
5
-
6
- const base = path.resolve(__dirname, './')
7
- const dist = path.join(base, 'dist')
8
-
9
- const items = fs.readdirSync(dist)
10
- child_process.execSync(`rm -rf ${items.join(' ')}`, { shell: true })
package/src/.eslintrc.cjs DELETED
@@ -1,4 +0,0 @@
1
- /* eslint-env node */
2
- module.exports = {
3
- extends: [require.resolve('@anjianshi/presets-eslint-typescript/exclusive.cjs')],
4
- }
@@ -1,6 +0,0 @@
1
- /* eslint-env node */
2
- module.exports = {
3
- env: {
4
- browser: true,
5
- },
6
- }
@@ -1,60 +0,0 @@
1
- /**
2
- * 自动计算最合适的 rem 大小,应用到页面样式上。
3
- *
4
- * rem 的意义是对于大屏幕,适当地同步放大界面元素,以提供更饱满的显示效果。
5
- * 不过也不是所有地方都原封不动地大就好了,所以还要配合 media query 来实现最佳效果。
6
- */
7
- export function autoRem() {
8
- const resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize'
9
-
10
- /**
11
- * 计算规则:
12
- * - 微信标准的 375px 宽度下,1rem 为 50px(这个比例的来由是这样的:移动端通常像素比 >= 2,这个 375px 的实际渲染像素是 750px,也就是实际渲染 750px 的情况下,1rem 为 100px)。
13
- * - 在平板等特大屏幕下,限制 rem 的最大值为 75px,再大就夸张了。
14
- * - 竖屏时取宽度来适配 rem,横屏时取高度来适配,以保证屏幕朝向不同时,界面元素的放大比例是一致的(这应该更符合人的认知:对于一个同样大小的屏幕,无论什么朝向,上面的文字应该是一样大的)。
15
- * 注意:此计算方式仅适合“不能任意调整浏览器大小的”移动设备,对于浏览器窗口可能任意尺寸、比例的桌面端,还是应该始终按照宽度来适配。
16
- *
17
- * 从 px 到 rem 的换算:
18
- * 原 px 单位的值换算成 rem,只要除以 50 即可。例如 12px 变成 0.24rem。
19
- */
20
- function updateRem() {
21
- const { clientWidth, clientHeight } = document.documentElement
22
- const refSize = Math.min(clientWidth, clientHeight)
23
- const rem = Math.min((refSize / 750) * 100, 75)
24
- document.documentElement.style.fontSize = `${rem}px`
25
- }
26
-
27
- window.addEventListener(resizeEvt, updateRem, false)
28
- document.addEventListener('DOMContentLoaded', updateRem, false)
29
- }
30
-
31
- /**
32
- * 当前设备是否是全面屏
33
- * 页面刚加载时可能取不到 offsetHeight,因此通过一个函数而不是常量来提供此值
34
- */
35
- export function isFullScreen() {
36
- const rate = document.documentElement.offsetHeight / document.documentElement.offsetWidth
37
- const limit = window.screen.height === window.screen.availHeight ? 1.8 : 1.65 // 临界判断值
38
- return rate > limit
39
- }
40
-
41
- /**
42
- * 当前是否是 iOS 设备
43
- */
44
- export const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
45
-
46
- /**
47
- * 当前是否是移动设备
48
- */
49
- export const isMobile =
50
- /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
51
- navigator.userAgent || navigator.vendor
52
- ) ||
53
- /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
54
- (navigator.userAgent || navigator.vendor).substr(0, 4)
55
- )
56
-
57
- /**
58
- * 当前是否是微信内部浏览器
59
- */
60
- export const isWechat = /micromessenger/i.test(navigator.userAgent)
@@ -1,21 +0,0 @@
1
- /**
2
- * 通过此模块注册全局变量以便于调试
3
- * 变量会注册到 window.app = {} 下
4
- */
5
-
6
- declare global {
7
- interface Window {
8
- app: { [key: string]: unknown }
9
- }
10
- }
11
-
12
- // web-worker 环境下不存在 window 变量,无法执行注册
13
- let hasWindow = false
14
- try {
15
- window.app = {}
16
- hasWindow = true
17
- } catch (e) {} // eslint-disable-line no-empty
18
-
19
- export default function registerGlobal(key: string, content: unknown) {
20
- if (hasWindow) window.app[key] = content
21
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * 加载脚本文件
3
- * 返回 Promise,成功则 resolve,失败 reject
4
- */
5
- export default async function loadScript(url: string): Promise<void> {
6
- return new Promise((resolve, reject) => {
7
- const script = document.createElement('script')
8
- script.src = url
9
- script.onload = () => resolve()
10
- script.onerror = err => reject(err)
11
- window.document.head.appendChild(script)
12
- })
13
- }
@@ -1,58 +0,0 @@
1
- /**
2
- * 针对浏览器环境定制 logging
3
- */
4
- import {
5
- logger as defaultLogger,
6
- type Logger,
7
- type LogInfo,
8
- LogLevel,
9
- LogHandler,
10
- formatters,
11
- } from '../logging/index.js'
12
-
13
- export * from '../logging/index.js'
14
-
15
- /**
16
- * 实现向浏览器 console 输出日志
17
- */
18
- export class ConsoleHandler extends LogHandler {
19
- log(info: LogInfo) {
20
- const color = (value = 'black') => `color: ${value};`
21
- const prefix =
22
- '%c' + [formatters.time(info), info.logger].map((v: string) => (v ? `[${v}]` : '')).join('')
23
- const prefixColor = info.logger ? color(ConsoleHandler.getColor(info.logger)) : color()
24
-
25
- const values = [prefix, prefixColor, ...info.args]
26
- if (info.level === LogLevel.Debug) console.debug(...values)
27
- else if (info.level === LogLevel.Info) console.log(...values)
28
- else if (info.level === LogLevel.Warning) console.warn(...values)
29
- else console.error(...values)
30
- }
31
-
32
- // 按顺序给各主题分配颜色(取自 http://chriskempson.com/projects/base16/)
33
- private static readonly colors = [
34
- '#dc9656',
35
- '#7cafc2',
36
- '#ba8baf',
37
- '#a16946',
38
- '#ab4642',
39
- '#86c1b9',
40
- '#a1b56c',
41
- '#f7ca88',
42
- ]
43
- private static readonly colorMap = new Map<string, string>()
44
- static getColor(name: string) {
45
- if (!ConsoleHandler.colorMap.has(name)) {
46
- const nextIndex = ConsoleHandler.colorMap.size % ConsoleHandler.colors.length
47
- ConsoleHandler.colorMap.set(name, ConsoleHandler.colors[nextIndex]!)
48
- }
49
- return ConsoleHandler.colorMap.get(name)!
50
- }
51
- }
52
-
53
- /**
54
- * 预设的初始化行为
55
- */
56
- export function initLogger(logger: Logger = defaultLogger) {
57
- logger.addHandler(new ConsoleHandler())
58
- }
@@ -1,54 +0,0 @@
1
- import { type default as VConsole } from 'vconsole'
2
-
3
- /**
4
- * 注册 VConsole 代码
5
- * 此类库自己并不加载 VConsole,需使用者自行通过依赖或者 CDN 加载 VConsole 然后传给此类库
6
- */
7
- let VConsoleLib: (new () => VConsole) | undefined
8
-
9
- export function registerVConsoleLib(lib: new () => VConsole) {
10
- VConsoleLib = lib
11
- }
12
-
13
- /**
14
- * 管理 VConsole 实例
15
- */
16
- declare global {
17
- interface Window {
18
- VConsole: VConsole | null
19
- }
20
- }
21
-
22
- window.VConsole = null
23
-
24
- export function isVConsoleEnabled() {
25
- return localStorage.getItem('vconsole') === '1'
26
- }
27
-
28
- export function detectVConsole() {
29
- if (isVConsoleEnabled()) {
30
- runVConsole()
31
- }
32
- }
33
-
34
- export function enableVConsole() {
35
- localStorage.setItem('vconsole', '1')
36
- runVConsole()
37
- }
38
-
39
- export function disableVConsole() {
40
- localStorage.setItem('vconsole', '0')
41
- destoryVConsole()
42
- }
43
-
44
- export function runVConsole() {
45
- if (window.VConsole !== null) return
46
- if (VConsoleLib === undefined) return void console.warn('尚未传入 VConsole 对象,无法启动')
47
- window.VConsole = new VConsoleLib()
48
- }
49
-
50
- export function destoryVConsole() {
51
- if (!window.VConsole) return
52
- window.VConsole.destroy()
53
- window.VConsole = null
54
- }
@@ -1,4 +0,0 @@
1
- /* eslint-env node */
2
- module.exports = {
3
- extends: [require.resolve('@anjianshi/presets-eslint-node/exclusive.cjs')],
4
- }
@@ -1,33 +0,0 @@
1
- import * as dotenv from 'dotenv'
2
-
3
- /**
4
- * 读取 .env 文件,并获取格式化后的数据
5
- * 注意:依赖 dotenv 包
6
- */
7
- export class EnvReader {
8
- envsFromFile: Record<string, string> = {}
9
-
10
- constructor(readonly envFile: string) {
11
- dotenv.config({
12
- path: this.envFile,
13
- processEnv: this.envsFromFile, // 把从 .env 文件读到的内容写入到此实例的属性,而不是 process.env
14
- })
15
- }
16
-
17
- get(key: string, defaults: string): string
18
- get(key: string, defaults: number): number
19
- get(key: string, defaults: boolean): boolean
20
- get(key: string, defaults: string | number | boolean) {
21
- const value = this.envsFromFile[key] ?? process.env[key]
22
- if (value === undefined) return defaults
23
-
24
- if (typeof defaults === 'number') {
25
- const numValue = parseInt(value, 10)
26
- return isFinite(numValue) ? numValue : defaults
27
- } else if (typeof defaults === 'boolean') {
28
- return ['1', 'true'].includes(value.toLowerCase().trim())
29
- } else {
30
- return value
31
- }
32
- }
33
- }
@@ -1,46 +0,0 @@
1
- /**
2
- * Node 环境下的工具函数
3
- */
4
- import * as fs from 'node:fs'
5
- import * as path from 'node:path'
6
- import { fileURLToPath } from 'node:url'
7
-
8
- /**
9
- * 确认一个路径是否存在且是文件
10
- */
11
- export async function isFileExists(filepath: string) {
12
- try {
13
- const res = await fs.promises.stat(filepath)
14
- return res.isFile()
15
- } catch (e) {
16
- return false
17
- }
18
- }
19
-
20
- /**
21
- * 确认一个路径是否存在且是文件夹
22
- */
23
- export async function isDirectoryExists(dirpath: string) {
24
- try {
25
- const res = await fs.promises.stat(dirpath)
26
- return res.isDirectory()
27
- } catch (e) {
28
- return false
29
- }
30
- }
31
-
32
- /**
33
- * 返回当前文件的绝对路径
34
- * 需要传入当前文件的 ImportMeta 对象(可通过 import.meta 取得)
35
- */
36
- export function getFilePath(fileUrl: string | ImportMeta) {
37
- if (typeof fileUrl !== 'string') fileUrl = fileUrl.url
38
- return fileURLToPath(new URL(fileUrl))
39
- }
40
-
41
- /**
42
- * 返回文件所处目录的绝对路径
43
- */
44
- export function getFileDir(fileUrl: string | ImportMeta) {
45
- return path.dirname(getFilePath(fileUrl))
46
- }
@@ -1,182 +0,0 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
- import chalk from 'chalk'
5
- import dayjs from 'dayjs'
6
- import { type LogInfo, LogLevel, LogHandler, formatters } from '../../logging/index.js'
7
-
8
- /**
9
- * 向 console 输出日志
10
- */
11
- export class ConsoleHandler extends LogHandler {
12
- log(info: LogInfo) {
13
- const { logger, level, args } = info
14
- const method = ConsoleHandler.consoleMethods[level]
15
- const levelColor = ConsoleHandler.levelColors[level]
16
- const levelName = formatters.level(info)
17
- const loggerColor = chalk[ConsoleHandler.getLoggerColor(logger)]
18
- const prefix = [
19
- chalk.white(`[${formatters.time(info)}]`),
20
- levelColor(`[${levelName}]`),
21
- loggerColor(`[${logger}]`),
22
- ].join('')
23
- method(prefix, ...args)
24
- }
25
-
26
- static readonly consoleMethods = {
27
- [LogLevel.Debug]: console.debug.bind(console),
28
- [LogLevel.Info]: console.info.bind(console),
29
- [LogLevel.Warning]: console.warn.bind(console),
30
- [LogLevel.Error]: console.error.bind(console),
31
- }
32
-
33
- static readonly levelColors = {
34
- [LogLevel.Debug]: chalk.whiteBright,
35
- [LogLevel.Info]: chalk.white,
36
- [LogLevel.Warning]: chalk.yellowBright,
37
- [LogLevel.Error]: chalk.redBright,
38
- }
39
-
40
- // 可供 logger 选择的颜色
41
- private static readonly loggerColors = [
42
- 'green',
43
- 'yellow',
44
- 'blue',
45
- 'magenta',
46
- 'cyan',
47
- 'greenBright',
48
- 'yellowBright',
49
- 'blueBright',
50
- 'magentaBright',
51
- 'cyanBright',
52
- ] as const
53
- private static readonly loggerColorMap = new Map<string, (typeof this.loggerColors)[number]>()
54
- static getLoggerColor(logger: string) {
55
- if (!ConsoleHandler.loggerColorMap.has(logger)) {
56
- const color =
57
- ConsoleHandler.loggerColors[
58
- ConsoleHandler.loggerColorMap.size % ConsoleHandler.loggerColors.length
59
- ]!
60
- ConsoleHandler.loggerColorMap.set(logger, color)
61
- }
62
- return ConsoleHandler.loggerColorMap.get(logger)!
63
- }
64
- }
65
-
66
- /**
67
- * 写入文件日志
68
- */
69
- export interface FileHandlerOptions {
70
- dir: string // 日志存放目录 Logs directory
71
- filePrefix: string // 日志文件名前缀 Prefix of the log file name
72
- maxLength: number // 单条日志最长多少字符 Maximum length of a single log message
73
- flushLength: number // 触发写入的缓存字符串数 Length of buffered strings that trigger a write operation
74
- flushInterval: number // 缓存定时写入间隔(单位:ms),为 0 则所有日志立刻写入 Buffered strings write interval, 0 means all logs are written immediately.
75
- }
76
-
77
- export class FileHandler extends LogHandler {
78
- readonly options: FileHandlerOptions
79
-
80
- constructor(options?: Partial<FileHandlerOptions>) {
81
- super()
82
-
83
- const dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))
84
- this.options = {
85
- dir: path.resolve(dirname, 'logs'),
86
- filePrefix: '',
87
- maxLength: 10000,
88
- flushLength: 100000,
89
- flushInterval: 1000,
90
- ...(options ?? {}),
91
- }
92
-
93
- this.initLogDir()
94
- this.initFlush()
95
- }
96
-
97
- // Format log content
98
- log(info: LogInfo) {
99
- const { logger, args } = info
100
- const prefix =
101
- [
102
- `[${formatters.datetime(info)}]`,
103
- `[${formatters.level(info)}]`,
104
- logger ? `[${logger}]` : '',
105
- ].join('') + ' '
106
-
107
- const itemStrings: string[] = []
108
- let totalLength = prefix.length
109
- for (const item of args) {
110
- const itemString = this.stringifyDataItem(item)
111
-
112
- // 截断过长的日志内容 Truncate overly long log messages
113
- if (totalLength + itemString.length < this.options.maxLength) {
114
- itemStrings.push((totalLength === prefix.length ? '' : ' ') + itemString)
115
- totalLength += itemString.length
116
- } else {
117
- itemStrings.push(
118
- itemString.slice(0, this.options.maxLength - totalLength) + ' [too long, sliced]'
119
- )
120
- break
121
- }
122
- }
123
-
124
- this.pushBuffer(prefix, ...itemStrings, '\n')
125
- }
126
-
127
- protected stringifyDataItem(item: unknown) {
128
- if (typeof item === 'string') return item
129
- if (item instanceof Error) return item.stack ?? String(item)
130
- try {
131
- const json = JSON.stringify(item) as string | undefined
132
- return json ?? String(json)
133
- } catch (e: unknown) {
134
- return String(item)
135
- }
136
- }
137
-
138
- // Handle buffer
139
- private buffer: string[] = []
140
- private bufferSize = 0
141
-
142
- protected pushBuffer(...strings: string[]) {
143
- this.buffer.push(...strings)
144
- this.bufferSize = strings.reduce((sum, v) => sum + v.length, this.bufferSize)
145
- if (this.options.flushInterval === 0 || this.bufferSize >= this.options.flushLength)
146
- this.flush()
147
- }
148
-
149
- protected flush() {
150
- if (this.buffer.length) {
151
- const content = this.buffer.join('')
152
- this.buffer = []
153
- this.bufferSize = 0
154
- this.write(content)
155
- }
156
- }
157
-
158
- protected initFlush() {
159
- if (this.options.flushInterval !== 0) {
160
- setInterval(() => this.flush(), this.options.flushInterval)
161
- }
162
- }
163
-
164
- // 文件系统交互 File system interaction
165
- get filepath() {
166
- const { dir, filePrefix } = this.options
167
- return path.join(
168
- dir,
169
- `${filePrefix ? `${filePrefix}-` : ''}${dayjs().format('YYYY-MM-DD')}.log`
170
- )
171
- }
172
-
173
- protected initLogDir() {
174
- if (!fs.existsSync(this.options.dir)) fs.mkdirSync(this.options.dir)
175
- }
176
-
177
- protected write(content: string) {
178
- fs.appendFile(this.filepath, content, error => {
179
- if (error) console.error('[logger] write failed: ' + String(error))
180
- })
181
- }
182
- }
@@ -1,16 +0,0 @@
1
- /**
2
- * 针对 Node.js 环境定制 logging
3
- * 注意:使用此模块需要 chalk 依赖
4
- */
5
- import { logger as defaultLogger, type Logger } from '../../logging/index.js'
6
- import { ConsoleHandler } from './handlers.js'
7
-
8
- export * from './handlers.js'
9
- export * from '../../logging/index.js'
10
-
11
- /**
12
- * 预设的初始化行为
13
- */
14
- export function initLogger(logger: Logger = defaultLogger) {
15
- logger.addHandler(new ConsoleHandler())
16
- }
@@ -1,54 +0,0 @@
1
- import {
2
- AbstractLogger,
3
- type LogLevel as TypeORMLogLevel,
4
- type LogMessage,
5
- type LoggerOptions,
6
- } from 'typeorm'
7
- import { type Logger as UtilsLogger } from '../logging/index.js'
8
-
9
- /**
10
- * 把 TypeORM 的日志导入到 js-utils 的 logger 中
11
- * 用法参考 https://typeorm.io/logging
12
- */
13
- export class AdaptedTypeORMLogger extends AbstractLogger {
14
- constructor(readonly utilsLogger: UtilsLogger, options?: LoggerOptions) {
15
- super(options)
16
- }
17
-
18
- protected writeLog(typeORMLevel: TypeORMLogLevel, logMessage: LogMessage | LogMessage[]) {
19
- const messages = this.prepareLogMessages(logMessage, {
20
- highlightSql: true,
21
- })
22
-
23
- for (const message of messages) {
24
- const args = message.prefix ?? '' ? [message.prefix, message.message] : [message.message]
25
-
26
- switch (message.type ?? typeORMLevel) {
27
- case 'log':
28
- case 'schema':
29
- case 'schema-build':
30
- case 'migration':
31
- this.utilsLogger.debug(...args)
32
- break
33
-
34
- case 'info':
35
- case 'query':
36
- this.utilsLogger.info(...args)
37
- break
38
-
39
- case 'warn':
40
- case 'query-slow':
41
- this.utilsLogger.warn(...args)
42
- break
43
-
44
- case 'error':
45
- case 'query-error':
46
- this.utilsLogger.error(...args)
47
- break
48
-
49
- default:
50
- this.utilsLogger.debug(...args)
51
- }
52
- }
53
- }
54
- }