@arkstack/common
Arkstack框架生态系统的核心公用设施、原始设施和共享基础设施。 这个软件包提供了所有Arkstack软件包中使用的构件——错误处理,记录,散列,加密,生命周期管理,钩子等.
目录
安装
pnpm add @arkstack/common
模块
系统
src/system.ts
为环境变量访问、配置加载、路径解析和动态文件导入提供核心应用程序级公用事业.
env(key, defaultValue?)
从 process.env 有自动类型的强制。 布尔( B)true, (中文(简体) ), false, (中文(简体) ), on, (中文(简体) ), off),数字, null,空字符串都处理得优雅.
import { env } from '@arkstack/common'
const port = env('PORT', 3000) // number
const debug = env('DEBUG', false) // boolean
const name = env('APP_NAME', 'App') // string
类型强制规则:
| 原始值 | 已解析类型 |
|---|---|
"true" 编号 : "on" | true |
"false" 编号 : "off" | false |
| 数字字符串 | number |
"null" | null |
"" | undefined (倒计时) defaultValue(中文(简体) ) |
config(key?, defaultValue?)
从构建输出中装入并合并所有配置文件 config/ 目录。 支持 dot-path 密钥访问, 并带有完整的 TypeScript 推论.
import { config } from '@arkstack/common'
// Get the full config object
const allConfig = config()
// Access a nested key
const dbHost = config('database.host', 'localhost')
配置文件从解析后加载 outputDir() 使用 createRequire在 CLI 语境中运行时会跳过中件配置文件 .
appUrl(link?)
构建一个完全合格的应用程序 URL APP_URL 财务报告和审定财务报表 PORT 环境变量.
import { appUrl } from '@arkstack/common'
appUrl() // "http://localhost:3000"
appUrl('/api/health') // "http://localhost:3000/api/health"
nodeEnv()
回返 'dev' 或者说 'prod' 基于 NODE_ENV 环境变量。 默认为 'dev' 对于任何未识别值.
import { nodeEnv } from '@arkstack/common'
nodeEnv() // "dev" | "prod"
outputDir(cwd?)
确定构建输出目录。 在开发中,默认为 .arkstack/build;在生产中,改为 dist两者都可以通过环境变量被推翻:
| 变量 | A. 背景情况 | 默认 |
|---|---|---|
OUTPUT_DIR_DEV | 发展 | .arkstack/build |
OUTPUT_DIR | 生产 | dist |
importFile<T>(filePath)
动态导入文件使用 贾蒂,带有类型脚本和 tsconfig 路径支持。 用于在运行时装入用户定义的配置文件或插件文件 .
import { importFile } from '@arkstack/common'
const module = await importFile<{ default: MyConfig }>('./config/app.ts')
锁定器
src/Logger.ts
一个有结构的,有粉笔功率控制控制台的收录器,两栏格式化,并可堆放解析 API.
基本对数水平
import { Logger } from '@arkstack/common'
Logger.success('Server started')
Logger.info('Listening on port 3000')
Logger.warn('Deprecated option used')
Logger.error('Something went wrong', false) // false = don't exit
Logger.debug('Internal state dump') // only shown at verbosity >= 3
每个关卡都使用不同的图标和颜色:
| 方法 | 图标 | 颜色 |
|---|---|---|
success | ✓ | 绿色 |
info | ℹ | 蓝色 |
warn | ⚠ | 黄色 |
error | ✖ | 红色 |
debug | 🐛 | 灰色 |
所有级别方法的第二个论据是 exit (boolean) (布尔语). 何时 true,该流程在日志后退出. error 默认退出( e)exit = true; 所有其他默认为 false。 。 。 .
Logger.configure(options)
设定了全球动词和压制行为.
Logger.configure({
verbosity: 3, // enables debug output
quiet: true, // suppresses info and success
silent: true, // suppresses all output
})
Logger.twoColumnDetail(name, value, log?, spacer?)
将一个右向的双柱布局加到终端宽度上.
Logger.twoColumnDetail('Route', 'GET /api/users')
// "Route ......................................... GET /api/users"
const row = Logger.twoColumnDetail('Route', 'GET /api/users', false)
// returns [name, dots, value] without printing
Logger.describe(name, desc, width?, log?)
类似 twoColumnDetail,但使用固定宽度的空间垫而不是点。 用于命令帮助列表 .
Logger.describe('--port', 'Port to listen on', 40)
// "--port Port to listen on"
Logger.split(name, value, status?, exit?, preserveCol?, spacer?)
喜欢 twoColumnDetail,但根据 status。 。 。 .
Logger.split('Database', 'Connected', 'success')
Logger.split('Migration', 'Failed', 'error', true) // exits after logging
Logger.parse(config, joiner?, log?, sc?)
从 a 组成一个有样式的字符串 [text, chalkStyle] 组合阵列.
Logger.parse(
[
['Arkstack', 'bold'],
['v1.0.0', 'gray'],
],
' ',
) // "Arkstack v1.0.0"
// Return instead of print
const str = Logger.parse([['Ready', 'green']], ' ', false)
Logger.log(config, joiner?, log?, sc?)
接受字符串+样式的灵活多态记录器,或 LoggerParseSignature 阵列。 返回 Logger 上课时没有争论.
Logger.log('PORT:3000', 'cyan')
Logger.log(
[
['PORT', 'bold'],
['3000', 'cyan'],
],
' ',
)
Logger.chalker(styles[])
返回一个对任意输入应用一串粉笔样式的函数.
const highlight = Logger.chalker(['bold', 'green'])
console.log(highlight('Ready'))
Logger.console()
回返a Console- 对象与 log, (中文(简体) ), debug, (中文(简体) ), warn, (中文(简体) ), info,以及 error 方法。 。 。 可用于替换 globalThis.console。 。 。 .
const console = Logger.console()
console.log('hello')
console.warn('watch out')
错误汉德勒
src/ErrorHandler.ts
用于常态化,序列化,分级化,和记录错误的静态通用类. 与 皮诺 用于持续错误文件记录.
ErrorHandler.createErrorPayload(err, fallbackMessage?)
初一法门. 将任意抛出值转换为相容 ArkstackErrorPayload 对象,处理验证错误,模型未发现错误,以及通用错误.
import { ErrorHandler } from '@arkstack/common'
try {
// ...
} catch (err) {
const payload = ErrorHandler.createErrorPayload(err, 'Request failed')
// { status: 'error', code: 422, message: '...', errors: {...} }
}
有效载荷形状 :
{
status: 'error'
code: number // HTTP status code (100–599)
message: string
errors?: unknown // present for validation errors
stack?: string // present in development unless HIDE_ERROR_STACK is set
}
分类逻辑 :
| 错误类型 | code | errors 人口数 |
|---|---|---|
校验错误( 有) .errors(中文(简体) ) | statusCode 编号 : status 或者说 422 | 对 |
未找到模型( 有) .getModelName()(中文(简体) ) | 404 | 没有 |
| 常规错误 | statusCode 编号 : status 或者说 500 | 堆叠跟踪为对象 |
ErrorHandler.serializeError(value, seen?)
递归序列化任何值——包括 Error 实例和循环引用——输入一个简单的JSON安全对象。 通知引用改为 '[Circular]'。 。 。 .
const serialized = ErrorHandler.serializeError(new Error('oops'))
// { name: 'Error', message: 'oops', stack: '...' }
ErrorHandler.normalizeStatusCode(value, fallback?)
确保状态代码是区域内的有效整数 100–599返回倒计时(默认) 500用于任何无效的东西.
ErrorHandler.normalizeStatusCode('422') // 422
ErrorHandler.normalizeStatusCode('xyz') // 500
ErrorHandler.getErrorLogger()
返回写到的 Pino 日志实例 storage/logs/error.log (自动创建). 实例按目的地路径缓存 .
ErrorHandler.logUnhandledError(err, request, message)
将一个未处理的错误延续到错误日志文件,包括序列错误和相关的请求上下文.
ErrorHandler.logUnhandledError(err, { method: 'GET', url: '/api' }, 'Unhandled exception')
分类助手
ErrorHandler.isValidationError(err) // true if err.errors is defined
ErrorHandler.isModelNotFoundError(err) // true if err.getModelName is a function
ErrorHandler.shouldLogError(err) // false for validation/model-not-found errors
ErrorHandler.shouldHideStack() // true if HIDE_ERROR_STACK env is set
ErrorHandler.getPrimaryError(err) // unwraps err.cause if present
ErrorHandler.toErrorShape(value) // casts unknown to ArkstackErrorShape if object
为了方便起见,所有静态方法也作为命名的独立函数被导出:
import {
createErrorPayload,
isValidationError,
serializeError,
logUnhandledError,
// ...
} from '@arkstack/common'
例外
src/Exceptions/
结构化错误投掷的三级例外等级 .
Exception
基类扩展 Error设置 .name 改为 'Exception'。 。 。 .
import { Exception } from '@arkstack/common'
throw new Exception('Something went wrong')
AppException
扩展 Exception添加 statusCode (默认) 400)和可选 errors 用于实地级验证错误的地图.
import { AppException } from '@arkstack/common'
const err = new AppException('Validation failed', 422)
err.errors = { email: ['Email is required'] }
RequestException
扩展 AppException。用于 HTTP 请求级别错误。 提供两个静态断言助手:
RequestException.assertNotEmpty(value, message, code?)
丢出一个 RequestException 如果值为 null 或者说 undefined缩小成功类型.
import { RequestException } from '@arkstack/common'
const user = await User.find(id)
RequestException.assertNotEmpty(user, 'User not found', 404)
// user is now User (not null | undefined)
RequestException.abortIf(condition, message, code?)
如果情况属实就扔出.
RequestException.abortIf(!user.isActive, 'Account is suspended', 403)
钩子
src/Hook.ts
再出口 Hook 从 @arkstack/foundry;两种进口都是相同的全过程登记册。 见 @arkstack/foundry 读取 用于API(on, (中文(简体) ), emit, (中文(简体) ), get, (中文(简体) ), has, (中文(简体) ), off, (中文(简体) ), all, (中文(简体) ), clear) (中文(简体) ). 通过扩展宣告已输入的钩子名称 HookRegistry 打开 @arkstack/foundry。 。 。 .
加密
src/utils/encryption.ts
一个薄薄的包裹 @arkstack/encryption键。 AES-256-GCM用于敏感值(如:两因子认证机密);下面的软件包运行在Web Crypto API上,因此服务器上加密的值可以在浏览器中解密,反之亦然.
Encryption.encrypt(value, key?)
加密字符串。 返回受冒号限制的碱基64url字符串 : <iv>:<authTag>:<ciphertext>。 。 。 .
import { Encryption } from '@arkstack/common'
const token = Encryption.encrypt('my-secret-value')
// "abc123:def456:ghi789"
Encryption.decrypt(payload, key?)
解密由下列人员生产的有效载荷: encrypt。如果格式无效或密钥出错,则丢出.
const original = Encryption.decrypt(token)
// "my-secret-value"
Encryption.encryptAsync(value, key?, options?) 编号 : Encryption.decryptAsync(payload, key?, options?)
在Web Crypto路径上相同的操作,采用相同的有效载荷格式,因此两者可以自由混合. 当调用代码与浏览器共享(或可能成为)时使用这些. options.aad 将额外认证数据绑入密码文本.
Encryption.cipher(key?)
A 类 Cipher 绑定在应用程序密钥上,用于加密许多值而不重新定义密钥,并通过 encryptBytes 编号 : decryptBytes。 。 。 .
主要公用事业
Encryption.generateKey() // random base64url key
await Encryption.generateKeyPair() // { publicKey, privateKey } ECDH identity
await Encryption.deriveKey(password) // PBKDF2 → { key, salt, iterations }
await Encryption.compareKeys(left, right) // constant time
await Encryption.fingerprint() // displayable digest of the app key
端到端加密
const channel = await Encryption.channel(myPrivateKey, peerPublicKey)
await channel.decrypt(await channel.encrypt('hey'))
await Encryption.seal('anonymous tip', peerPublicKey)
await Encryption.open(payload, myPrivateKey)
await Encryption.safetyNumber(myPublicKey, peerPublicKey)
全数 @arkstack/encryption 水面- Cipher, (中文(简体) ), Codec, (中文(简体) ), EncryptionKey, (中文(简体) ), KeyPair, (中文(简体) ), Keys, (中文(简体) ), SealedBox, (中文(简体) ), SecureChannel, (中文(简体) ), NodeCipher ——从这批货再出口.
环境变量:
| 变量 | 需求 | 说明 |
|---|---|---|
APP_KEY | 对 | 原始密钥;通过SHA-256向内部256位密钥散列 |
生成一个 ark key:generate遗产 TWO_FACTOR_ENCRYPTION_KEY 仍然受到尊重,当 APP_KEY 未设置.
散开
src/utils/hash.ts
密码散列和检察官办公室发电设施.
Hash.make(value)
Hashes 使用 bcrypt 的字符串,盐因子为 10.
import { Hash } from '@arkstack/common'
const hashed = await Hash.make('user-password')
Hash.verify(value, hashedValue)
比较纯文本值和 bcrypt 散列值.
const isValid = await Hash.verify('user-password', hashed)
Hash.otp(digits?, label?, period?)
使用 otpauth 图书馆 SHA1 和一个静态的秘密。 适合简单的基于时间的检察官办公室流动.
const totp = Hash.otp(6, '[email protected]', 60)
const token = totp.generate()
Hash.totp(secret, label, issuer?, period?)
从一个已编码的 Base32 密钥创建 TOTP 实例 。 用于用户专用的TOTP(如认证器应用集成).
const totp = Hash.totp(user.totpSecret, user.email)
const isValid = totp.validate({ token: userInput }) !== null
网络
src/network.ts
启动 HTTP 服务器、 自动端口检测和渲染出错视图的工具 .
bootWithDetectedPort(boot, preferredPort?, app?)
检测是否可用的首选端口( 使用 ) detect-port),并在第一个自由端口上为服务器提供靴子. 添加关键全局 : env, (中文(简体) ), config, (中文(简体) ), str, (中文(简体) ), app,以及 arkctx。 。 。 .
import { bootWithDetectedPort } from '@arkstack/common'
await bootWithDetectedPort(
async (port) => {
server.listen(port)
Logger.success(`Server:http://localhost:${port}`)
},
3000,
appInstance,
)
全局设置 :
| 全球 | 数值 |
|---|---|
globalThis.app | () => app |
globalThis.env | env |
globalThis.config | config |
globalThis.str | str (从) @h3ravel/support(中文(简体) ) |
globalThis.arkctx | { runtime: 'HTTP' } |
renderError({ message, stack, title, code })
使用 ~arkstack/common.error 视图模板。 倒从内置状态代码地图回落为人可读标题.
import { renderError } from '@arkstack/common'
const html = renderError({ code: 404, message: 'Page not found' })
内建状态头衔 : 400, (中文(简体) ), 401, (中文(简体) ), 403, (中文(简体) ), 404, (中文(简体) ), 500, (中文(简体) ), 502, (中文(简体) ), 503, (中文(简体) ), 504。 。 。 .
生命周期
src/lifecycle.ts
bindGracefulShutdown(shutdown)
注册清理召回 SIGINT, (中文(简体) ), SIGTERM,以及 SIGQUIT 信号,确保应用程序干净关闭.
import { bindGracefulShutdown } from '@arkstack/common'
bindGracefulShutdown(async () => {
await db.disconnect()
Logger.info('Server shut down gracefully')
})
原型
src/prototypes.ts
loadPrototypes()
扩展 String.prototype 使用四种实用方法。 在应用拖带时调用它一次 .
import { loadPrototypes } from '@arkstack/common'
loadPrototypes()
增加的方法:
| 方法 | 说明 | 示例 |
|---|---|---|
.titleCase() | 转换为标题大小写( 处理) _ 财务报告和审定财务报表 -(中文(简体) ) | "hello_world".titleCase() → "Hello World" |
.camelCase() | 转换为骆驼Case | "Hello World".camelCase() → "helloWorld" |
.pascalCase() | 转换为 PascalCase | "hello world".pascalCase() → "HelloWorld" |
.truncate(len, suffix?) | 在字段边界上断线 | "Hello World".truncate(7) → "Hello..." |
全球增强
src/app.d.ts
何时 loadPrototypes() 名为 bootWithDetectedPort() 将运行时间、以下全局和字符串原型扩展设置在应用程序中 :
// Globals (set by bootWithDetectedPort)
globalThis.env // GlobalEnv — typed env() accessor
globalThis.config // GlobalConfig — typed config() accessor
// String prototype extensions (set by loadPrototypes)
'my_string'.titleCase()
'my_string'.camelCase()
'my_string'.pascalCase()
'my long string'.truncate(10, '…')
类型
src/types.ts
从包中导出密钥类型 :
| 类型 | 说明 |
|---|---|
GlobalEnv | 打印的签名 env() 函数 |
GlobalConfig | 打印的签名 config() 带有点路径支持的函数 |
ArkstackErrorShape | 跨框架常见错误属性的结合(statusCode, (中文(简体) ), status, (中文(简体) ), errors, (中文(简体) ), cause页:1 |
ArkstackErrorPayload | 由 : ErrorHandler |
LoggerChalk | Chark 样式标识符被接受 Logger 方法 |
LoggerParseSignature | 矩阵 [string, LoggerChalk] 用于 Logger.parse() |
LoggerLog | 超载函数类型 Logger.log() |
环境变量参考
| 变量 | 模块 | 需求 | 说明 |
|---|---|---|---|
PORT | system, (中文(简体) ), network | 没有 | HTTP 服务器端口( 默认 ): 3000(中文(简体) ) |
APP_URL | system | 没有 | 基础应用程序 URL |
APP_NAME | hash | 没有 | 用作 TOTP 发行器的应用程序名称 |
NODE_ENV | system | 没有 | development 或者说 production |
OUTPUT_DIR | system | 没有 | 生产构建输出目录( 默认 ): dist(中文(简体) ) |
OUTPUT_DIR_DEV | system | 没有 | 开发构建输出目录( 默认值 : .arkstack/build(中文(简体) ) |
TWO_FACTOR_ENCRYPTION_KEY | encryption | 对* | AES-256-GCM 加密的秘密密钥( * 仅在使用时需要) Encryption(中文(简体) ) |
HIDE_ERROR_STACK | ErrorHandler | 没有 | 设定为 true, (中文(简体) ), 1,或 on 以压制错误有效载荷中的堆栈痕迹 |
帮助者
src/utils/helpers.ts
perPage(query, defaults?)
从查询对象中提取安全页标限制。 粘接结果介于 1 财务报告和审定财务报表 50 或配置的默认值 maxPerPage,默认为 25 或配置的默认值 perPage。 。 。 .
import { perPage } from '@arkstack/common'
const limit = perPage({ limit: 100 }) // 50 (clamped)
const limit2 = perPage({ limit: 100 }, { maxPerPage: 100 }) // 100
const limit3 = perPage({}) // 25 (default)
const limit3 = perPage({}, { perPage: 100 }) // 100
resolvePagination(query, defaults?)
从查询对象中提取当前页面和安全页码限制。 粘接结果 perPage 介于 1 财务报告和审定财务报表 50 或配置的默认值 maxPerPage,默认为 25 或配置的默认值 perPage。 。 。 .
import { resolvePagination } from '@arkstack/common'
const limit = resolvePagination({ limit: 100 }) // {perPage: 50, page: 1} (clamped)
const limi2 = resolvePagination({ limit: 100 }, { maxPerPage: 100 }) // {perPage: 100, page: 1}
const limit3 = resolvePagination({}) // {perPage: 50, page: 1} (default)
const limit4 = resolvePagination({}, { perPage: 100, page: 2 }) // {perPage: 100, page: 2}
getModel(modelName)
动态地从配置的模型目录中以名称导入应用程序模型( 默认 ): ./src/app/models) (中文(简体) ). 增加支助 ModelRegistry 用于类型安全检查.
import { getModel } from '@arkstack/common'
const User = await getModel('User')
const users = await User.findAll()
// With type augmentation:
declare module '@arkstack/common' {
interface ModelRegistry {
User: typeof User
}
}
const TypedUser = await getModel('User') // typeof User