urgot
Advanced tools
Comparing version 0.5.7 to 1.0.5
#!/usr/bin/env node | ||
import Path from 'path'; | ||
const resolve = (...args) => Path.resolve(process.env.INIT_CWD, ...args); | ||
const [command, target] = process.argv.slice(2); | ||
if (command === '-v') { | ||
console.log('1.0.0'); | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
import url from 'url'; | ||
import childProcess from 'child_process'; | ||
import options, { files } from './options.js'; | ||
const resolve = (...args) => path.resolve(process.cwd(), ...args); | ||
class Base { | ||
static init(name) { | ||
options.name = name ?? 'MyApp'; | ||
const packagePath = path.resolve(url.fileURLToPath(import.meta.url), '../../../package.json'); | ||
const packageJSON = JSON.parse(fs.readFileSync(packagePath).toString()); | ||
options.dependencies.urgot = `^${packageJSON.version}`; | ||
fs.writeFileSync(resolve('package.json'), JSON.stringify(options, null, 2)); | ||
const create = (root, value) => { | ||
for (const key in value) { | ||
const item = value[key]; | ||
try { | ||
if (item === null) { | ||
fs.mkdirSync(resolve(root, key)); | ||
} | ||
if (typeof item === 'string') { | ||
fs.writeFileSync(resolve(root, key), item); | ||
continue; | ||
} | ||
if (typeof item === 'object') { | ||
create(resolve(root, key), item); | ||
} | ||
} | ||
catch (error) { } | ||
} | ||
}; | ||
create(resolve(), files); | ||
try { | ||
childProcess.execSync('pnpm i', { cwd: resolve() }); | ||
} | ||
catch (error) { | ||
childProcess.execSync('npm i', { cwd: resolve() }); | ||
} | ||
console.log('[urgot]', 'completed'); | ||
} | ||
static dev() { | ||
childProcess.exec('tsc -w', { cwd: resolve(), windowsHide: true }); | ||
childProcess.execSync('node --experimental-loader urgot/system/loader dist/main.js', { cwd: resolve() }); | ||
} | ||
} | ||
{ | ||
const [command, target] = process.argv.slice(2); | ||
const name = command; | ||
if (name !== 'prototype' && typeof Base[name] === 'function') { | ||
Base[name](target); | ||
} | ||
} |
@@ -1,36 +0,29 @@ | ||
import Context from '../context.js'; | ||
import AppContext from './appContext.js'; | ||
export interface AppCallback { | ||
onRequest?: (context: AppContext) => Promise<void> | void; | ||
onResponse?: (context: AppContext, value: unknown) => Promise<void> | void; | ||
onError?: (context: AppContext, value: unknown) => Promise<void> | void; | ||
import Context from './context.js'; | ||
export declare class App { | ||
protected context: Context; | ||
/** | ||
* 实例化后触发 | ||
**/ | ||
onCreate(): Promise<void>; | ||
constructor(context: Context); | ||
} | ||
export declare type AppOptions = { | ||
path?: { | ||
include?: string; | ||
app?: string; | ||
}; | ||
fileFilter?: { | ||
dir?: string[]; | ||
ext?: string[]; | ||
}; | ||
session?: { | ||
action?: { | ||
get(key: string): Promise<string | null>; | ||
setex(key: string, seconds: number, value: string): Promise<'OK'>; | ||
expire(key: string, seconds: number): Promise<number>; | ||
del(key: string): Promise<number>; | ||
}; | ||
expires?: number; | ||
cookieName?: string; | ||
cookie?: () => { | ||
domain?: string; | ||
path?: string; | ||
expires?: string; | ||
maxAge?: number; | ||
httpOnly?: boolean; | ||
}; | ||
}; | ||
}; | ||
declare const _default: (calls?: AppCallback, options?: AppOptions) => Promise<(context: Context, next: () => Promise<void>) => Promise<void>>; | ||
interface CreateApp { | ||
/** | ||
* 发生错误时触发 | ||
**/ | ||
onError?: (error: unknown, context: Context) => unknown; | ||
/** | ||
* 请求时触发(在实例化应用类之前) | ||
**/ | ||
onRequest?: (context: Context) => unknown; | ||
/** | ||
* 应用响应时触发(路由方法返回值时触发) | ||
**/ | ||
onResponse?: (value: unknown, context: Context) => unknown; | ||
/** | ||
* 执行结束销毁时触发(无论是否抛出错误始终触发) | ||
**/ | ||
onDestroy?: (context: Context) => unknown; | ||
} | ||
declare const _default: (apps: (typeof App)[], options?: CreateApp) => (context: Context, next: Function) => Promise<void>; | ||
export default _default; |
@@ -1,110 +0,65 @@ | ||
import * as Uuid from 'uuid'; | ||
import AppLoader from './appLoader.js'; | ||
import { JSONImprove } from '../util.js'; | ||
export default async (calls = {}, options = {}) => { | ||
const routes = await AppLoader(options?.path?.include || './lib', options?.path?.app || './lib/app', options.fileFilter || {}); | ||
const session = { | ||
name: options.session?.cookieName || 'sessionKey', | ||
expires: options.session?.expires || 60 * 60 * 24 * 30, | ||
action: options.session?.action, | ||
cookie: options.session?.cookie | ||
}; | ||
const handler = async (context, apps) => { | ||
const appContext = context; | ||
for (const app of apps) { | ||
try { | ||
let state; | ||
let sessionkey = String(context.request.cookies[session.name]); | ||
appContext.session = { | ||
key: sessionkey, | ||
get state() { | ||
if (state) | ||
return state; | ||
return state = new Promise(async (res, rej) => { | ||
if (!session.action) | ||
return rej(new Error('Did not implement session action.get method')); | ||
const data = await session.action.get(appContext.session.key); | ||
if (!data) | ||
return res({}); | ||
res(JSONImprove.parse(data)); | ||
}); | ||
}, | ||
async setState() { | ||
if (!session.action) | ||
throw new Error('Did not implement session action.setex method'); | ||
if (!state) | ||
return; | ||
const val = await state; | ||
await session.action.setex(appContext.session.key, session.expires, JSONImprove.stringify(val)); | ||
if (context.request.cookies[session.name] !== appContext.session.key) { | ||
context.cookies[session.name] = { ...session.cookie?.(), value: appContext.session.key }; | ||
} | ||
}, | ||
async destroy() { | ||
if (!session.action) | ||
throw new Error('Did not implement session action.del method'); | ||
await session.action.del(appContext.session.key); | ||
}, | ||
async refreshExpires() { | ||
if (!session.action) | ||
throw new Error('Did not implement session action.expire method'); | ||
await session.action.expire(appContext.session.key, session.expires); | ||
} | ||
}; | ||
if (!Uuid.validate(sessionkey)) { | ||
const uuid = Uuid.v4(); | ||
sessionkey = uuid; | ||
appContext.session.key = uuid; | ||
export class App { | ||
context; | ||
/** | ||
* 实例化后触发 | ||
**/ | ||
async onCreate() { } | ||
constructor(context) { | ||
this.context = context; | ||
} | ||
} | ||
export default (apps, options) => { | ||
const appRoutes = {}; | ||
for (const item of apps) { | ||
const app = item; | ||
for (const prefix of app.__$routes ?? ['']) { | ||
for (const methodName of Object.getOwnPropertyNames(app.prototype)) { | ||
if (methodName === 'constructor') | ||
continue; | ||
const method = app.prototype[methodName]; | ||
if (typeof method !== 'function' || !method.__$routes) | ||
continue; | ||
for (const httpObject of method.__$routes) { | ||
const route = `${prefix}${httpObject.route === '' && prefix === '' ? '/' : httpObject.route}`; | ||
if (!appRoutes[route]) | ||
appRoutes[route] = {}; | ||
if (appRoutes[route][httpObject.method]) | ||
console.warn('[urgot]', '路由定义重复', `${httpObject.method} ${route}`); | ||
appRoutes[route][httpObject.method] = { instance: app, func: methodName }; | ||
} | ||
const object = new app(appContext); | ||
if (typeof object.onCreate === 'function') | ||
await object.onCreate(); | ||
try { | ||
if (typeof object.all === 'function') { | ||
const all = await object.all(); | ||
if (all !== undefined) { | ||
calls.onResponse && await calls.onResponse(appContext, all); | ||
break; | ||
} | ||
} | ||
const method = object[appContext.method.toLocaleLowerCase()]; | ||
if (typeof method === 'function') { | ||
const methdsObj = method; | ||
const args = methdsObj.validat ? await methdsObj.validat(appContext) : []; | ||
const called = await methdsObj.apply(object, args); | ||
if (called !== undefined) { | ||
calls.onResponse && await calls.onResponse(appContext, called); | ||
break; | ||
} | ||
} | ||
} | ||
catch (error) { | ||
if (calls.onError) | ||
await calls.onError(appContext, error); | ||
break; | ||
} | ||
finally { | ||
if (typeof object.onDestroy === 'function') | ||
await object.onDestroy(); | ||
if (sessionkey !== appContext.session.key) { | ||
if (state !== undefined && session.action) | ||
await session.action.del(sessionkey); | ||
context.cookies[session.name] = { ...session.cookie?.(), value: appContext.session.key }; | ||
} | ||
} | ||
} | ||
catch (error) { | ||
if (calls.onError) | ||
await calls.onError(appContext, error); | ||
break; | ||
} | ||
} | ||
}; | ||
} | ||
return async (context, next) => { | ||
await next(); | ||
const pathname = context.url.pathname; | ||
if (!context.method || !routes[pathname]) | ||
return; | ||
await handler(context, routes[pathname]); | ||
try { | ||
const route = appRoutes[context.url.pathname]; | ||
if (!route) | ||
return; | ||
const method = context.method?.toLocaleUpperCase(); | ||
if (!method || !route[method]) | ||
return; | ||
await options?.onRequest?.(context); | ||
const obj = route[method]; | ||
const app = new obj.instance(context); | ||
await app.onCreate?.(); | ||
const func = app[obj.func]; | ||
for (const item of func.__$uses ?? []) | ||
await item(context); | ||
const args = []; | ||
if (func.__$paramers) { | ||
const params = new func.__$paramers(context); | ||
await params.onCreate?.(); | ||
args.push(params); | ||
} | ||
const out = await func.apply(app, args); | ||
await options?.onResponse?.(out, context); | ||
} | ||
catch (error) { | ||
await options?.onError?.(error, context); | ||
} | ||
finally { | ||
await options?.onDestroy?.(context); | ||
} | ||
}; | ||
}; |
@@ -1,20 +0,21 @@ | ||
import Context, { File } from './context.js'; | ||
import AppContext, { App } from './core/appContext.js'; | ||
import Server from './server.js'; | ||
import { JSONImprove } from './util.js'; | ||
import Server from './core/server.js'; | ||
import Context, { File } from './core/context.js'; | ||
import * as utils from './utils.js'; | ||
import { App } from './core/app.js'; | ||
declare type Methods = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'TRACE'; | ||
declare type RouterDecorator = (target: unknown) => void; | ||
export declare const Router: (path: string | string[]) => RouterDecorator; | ||
declare type ValidatorCallback<T> = (context: AppContext) => T | Promise<T>; | ||
declare type ValidatorDecorator<T extends readonly unknown[]> = (target: unknown, key: string, desc: { | ||
value?: { | ||
(...args: T): unknown; | ||
}; | ||
}) => void; | ||
export declare const Validator: <T extends readonly unknown[]>(func: ValidatorCallback<T>) => ValidatorDecorator<T>; | ||
declare type ParamsDecorator<T> = (target: unknown, key: string, desc: { | ||
declare type RouterMethodDecorator = (target: unknown, key: string, desc: PropertyDescriptor) => void; | ||
declare type Router = { | ||
(method: Methods, path?: string): RouterMethodDecorator; | ||
(prefix: string): RouterDecorator; | ||
}; | ||
declare const Router: Router; | ||
declare type Use = (target: unknown, key: string, desc: PropertyDescriptor) => void; | ||
declare const Use: (...hooks: ((context: Context) => unknown)[]) => Use; | ||
declare type Paramer<T> = (target: unknown, key: string, desc: { | ||
value?: (params: T) => void; | ||
}) => void; | ||
export declare const Params: <T extends { | ||
onStart: (context: AppContext) => Promise<void>; | ||
}>(dto: new () => T) => ParamsDecorator<T>; | ||
export { Server, App, Context, AppContext, File, JSONImprove }; | ||
declare const Paramer: <T extends { | ||
onCreate: () => unknown; | ||
}>(paramsConstructor: new (context: Context) => T) => Paramer<T>; | ||
export { Server, App, Context, File, Use, Router, Paramer, utils }; |
@@ -1,27 +0,32 @@ | ||
import Context, { File } from './context.js'; | ||
import { App } from './core/appContext.js'; | ||
import Server from './server.js'; | ||
import { JSONImprove } from './util.js'; | ||
export const Router = (path) => { | ||
return (target) => { | ||
const app = target; | ||
app.routes = Array.isArray(path) ? path : [path]; | ||
import Server from './core/server.js'; | ||
import Context, { File } from './core/context.js'; | ||
import * as utils from './utils.js'; | ||
import { App } from './core/app.js'; | ||
const Router = (prefix, path) => { | ||
return (target, key, desc) => { | ||
if (!key) { | ||
const newTarget = target; | ||
if (!newTarget.__$routes) | ||
newTarget.__$routes = []; | ||
newTarget.__$routes.push(prefix); | ||
return; | ||
} | ||
const func = desc?.value; | ||
if (!func.__$routes) | ||
func.__$routes = []; | ||
func.__$routes.push({ method: prefix, route: path ?? '' }); | ||
}; | ||
}; | ||
export const Validator = (func) => { | ||
const Use = (...hooks) => { | ||
return (target, key, desc) => { | ||
const value = desc.value; | ||
value.validat = func; | ||
const func = desc?.value; | ||
func.__$uses = hooks; | ||
}; | ||
}; | ||
export const Params = (dto) => { | ||
return async (target, key, desc) => { | ||
const fn = desc.value; | ||
fn.validat = async (context) => { | ||
const obj = new dto(); | ||
await obj.onStart(context); | ||
return [obj]; | ||
}; | ||
const Paramer = (paramsConstructor) => { | ||
return (target, key, desc) => { | ||
const func = desc?.value; | ||
func.__$paramers = paramsConstructor; | ||
}; | ||
}; | ||
export { Server, App, Context, File, JSONImprove }; | ||
export { Server, App, Context, File, Use, Router, Paramer, utils }; |
{ | ||
"name": "urgot", | ||
"version": "0.5.7", | ||
"version": "1.0.05", | ||
"description": "a Node.js web framework", | ||
"type": "module", | ||
"keywords": [ | ||
"web frameworks", | ||
"web", | ||
"framework", | ||
"middleware", | ||
"app" | ||
"web frameworks" | ||
], | ||
"scripts": { | ||
"dev": "nodemon ./dist/main.js", | ||
"test": "nodemon --exec ts-node-esm ./src/test/main.ts", | ||
"test:pm2": "pm2 start dist/test.js" | ||
}, | ||
"bin": { | ||
@@ -22,32 +23,18 @@ "urgot": "./dist/bin/main.js" | ||
".": "./dist/main.js", | ||
"./loader": "./dist/core/loader.js", | ||
"./context": "./dist/context.js", | ||
"./server": "./dist/server.js" | ||
"./system/loader": "./dist/system/loader.js" | ||
}, | ||
"types": "dist/main.d.ts", | ||
"typesVersions": { | ||
"*": { | ||
"context": [ | ||
"dist/context.d.ts" | ||
], | ||
"server": [ | ||
"dist/server.d.ts" | ||
] | ||
} | ||
}, | ||
"dependencies": { | ||
"@types/busboy": "^0.2.4", | ||
"@types/graceful-fs": "^4.1.5", | ||
"@types/busboy": "^1.5.0", | ||
"@types/json-bigint": "^1.0.1", | ||
"@types/mime-types": "^2.1.1", | ||
"@types/node": "^16.4.8", | ||
"@types/uuid": "^8.3.1", | ||
"busboy": "^0.3.1", | ||
"chokidar": "^3.5.2", | ||
"file-type": "^16.5.2", | ||
"graceful-fs": "^4.2.8", | ||
"@types/node": "^18.7.18", | ||
"@types/uuid": "^8.3.4", | ||
"busboy": "^1.6.0", | ||
"chokidar": "^3.5.3", | ||
"file-type": "^18.0.0", | ||
"json-bigint": "^1.0.0", | ||
"mime-types": "^2.1.32", | ||
"uuid": "^8.3.2" | ||
"mime-types": "^2.1.35", | ||
"uuid": "^9.0.0" | ||
} | ||
} |
173
README.md
@@ -1,28 +0,27 @@ | ||
# Language | ||
English | [简体中文](./README.cn.md) | ||
Urgot 是一个轻量级高性能的 Node.js 框架,可以用TypeScript构建服务器端应用程序。 | ||
Urgot is a Nodejs HTTP middleware framework written in Typescript. It allows you to create and pass down requests in the middleware, then filter and respond in reverse order. It comes with an out-of-the-box application development framework, hot update, Session, etc. It supports both Javascript and Typescript. | ||
# Installation | ||
# 安装 | ||
```shell | ||
$ npm install urgot | ||
npm i urgot | ||
``` | ||
安装 **typescript** 和 **ts-node** | ||
```shell | ||
npm i -g typescript ts-node | ||
``` | ||
# Hello World | ||
```js | ||
//index.js | ||
import { Server } from "urgot" | ||
## Hello World | ||
```typescript | ||
import { Server } from 'urgot' | ||
const server = new Server() | ||
server.use(async (context, next) => { | ||
context.body = "Hello World" | ||
context.body = 'Hello World' | ||
await next() | ||
}) | ||
server.run(8080) | ||
server.listen(5611) | ||
``` | ||
# Middleware | ||
Middleware is the main function of urgot. Calling the `use` method to register a middleware requires an asynchronous function (Promise) as a parameter. | ||
调用 `use` 方法注册一个中间件,接受一个异步函数,当监听到请求时传入请求上下文(`Context`)和一个`Next`函数用于响应并执行下一个中间件 | ||
```js | ||
```typescript | ||
server.use(async (context, next) => { | ||
@@ -32,15 +31,141 @@ const start = Date.now() | ||
const ms = Date.now() - start | ||
context.setHeader("X-Response-Time", `${ms}ms`) | ||
context.body = "Hello World" | ||
context.setHeader('X-Response-Time', `${ms}ms`) | ||
context.body = 'Hello World' | ||
}) | ||
``` | ||
Tips: Whenever you should call await next() to let the downstream intermediate response execution. | ||
中间件使用洋葱模型,从请求(**next()前**)到响应(**next()后**),每一个中间件都有两次处理时机。 | ||
# Context and Next functions | ||
Each middleware will receive a Context object and a `next` asynchronous function. The `Context` object is the encapsulation of `Http.ServerResponse`, of course you can visit `context.response` to access it, but usually we do not recommend it. After processing the middleware, you need to call the `next` function to let the downstream middleware Respond to execution. | ||
# 创建应用 | ||
调用 **createApp** 创建您的应用程序并传入您的应用和配置项, | ||
```typescript | ||
import { App } from 'urgot' | ||
# Demo | ||
[https://gitee.com/apprat/urgot-server](https://gitee.com/apprat/urgot-server) | ||
class MyApp extends App { | ||
//实现 | ||
} | ||
# License | ||
[MIT](./LICENSE) | ||
const server = new Server() | ||
server.createApp([MyApp]) | ||
server.listen(5611) | ||
``` | ||
## Router 注解 | ||
使用 `Router` 注解去设置路由和HTTP方法 | ||
```typescript | ||
import { App, Server, Router } from 'urgot' | ||
class MyApp extends App { | ||
@Router('GET') | ||
find() { | ||
const { searchParams } = this.context.url | ||
return { id: searchParams.get('id') } | ||
} | ||
} | ||
const server = new Server() | ||
server.createApp([MyApp],{ onResponse: (vlaue, context) => context.body = vlaue as object }) | ||
server.listen(5611) | ||
``` | ||
>访问地址:http://localhost/?id=123456 | ||
>页面响应:{ id: 123456 } | ||
`Reouter` 不仅可以注解方法,还可注解控制器类来添加固定的路由前缀 | ||
```typescript | ||
import { App, Router } from 'urgot' | ||
@Router('/video') | ||
class MyApp extends App { | ||
@Router('GET','/list') | ||
find() { | ||
const { searchParams } = this.context.url | ||
return { targets: searchParams.getAll('name') } | ||
} | ||
} | ||
``` | ||
>访问地址:http://localhost/video/list?name=Yone&name=Yasuo | ||
>页面响应: { tagrets: ["Yone", "Yasuo"] } | ||
PS:`Router` 可以注册多次来定义多对一的路由 | ||
## Use 注解 | ||
使用 **Use** 注解来注册自定义的拦截器 | ||
```typescript | ||
import { App, use, Router, Context } from 'urgot' | ||
const auth = (context: Context) => { | ||
if (!context.url.searchParams.get('authed')) throw 'Please login' | ||
} | ||
@Router('/video') | ||
class MyApp extends App { | ||
@Use(auth) | ||
@Router('GET','/list') | ||
find() { | ||
const { searchParams } = this.context.url | ||
return { targets: searchParams.getAll('name') } | ||
} | ||
} | ||
``` | ||
## Paramer 注解 | ||
获取请求提交的参数是必不可少的一项,使用 **Paramer** 参数注解获取和转换用户提交的参数 | ||
```typescript | ||
import { App, Use, Router, Context } from 'urgot' | ||
class Params { | ||
id!: number | ||
constructor(private context: Context) { } | ||
async onCreate() { | ||
const { searchParams } = this.context.url | ||
this.id = Number(searchParams.get('id')) | ||
} | ||
} | ||
@Router('/video') | ||
class MyApp extends App { | ||
@Paramer(Params) | ||
@Router('GET','/list') | ||
find(params: Params) { | ||
return { upload: params.id } | ||
} | ||
} | ||
``` | ||
>所有注解在抛出错误后都会阻止继续执行,并响应该错误值,利用这个特性来拦截一些操作。 | ||
# 命令行 | ||
可以使用 `urgot` 命令行来使用 **urgot** 的内置命令(局部安装需要使用 `npx` 命令) | ||
| 命令 | 说明 | 示例 | | ||
|---|---|---| | ||
| init | 初始化项目 (会覆盖当前文件夹) | `urtot init MyApp` | | ||
| dev | 运行开发环境 | `urtot dev` | | ||
# 常见问题 | ||
- **HTTP Body 解析** | ||
>调用`context.request.body()`解析请求Body,它返回一个`Promise`(注意多次调用仅在第一次解析,后续调用将返回缓存值 | ||
- **Session 的使用** | ||
>要使用`Session`需要在实例化`Server`时配置`session`实现参数,否则在调用时将抛出错误 | ||
```typescript | ||
const server = new Server({ | ||
session: { | ||
handlers: { | ||
//实现方法,建议使用IORedis库,传递client即可 | ||
} | ||
} | ||
}) | ||
``` | ||
- **处理路由函数返回的值** | ||
>你必须在调用 `server.createApp()`时传递 `options`选项来处理返回值 | ||
```typescript | ||
import { Server } from 'urgot' | ||
const server = new Server() | ||
server.createApp([],{ | ||
//当路由方法返回时触发 | ||
onResponse: (vlaue, context) => { | ||
context.body = vlaue as object | ||
} | ||
}) | ||
server.listen(5611) | ||
``` |
Sorry, the diff of this file is not supported yet
Unpublished package
Supply chain riskPackage version was not found on the registry. It may exist on a different registry and need to be configured to pull from that registry.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Unpopular package
QualityThis package is not very popular.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
11
1
0
170
2
34740
24
795
+ Added@types/busboy@1.5.4(transitive)
+ Added@types/node@18.19.33(transitive)
+ Addedbusboy@1.6.0(transitive)
+ Addedfile-type@18.7.0(transitive)
+ Addedpeek-readable@5.0.0(transitive)
+ Addedstreamsearch@1.1.0(transitive)
+ Addedstrtok3@7.0.0(transitive)
+ Addedtoken-types@5.0.1(transitive)
+ Addedundici-types@5.26.5(transitive)
+ Addeduuid@9.0.1(transitive)
- Removed@types/graceful-fs@^4.1.5
- Removedgraceful-fs@^4.2.8
- Removed@types/busboy@0.2.4(transitive)
- Removed@types/graceful-fs@4.1.9(transitive)
- Removed@types/node@16.18.97(transitive)
- Removedbusboy@0.3.1(transitive)
- Removeddicer@0.3.0(transitive)
- Removedfile-type@16.5.4(transitive)
- Removedgraceful-fs@4.2.11(transitive)
- Removedpeek-readable@4.1.0(transitive)
- Removedstreamsearch@0.1.2(transitive)
- Removedstrtok3@6.3.0(transitive)
- Removedtoken-types@4.2.1(transitive)
- Removeduuid@8.3.2(transitive)
Updated@types/busboy@^1.5.0
Updated@types/node@^18.7.18
Updated@types/uuid@^8.3.4
Updatedbusboy@^1.6.0
Updatedchokidar@^3.5.3
Updatedfile-type@^18.0.0
Updatedmime-types@^2.1.35
Updateduuid@^9.0.0