From d29e5b0de7d93a7c11221c00cfa6d84e3f308f0d Mon Sep 17 00:00:00 2001 From: yafu Date: Tue, 6 Feb 2024 16:49:46 +0800 Subject: [PATCH] v0.9.0 --- CHANGELOG.md | 13 + README.md | 571 ++----------------------------------------- jj.js | 6 + jsconfig.json | 9 + jsdoc.conf.js | 17 ++ lib/app.js | 69 +++--- lib/cache.js | 57 ++++- lib/config.js | 28 ++- lib/context.js | 8 + lib/controller.js | 42 +++- lib/cookie.js | 48 +++- lib/ctx.js | 9 + lib/db.js | 363 ++++++++++++++++++++++++++- lib/loader.js | 14 +- lib/logger.js | 87 ++++++- lib/middleware.js | 38 ++- lib/model.js | 55 ++++- lib/pagination.js | 55 ++++- lib/response.js | 70 +++++- lib/router.js | 6 +- lib/types.js | 286 ++++++++++++++++++++++ lib/upload.js | 87 ++++++- lib/url.js | 19 ++ lib/utils/date.js | 17 +- lib/utils/error.js | 12 +- lib/utils/md5.js | 16 +- lib/utils/str.js | 10 + lib/utils/utils.js | 8 +- lib/view.js | 91 ++++++- package-lock.json | 596 +++++++++++++++++++++++++-------------------- package.json | 14 +- 31 files changed, 1770 insertions(+), 951 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 jsconfig.json create mode 100644 jsdoc.conf.js create mode 100644 lib/types.js diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..333c8fb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# v0.9.0 / 2024-02-06 +1. 支持jsdoc,完善vscode代码提示 +2. 系统核心库app由对象改为class,使用const app = new App() +3. 修复Logger类输出格式化bug,log函数参数调换 +4. 系统日志配置,默认输出['app', 'error']级别的日志 +5. 系统级屏蔽favicon.ico请求 +6. 系统loader支持并优先加载文件 +7. 依赖升级@koa/router v10.1.1 -> v12.0.1 +8. 依赖升级koa v2.13.4 -> v2.15.0 +9. node版本要求 >= v12.7.0 + +# v0.8.8 / 2022-09-07 +1. 第一个tag \ No newline at end of file diff --git a/README.md b/README.md index 557fe31..565b668 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ![jj.js](https://me.i-i.me/static/images/jjjs.png "jj.js") -A simple and lightweight MVC framework built on nodejs+koa2(一个基于nodejs+koa2构建的简单轻量级MVC框架) +A super simple lightweight NodeJS MVC framework(一个超级简单轻量的NodeJS MVC框架) ## 项目介绍 -本框架依赖koa2、koa-router、art-template、mysql,基于proxy实现了代码自动加载及懒加载技术,最低运行依赖仅仅为koa和koa-router,非常轻量。 +本框架依赖koa2、@koa/router、art-template、mysql,基于proxy实现了代码自动加载及懒加载技术,最低运行依赖仅仅为koa和koa-router,非常轻量。 ### 项目特性 @@ -14,6 +14,7 @@ A simple and lightweight MVC framework built on nodejs+koa2(一个基于nodejs 2. 系统类库、用户类库都支持自动加载、懒加载、自动生成单实例 3. 支持应用级、路由级、控制器级三级中间件,方便插件及二次开发 4. 支持单应用和多应用两种运行模式 +5. jsdoc支持,完善vscode代码提示 ### 项目地址 @@ -30,7 +31,7 @@ A simple and lightweight MVC framework built on nodejs+koa2(一个基于nodejs npm i jj.js ``` -> 运行环境要求:node.js >= v12 mysql >= 5.3 +> 运行环境要求:node.js >= v12.7.0 ## Hello world ! @@ -52,10 +53,11 @@ module.exports = Index; 2、创建应用入口文件 `./server.js` ```javascript -const {app, Logger} = require('jj.js'); +const {App, Logger} = require('jj.js'); +const app = new App(); -app.run(3000, '127.0.0.1', function(err){ - !err && Logger.info('http server is ready on 3000'); +app.run(3000, '0.0.0.0', function(err){ + !err && Logger.log('app', 'http server is ready on 3000'); }); ``` @@ -110,12 +112,12 @@ node server.js ### 系统类库 ```javascript -const {app, Controller, Db, Model, Pagination, View, Logger, Cookie, Response, Upload, Url, Middleware, Cache, Context, View} = require('jj.js'); +const {App, Controller, Db, Model, Pagination, View, Logger, Cookie, Response, Upload, Url, Middleware, Cache, Context, View} = require('jj.js'); ``` ![jj.js类库继承关系图](https://me.i-i.me/static/images/jj_class.png "jj.js类库继承关系图") -系统类库除了`app`,其他都是`Class`类型,其中`Logger`和`Cache`是静态类,开发时建议继承系统类库,这样可以在类内使用`$`开头的属性,实现自动加载功能,链式调用类方法时会自动实例化一个单例。例如,在控制器内使用 `this.$logger` 会返回系统Logger类,使用 `this.$logger.info()`,会自动生成一个`logger`单例,并调用`info`方法,其他系统类库及类库内可以以这种方法调用。 +系统类库都是`Class`类型,其中`Logger`和`Cache`是静态类;开发时建议继承系统类库,这样可以在类内使用`$`开头的属性,实现自动加载功能,链式调用类方法时会自动实例化一个单例。例如,在控制器内使用 `this.$logger` 会返回系统Logger类,使用 `this.$logger.info()`,会自动生成一个`logger`单例,并调用`info`方法,其他系统类库及类库内可以以这种方法调用。 ### 类库自动加载 @@ -203,546 +205,13 @@ module.exports = Index; 3、除了框架db类、自定义模型类,整个应用和框架的所有自定义类库、配置文件,都支持同过`this.$xxxx`调用。 -### Controller控制器 - -系统控制器类继承自系统中间件类Middleware,包含所有Middleware方法。 - -> 属性:**middleware** 定义控制器中间件 - -数组,定义控制器中间件,一个元素为一个中间件,元素为字符串或对象。 - -示例1: `this.middleware = ['index']`,则控制器内所有方法访问之前都会调用当前应用目录(app)下中间件目录(middleware )下的控制器同名中间件的index方法。 - -示例2: `this.middleware = ['index', {middleware: 'auth/test', accept: 'middleTest'}]`,则控制器内所有方法访问之前都会先调用当前应用目录(app)下中间件目录(middleware )下的控制器同名中间件的index方法。仅当访问控制器middleTest方法之前,还会再调用当前应用下中间件目录下的auth中间件的test方法(需index中间件内调用`this.$next()`方法,否则后续程序不会执行)。 - -> 方法:**$assign(name, value)** 赋值模版变量 - -示例1:`this.$assign('title', 'Hello jj.js !')`,在模版内使用变量 `{{title}}` - -如果name为一个对象,则清除之前赋值的模板变量,并将name设置为模板变量对象。 - -示例2:`this.$assign({'title': 'jj.js', 'content': 'Hello jj.js !'})`,在模版内可使用变量 `{{title}}`、`{{content}}` - -> 方法:**$data(name)** 获取已赋模版变量 - -如果name为空,则获取全部变量。 - -> 方法:**async $fetch(template)** 渲染模板文件并输出 - -异步方法,来自`View`类,渲染模板文件,并输出内容。 - -其中template会自动自动定位模板文件,如果name为空,则定位到当前应用下view目录下的控制器同名目录下的方法同名htm文件。 - -示例1:`this.$fetch()`,在index控制器类的index方法调用,则模板定位到`/app/view/index/index.htm` - -示例2:`this.$fetch('list/show')`,则模板定位到`/app/view/list/show.htm` - -> 方法:**async $load(template)** 加载并输出模板文件 - -异步方法,来自`View`类,直接加载模板文件,并输出文件内容。模板定位规则同$fetch方法。 - -> 方法:**async $render(data)** 渲染字符串模板 - -来自`View`类,渲染字符串模板,并输出。data为字符串模版内容。 - -示例1:`this.$render('
{{title}}
')`,会输出`
jj.js
` - -> 方法:**$show(content)** 输出字符串内容或转换后的json字符串 - -继承自`Middleware`类,输出content内容,如果content为对象或数组,则输出json字符串。 - -> 方法:**$redirect(name, status = 302)** 302或其他跳转 - -继承自`Middleware`类,网页跳转,name解析同$fetch方法。 - -示例1:`this.$redirect('test')`,在index控制器类的index方法调用,则跳转到到`/app/index/test`地址 - -示例2:`this.$redirect('/show')`,name包含'/'前缀,则跳转到地址`/show` - -> 方法:**$success(msg, name)** 成功跳转或输出 - -继承自`Middleware`类,返回成功提示,name解析同$redirect方法,如果是ajax请求,则返回json数据。 - -示例1:`this.$success('操作成功!', 'test')`,返回成功提示,并跳转到`/app/index/test`地址 - -示例2:`this.$success({ajax: 'data'})`,如果是ajax请求,则返回json `{state: 1, msg: '操作成功!', data: {ajax: 'data'}}` - -> 方法:**$error(msg, name)** 错误跳转或输出 - -继承自`Middleware`类,返回失败提示,name解析同$success方法,name为空,则跳转来源网址。 - -> 方法:**async $next()** 执行下一个路由匹配 - -异步方法,继承自`Middleware`类,调用后,会等待执行全局路由配置里后面能匹配的路由。 - -> 注意:所有的输出和跳转并不会阻止后续代码执行,所以要终止代码执行,需在前面加上`return`。 - -> 方法:**async _init()** 控制器初始化 - -异步方法,除了使用控制器中间件执行一些初始化或验证外,控制器提供了这个更简单的方法。一旦控制器定义了这个方法,在通过url访问控制器其他公开方法时,在执行完控制器中间件后,都会先执行这个函数,如果想终止后续代码执行,可以在方法里返回`false`。 - -> 方法:**async _end()** 控制器结束 - -如果定义了这个函数,在通过url访问控制器其他公开方法后,会调用这个函数。 - -> 注意:因为系统默认注册了 `应用/控制器/方法` 的全局路由,所以控制器里的方法都能被直接访问,如果想作为私有方法不被访问,可以在方法前加下划线`_`前缀。 - -### Middleware中间件类 - -> 方法:**$show(content)** 参照控制器类介绍 - -> 方法:**$redirect(url, status)** 参照控制器类介绍 - -> 方法:**$success(msg, url)** 参照控制器类介绍 - -> 方法:**$error(msg, url)** 参照控制器类介绍 - -> 方法:**async $next()** 执行下一个中间件 - -### Db数据库类 - -> 方法:**connect(options)** 连接数据库 - -> 方法:**async close()** 关闭数据库连接 - -> 方法:**async startTrans(fun)** 开启事务 - -异步方法,如果`fun`不为空,并且是函数,则开启事务后,会自动执行此函数,并提交事务,不用再手工提交或回滚事务。`fun`为异步函数。 - -> 方法:**async commit()** 提交事务 - -> 方法:**async rollback()** 事务回滚 - -> 方法:**prefix(prefix)** 设置数据表前缀 - -> 方法:**table(table)** 设置数据表 - -table参数:数据表名字,不带前缀 - -> 方法:**field(field)** 设置查询字段 - -支持字符串或数组,支持多次调用 - -示例1:`this.field('id,name');` - -示例2:`this.field(['id', 'name']).field('mobile');` - -> 方法:**where(where, logic)** 设置查询条件 - -支持多次调用,`logic`设置多次调用之间的连接条件,默认为`and`,where参数为对象 - -示例1:设置查询id为1的条件 - -`this.where({id: 1});` // where id = 1 - -示例2:设置查询name为'aaa',并且sex为1的条件 - -`this.where({name: 'aaa', sex: 1});` // where name = 'aaa' and sex = 1 - -示例3:多次调用`where`,设置查询name为'aaa',并且sex为1的条件,并且age大于18的条件 - -`this.where({name: 'aaa', sex: 1}).where({age: ['>', 18]}); // where (name = 'aaa' and sex = 1) and (age > 18)` - -可以看到,当字段值不是等于(=)时,字段值使用数组来标识,其中数组第一项为表达式(>),数组第二项为表达式要对比的值。数组的第三项为一个where内的连接逻辑,不设置的话默认为`and`,或连接的话,设置为`or`。 - -示例4:设置查询name为'aaa',或着sex为1的条件 - -`this.where({name: 'aaa', sex: ['=', 1, 'or']});` // where name = 'aaa' or sex = 1 - -> 说明:where支持所有表达式('=', '<>', '!=', '>', '>=', '<', '<=', 'like', 'not like', 'in', 'not in', 'between', 'not between', 'is', 'is not', 'exp') - -> 方法:**distinct()** 数据去重 - -> 方法:**group(field)** 数据分组 - -> 方法:**having(condition)** 数据筛选 - -> 方法:**order(field, order='asc')** 查询排序 - -order方法支持多次调用。 - -示例:`this.order('id', 'desc').order('sort');` // order by id desc, sort asc - -> 方法:**limit(offset, rows)** 查询数据限制 - -示例1:`this.limit(1, 10);` // limit 1, 10 - -示例2:`this.limit(10);` // limit 10 - -> 方法:**page(page, pageSize)** 分页查询 - -当要进行分页查询时,用这个方法会更方便。 - -示例1:`this.page(1, 10);` // limit 0, 10 - -示例2:`this.page(2, 10);` // limit 10, 10 - -> 方法:**cache(time)** 设置缓存时间 - -`time`单位为秒,设置缓存的话,在这个时间内再次执行相同条件的查询,将直接返回缓存的数据。 - -示例:`this.cache(600);` // 设置缓存时间为10分钟 - -> 方法:**join(table, on, type='left')** 设置表连接 - -假如设置文章表和用户表以用户id为条件连接: - -示例:`this.table('article').join('user', 'article.user_id=user.id', 'left');` // from article article left join user user on article=user.id - -> 方法:**getSql(fetch = true)** 设置是否返回sql语句 - -设置后,调用查询方法不会再进行真正的查询,而是直接返回编译后的sql语句字符串。 - -示例:`this.table('user').getSql().select();` // 返回:'select * from user' - -> 方法:**async select(condition)** 查询多条数据 - -如果`condition`不为空,则会清空前面使用`where`方法设置的查询条件,然后以`condition`为参数调用一次`where`方法。 - -示例:`this.where({name: 'aaa', sex: 1}).where({id: 1}).select({id: 2});` // 等效于:this.where({id: 2}).select(); - -> 方法:**async find(condition)** 查询单条数据 - -使用方法同`select()` - -> 方法:**async value(field)** 查询单个值 - -返回单条数据里某个字段的值,假如查询user表id为1的用户的年龄值: - -示例:`this.table('user').where({id: 1}).value('age');` // 返回age值 - -> 方法:**async count(field='*')** 查询记录数 - -以某个字段查询记录总数,假如查询user表age为18的用户数: - -示例:`this.table('user').where({age: 18}).count();` // 返回记录总数 - -> 方法:**async max(field)** 查询最大值 - -> 方法:**async min(field)** 查询最小值 - -> 方法:**async avg(field)** 查询平均值 - -> 方法:**async sum(field)** 对列求和 - -> 方法:**async column(field, key)** 获取一列数据 - -如果设置key则获取一列键值对 - -示例1:`this.column('age');` // [16, 18, 20] 数字为年龄 - -示例2:`this.column('age','id');` // {1: 16, 2: 18, 3: 20} 数字为年龄,1 2 3为id值 - -> 方法:**async pagination({page, page_size, pagination})** 分页查询并返回分页实例 - -相当于`page()+Pagination`类分页,如果传入`pagination`实例,则以这个实例渲染分页,否则会自动创建一个分页实例。如果没有传page、page_size,会使用`pagination`实例的`page()`方法`pageSize()`自动生成。 - -示例:`this.pagination();` // 返回[data_list, pagination] - -> 方法:**data(data)** 设置写入数据 - -此方法用于更新或写入数据,提前设置需要的数据,支持多次调用 - -示例:`this.data({age: 18}).data({sex: 1}).insert();` // 返回[data_list, pagination] - -> 方法:**allowField(field = true)** 设置过滤非数据表字段 - -更新或写入数据时,会过滤掉非数据表里的字段 - -> 方法:**async insert(data)** 插入一条数据 - -如果data参数不为空,会清除前面使用`data()`方法设置的数据,并以此处传入数据为准 - -> 方法:**async update(data, condition)** 更新数据 - -如果`data`参数不为空,会清除前面使用`data()`方法设置的数据,并以此处传入数据为准。如果`condition`参数不为空,会清除前面设置`where()`设置的条件 - -> 方法:**async inc(field, step)** 数据表字段自增 - -设置字段field自增,step默认为1 - -> 方法:**async dec(field, step)** 数据表字段自减 - -设置字段field自减,step默认为1 - -> 方法:**async exp(field, step)** 数据表字段执行自定义方法 - -> 方法:**async delete(condition)** 删除数据 - -如果`condition`参数不为空,会清除前面设置`where()`设置的条件,并以此处传入条件为准。 - -> 方法:**async execute(sql, params, reset=true)** 解析并执行sql语句 - -执行sql语句查询 - -> 方法:**format(sql, params)** 解析sql语句 - -> 方法:**async tableInfo(table)** 获取表信息 - -> 方法:**async tableField(table)** 获取表字段信息 - -> 方法:**deleteCache()** 清空数据库查询缓存 - -### Model模型类 - -> 属性:**db** 模型的db实例 - -每个模型文件,会懒自动创建一个独有的db实例 - -> 属性:**table** 数据表名字,默认为模型文件名 - -> 属性:**pk** 数据表主键字段,默认为`id` - -> 方法:**async add(data)** 同Db类insert方法 - -> 方法:**async save(data, condition = {})** 智能调用Db类insert或update方法 - -当data含有主键字段或condition不为空,则执行db实例`update()`方法,否则执行`insert()`方法 - -> 方法:**async del(condition)** 同Db类delete方法 - -> 方法:**async get(condition)** 同Db类find方法 - -> 方法:**async all(condition)** 同Db类select方法 - -### Pagination分页类 - -> 方法:**init(options)** 初始化 - -options参数默认继承自`./config/page.js`或框架`config.page`参数,框架page参数如下: - -```javascript -const page = { - page_key : 'page', // 默认分页标识 - key_origin : 'query', // query 或 params - page_size : 10, // 默认分页大小 - page_length : 5, // 默认分页长度,数字页码链接数量 - - //网址规则,可为空,可为路由名字,可用参数:页码${page} - //样例:':name' - //样例:'/list_${page}.html' - url_page : '', - url_index : '', - - //模块样式 可用参数:网址${url},页码${page},总数${total_page},总页数${total_page} - index_tpl : '
  • 首页
  • ', - end_tpl : '
  • 末页
  • ', - prev_tpl : '', - next_tpl : '', - list_tpl : '
  • ${page}
  • ', - active_tpl : '
  • ${page}
  • ', - info_tpl : '共${total_page}页,${total}条记录', - - //渲染模版 - template : '' -} -``` -- key_origin:设置分页参数获取位置,url或params -- page_key:设置分页标识,即设置或获取当前分页的字段 -- url_page、url_index:设置生成的分页url模板或规则,网址规则可以为路由名字,可用参数为`${page}` - -> 方法:**page(page)** 设置或获取当前页 - -> 方法:**pageSize(page_size)** 设置或获取分页大小 - -> 方法:**total(total)** 设置或获取总数 - -> 方法:**render(total, page, page_size)** 生成分页html代码 - -传参,可以快速设置page、page_size、total - -### View模板引擎类 - -> 方法:**assign(name, value)** 赋值模版变量,用法参考Controller类 - -> 方法:**data(name)** 获取已赋模版变量,用法参考Controller类 - -> 方法:**async fetch(template)** 渲染模板文件并输出,用法参考Controller类 - -> 方法:**async load(template)** 加载并输出模板文件,用法参考Controller类 - -> 方法:**async render(data)** 渲染字符串模板,用法参考Controller类 - -> 方法:**setFilter(fun_obj, fun)** 动态设置模版函数 - -设置一个或多个模板函数: - -示例1:`this.setFilter('sum', (a, b) => {return a + b;});` // 设置一个函数sum - -示例2:`this.setFilter({'sum': (a, b) => {return a + b;}, 'fun2': () => {}});` // 同时设置sum、fun2两个函数,设置后可以在模板中使用 - -> 提示:View类初始化时,默认会设置一个`url`的模板函数,即在模板文件里可以直接使用`url`函数生成网址,示例: - -```javascript -// 首页模板代码 -{{url('user')}} - -// 生成网址(单应用模式) -'/index/user' -``` -`url`函数具体用法,请参考Url类的`build`方法 - -> 方法:**setFolder(view_folder)** 动态设置模版函数 - -> 方法:**setDepr(view_depr)** 动态设置文件分割符 - -> 本框架默认使用的模板引擎为`art-template`,关于模板语法,可以参考[art-template文档](http://aui.github.io/art-template/zh-cn/docs/ "art-template文档") - -### Logger日志类 - -> 说明:日志类是一个静态类,方法都为静态方法,但同时也支持new创建一个新的静态实例。 - -> 提示:目前日志类输出没有具体的代码实现,只是数据格式化后,用node自带log打印。 - -> 方法:**static log(msg, level='info')** 输出日志 - -> 方法:**static error(...args)** 输出错误日志 - -> 方法:**static warning(...args)** 输出错误日志 - -> 方法:**static info(...args)** 输出错误日志 - -> 方法:**static debug(...args)** 输出错误日志 - -> 方法:**static sql(...args)** 输出错误日志 - -> 方法:**static http(...args)** 输出错误日志 - -> 方法:**static setHandle(...args)** 输出错误日志 - -### Cookie类 - -> 方法:**set(key, value, options)** 设置cookie - -options默认继承`config.cookie`参数 - -> 方法:**get(key)** 获取cookie - -> 方法:**delete(key)** 删除一个cookie - -> 方法:**all()** 获取所有cookie - -> 方法:**clear()** 清除所有cookie - -> 方法:**keys()** 获取所有cookie的key - -### Response跳转响应类 - -> 方法:**show(data)** 渲染模板文件并输出,用法参考Controller类 - -> 方法:**redirect(url, status = 302)** 302或其他跳转,用法参考Controller类 - -> 方法:**success(msg='操作成功!', name)** 成功跳转或输出,用法参考Controller类 - -> 方法:**error(msg='操作失败!', name)** 错误跳转或输出,用法参考Controller类 - -> 方法:**jump(msg, url, state=1)** 跳转或输出 - -> 方法:**exception(err)** 输出异常页面 - -> 方法:**wait(time)** 设置页面跳转时等待时间 - -### Upload上传类 - -> 注意:文件上传,需要先开启`config.app.koa_body`参数,具体设置可参考[koa-body文档](https://github.com/koajs/koa-body#readme) - -> 方法:**file(file)** 设置file文件 - -当参数`file`为字符串时,会调用`getFile(name)`方法获取上传文件并配置,如果为文件,则直接赋值。 - -> 方法:**getFile(name)** 获取一个上传文件 - -> 方法:**validate(rule={})** 设置文件验证规则参数 - -- rule.size,文件大小限制 -- rule.ext,文件后缀 -- rule.type,文件MIME类型 -- rule.size,文件大小限制 -- 默认会对图片后缀的文件做图片MIME验证 - -> 方法:**rule(name)** 设置文件保存名字或规则 - -参数name为字符串或函数,不传的话按内部规则。 - -> 方法:**checkExt(ext)** 验证文件后缀 - -> 方法:**checkType(type)** 验证文件MIME - -> 方法:**checkImg()** 验证图片文件 - -> 方法:**check()** 对设置的规则执行验证 - -> 方法:**getError()** 获取验证错误或上传文件失败信息 - -> 方法:**async save(dir)** 保存上传文件 - -参数dir为文件保存目录,相对应用的根目录。内部会自动执行`check()`方法。 - -示例1:`await this.file('img').save('upload');` // 会将上传的图片保存到upload目录下 - -示例2:`await this.file('img').validate({size: 1024, ext: 'png', type: 'png'}).save('upload');` // 设置只能上传小于1M的png图片 - -上传成功返回参数: - -```javascript -return { - filename, // 保存后的文件名 - extname, // 文件后缀名 - savename, // 除去上传目录的完成名字 - filepath, // 文件保存目录 - name, // 文件原始名字 - size, // 文件大小 - mimetype, // mimetype - hash // hash -}; -``` - -### Url网址解析类 - -> 方法:**build(url='', vars, ext='', domain='')** 生成url网址 - -Url类build方法,会根据当前访问url参数,智能生成需要的网址,假如单应用模式访问`127.0.0.1/user/info` - -示例1:`this.build()` // '/user/list' - -示例2:`this.build('list')` // '/user/list' - -示例3:`this.build('article/list')` // '/article/list' - -示例4:`this.build('list', {type: 'hot', order: 'click'})` // '/user/list?type=host&order=click' - -示例5:`this.build('list', '.html')` // '/user/list.html' - -示例6:`this.build('list', '.html', 'localhost')` // 'localhost/user/list.html' - -如果`url`参数包含`/`前缀,则直接做为网址使用 - -示例7:`this.build('/list', {type: 'hot'})` // '/list?type=host' - -如果自定义的有路由地址,通过路由名字可以反向编译地址,假如有路由定义`./config/routes.js` - -```javascript -route = [ - {url: '/article/:id.html', path: 'article/article', name: 'article'}, -]; - -module.exports = route; -``` -这是一个自定义文章页路由地址,当访问`127.0.0.1/article/123.html`时会匹配到这个地址,并且这条路由的名字`name`为`article`,在url反向编译路由时,通过带`:`号的名字来定义: - -示例8:`this.build(':article', {id: 456})` // '/article/456.html' - -### Context配置上下文类 - -> 属性:**ctx** 整个框架的上下文,具体参数,可以参考[Koa上下文(Context)ctx文档](https://www.itying.com/koa/) - ### config配置 -应用的配置可以为`.config/`目录+`.config/xxx.js`文件的形式,也可以直接写到一个`.config.js`文件里。 +应用的配置可以为`./config/`目录+`./config/xxx.js`文件的形式,也可以直接写到一个`./config.js`文件里。 应用配置不用每项都设置,只设置自己需要改的,默认会继承框架的默认配置,在控制器、中间件、模板类、模型类里都可以通过`this.$config.xxx`使用。 -- app配置`.config/app.js`: +- app配置`./config/app.js`: ```javascript const app = { app_debug: true, // 调试模式 @@ -761,7 +230,7 @@ const app = { } module.exports = app; ``` -- 模板配置`.config/view.js`: +- 模板配置`./config/view.js`: ```javascript const view = { view_folder: 'view', // 模板目录名 @@ -772,7 +241,7 @@ const view = { } module.exports = view; ``` -- 数据库配置`.config/db.js`:可以配置多个,方便程序里切换使用。 +- 数据库配置`./config/db.js`:可以配置多个,方便程序里切换使用。 ```javascript const db = { default: { @@ -788,7 +257,7 @@ const db = { } module.exports = db; ``` -- 日志配置`.config/log.js`:log_handle可以自定义日志handle +- 日志配置`./config/log.js`:log_handle可以自定义日志handle ```javascript const log = { log_level: [], // [error, warning, info, debug, http, sql] @@ -796,7 +265,7 @@ const log = { } module.exports = log; ``` -- 缓存配置`.config/cache.js`: +- 缓存配置`./config/cache.js`: ```javascript const cache = { cache_time: 60 * 60 * 24, // 默认缓存时间(1天),为空或false则为10年 @@ -804,7 +273,7 @@ const cache = { } module.exports = cache; ``` -- 分页配置`.config/page.js`: +- 分页配置`./config/page.js`: ```javascript const page = { page_key : 'page', // 默认分页标识 @@ -832,7 +301,7 @@ const page = { } module.exports = page; ``` -- 跳转模板配置`.config/tpl.js`:模板可以配置为自定义的 +- 跳转模板配置`./config/tpl.js`:模板可以配置为自定义的 ```javascript const tpl = { jump: require('./tpl/jump'), // 跳转模板 @@ -840,7 +309,7 @@ const tpl = { } module.exports = tpl; ``` -- 自定义配置`.config/self.js`:自定义配置同样可以直接通过`this.$config.self`使用。 +- 自定义配置`./config/self.js`:自定义配置同样可以直接通过`this.$config.self`使用。 ```javascript const self = { option1: '' @@ -849,7 +318,7 @@ const self = { } module.exports = self; ``` -- 路由配置`.config/routes.js`: +- 路由配置`./config/routes.js`: 路由功能基于`@koa/router`开发,关于url匹配规则可以参考官方文档:[文档地址](https://www.npmjs.com/package/@koa/router) > 本框架默认内置 `应用/控制器/方法` 的全局路由,即如果不需要定制url,可以直接访问,无需配置路由。 diff --git a/jj.js b/jj.js index 4d64156..33f55b0 100644 --- a/jj.js +++ b/jj.js @@ -1,3 +1,9 @@ +/** + * jj.js核心库
    + * {App, Controller, Db, Model, Pagination, View, Logger, Cookie, Response, Upload, Url, Middleware, Cache, Context, View, utils} + * @module core + * @type {import('./lib/types').Core} + */ module.exports = new Proxy({}, { get: (target, prop) => { if(prop in target || typeof prop == 'symbol' || prop == 'inspect'){ diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..ad03edf --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "resolveJsonModule": true, + "checkJs": true + }, + "exclude": ["node_modules", "docs", "./lib/ctx.js"] + } \ No newline at end of file diff --git a/jsdoc.conf.js b/jsdoc.conf.js new file mode 100644 index 0000000..a74eee7 --- /dev/null +++ b/jsdoc.conf.js @@ -0,0 +1,17 @@ +module.exports = { + source: { + include: ['./jj.js', './lib'], + includePattern: '.+\\.js(doc)?$', + }, + plugins: [ + "jsdoc-tsimport-plugin" + ], + templates: { + cleverLinks: true, + monospaceLinks: true, + }, + opts: { + recurse: true, + destination: './docs', + } +}; \ No newline at end of file diff --git a/lib/app.js b/lib/app.js index bd0aa6e..4034bbb 100644 --- a/lib/app.js +++ b/lib/app.js @@ -1,48 +1,53 @@ const path = require('path'); const Koa = require('koa'); -const app = new Koa(); const {app: cfg_app} = require('./config'); const Logger = require('./logger'); const Response = require('./response'); const router = require('./router'); const pjson = require('../package.json'); -app.run = (...args) => { - // exception - app.use(async (ctx, next) => { - ctx.APP_TIME = new Date(); - ctx.APP_VERSION = pjson.version; - - try { - await next(); - } catch (err) { - Logger.error(...err.stack.split("\n")); - if(cfg_app.app_debug) { - new Response(ctx).exception(err); - } else { - ctx.response.status = err.statusCode || err.status || 500; - ctx.body = 'Internal Server Error'; +/** + * @extends Koa + */ +class App extends Koa +{ + /** + * @override + */ + listen(...args) { + // exception + this.use(async (ctx, next) => { + ctx.APP_TIME = Date.now(); + ctx.APP_VERSION = pjson.version; + + try { + await next(); + } catch (err) { + Logger.error(...err.stack.split("\n")); + if(cfg_app.app_debug) { + new Response(ctx).exception(err); + } else { + ctx.response.status = err.statusCode || err.status || 500; + ctx.body = 'Internal Server Error'; + } } - } - const ms = new Date() - ctx.APP_TIME; - Logger.http(`${ctx.method} ${ctx.status} ${ctx.url} - ${ms}ms`); - }); + const ms = Date.now() - ctx.APP_TIME; + Logger.http(`${ctx.method} ${ctx.status} ${ctx.url} - ${ms}ms`); + }); - // static - cfg_app.static_dir && app.use(require('koa-static')(path.join(cfg_app.base_dir, cfg_app.static_dir))); + // static + cfg_app.static_dir && this.use(require('koa-static')(path.join(cfg_app.base_dir, cfg_app.static_dir))); - // koa-body - cfg_app.koa_body && app.use(require('koa-body')(cfg_app.koa_body)); + // koa-body + cfg_app.koa_body && this.use(require('koa-body')(cfg_app.koa_body)); - // router - app.use(router.routes()).use(router.allowedMethods()); + // router + this.use(router.routes()).use(router.allowedMethods()); - // server - app.listen(...args); - - // run once - delete app.run; + // server + return super.listen(...args); + } } -module.exports = app; \ No newline at end of file +module.exports = App; \ No newline at end of file diff --git a/lib/cache.js b/lib/cache.js index 6b1baa5..b96263a 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -2,21 +2,34 @@ const {cache: cfg_cache} = require('./config'); class Cache { + /** + * Creat a new `Cache` class + * @public + */ constructor() { if(new.target) { + // @ts-ignore class ChildCache extends this.constructor {} ChildCache.cache = {}; ChildCache.timer = null; + // @ts-ignore ChildCache.setIntervalTime(cfg_cache.clear_time); return ChildCache; } } + /** + * 获取缓存 + * @public + * @static + * @param {string} [key] - 为空时,返回所有缓存 + * @returns {*} + */ static get(key) { if(key === undefined) { return this.cache; } - const now_time = Math.round(new Date() / 1000); + const now_time = Math.round(Date.now() / 1000); if(this.cache[key] && this.cache[key].time > now_time) { return this.cache[key].data; } else { @@ -25,23 +38,45 @@ class Cache } } + /** + * 设置缓存 + * @public + * @static + * @param {string} key - 缓存键 + * @param {*} data - 缓存值 + * @param {number} [cache_time] 单位秒,默认10年 + */ static set(key, data, cache_time) { cache_time || (cache_time = cfg_cache.cache_time || 60 * 60 * 24 * 365 * 10); const now_time = this.time(); this.cache[key] = {data: data, time: cache_time + now_time}; } + /** + * 删除或清理缓存 + * @public + * @static + * @param {string} [key] - 为空时,清理所有缓存 + */ static delete(key) { if(key) { delete this.cache[key]; } else { + // @ts-ignore this.cache = {}; } } + /** + * 设置缓存自动清理 + * @public + * @static + * @param {number} [time] - 清理周期,单位秒;为空或0,则关闭自动清理功能 + */ static setIntervalTime(time) { if(time) { this.timer && clearInterval(this.timer); + // @ts-ignore this.timer = setInterval(() => { const now_time = this.time(); for(let key in this.cache) { @@ -50,17 +85,35 @@ class Cache }, time * 1000); } else { this.timer && clearInterval(this.timer); + // @ts-ignore this.timer = null; } } + /** + * 获取当前时间戳 + * @public + * @static + * @returns {number} + */ static time() { - return Math.round(new Date() / 1000); + return Math.round(Date.now() / 1000); } } +/** + * 缓存store + */ +// @ts-ignore Cache.cache = {}; +/** + * 缓存自动清理定时器 + */ +// @ts-ignore Cache.timer = null; +/** + * 开启缓存自动清理 + */ Cache.setIntervalTime(cfg_cache.clear_time); module.exports = Cache; \ No newline at end of file diff --git a/lib/config.js b/lib/config.js index 1f59686..b7d324c 100644 --- a/lib/config.js +++ b/lib/config.js @@ -40,8 +40,8 @@ const db = { } const log = { - log_level: [], // [error, warning, info, debug, http, sql] - log_handle: function(msg, level) {console.log(`[${format('YY-mm-dd HH:ii:ss')}] [${level}] ${typeof msg == 'String' ? msg : JSON.stringify(msg)}`);} //function(msg, level) {} + log_level: ['app', 'error'], // [app, error, warning, info, debug, http, sql] + log_handle: function(level, ...args) {console.log(`[${format('YY-mm-dd HH:ii:ss')}] [${level}]`, ...args.map(msg => typeof msg == 'string' ? msg : JSON.stringify(msg)));} } const cache = { @@ -80,16 +80,20 @@ const tpl = { } const base_dir = path.dirname(module.parent.parent.parent.filename); -const config = loader(path.join(base_dir, './config')); +const base_config = loader(path.join(base_dir, './config')); +/** + * @module config + * @type {import('./types').Config} + */ module.exports = { - app: {...app, ...config.app, base_dir}, - view: {...view, ...config.view}, - db: {...db, ...config.db}, - log: {...log, ...config.log}, - cache: {...cache, ...config.cache}, - page: {...page, ...config.page}, - routes: config.routes, - cookie: config.cookie, - tpl: {...tpl, ...config.tpl} + app: {...app, ...base_config.app, base_dir}, + view: {...view, ...base_config.view}, + db: {...db, ...base_config.db}, + log: {...log, ...base_config.log}, + cache: {...cache, ...base_config.cache}, + page: {...page, ...base_config.page}, + routes: base_config.routes, + cookie: base_config.cookie, + tpl: {...tpl, ...base_config.tpl} }; \ No newline at end of file diff --git a/lib/context.js b/lib/context.js index bf23e37..2e34df4 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1,7 +1,15 @@ const Ctx = require('./ctx'); +/** + * @extends Ctx + */ class Context extends Ctx { + /** + * Initialize a new `Context` + * @public + * @param {import('./types').Context} ctx + */ constructor(ctx) { super(); this.ctx = ctx; diff --git a/lib/controller.js b/lib/controller.js index 93e8d3d..a3c2e92 100644 --- a/lib/controller.js +++ b/lib/controller.js @@ -1,31 +1,57 @@ const Middleware = require('./middleware'); +/** + * @extends Middleware + */ class Controller extends Middleware { - // 赋值模版数据 + /** + * 模版数据赋值 + * @public + * @param {string} name + * @param {any} value + * @returns {this} + */ $assign(name, value) { this.$view.assign(name, value); return this; } - // 获取模版数据 + /** + * 获取模版数据 + * @public + * @param {string} [name] + * @returns {object} + */ $data(name) { return this.$view.data(name); } - // 直接文件输出 + /** + * 获取文件内容并输出 + * @public + * @param {string} [template] + */ async $load(template) { const content = await this.$view.load(template); this.$show(content); } - // 渲染内容输出 - async $render(data) { - const content = await this.$view.render(data); - this.$show(content); + /** + * 渲染(解析数据)内容并输出 + * @public + * @param {string} content + */ + async $render(content) { + const html = await this.$view.render(content); + this.$show(html); } - // 渲染文件输出 + /** + * 渲染(解析数据)文件并输出 + * @public + * @param {string} [template] + */ async $fetch(template) { const content = await this.$view.fetch(template); this.$show(content); diff --git a/lib/cookie.js b/lib/cookie.js index f845c09..9bda34b 100644 --- a/lib/cookie.js +++ b/lib/cookie.js @@ -1,37 +1,79 @@ const {cookie: cfg_cookie} = require('./config'); const Context = require('./context'); +/** + * @extends Context + */ class Cookie extends Context { + /** + * 设置cookie + * @public + * @param {string} key + * @param {(string|null)} [value] + * @param {import('cookies').SetOption} [options] + * @returns {this} + */ set(key, value, options) { this.ctx.cookies.set(key, value, {...cfg_cookie, ...options}); + return this; } - get(key) { + /** + * 获取cookie + * @public + * @param {string} [key] - 为空,则获取所有cookie + * @param {import('cookies').GetOption} [options] + * @returns {string} + */ + get(key, options) { if(key === undefined) { return this.all(); } - return this.ctx.cookies.get(key); + return this.ctx.cookies.get(key, options); } + /** + * 删除或清理cookie + * @public + * @param {*} [key] - 为空则清理所有cookie + * @returns {this} + */ delete(key) { if(key === undefined) { - return this.clear(); + this.clear(); } else { this.ctx.cookies.set(key, '', {maxAge: 0}); } + return this; } + /** + * 获取所有cookie + * @public + * @returns {object} + */ all() { const cookies = {}; this.keys().forEach(key => cookies[key] = this.get(key)); return cookies; } + /** + * 清理所有cookie + * @public + * @returns {this} + */ clear() { this.keys().forEach(key => this.delete(key)); + return this; } + /** + * 获取所有cookie key + * @public + * @returns {array} + */ keys() { const cookie_header = this.ctx.request.headers.cookie || ''; return cookie_header ? cookie_header.split(';').map(value => value.split('=')[0].trim()) : []; diff --git a/lib/ctx.js b/lib/ctx.js index 64f3a19..8cefda3 100644 --- a/lib/ctx.js +++ b/lib/ctx.js @@ -1,5 +1,14 @@ +// @ts-nocheck const loader = require('./loader'); +/** + * @typedef {typeof import('./types')} Ctx + */ + +/** + * @class Ctx + * @type {Ctx} + */ const Ctx = new Proxy(class {}, { construct() { return new Proxy({__proto__: arguments[2].prototype}, { diff --git a/lib/db.js b/lib/db.js index f4077e0..9ec5c6a 100644 --- a/lib/db.js +++ b/lib/db.js @@ -2,26 +2,60 @@ const {db: cfg_db} = require('./config'); const md5 = require('./utils/md5'); const Context = require('./context'); +/** + * @typedef {import('./types').Pagination} Pagination + * @typedef {import('./types').PaginationInstance} PaginationInstance + * @typedef {import('./types').Pool} Pool + * @typedef {import('./types').PoolConfig} PoolConfig + * @typedef {import('./types').PoolConnection} PoolConnection + * @typedef {import('./types').QueryOptions} QueryOptions + * @typedef {import('./types').OkPacket} OkPacket + * @typedef {import('./types').RowData} RowData + * @typedef {import('./types').ListData} ListData + * @typedef {import('./types').FieldInfo} FieldInfo + * @typedef {import('./types').PoolMap} PoolMap + */ + //连接池 +/** + * @type {PoolMap} + */ const pool = new Map(); //事务连接 const trans = new Map(); //事务嵌套 const nest = new Map(); +/** + * @extends Context + */ class Db extends Context { + /** + * Initialize a new `Db` + * @public + * @param {(import('./types').Context|object)} ctx + * @param {(string|PoolConfig)} [options] - 数据库配置标识或连接参数 + */ constructor(ctx, options) { ctx = ctx || {}; super(ctx); this._config = null; this._table = ''; this._options = {}; + /** + * @type {string} + */ this._queryStr = ''; this._tableField = {}; this.connect(options); } + /** + * 重置参数 + * @public + * @returns {this} + */ reset() { this._options = { distinct: '', @@ -41,10 +75,19 @@ class Db extends Context return this; } + /** + * 连接数据库连接池 + * @public + * @param {(string|PoolConfig)} options - 数据库配置标识或连接参数 + * @returns {this} + */ connect(options='default') { this._config = typeof options === 'string' ? cfg_db[options] : options; this.reset(); + /** + * @type {Pool} + */ let cur_pool = pool.get(this._config); if(!cur_pool) { switch(this._config.type) { @@ -62,6 +105,11 @@ class Db extends Context return this; } + /** + * 关闭数据库连接池 + * @public + * @returns {Promise} + */ async close() { return new Promise((resolve, reject) => { pool.has(this._config) && pool.get(this._config).end(err => { @@ -79,6 +127,11 @@ class Db extends Context }); } + /** + * 释放数据库连接 + * @public + * @param {PoolConnection} conn - 数据库连接 + */ release(conn) { try { conn.release(); @@ -90,6 +143,12 @@ class Db extends Context } } + /** + * 获取数据库连接 + * @private + * @param {Pool} p + * @returns {Promise} + */ async _creatConnect(p) { return new Promise((resolve, reject) => { p.getConnection((err, connection) => { @@ -106,10 +165,21 @@ class Db extends Context }); } + /** + * 获取数据库连接 + * @private + * @returns {Promise} + */ async _getConnect() { return trans.get(this.ctx) || await this._creatConnect(pool.get(this._config)); } + /** + * 开启事务 + * @public + * @param {function} [fun] + * @returns {Promise} + */ async startTrans(fun) { const conn = await this._getConnect(); trans.set(this.ctx, conn); @@ -156,6 +226,11 @@ class Db extends Context }); } + /** + * 事务回滚 + * @public + * @returns {Promise} + */ async rollback() { const conn = await this._getConnect(); const trans_nest = nest.get(conn) || 0; @@ -179,6 +254,11 @@ class Db extends Context }); } + /** + * 提交事务 + * @public + * @returns {Promise} + */ async commit() { const conn = await this._getConnect(); const trans_nest = nest.get(conn) || 0; @@ -210,10 +290,21 @@ class Db extends Context }); } + /** + * 执行sql查询 + * @public + * @param {string} sql - sql语句或参数 + * @param {*} params - sql参数 + * @param {*} [reset=true] - 是否重置参数 + * @returns {Promise} + */ async query(sql, params, reset=true) { params !== false && reset !== false && this.reset(); params || (params = []); + /** + * @type {PoolConnection} + */ const conn = await this._getConnect(); return new Promise((resolve, reject) => { @@ -234,6 +325,12 @@ class Db extends Context }); } + /** + * 设置数据表名 + * @public + * @param {string} [table] - 表名字,不带前缀 + * @returns {this} + */ table(table) { if(table) { this._table = table.trim().replace(/ +/g, ' '); @@ -241,6 +338,12 @@ class Db extends Context return this; } + /** + * 设置数据表名前缀 + * @public + * @param {string} prefix - 表名前缀 + * @returns {this} + */ prefix(prefix) { if(typeof prefix !== 'undefined') { this._options.prefix = prefix.trim(); @@ -248,11 +351,21 @@ class Db extends Context return this; } + /** + * 设置distinct查询 + * @returns {this} + */ distinct() { this._options.distinct = 'distinct'; return this; } + /** + * 设置查询字段,支持多次调用 + * @public + * @param {(string|array)} field - 表名前缀 + * @returns {this} + */ field(field) { if(field) { if(typeof field === 'string') { @@ -263,6 +376,14 @@ class Db extends Context return this; } + /** + * 设置表连接,支持多次调用 + * @public + * @param {string} table - 要连接的表名 + * @param {string} on - 连接条件 + * @param {string} [type=left] - 连接方式 + * @returns {this} + */ join(table, on, type='left') { if(table) { this._options.join[table.trim().replace(/ +/g, ' ')] = {on, type}; @@ -270,6 +391,13 @@ class Db extends Context return this; } + /** + * 设置查询条件,支持多次调用 + * @public + * @param {object} where - 查询条件 + * @param {object} [logic] - 多次调用之间的连接逻辑,默认and + * @returns {this} + */ where(where, logic) { if(where) { this._options.where.push([where, logic]); @@ -277,6 +405,12 @@ class Db extends Context return this; } + /** + * 设置分组查询 + * @public + * @param {string} field - 分组字段 + * @returns {this} + */ group(field) { if(field) { this._options.group = 'group by `' + field.trim().replace(/\./g, '`.`') + '`'; @@ -284,13 +418,26 @@ class Db extends Context return this; } - having(condition) { - if(condition) { - this._options.having = 'having ' + condition; + /** + * 设置having筛选 + * @public + * @param {string} having - 筛选条件 + * @returns {this} + */ + having(having) { + if(having) { + this._options.having = 'having ' + having; } return this; } + /** + * 设置排序方式,支持多次调用 + * @public + * @param {string} field - 排序字段 + * @param {string} [order] - 排序方式,默认asc + * @returns {this} + */ order(field, order='asc') { if(field) { this._options.order[field.trim()] = order === 'asc' ? 'asc' : 'desc'; @@ -298,33 +445,65 @@ class Db extends Context return this; } + /** + * 设置查询数量 + * @public + * @param {number} offset - 开始位置 + * @param {number} [rows] - 行数,不传,则按offset + * @returns {this} + */ limit(offset, rows) { if(typeof offset === 'undefined') return this; - offset = offset ? parseInt(offset) : 0; - rows = rows ? parseInt(rows) : null; + offset = offset ? offset : 0; + rows = rows ? rows : null; this._options.limit = 'limit ' + offset + (rows ? ',' + rows : ''); return this; } + /** + * 按分页设置查询数量 + * @public + * @param {number} page - 页码 + * @param {number} pageSize - 每页行数 + * @returns {this} + */ page(page, pageSize) { if(typeof page === 'undefined') return this; - page = page ? parseInt(page) : 1; - pageSize = pageSize ? parseInt(pageSize) : 10; + page = page ? page : 1; + pageSize = pageSize ? pageSize : 10; this.limit((page - 1) * pageSize, pageSize); this._options.page = {page, pageSize}; return this; } + /** + * 设置查询结果缓存 + * @public + * @param {number} time - 为0则不缓存 + * @returns {this} + */ cache(time) { this._options.cache_time = time; return this; } + /** + * 设置返回sql语句(最终会返回序列化后的sql,不会真实查询数据库) + * @public + * @param {boolean} [fetch] - 是否返回sql + * @returns {this} + */ getSql(fetch = true) { this._options.getSql = fetch; return this; } + /** + * 设置字段过滤(插入或更新数据时) + * @public + * @param {(boolean|string|string[])} [field] - 允许保存字段,为true,则按数据表字段 + * @returns {this} + */ allowField(field = true) { if(!field) { field = false; @@ -335,6 +514,12 @@ class Db extends Context return this; } + /** + * 设置要插入或更新的数据 + * @public + * @param {object} data - 要插入或更新的数据 + * @returns {this} + */ data(data) { if(data) { this._options.data = {...this._options.data, ...data}; @@ -342,6 +527,12 @@ class Db extends Context return this; } + /** + * 获取多条数据 + * @public + * @param {object} condition - 查询条件 + * @returns {Promise<(string|ListData)>} + */ async select(condition) { condition && (this._options.where = [], this._options.where.push([condition])); @@ -379,9 +570,15 @@ class Db extends Context return result; } - return await this.query(this._queryStr, params); + return await this.query(this._queryStr, params) || []; } + /** + * 获取一条数据 + * @public + * @param {object} condition - 查询条件 + * @returns {Promise<(RowData|null)>} + */ async find(condition) { condition && (this._options.where = [], this._options.where.push([condition])); this.limit(1); @@ -394,6 +591,12 @@ class Db extends Context return rows.length ? rows[0] : null; } + /** + * 获取一个字段值 + * @public + * @param {string} field - 字段 + * @returns {Promise<(string|number|null)>} + */ async value(field) { this._options.field = []; this.field(field); @@ -406,26 +609,63 @@ class Db extends Context return row && row[field]; } + /** + * 获取总数 + * @public + * @param {string} [field] - 字段 + * @returns {Promise<(string|number|0)>} + */ async count(field='*') { - return await this.value(`count(${field})`); + return await this.value(`count(${field})`) || 0; } + /** + * 获取字段最大值 + * @public + * @param {string} field - 字段 + * @returns {Promise<(string|number|0)>} + */ async max(field) { return await this.value(`max(${field})`); } + /** + * 获取字段最小值 + * @public + * @param {string} field - 字段 + * @returns {Promise<(string|number|0)>} + */ async min(field) { return await this.value(`min(${field})`); } + /** + * 获取字段平均值 + * @public + * @param {string} field - 字段 + * @returns {Promise<(string|number|0)>} + */ async avg(field) { return await this.value(`avg(${field})`); } + /** + * 获取字段总和 + * @public + * @param {string} field - 字段 + * @returns {Promise<(string|number|0)>} + */ async sum(field) { return await this.value(`sum(${field})`); } + /** + * 获取字段列数据 + * @public + * @param {string} field - 数据字段 + * @param {string} [key] - key字段,不设置返回数据数组,设置则返回{key: field}对象数组 + * @returns {Promise<(string|ListData|object)>} + */ async column(field, key) { this._options.field = []; this.field(field); @@ -435,7 +675,7 @@ class Db extends Context return await this.select(); } - const rows = await this.select(); + const rows = /** @type {ListData} */(await this.select()); const result = key ? {} : []; rows.forEach(row => { if(key) { @@ -447,18 +687,31 @@ class Db extends Context return result; } + /** + * 获取多条数据(按分页) + * @public + * @param {object} [param0] - 分页参数 + * @param {number} [param0.page] - 页码 + * @param {number} [param0.page_size] - 每页行数 + * @param {PaginationInstance} [param0.pagination] - 分页类实例 + * @returns {Promise} - [ListData, PaginationInstance] + */ async pagination({page, page_size, pagination} = {}) { !page && (page = this._options.page.page); !page_size && (page_size = this._options.page.pageSize); if(!pagination) { - pagination = this.$pagination.__node.isClass ? this.$pagination : this.$.pagination; + pagination = this.$pagination.__node && this.$pagination.__node.isClass ? this.$pagination : this.$.pagination; } const options = {...this._options}; // 暂存options + // @ts-ignore const total = await this.count(); + // @ts-ignore pagination.total(total); + // @ts-ignore page ? pagination.page(page) : (page = pagination.page()); + // @ts-ignore page_size ? pagination.pageSize(page_size) : (page_size = pagination.pageSize()); if(total) { @@ -470,6 +723,12 @@ class Db extends Context } } + /** + * 插入一条数据 + * @public + * @param {object} data - 待插入数据 + * @returns {Promise<(string|OkPacket)>} + */ async insert(data) { data && (this._options.data = data); @@ -489,6 +748,13 @@ class Db extends Context return await this.query(this._queryStr, params); } + /** + * 更新数据 + * @public + * @param {object} data - 更新数据 + * @param {object} condition - 更新条件 + * @returns {Promise<(string|OkPacket)>} + */ async update(data, condition) { data && (this._options.data = data); condition && (this._options.where = [], this._options.where.push([condition])); @@ -517,18 +783,45 @@ class Db extends Context return await this.query(this._queryStr, params); } + /** + * 字段值增加 + * @public + * @param {string} field - 字段 + * @param {number} [step=1] - 增加值,默认1 + * @returns {Promise<(string|OkPacket)>} + */ async inc(field, step) { return await this.update({[field]: ['inc', step]}); } + /** + * 字段值减少 + * @public + * @param {string} field - 字段 + * @param {number} [step=1] - 减少值,默认1 + * @returns {Promise<(string|OkPacket)>} + */ async dec(field, step) { return await this.update({[field]: ['dec', step]}); } + /** + * 字段值执行自定义表达式 + * @public + * @param {string} field - 字段 + * @param {string} value - 自定义表达式 + * @returns {Promise<(string|OkPacket)>} + */ async exp(field, value) { return await this.update({[field]: ['exp', value]}); } + /** + * 删除数据 + * @public + * @param {object} condition - 山粗条件 + * @returns {Promise<(string|OkPacket)>} + */ async delete(condition) { condition && (this._options.where = [], this._options.where.push([condition])); @@ -550,6 +843,14 @@ class Db extends Context return await this.query(this._queryStr, params); } + /** + * 执行sql查询 + * @public + * @param {string} sql - sql语句或参数 + * @param {*} params - sql参数 + * @param {*} [reset=true] - 是否重置参数 + * @returns {Promise<(string|OkPacket|ListData|RowData)>} + */ async execute(sql, params, reset=true) { this._queryStr = sql; if(this._options.getSql) { @@ -570,6 +871,12 @@ class Db extends Context return await this.query(this._queryStr, params, reset); } + /** + * 获取数据表信息 + * @public + * @param {string} table - 表名字 + * @returns {Promise} + */ async tableInfo(table) { const get_sql = this._options.getSql; this._options.getSql = false; @@ -578,11 +885,24 @@ class Db extends Context return table_info; } + /** + * 获取数据表字段 + * @public + * @param {string} [table] - 表名字 + * @returns {Promise} + */ async tableField(table) { const table_info = await this.tableInfo(table); return table_info.map(item => item.Field); } + /** + * 序列化sql语句 + * @public + * @param {string} sql - sql语句或参数 + * @param {*} params - sql参数 + * @returns {string} + */ format(sql, params) { params || (params = []); return require('mysql').format(sql, params); @@ -740,20 +1060,41 @@ class Db extends Context return this._options.data; } + /** + * 获取sql缓存 + * @param {string} key + * @returns {*} + */ getCache(key) { return Db.cache.get(key); } + /** + * 设置sql缓存 + * @param {string} key + */ setCache(key, data, cache_time) { Db.cache.set(key, data, cache_time); } + /** + * 删除sql缓存 + * @param {string} key + */ deleteCache(key) { Db.cache.delete(key); } } +/** + * 设置数据库缓存实例 + * @type {typeof import('./cache')} + */ +// @ts-ignore Db.cache = new (require('./cache'))(); +/** + * 开启缓存自动清理 + */ Db.cache.setIntervalTime(20 * 60 * 60); module.exports = Db; \ No newline at end of file diff --git a/lib/loader.js b/lib/loader.js index 505cb32..3d67c17 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -5,12 +5,17 @@ const {isFileSync, isDirSync} = require('./utils/fs'); const nodeType = {}; function loader(dir='./', ...args) { - const node = {}; + // @ts-ignore + dir = pt.isAbsolute(dir) ? dir : pt.join(pt.dirname(module.parent.filename), dir); + const dirPath = pt.join(dir, './'); + const dirType = isFileSync(dir + '.js') ? 'file' : 'dir'; + const node = dirType == 'file' ? require(dir) : {}; const info = new Map(); info.set(node, { - path: pt.isAbsolute(dir) ? pt.join(dir, './') : pt.join(pt.dirname(module.parent.filename), dir, './'), - nodeType: 'dir', - isClass: false + path: dirPath, + nodeType: dirType, + // @ts-ignore + isClass: isClass(node) }); return creatLoader(node); @@ -51,6 +56,7 @@ function loader(dir='./', ...args) { info.set(_node, { path: _nodePath, nodeType: nodeType[_nodePath], + // @ts-ignore isClass: isClass(_node) }); node[prop] = creatLoader(_node); diff --git a/lib/logger.js b/lib/logger.js index bd980f8..99bad63 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,48 +1,117 @@ const {app: cfg_app, log: cfg_log} = require('./config'); +/** + * @typedef {import('./types').LogHandle} LogHandle + */ + class Logger { + /** + * Creat a new `Logger` class + * @public + * @static + * @param {LogHandle} [handle] + */ constructor(handle) { if(new.target) { + // @ts-ignore class ClildLogger extends this.constructor {} + // @ts-ignore ClildLogger.setHandle(handle); return ClildLogger; } } + /** + * 输出error日志 + * @public + * @static + * @param {...any} args + */ static error(...args) { - args.forEach(msg => this.log(msg, 'error')); + this.log('error', ...args); } + /** + * 输出warning日志 + * @public + * @static + * @param {...any} args + */ static warning(...args) { - args.forEach(msg => this.log(msg, 'warning')); + this.log('warning', ...args); } + /** + * 输出info日志 + * @public + * @static + * @param {...any} args + */ static info(...args) { - args.forEach(msg => this.log(msg, 'info')); + this.log('info', ...args); } + /** + * 输出debug日志 + * @public + * @static + * @param {...any} args + */ static debug(...args) { - args.forEach(msg => this.log(msg, 'debug')); + this.log('debug', ...args); } + /** + * 输出sql日志 + * @public + * @static + * @param {...any} args + */ static sql(...args) { - args.forEach(msg => this.log(msg, 'sql')); + this.log('sql', ...args); } + /** + * 输出http日志 + * @public + * @static + * @param {...any} args + */ static http(...args) { - args.forEach(msg => this.log(msg, 'http')); + this.log('http', ...args); } - static log(msg, level='info') { - (cfg_app.app_debug || ~cfg_log.log_level.indexOf(level)) && this.handle(msg, level); + /** + * 输出error日志 + * @public + * @static + * @param {string} level - 日志级别、标识 + * @param {...any} args - 日志内容 + */ + static log(level='info', ...args) { + (cfg_app.app_debug || ~cfg_log.log_level.indexOf(level)) && this.handle(level, ...args); } + /** + * 设置日志handle + * @public + * @static + * @param {LogHandle} handle + * @returns {Logger} + */ static setHandle(handle) { - this.handle = typeof(handle) == 'function' ? handle : cfg_log.log_handle; + /** + * @type {LogHandle} + */ + this.handle = typeof handle == 'function' ? handle : cfg_log.log_handle; + return this; } } +/** + * 设置日志handle + */ Logger.setHandle(cfg_log.log_handle); module.exports = Logger; \ No newline at end of file diff --git a/lib/middleware.js b/lib/middleware.js index 03b5239..d6d1408 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -1,24 +1,56 @@ const Context = require('./context'); +/** + * @extends Context + */ class Middleware extends Context { + /** + * Initialize a new `Middleware` + * @public + * @param {import('./types').Context} ctx + * @param {import('./types').Middleware} [next] + */ constructor(ctx, next) { super(ctx); this.$next = next; } - $show(content) { - this.$response.show(content); + /** + * 直接输出内容 + * @public + * @param {*} data + */ + $show(data) { + this.$response.show(data); } - + + /** + * 跳转重定向 + * @public + * @param {(string|object)} url + * @param {number} [status] + */ $redirect(url, status) { this.$response.redirect(url, status); } + /** + * 输出成功提示(html|json) + * @public + * @param {(string|object)} [msg] + * @param {(string|object)} [url] + */ $success(msg, url) { this.$response.success(msg, url); } + /** + * 输出错误提示(html|json) + * @public + * @param {(string|object)} [msg] + * @param {(string|object)} [url] + */ $error(msg, url) { this.$response.error(msg, url); } diff --git a/lib/model.js b/lib/model.js index df8fbe3..18bcd40 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1,25 +1,55 @@ const Context = require('./context'); const {toLine} = require('./utils/str'); +/** + * @typedef {import('./types').OkPacket} OkPacket + * @typedef {import('./types').RowData} RowData + * @typedef {import('./types').ListData} ListData + */ + +/** + * @extends Context + */ class Model extends Context { + /** + * Initialize a new `Model` + * @public + * @param {import('./types').Context} ctx + */ constructor(ctx) { super(ctx); this.connection = null; this.table = toLine(this.constructor.name); this.pk = 'id'; + this._db; } + /** + * 插入一条数据 + * @public + * @param {object} data - 待插入数据 + * @returns {Promise} + */ async add(data) { + // @ts-ignore return await this.db.allowField().insert(data); } + /** + * 更新或插入一条数据 + * @public + * @param {object} data - 更新或插入数据 + * @param {object} [condition] - 更新条件,不为空或data包含主键则更新数据,否则插入数据 + * @returns {Promise} + */ async save(data, condition = {}) { if(data[this.pk] || Object.keys(condition).length) { if(data[this.pk]) { condition[this.pk] = data[this.pk]; delete data[this.pk]; } + // @ts-ignore return await this.db.allowField().update(data, condition); } @@ -27,19 +57,42 @@ class Model extends Context return await this.add(data); } + /** + * 删除数据 + * @public + * @param {object} condition - 删除条件 + * @returns {Promise} + */ async del(condition) { + // @ts-ignore return await this.db.delete(condition); } + /** + * 查询一条数据 + * @public + * @param {object} condition - 查询条件 + * @returns {Promise<(RowData|null)>} + */ async get(condition) { return await this.db.find(condition); } + /** + * 查询多条数据 + * @public + * @param {object} condition - 查询条件 + * @returns {Promise<(ListData)>} + */ async all(condition) { + // @ts-ignore return await this.db.select(condition); } - //数据库实例 + /** + * 数据库实例 + * @type {typeof import('./db').prototype} + */ get db() { if(!this._db){ this._db = new (require('./db'))(this.ctx); diff --git a/lib/pagination.js b/lib/pagination.js index 7920f7b..a166975 100644 --- a/lib/pagination.js +++ b/lib/pagination.js @@ -1,8 +1,16 @@ const {page: cfg_page} = require('./config'); const Context = require('./context'); +/** + * @extends Context + */ class Pagination extends Context { + /** + * Initialize a new `Pagination` + * @public + * @param {import('./types').Context} ctx + */ constructor(ctx) { super(ctx); this._page = 1; @@ -12,8 +20,17 @@ class Pagination extends Context this.init(); } - // 分页配置 + /** + * 分页配置 + * @public + * @param {import('./types').PageConfig} [options] + * @returns {this} + */ init(options) { + /** + * @type {import('./types').PageConfig} + * @private + */ this.options = {...cfg_page, ...options}; this._pageKey = this.options.page_key; this._keyOrigin = this.options.key_origin; @@ -26,17 +43,27 @@ class Pagination extends Context return this; } - // 设置或获取当前页 + /** + * 设置或获取当前页码 + * @public + * @param {number} [page] + * @returns {(this|number)} + */ page(page) { if(typeof page != 'undefined') { - this._page = parseInt(page) || 1; + this._page = page || 1; return this; } else { return this._page; } } - // 设置或获取分页大小 + /** + * 设置或获取分页大小 + * @public + * @param {number} [page_size] + * @returns {(this|number)} + */ pageSize(page_size) { if(page_size) { this._pageSize = page_size; @@ -46,17 +73,29 @@ class Pagination extends Context } } - // 设置或获取总数 + /** + * 设置或获取总数 + * @public + * @param {number} [total] + * @returns {(this|number)} + */ total(total) { if(typeof total != 'undefined') { - this._total = parseInt(total) || 0; + this._total = total || 0; return this; } else { return this._total; } } - // 渲染html + /** + * 渲染生成分页html + * @public + * @param {number} [total] - 数据总数 + * @param {number} [page] - 当前分页 + * @param {number} [page_size] - 分页大小 + * @returns {string} + */ render(total, page, page_size) { this.total(total); this.page(page); @@ -98,7 +137,7 @@ class Pagination extends Context let list = ''; let list_start = 1; let list_end = this.options.page_length; - const list_left = parseInt(this.options.page_length / 2); + const list_left = this.options.page_length / 2; if(this._page >= list_end) { list_start = this._page - list_left; diff --git a/lib/response.js b/lib/response.js index 0658e13..f9af727 100644 --- a/lib/response.js +++ b/lib/response.js @@ -2,61 +2,115 @@ const {tpl: cfg_tpl} = require('./config'); const {parseError} = require('./utils/error'); const Context = require('./context'); +/** + * @extends Context + */ class Response extends Context { + /** + * Initialize a new `Response` + * @public + * @param {import('./types').Context} ctx + */ constructor(ctx) { super(ctx); - this.tpl_jump = cfg_tpl.jump; - this.wait = 3; - this.tpl_exception = cfg_tpl.exception; + this._tpl_jump = cfg_tpl.jump; + this._wait = 3; + this._tpl_exception = cfg_tpl.exception; } + /** + * 直接输出内容 + * @public + * @param {*} data + */ show(data) { this.ctx.body = data; } + /** + * 跳转重定向 + * @public + * @param {*} url + * @param {number} status + */ redirect(url, status=302) { this.ctx.status = status; this.ctx.redirect(this.$url.build(url)); } + /** + * 输出成功提示(html|json) + * @public + * @param {(string|object)} [msg] + * @param {(string|object)} [url] + */ success(msg='操作成功!', url) { typeof msg == 'object' && ([msg, url] = ['操作成功!', msg]); url = typeof url == 'string' ? this.$url.build(url) : (url || this.ctx.header.referer); this.jump(msg, url, 1); } + /** + * 输出错误提示(html|json) + * @public + * @param {(string|object)} [msg] + * @param {(string|object)} [url] + */ error(msg='操作失败!', url) { typeof msg == 'object' && ([msg, url] = ['操作失败!', msg]); url = typeof url == 'string' ? this.$url.build(url) : (url || 'javascript:history.back(-1);'); this.jump(msg, url, 0); } + /** + * jump + * @api protected + * @param {(string|object)} msg + * @param {(string|object)} url + * @param {number} state + */ jump(msg, url, state=1) { - const tplData = {state, msg, url, wait: this.wait}; - this.ctx.body = this.isAjax() ? {state, msg, data: url} : this.tpl_jump.replace(/\{\$(\w+)\}/g, (...args) => { + const tplData = {state, msg, url, wait: this._wait}; + this.ctx.body = this.isAjax() ? {state, msg, data: url} : this._tpl_jump.replace(/\{\$(\w+)\}/g, (...args) => { return tplData[args[1]]; }); } + /** + * 设置跳转等待时间 + * @public + * @param {number} time - 单位秒 + * @returns {this} + */ wait(time) { - this.wait = time; + this._wait = time; return this; } + /** + * 输出异常信息(html|json) + * @public + * @param {Error} err + */ exception(err) { const escapeHtml = require('escape-html'); const tplData = parseError(err); tplData.msg = escapeHtml(tplData.msg); tplData.code = '' + tplData.code.map(str => escapeHtml(str)).join('') + ''; tplData.stack = tplData.stack.map(str => escapeHtml(str)).join('
    '); - this.ctx.body = this.isAjax() ? {state: 0, msg: tplData.msg} : this.tpl_exception.replace(/\{\$(\w+)\}/g, (...args) => { + this.ctx.body = this.isAjax() ? {state: 0, msg: tplData.msg} : this._tpl_exception.replace(/\{\$(\w+)\}/g, (...args) => { return tplData[args[1]]; }); } + /** + * 判断是否ajax请求 + * @public + * @returns {boolean} + */ isAjax() { - return this.ctx.headers['x-requested-with'] && this.ctx.headers['x-requested-with'].toLowerCase() == 'xmlhttprequest' + return this.ctx.headers['x-requested-with'] && String(this.ctx.headers['x-requested-with']).toLowerCase() == 'xmlhttprequest' || this.ctx.request.type.toLowerCase() == 'application/json' || this.ctx.query.is_ajax || this.ctx.request.body && this.ctx.request.body.is_ajax diff --git a/lib/router.js b/lib/router.js index 1cd9c16..f58324b 100644 --- a/lib/router.js +++ b/lib/router.js @@ -1,4 +1,4 @@ -const router = require('@koa/router')(); +const router = new (require('@koa/router')); const {app: cfg_app, routes: cfg_routes} = require('./config'); const {toLine} = require('./utils/str'); const run = require('./run'); @@ -11,7 +11,7 @@ if (cfg_routes) { if(!~methods.indexOf(item.method)) { throw new Error(`RouteError: 未知路由方法:${item.method}`); } - if(typeof item.path === 'function'){ + if(typeof item.path === 'function') { router[item.method](item.url, item.path); continue; } @@ -39,7 +39,7 @@ router.all(cfg_app.app_multi ? '/:APP?/:CONTROLLER?/:ACTION?' : '/:CONTROLLER?/: delete ctx.params.APP; delete ctx.params.CONTROLLER; delete ctx.params.ACTION; - await run(ctx, next); + ctx.APP != 'favicon.ico' && ctx.CONTROLLER != 'favicon.ico' && await run(ctx, next); }); module.exports = router; \ No newline at end of file diff --git a/lib/types.js b/lib/types.js new file mode 100644 index 0000000..f9a815e --- /dev/null +++ b/lib/types.js @@ -0,0 +1,286 @@ +//------------------系统核心库------------------ +/** + * @typedef {Object} Core + * @property {typeof import('./app')} App + * @property {typeof import('./cache')} Cache + * @property {typeof import('./context')} Context + * @property {typeof import('./controller')} Controller + * @property {typeof import('./cookie')} Cookie + * @property {typeof import('./db')} Db + * @property {typeof import('./logger')} Logger + * @property {typeof import('./middleware')} Middleware + * @property {typeof import('./model')} Model + * @property {typeof import('./pagination')} Pagination + * @property {typeof import('./response')} Response + * @property {typeof import('./upload')} Upload + * @property {typeof import('./url')} Url + * @property {typeof import('./view')} View + * @property {Utils} utils + */ + + +//------------------系统App配置--------------------- +/** + * @typedef {Object} AppConfig - 系统配置 + * @property {boolean} [app_debug=true] - 调试模式 + * @property {boolean} [app_multi=false] - 是否开启多应用 + * @property {string} [default_app=app] - 默认应用 + * @property {string} [default_controller=index] - 默认控制器 + * @property {string} [default_action=index] - 默认方法 + * @property {string} [common_app=common] - 公共应用,存放公共模型及逻辑 + * @property {string} [controller_folder=controller] - 控制器目录名 + * @property {(string|boolean)} [static_dir=''] - 静态文件目录,相对于应用根目录,为空或false时,关闭静态访问 + * @property {(string|boolean|null|object)} [koa_body=null] - koa-body配置参数,为''、null、false时,关闭koa-body + * @property {string} [base_dir] - 应用根目录(会自动计算) + */ + +/** + * @typedef {Object} ViewConfig - 模板配置 + * @property {string} [view_folder=view] - 模板目录名 + * @property {string} [view_depr='/'] - 模版文件名分割符,'/'代表二级目录 + * @property {string} [view_ext='.htm'] - 模版文件后缀 + * @property {string} [view_engine=art-template] - 默认模版引擎,字符串或引擎类 + * @property {object} [view_filte={}] - 模版函数 + */ + +/** + * @typedef {Object} DbConfigItem - 数据库参数 + * @property {string} [type=mysql] - 数据库类型 + * @property {string} [host='127.0.0.1'] - 服务器地址 + * @property {string} [database=jj] - 数据库名 + * @property {string} [user=root] - 数据库用户名 + * @property {string} [password=''] - 数据库密码 + * @property {string} [port=''] - 数据库连接端口 + * @property {string} [charset=utf8] - 数据库编码默认采用utf8 + * @property {string} [prefix=jj_] - 数据库表前缀 + */ + +/** + * @typedef {object} DbConfig - 数据库配置 + * @property {DbConfigItem} default - 数据库参数 + */ + +/** + * @callback LogHandle - 日志驱动 + * @param {string} level - 日志级别 + * @param {array} args - 日志数据,支持多个,支持对象 + */ + +/** + * @typedef {object} LogConfig - 日志配置 + * @property {array} [log_level] - 允许输出的日志级别 + * @property {LogHandle} [log_handle] - 日志驱动 + */ + +/** + * @typedef {Object} CacheConfig - 缓存配置 + * @property {number} [cache_time='60 * 60 * 24'] - 缓存时间,默认1天,为空或false则为10年 + * @property {number} [clear_time=undefined] - 缓存自动清理周期,undefined: 清理一次, 0: 关闭自动清理, >0: 为周期时间,单位秒 + */ + +/** + * @typedef {Object} PageConfig - 分页配置 + * @property {string} [page_key=page] - 分页标识 + * @property {string} [key_origin=query] - page_key来源 + * @property {number} [page_size=10] - page_key来源 + * @property {number} [page_length=5] - page_key来源 + * @property {string} [url_page] - page_key来源 + * @property {string} [url_index] - page_key来源 + * @property {string} [index_tpl] - 首页模板 + * @property {string} [end_tpl] - 末页模板 + * @property {string} [prev_tpl] - 上一页模板 + * @property {string} [next_tpl] - 下一页模板 + * @property {string} [list_tpl] - 数字页模板 + * @property {string} [active_tpl] - 当前页模板 + * @property {string} [info_tpl] - 分页信息模板 + * @property {string} [template] - 渲染模板 + */ + +/** + * @typedef {Object} RouteConfigItem - 路由配置 + * @property {string} [method=all] - 请求方法,支持['all', 'get', 'put', 'post', 'patch', 'delete', 'del'] + * @property {string} url - 请求url,支持变量正则,详细参考@koa/router + * @property {(string|Middleware)} path - 响应地址(支持智能解析)或中间件函数,如果为中间件函数,则不会再执行后续代码 + * @property {string} [type='AppConfig.controller_folder'] - 响应类型,即path对应的类型,支持controller、middleware、view(ViewConfig.view_folder) + * @property {string} [name] - 路由命名,命一个名字后,可以使用Url类反向编译路由url + */ + +/** + * @typedef {RouteConfigItem[]} RouteConfig - 路由配置 + */ + +/** + * @typedef {import('cookies').SetOption} CookieConfig - Cookie配置,一般不用设置 + */ + +/** + * @typedef {Object} TplConfig - 跳转、调试模板配置 + * @property {string} [jump] - 跳转模板,默认require('./tpl/jump') + * @property {string} [exception] - 调试异常输出模板,默认require('./tpl/exception') + */ + +/** + * @typedef {Object} Config - 系统配置 + * @property {AppConfig} [app] + * @property {ViewConfig} [view] + * @property {DbConfig} [db] + * @property {LogConfig} [log] + * @property {CacheConfig} [cache] + * @property {PageConfig} [page] + * @property {RouteConfig} [routes] + * @property {CookieConfig} [cookie] + * @property {TplConfig} [tpl] + */ + + +//------------------分页类-------------------- +/** + * @typedef {typeof import('./pagination')} Pagination - 分页类 + * @typedef {typeof import('./pagination').prototype} PaginationInstance - 分页类实例 + */ + + +//------------------数据库类-------------------- +/** + * @typedef {import('mysql').Pool} Pool - 连接池 + * @typedef {import('mysql').PoolConfig} PoolConfig - 连接池配置 + * @typedef {import('mysql').PoolConnection} PoolConnection - 连接池连接 + * @typedef {import('mysql').QueryOptions} QueryOptions - 查询参数 + * @typedef {import('mysql').OkPacket} OkPacket - 数据库查询结果 + * @typedef {Object} RowData - 单条数据 + * @typedef {Array} ListData - 多条数据 + * @typedef {Object} FieldInfo - 字段信息 + * @property {string} Field + * @property {string} Type + * @property {string} Null + * @property {string} Key + * @property {string} Default + * @property {string} Extra + * @typedef {Map} PoolMap + */ + + +//------------------上传类-------------------- +/** + * @typedef {Object} UploadData + * @property {string} [filename] + * @property {string} [extname] + * @property {string} [savename] + * @property {string} [filepath] + * @property {string} [name] + * @property {number} [size] + * @property {string} [mimetype] + * @property {string} [hash] + * / + * +/** + * @typedef {Object} ValidateRule + * @property {number} [size] - 文件大小 + * @property {string} [ext] - 文件名后缀,多个用','隔开 + * @property {string} [type] - 文件mimetype,多个用','隔开 + */ + + +//------------------系统工具-------------------- +/** + * @typedef {Object} Utils + * @property {typeof import('./utils/date')} date + * @property {typeof import('./utils/error')} error + * @property {typeof import('./utils/fs')} fs + * @property {typeof import('./utils/md5')} md5 + * @property {typeof import('./utils/str')} str + */ + + +//------------------系统Context-------------------- +/** + * @typedef {import('koa').Context} Context + * @typedef {import('koa').Middleware} Middleware + */ + + +//------------------Ctx系统基类-------------------- +class Ctx { + constructor() { + /** + * @desc 系统缓存类 + * @type {typeof import('./cache')} + */ + this.$cache; + + /** + * @type {typeof import('./context').prototype} + */ + this.$context; + + /** + * @type {typeof import('./controller').prototype} + */ + this.$controller; + + /** + * @type {typeof import('./cookie').prototype} + */ + this.$cookie; + + /** + * @type {typeof import('./db').prototype} + */ + this.$db; + + /** + * @type {typeof import('./logger')} + */ + this.$logger; + + /** + * @type {typeof import('./middleware').prototype} + */ + this.$middleware; + + /** + * @type {typeof import('./model').prototype} + */ + this.$model; + + /** + * @type {typeof import('./pagination').prototype} + */ + this.$pagination; + + /** + * @type {typeof import('./response').prototype} + */ + this.$response; + + /** + * @type {typeof import('./upload').prototype} + */ + this.$upload; + + /** + * @type {typeof import('./url').prototype} + */ + this.$url; + + /** + * @type {typeof import('./view').prototype} + */ + this.$view; + + /** + * @type {Utils} + */ + this.$utils; + + /** + * @type {import('./config')} + */ + this.$config; + } +} + +/** + * @module Types + */ +module.exports = Ctx; \ No newline at end of file diff --git a/lib/upload.js b/lib/upload.js index 45f4065..2a17077 100644 --- a/lib/upload.js +++ b/lib/upload.js @@ -2,41 +2,85 @@ const pt = require('path'); const fs = require('fs'); const utils = require('./utils/utils'); const Context = require('./context'); - +const {app: cfg_app} = require('./config'); + +/** + * @typedef {import('formidable').Files} Files + * @typedef {import('formidable').File} File + * @typedef {import('./types').UploadData} UploadData + * @typedef {import('./types').ValidateRule} ValidateRule + */ + +/** + * @extends Context + */ class Upload extends Context { + /** + * 设置上传文件 + * @public + * @param {(string|File)} file + * @returns {this} + */ file(file) { - this._file = typeof file == 'string' ? this.getFile(file) : file; + /** + * @private + */ + this._file = /** @type {File} */ (typeof file == 'string' ? this.getFile(file) : file); return this; } + /** + * 设置检查规则 + * @public + * @param {ValidateRule} [rule] + * @returns {this} + */ validate(rule={}) { + /** + * @private + */ this._rule = rule; return this; } + /** + * 设置存储文件名或函数 + * @public + * @param {(string|function)} [name] + * @returns {this} + */ rule(name) { + /** + * @private + */ this._name = name; return this; } + /** + * 保存文件到目录 + * @public + * @param {string} [dir] + * @returns {Promise<(boolean|UploadData)>} + */ async save(dir) { if(!this.check()) { return false; } let savename = ''; - if(this._name || this._name === 0) { + if(this._name) { savename = (typeof this._name == 'function' ? this._name() : this._name) + ''; } else { - savename = utils.date.format('YYYY/mmdd/') + utils.md5(new Date().getTime() + Math.random().toString(36).substr(2)); + savename = utils.date.format('YYYY/mmdd/') + utils.md5(Date.now() + Math.random().toString(36).substr(2)); } if(!pt.extname(savename)) { savename += pt.extname(this._file.originalFilename); } try { - const filePath = pt.join(this.$config.app.base_dir, dir, savename); + const filePath = pt.join(cfg_app.base_dir, dir, savename); await utils.fs.mkdirs(pt.dirname(filePath)); const reader = fs.createReadStream(this._file.filepath); const writer = fs.createWriteStream(filePath); @@ -65,14 +109,30 @@ class Upload extends Context } } + /** + * 获取上传文件 + * @public + * @param {string} [name] - 为空时,获取所有上传文件 + * @returns {(File|File[]|Files)} + */ getFile(name) { return typeof name == 'string' ? this.ctx.request.files[name] : this.ctx.request.files; } + /** + * 获取错误信息 + * @public + * @returns {string} + */ getError() { return this._error; } + /** + * 检查上传文件 + * @public + * @returns {boolean} + */ check() { if(!this._file) { this._error = '上传文件为找到!'; @@ -104,6 +164,12 @@ class Upload extends Context return true; } + /** + * 检查上传文件后缀 + * @public + * @param {(string|array)} ext + * @returns {boolean} + */ checkExt(ext) { if(typeof ext == 'string') { ext = ext.split(','); @@ -114,6 +180,12 @@ class Upload extends Context return true; } + /** + * 检查上传文件mimetype + * @public + * @param {(string|array)} type + * @returns {boolean} + */ checkType(type) { if(typeof type == 'string') { type = type.split(','); @@ -124,6 +196,11 @@ class Upload extends Context return true; } + /** + * 检查上传图片 + * @public + * @returns {boolean} + */ checkImg() { const fileExt = pt.extname(this._file.originalFilename).slice(1).toLowerCase(); const fileType = this._file.mimetype.toLowerCase().split('/'); diff --git a/lib/url.js b/lib/url.js index 10b0102..4a3e05e 100644 --- a/lib/url.js +++ b/lib/url.js @@ -9,8 +9,20 @@ const querystring = require("querystring"); const Context = require('./context'); const {toLine} = require('./utils/str'); +/** + * @extends Context + */ class Url extends Context { + /** + * 编译生成url + * @public + * @param {string} [url] - 网址,支持智能解析 + * @param {object} [vars] - 网址参数 + * @param {string} [ext] - 网址后缀 + * @param {string} [domain] - 网址域名 + * @returns {string} + */ build(url='', vars, ext='', domain='') { if(vars && typeof vars != 'object') { [vars, ext, domain] = [{}, vars, ext]; @@ -47,6 +59,13 @@ class Url extends Context return domain + urls.join('#'); } + /** + * 由命名路由反向生成url + * @public + * @param {*} url - 路由名字 + * @param {*} query - 网址参数 + * @returns {string} + */ ruleUrl(url, query = {}) { if(!routes[url]) { return url; diff --git a/lib/utils/date.js b/lib/utils/date.js index 970d707..6b78ee5 100644 --- a/lib/utils/date.js +++ b/lib/utils/date.js @@ -1,3 +1,9 @@ +/** + * @function + * @param {string} fmt - 格式化模板 + * @param {(number|Date)} [date] - 时间戳或Date实例 + * @returns {string} - 格式化后时间 + */ function format(fmt, date) { (typeof date === 'string' || typeof date === 'number') && (date = new Date(parseInt(date + '000'))); date = date || new Date(); @@ -21,17 +27,22 @@ function format(fmt, date) { return fmt; } +/** + * @function - 获取多长时间前 + * @param {number} time - 时间戳 + * @returns {string} - 例如5分钟前 + */ function before(time) { time -= 0; - let difTime = new Date().getTime() / 1000 - time; - let { h, m } = { h: parseInt(difTime / 3600), m: parseInt(difTime / 60) }; + let difTime = Date.now() / 1000 - time; + let { h, m } = { h: difTime / 3600, m: difTime / 60 }; let msg = ""; if (h < 1) { msg = `${m}分钟前`; } else if (h >= 1 && h <= 24) { msg = `${h}小时前`; } else if (h > 24) { - h = parseInt(h / 24) + h = h / 24 msg = `${h}天前`; } return msg; diff --git a/lib/utils/error.js b/lib/utils/error.js index f01aa73..65812c1 100644 --- a/lib/utils/error.js +++ b/lib/utils/error.js @@ -1,5 +1,10 @@ const fs = require('./fs.js'); +/** + * @function - 解析Error数据 + * @param {Error} err - 错误对象 + * @returns {object} - 解析后数据 + */ function parseError(err) { let msg = err.message; let stack = err.stack.replace(/(\r\n)/g, "\n").split("\n"); @@ -18,7 +23,7 @@ function parseError(err) { let begin = row - 10; let end = row + 10; let nth = 0; - let code = ''; + let code = []; if(begin < 0) { end -= begin; begin = 0; @@ -26,8 +31,7 @@ function parseError(err) { if(file_path == 'anonymous' || !file_path || !fs.isFileSync(file_path)) { code = stack; } else { - code = require('fs').readFileSync(file_path); - code = code.toString().replace(/(\r\n)/g, "\n").split("\n"); + code = require('fs').readFileSync(file_path).toString().replace(/(\r\n)/g, "\n").split("\n"); if(begin < 0) { end -= begin; begin = 0; @@ -43,7 +47,7 @@ function parseError(err) { nth = row - begin; } - return data = { + return { msg, code, stack, diff --git a/lib/utils/md5.js b/lib/utils/md5.js index ce2032e..654bf38 100644 --- a/lib/utils/md5.js +++ b/lib/utils/md5.js @@ -1,7 +1,9 @@ -let crypto; -module.exports = (str) => { - if(!crypto) { - crypto = require('crypto'); - } - return crypto.createHash('md5').update(str).digest('hex'); -} \ No newline at end of file +/** + * @function - md5 + * @param {string} str + * @returns {string} + */ +function md5(str) { + return require('crypto').createHash('md5').update(str).digest('hex'); +} +module.exports = md5; \ No newline at end of file diff --git a/lib/utils/str.js b/lib/utils/str.js index 84806bc..a2d8e91 100644 --- a/lib/utils/str.js +++ b/lib/utils/str.js @@ -1,9 +1,19 @@ +/** + * @function - 转为驼峰 + * @param {string} name + * @returns {string} + */ function toHump(name) { return name.replace(/\_(\w)/g, function(all, letter){ return letter.toUpperCase(); }); } +/** + * @function - 转为下划线 + * @param {string} name + * @returns {string} + */ function toLine(name) { return name.replace(/(?!^)([A-Z])/g, "_$1").toLowerCase(); } diff --git a/lib/utils/utils.js b/lib/utils/utils.js index 4220185..d900c04 100644 --- a/lib/utils/utils.js +++ b/lib/utils/utils.js @@ -1,10 +1,16 @@ +/** + * jj.js内置工具集
    + * {date, error, fs, md5, str} + * @module utils + * @type {import('../types').Utils} + */ module.exports = new Proxy({}, { get: (target, prop) => { if(prop in target || typeof prop == 'symbol' || prop == 'inspect') { return target[prop]; } if(prop == 'type') { - return para => Object.prototype.toString.call(para).slice(8,-1); + return para => Object.prototype.toString.call(para).slice(8, -1); } return require('./' + prop); } diff --git a/lib/view.js b/lib/view.js index 06405ea..dc57697 100644 --- a/lib/view.js +++ b/lib/view.js @@ -4,32 +4,61 @@ const {app: cfg_app, view: cfg_view} = require('./config'); const {toLine} = require('./utils/str'); const Context = require('./context'); +/** + * @extends Context + */ class View extends Context { + /** + * Initialize a new `View` + * @public + * @param {import('./types').Context} ctx + */ constructor(ctx) { super(ctx); this._data = {}; this.setEngine(cfg_view.view_engine); } - // 加载文件 + /** + * 获取文件内容 + * @public + * @param {string} [template] + * @returns {Promise} + */ async load(template) { const file_name = this.parseTplName(template); return await readFile(file_name); } - // 渲染内容 + /** + * 渲染(解析数据)内容 + * @public + * @param {string} content + * @returns {Promise} + */ async render(content) { return this._engine.render(content, this._data); } - // 渲染文件 + /** + * 渲染(解析数据)文件 + * @public + * @param {string} [template] + * @returns {Promise} + */ async fetch(template) { const file_name = this.parseTplName(template); return await this._engine(file_name, this._data || {}); } - //赋值模版数据 + /** + * 模版数据赋值 + * @public + * @param {(string|object)} name + * @param {*} [value] + * @returns {this} + */ assign(name, value) { if(typeof name == 'string') { this._data[name] = value; @@ -39,13 +68,25 @@ class View extends Context return this; } - //获取模版数据 + /** + * 获取模版数据 + * @public + * @param {string} [name] + * @returns {object} + */ data(name) { if(name) return this._data[name]; else return this._data; } - //解析模板地址 + /** + * 解析模板地址 + * @public + * @param {string} [template] - 默认当前请求方法名,支持智能解析 + * @param {string} [view_folder] - 模板文件夹名字 + * @param {string} [view_depr] - 模板文件分隔符 + * @returns {string} + */ parsePath(template=this.ctx.ACTION, view_folder, view_depr) { !view_folder && (view_folder = cfg_view.view_folder); !view_depr && (view_depr = cfg_view.view_depr); @@ -58,10 +99,15 @@ class View extends Context } } path.extname(view_file) || (view_file += cfg_view.view_ext); - return path.join(this.$config.app.base_dir, view_file); + return path.join(cfg_app.base_dir, view_file); } - //解析模板名字 + /** + * 解析模板名字 + * @public + * @param {string} [template] - 默认当前请求方法名,支持智能解析 + * @returns {string} + */ parseTplName(template) { let file_name = this.parsePath(template, this._folder, this._depr); let is_file = isFileSync(file_name); @@ -76,7 +122,12 @@ class View extends Context return file_name; } - // 设置模版引擎 + /** + * 设置模版引擎 + * @public + * @param {(string|object)} engine + * @returns {this} + */ setEngine(engine) { this._engine = typeof engine == 'string' ? require(engine) : engine; this._engine.defaults.debug = cfg_app.app_debug; @@ -87,7 +138,13 @@ class View extends Context return this; } - //设置模版函数 + /** + * 设置模版函数 + * @public + * @param {(string|object)} fun_obj - 函数名字或者包含多个函数的对象 + * @param {*} [fun] - 函数 + * @returns {this} + */ setFilter(fun_obj, fun) { if(typeof fun_obj === 'string') { fun_obj = {[fun_obj]: fun}; @@ -99,13 +156,23 @@ class View extends Context return this; } - //设置模版目录 + /** + * 设置模板目录 + * @public + * @param {string} view_folder - 文件夹名字 + * @returns {this} + */ setFolder(view_folder) { this._folder = view_folder; return this; } - //设置文件分割符 + /** + * 设置文件分割符 + * @public + * @param {string} view_depr - 文件分隔符(默认'/',不分二级目录,可以设置为'_'或其他) + * @returns {this} + */ setDepr(view_depr) { this._depr = view_depr; return this; diff --git a/package-lock.json b/package-lock.json index e567778..13a6bcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,56 +1,44 @@ { "name": "jj.js", - "version": "0.8.8", + "version": "0.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@koa/router": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@koa/router/-/router-10.1.1.tgz", - "integrity": "sha512-ORNjq5z4EmQPriKbR0ER3k4Gh7YGNhWDL7JBW+8wXDrHLbWYKYSJaOJ9aN06npF5tbTxe2JBOsurpJDAvjiXKw==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-12.0.1.tgz", + "integrity": "sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==", "requires": { - "debug": "^4.1.1", - "http-errors": "^1.7.3", + "debug": "^4.3.4", + "http-errors": "^2.0.0", "koa-compose": "^4.1.0", "methods": "^1.1.2", - "path-to-regexp": "^6.1.0" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.nlark.com/debug/download/debug-4.3.2.tgz?cache=0&sync_timestamp=1625374648057&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fdebug%2Fdownload%2Fdebug-4.3.2.tgz", - "integrity": "sha1-8KScGKyHeeMdSgxgKd+3aHPHQos=", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.nlark.com/ms/download/ms-2.1.2.tgz", - "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=" - } + "path-to-regexp": "^6.2.1" } }, "@types/formidable": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-2.0.5.tgz", - "integrity": "sha512-uvMcdn/KK3maPOaVUAc3HEYbCEhjaGFwww4EsX6IJfWIJ1tzHtDHczuImH3GKdusPnAAmzB07St90uabZeCKPA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-2.0.6.tgz", + "integrity": "sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w==", "requires": { "@types/node": "*" } }, "@types/node": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.33.tgz", - "integrity": "sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ==" + "version": "20.11.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", + "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", + "requires": { + "undici-types": "~5.26.4" + } }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, "acorn": { @@ -76,7 +64,7 @@ "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "bignumber.js": { "version": "9.0.0", @@ -84,9 +72,9 @@ "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "cache-content-type": { "version": "1.0.1", @@ -98,27 +86,29 @@ } }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz", + "integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "set-function-length": "^1.2.0" } }, "camel-case": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", "requires": { "no-case": "^2.2.0", "upper-case": "^1.1.1" } }, "clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", "requires": { "source-map": "~0.6.0" }, @@ -133,7 +123,7 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==" }, "co-body": { "version": "5.2.0", @@ -152,54 +142,65 @@ "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "cookies": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", - "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", "requires": { "depd": "~2.0.0", "keygrip": "~1.1.0" } }, "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "ms": "2.0.0" + "ms": "2.1.2" } }, "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==" }, "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "define-data-property": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz", + "integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==", + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.2", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + } }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, "depd": { "version": "2.0.0", @@ -207,14 +208,14 @@ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "requires": { "asap": "^2.0.0", "wrappy": "1" @@ -223,17 +224,22 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "escodegen": { "version": "1.14.3", @@ -273,58 +279,82 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "formidable": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", - "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", "requires": { - "dezalgo": "1.0.3", - "hexoid": "1.0.0", - "once": "1.4.0", - "qs": "6.9.3" - }, - "dependencies": { - "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==" - } + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" } }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "requires": { - "function-bind": "^1.1.1" + "get-intrinsic": "^1.1.3" } }, + "has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "requires": { + "get-intrinsic": "^1.2.2" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "requires": { + "function-bind": "^1.1.2" + } }, "he": { "version": "1.2.0", @@ -351,55 +381,48 @@ } }, "http-assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", - "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", "requires": { "deep-equal": "~1.0.1", - "http-errors": "~1.7.2" + "http-errors": "~1.8.0" }, "dependencies": { "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" }, "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "requires": { "depd": "~1.1.2", "inherits": "2.0.4", - "setprototypeof": "1.1.1", + "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "toidentifier": "1.0.1" } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" } } }, "http-errors": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", - "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - } + "statuses": "2.0.1", + "toidentifier": "1.0.1" } }, "iconv-lite": { @@ -411,9 +434,9 @@ } }, "inflation": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", - "integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz", + "integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==" }, "inherits": { "version": "2.0.4", @@ -426,24 +449,27 @@ "integrity": "sha512-kUfRnejcRC9YLgblxoJ76dp9gZ3vMKTrDS5I6z2UVMOsHHSImNKCJocjQTkZr38PwiSZ9LVklaHEENaVYeFTXg==" }, "is-generator-function": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.8.tgz", - "integrity": "sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ==" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-keyword-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-keyword-js/-/is-keyword-js-1.0.3.tgz", - "integrity": "sha1-rDDc81tnH0snsX9ctXI1EmAhEy0=" + "integrity": "sha512-EW8wNCNvomPa/jsH1g0DmLfPakkRCRTcTML1v1fZMLiVCvQ/1YB+tKsRzShBiWQhqrYCi5a+WsepA4Z8TA9iaA==" }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==" }, "keygrip": { "version": "1.1.0", @@ -454,15 +480,15 @@ } }, "koa": { - "version": "2.13.4", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.4.tgz", - "integrity": "sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-KEL/vU1knsoUvfP4MC4/GthpQrY/p6dzwaaGI6Rt4NQuFqkw3qrvsdYF5pz3wOfi7IGTvMPHC9aZIcUKYFNxsw==", "requires": { "accepts": "^1.3.5", "cache-content-type": "^1.0.0", "content-disposition": "~0.5.2", "content-type": "^1.0.4", - "cookies": "~0.8.0", + "cookies": "~0.9.0", "debug": "^4.3.2", "delegates": "^1.0.0", "depd": "^2.0.0", @@ -483,18 +509,29 @@ "vary": "^1.1.2" }, "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "requires": { - "ms": "2.1.2" + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" + } } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" } } }, @@ -532,18 +569,27 @@ "resolve-path": "^1.4.0" }, "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "requires": { - "ms": "2.1.2" + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" } } }, @@ -554,12 +600,22 @@ "requires": { "debug": "^3.1.0", "koa-send": "^5.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } } }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "requires": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -568,12 +624,12 @@ "lower-case": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "merge-source-map": { "version": "1.1.0", @@ -593,25 +649,25 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "mime-db": { - "version": "1.46.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", - "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.29", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", - "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.46.0" + "mime-db": "1.52.0" } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mysql": { "version": "2.18.1", @@ -622,12 +678,19 @@ "readable-stream": "2.3.7", "safe-buffer": "5.1.2", "sqlstring": "2.3.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } } }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, "no-case": { "version": "2.3.2", @@ -638,14 +701,14 @@ } }, "object-inspect": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", - "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } @@ -653,7 +716,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "requires": { "wrappy": "1" } @@ -661,7 +724,7 @@ "only": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", - "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=" + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==" }, "optionator": { "version": "0.8.3", @@ -679,7 +742,7 @@ "param-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", - "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", "requires": { "no-case": "^2.2.0" } @@ -692,17 +755,17 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-to-regexp": { - "version": "6.2.0", - "resolved": "https://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-6.2.0.tgz?cache=0&sync_timestamp=1601400247487&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpath-to-regexp%2Fdownload%2Fpath-to-regexp-6.2.0.tgz", - "integrity": "sha1-97OAMzYQTDRoia3s5hRmkjBkXzg=" + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" }, "process-nextick-args": { "version": "2.0.1", @@ -710,41 +773,22 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "requires": { "side-channel": "^1.0.4" } }, "raw-body": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", - "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.3", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - } } }, "readable-stream": { @@ -759,17 +803,24 @@ "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } } }, "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==" }, "resolve-path": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", - "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", + "integrity": "sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==", "requires": { "http-errors": "~1.6.2", "path-is-absolute": "1.0.1" @@ -778,12 +829,12 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -794,29 +845,46 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" } } }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "set-function-length": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", + "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "requires": { + "define-data-property": "^1.1.1", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.2", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + } + }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "side-channel": { "version": "1.0.4", @@ -831,17 +899,17 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" }, "sqlstring": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", - "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==" }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "string_decoder": { "version": "1.1.1", @@ -849,12 +917,19 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } } }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "tsscmp": { "version": "1.0.6", @@ -864,7 +939,7 @@ "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "requires": { "prelude-ls": "~1.1.2" } @@ -899,40 +974,45 @@ } } }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "upper-case": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=" + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==" }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "ylru": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", - "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.3.2.tgz", + "integrity": "sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==" } } } diff --git a/package.json b/package.json index 2f88aed..3a301e7 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,9 @@ { "name": "jj.js", - "version": "0.8.8", - "description": "A simple and lightweight MVC framework built on nodejs+koa2", + "version": "0.9.0", + "description": "A super simple lightweight NodeJS MVC framework(一个超级简单轻量的NodeJS MVC框架)", "main": "jj.js", - "scripts": { - }, + "scripts": {}, "repository": { "type": "git", "url": "git+https://github.com/yafoo/jj.js.git" @@ -21,13 +20,16 @@ }, "homepage": "https://github.com/yafoo/jj.js#readme", "dependencies": { - "@koa/router": "^10.1.1", + "@koa/router": "^12.0.1", "art-template": "^4.13.2", "escape-html": "^1.0.3", "is-class": "0.0.9", - "koa": "^2.13.4", + "koa": "^2.15.0", "koa-body": "^5.0.0", "koa-static": "^5.0.0", "mysql": "^2.18.1" + }, + "engines": { + "node": ">= 12.17.0" } }