jj.js 0.7.5 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/controller.js CHANGED
@@ -1,35 +1,35 @@
1
- const Middleware = require('./middleware');
2
-
3
- class Controller extends Middleware
4
- {
5
- // 赋值模版数据
6
- $assign(name, value) {
7
- this.$view.assign(name, value);
8
- return this;
9
- }
10
-
11
- // 获取模版数据
12
- $data(name) {
13
- return this.$view.data(name);
14
- }
15
-
16
- // 直接文件输出
17
- async $load(template) {
18
- const content = await this.$view.load(template);
19
- this.$show(content);
20
- }
21
-
22
- // 渲染内容输出
23
- async $render(data) {
24
- const content = await this.$view.render(data);
25
- this.$show(content);
26
- }
27
-
28
- // 渲染文件输出
29
- async $fetch(template) {
30
- const content = await this.$view.fetch(template);
31
- this.$show(content);
32
- }
33
- }
34
-
1
+ const Middleware = require('./middleware');
2
+
3
+ class Controller extends Middleware
4
+ {
5
+ // 赋值模版数据
6
+ $assign(name, value) {
7
+ this.$view.assign(name, value);
8
+ return this;
9
+ }
10
+
11
+ // 获取模版数据
12
+ $data(name) {
13
+ return this.$view.data(name);
14
+ }
15
+
16
+ // 直接文件输出
17
+ async $load(template) {
18
+ const content = await this.$view.load(template);
19
+ this.$show(content);
20
+ }
21
+
22
+ // 渲染内容输出
23
+ async $render(data) {
24
+ const content = await this.$view.render(data);
25
+ this.$show(content);
26
+ }
27
+
28
+ // 渲染文件输出
29
+ async $fetch(template) {
30
+ const content = await this.$view.fetch(template);
31
+ this.$show(content);
32
+ }
33
+ }
34
+
35
35
  module.exports = Controller;
package/lib/ctx.js CHANGED
@@ -1,51 +1,51 @@
1
- const loader = require('./loader');
2
-
3
- const Ctx = new Proxy(class {}, {
4
- construct() {
5
- return new Proxy({__proto__: arguments[2].prototype}, {
6
- get: (target, prop, receiver) => {
7
- if(prop in target || typeof prop == 'symbol' || prop == 'inspect') {
8
- return Reflect.get(target, prop, receiver);
9
- }
10
- if(prop == '$' || prop == '_') {
11
- return target.ctx && target.ctx[prop]
12
- || prop == '$' && (target.$ || (target.$ = loader('../'))) && target.$
13
- || undefined;
14
- }
15
- if(prop.slice(0, 1) != '$') {
16
- return undefined;
17
- }
18
- const pp = prop.slice(1);
19
- const ctx = target.ctx;
20
- if(ctx && ctx._) {
21
- const _ = ctx._;
22
- const APP = ctx.APP;
23
- const COMMON = _.config.app.common_app;
24
- if(_[APP][prop] === undefined) {
25
- if(_[APP][pp] && _[COMMON] && _[COMMON][pp]) {
26
- _[APP][prop] = new Proxy({}, {
27
- get: (...args) => {
28
- return _[APP][pp][args[1]] || _[COMMON][pp][args[1]];
29
- }
30
- });
31
- } else if(_[APP][pp]) {
32
- _[APP][prop] = _[APP][pp];
33
- } else if(_[COMMON] && _[COMMON][pp]) {
34
- _[APP][prop] = _[COMMON][pp];
35
- } else {
36
- _[APP][prop] = null;
37
- }
38
- if(~['view', 'response'].indexOf(pp) && _[APP][prop] && _[APP][prop].__node && !_[APP][prop].__node.isClass && ctx.$ && ctx.$[pp]) {
39
- _[APP][prop] = ctx.$[pp];
40
- }
41
- }
42
- return _[APP][prop] || _[pp] || (ctx.$ && ctx.$[pp]) || undefined;
43
- } else {
44
- return (target.$ || (target.$ = loader('../'))) && target.$[pp];
45
- }
46
- }
47
- });
48
- }
49
- });
50
-
1
+ const loader = require('./loader');
2
+
3
+ const Ctx = new Proxy(class {}, {
4
+ construct() {
5
+ return new Proxy({__proto__: arguments[2].prototype}, {
6
+ get: (target, prop, receiver) => {
7
+ if(prop in target || typeof prop == 'symbol' || prop == 'inspect') {
8
+ return Reflect.get(target, prop, receiver);
9
+ }
10
+ if(prop == '$' || prop == '_') {
11
+ return target.ctx && target.ctx[prop]
12
+ || prop == '$' && (target.$ || (target.$ = loader('../'))) && target.$
13
+ || undefined;
14
+ }
15
+ if(prop.slice(0, 1) != '$') {
16
+ return undefined;
17
+ }
18
+ const pp = prop.slice(1);
19
+ const ctx = target.ctx;
20
+ if(ctx && ctx._) {
21
+ const _ = ctx._;
22
+ const APP = ctx.APP;
23
+ const COMMON = _.config.app.common_app;
24
+ if(_[APP][prop] === undefined) {
25
+ if(_[APP][pp] && _[COMMON] && _[COMMON][pp]) {
26
+ _[APP][prop] = new Proxy({}, {
27
+ get: (...args) => {
28
+ return _[APP][pp][args[1]] || _[COMMON][pp][args[1]];
29
+ }
30
+ });
31
+ } else if(_[APP][pp]) {
32
+ _[APP][prop] = _[APP][pp];
33
+ } else if(_[COMMON] && _[COMMON][pp]) {
34
+ _[APP][prop] = _[COMMON][pp];
35
+ } else {
36
+ _[APP][prop] = null;
37
+ }
38
+ if(~['view', 'response'].indexOf(pp) && _[APP][prop] && _[APP][prop].__node && !_[APP][prop].__node.isClass && ctx.$ && ctx.$[pp]) {
39
+ _[APP][prop] = ctx.$[pp];
40
+ }
41
+ }
42
+ return _[APP][prop] || _[pp] || (ctx.$ && ctx.$[pp]) || undefined;
43
+ } else {
44
+ return (target.$ || (target.$ = loader('../'))) && target.$[pp];
45
+ }
46
+ }
47
+ });
48
+ }
49
+ });
50
+
51
51
  module.exports = Ctx;
package/lib/db.js CHANGED
@@ -106,7 +106,6 @@ class Db extends Context
106
106
  try {
107
107
  await fun();
108
108
  await this.commit();
109
- resolve('事务执行成功!');
110
109
  } catch(e) {
111
110
  await this.rollback();
112
111
  throw e;
@@ -450,10 +449,7 @@ class Db extends Context
450
449
  return result;
451
450
  }
452
451
 
453
- async pagination(page, page_size, pagination) {
454
- if(typeof page == 'object' || typeof page == 'function') {
455
- [page, pagination] = [pagination, page];
456
- }
452
+ async pagination({page, page_size, pagination} = {}) {
457
453
  if(!pagination) {
458
454
  pagination = this.$pagination.__node.isClass ? this.$pagination : this.$.pagination;
459
455
  }
@@ -560,6 +556,17 @@ class Db extends Context
560
556
  this._options.getSql = false;
561
557
  return this.format(this._queryStr, params);
562
558
  }
559
+
560
+ if(this._options.cache_time) {
561
+ const cache_time = this._options.cache_time;
562
+ const cache_key = md5(this.format(this._queryStr, params));
563
+ if(this.getCache(cache_key)) {
564
+ return this.getCache(cache_key);
565
+ }
566
+ const result = await this.query(this._queryStr, params);
567
+ this.setCache(cache_key, result, cache_time);
568
+ return result;
569
+ }
563
570
  return await this.query(this._queryStr, params, reset);
564
571
  }
565
572
 
@@ -741,7 +748,7 @@ class Db extends Context
741
748
  Db.cache.set(key, data, cache_time);
742
749
  }
743
750
 
744
- deleteCache(key) {console.log('deleteCache', key);
751
+ deleteCache(key) {
745
752
  Db.cache.delete(key);
746
753
  }
747
754
  }
package/lib/loader.js CHANGED
@@ -1,80 +1,80 @@
1
- const fs = require('fs');
2
- const pt = require('path');
3
- const isClass = require('is-class');
4
- const {isFileSync, isDirSync} = require('./utils/fs');
5
-
6
- const nodeType = {};
7
-
8
- function loader(dir='./', ...args) {
9
- const node = {};
10
- const info = new Map();
11
- info.set(node, {
12
- path: pt.isAbsolute(dir) ? pt.join(dir, './') : pt.join(pt.dirname(module.parent.filename), dir, './'),
13
- nodeType: 'dir',
14
- isClass: false
15
- });
16
- return creatLoader(node);
17
-
18
- function creatLoader(node) {
19
- return new Proxy(node, {
20
- get: (node, prop, receiver) => {
21
- if(prop in node || typeof prop == 'symbol' || prop == 'inspect') {
22
- return Reflect.get(node, prop, receiver);
23
- }
24
- const nodeInfo = info.get(node);
25
- if(nodeInfo.isClass) {
26
- if(!nodeInfo.instance) {
27
- nodeInfo.instance = new node(...args);
28
- }
29
- return prop in nodeInfo.instance ? nodeInfo.instance[prop] : (prop == '__node' ? nodeInfo : nodeInfo.instance[prop]);
30
- } else if(prop == '__node') {
31
- return nodeInfo;
32
- }
33
-
34
- let _node = {};
35
- const _nodePath = nodeInfo.path + prop + '/';
36
- const _nodeFile = nodeInfo.path + prop + '.js';
37
- if(!nodeType[_nodePath]) {
38
- if(isFileSync(_nodeFile)) {
39
- nodeType[_nodePath] = 'file';
40
- } else if(isDirSync(_nodePath)) {
41
- nodeType[_nodePath] = 'dir';
42
- } else{
43
- nodeType[_nodePath] = 'none';
44
- }
45
- }
46
- if(nodeType[_nodePath] == 'file') {
47
- _node = require(_nodeFile);
48
- } else if(nodeType[_nodePath] != 'dir') {
49
- node[prop] = undefined;
50
- return node[prop];
51
- }
52
- info.set(_node, {
53
- path: _nodePath,
54
- nodeType: nodeType[_nodePath],
55
- isClass: isClass(_node)
56
- });
57
- node[prop] = creatLoader(_node);
58
- return node[prop];
59
- },
60
- set: (_node, prop, value) => {
61
- if(prop in node) {
62
- node[prop] = value;
63
- return true;
64
- }
65
- const nodeInfo = info.get(_node);
66
- if(nodeInfo.isClass) {
67
- if(!nodeInfo.instance) {
68
- nodeInfo.instance = new node(...args);
69
- }
70
- nodeInfo.instance[prop] = value;
71
- return true;
72
- }
73
- node[prop] = value;
74
- return true;
75
- }
76
- });
77
- }
78
- }
79
-
1
+ const fs = require('fs');
2
+ const pt = require('path');
3
+ const isClass = require('is-class');
4
+ const {isFileSync, isDirSync} = require('./utils/fs');
5
+
6
+ const nodeType = {};
7
+
8
+ function loader(dir='./', ...args) {
9
+ const node = {};
10
+ const info = new Map();
11
+ info.set(node, {
12
+ path: pt.isAbsolute(dir) ? pt.join(dir, './') : pt.join(pt.dirname(module.parent.filename), dir, './'),
13
+ nodeType: 'dir',
14
+ isClass: false
15
+ });
16
+ return creatLoader(node);
17
+
18
+ function creatLoader(node) {
19
+ return new Proxy(node, {
20
+ get: (node, prop, receiver) => {
21
+ if(prop in node || typeof prop == 'symbol' || prop == 'inspect') {
22
+ return Reflect.get(node, prop, receiver);
23
+ }
24
+ const nodeInfo = info.get(node);
25
+ if(nodeInfo.isClass) {
26
+ if(!nodeInfo.instance) {
27
+ nodeInfo.instance = new node(...args);
28
+ }
29
+ return prop in nodeInfo.instance ? nodeInfo.instance[prop] : (prop == '__node' ? nodeInfo : nodeInfo.instance[prop]);
30
+ } else if(prop == '__node') {
31
+ return nodeInfo;
32
+ }
33
+
34
+ let _node = {};
35
+ const _nodePath = nodeInfo.path + prop + '/';
36
+ const _nodeFile = nodeInfo.path + prop + '.js';
37
+ if(!nodeType[_nodePath]) {
38
+ if(isFileSync(_nodeFile)) {
39
+ nodeType[_nodePath] = 'file';
40
+ } else if(isDirSync(_nodePath)) {
41
+ nodeType[_nodePath] = 'dir';
42
+ } else{
43
+ nodeType[_nodePath] = 'none';
44
+ }
45
+ }
46
+ if(nodeType[_nodePath] == 'file') {
47
+ _node = require(_nodeFile);
48
+ } else if(nodeType[_nodePath] != 'dir') {
49
+ node[prop] = undefined;
50
+ return node[prop];
51
+ }
52
+ info.set(_node, {
53
+ path: _nodePath,
54
+ nodeType: nodeType[_nodePath],
55
+ isClass: isClass(_node)
56
+ });
57
+ node[prop] = creatLoader(_node);
58
+ return node[prop];
59
+ },
60
+ set: (_node, prop, value) => {
61
+ if(prop in node) {
62
+ node[prop] = value;
63
+ return true;
64
+ }
65
+ const nodeInfo = info.get(_node);
66
+ if(nodeInfo.isClass) {
67
+ if(!nodeInfo.instance) {
68
+ nodeInfo.instance = new node(...args);
69
+ }
70
+ nodeInfo.instance[prop] = value;
71
+ return true;
72
+ }
73
+ node[prop] = value;
74
+ return true;
75
+ }
76
+ });
77
+ }
78
+ }
79
+
80
80
  module.exports = loader;
package/lib/model.js CHANGED
@@ -16,8 +16,9 @@ class Model extends Context
16
16
 
17
17
  async save(data, condition = {}) {
18
18
  if(data[this.pk] || Object.keys(condition).length) {
19
- if(data[this.pk] && !condition[this.pk]) {
19
+ if(data[this.pk]) {
20
20
  condition[this.pk] = data[this.pk];
21
+ delete data[this.pk];
21
22
  }
22
23
  return await this.db.allowField().update(data, condition);
23
24
  }
package/lib/response.js CHANGED
@@ -57,6 +57,7 @@ class Response extends Context
57
57
 
58
58
  isAjax() {
59
59
  return this.ctx.headers['x-requested-with'] && this.ctx.headers['x-requested-with'].toLowerCase() == 'xmlhttprequest'
60
+ || this.ctx.request.type.toLowerCase() == 'application/json'
60
61
  || this.ctx.query.is_ajax
61
62
  || this.ctx.request.body && this.ctx.request.body.is_ajax
62
63
  ? true : false;
package/lib/router.js CHANGED
@@ -1,7 +1,6 @@
1
1
  const router = require('@koa/router')();
2
2
  const {app: cfg_app, routes: cfg_routes} = require('./config');
3
3
  const {toLine} = require('./utils/str');
4
- const Logger = require('./logger');
5
4
  const run = require('./run');
6
5
 
7
6
  // 注册用户路由
@@ -27,21 +26,19 @@ if (cfg_routes) {
27
26
  ctx.APP = paths[2] || cfg_app.default_app;
28
27
  ctx.CONTROLLER = paths[1] || cfg_app.default_controller;
29
28
  ctx.ACTION = paths[0] || cfg_app.default_action;
30
- Logger.debug(`Router: {APP: ${ctx.APP}, CONTROLLER: ${ctx.CONTROLLER}, ACTION: ${ctx.ACTION}}`);
31
29
  await run(ctx, next, item.type);
32
30
  });
33
31
  }
34
32
  }
35
33
 
36
34
  // 注册系统路由
37
- router.all(cfg_app.app_multi ? '/:app?/:controller?/:action?' : '/:controller?/:action?', async (ctx, next) => {
38
- ctx.APP = ctx.params.app || cfg_app.default_app;
39
- ctx.CONTROLLER = ctx.params.controller || cfg_app.default_controller;
40
- ctx.ACTION = ctx.params.action || cfg_app.default_action;
41
- delete ctx.params.app;
42
- delete ctx.params.controller;
43
- delete ctx.params.action;
44
- Logger.debug(`Router: {APP: ${ctx.APP}, CONTROLLER: ${ctx.CONTROLLER}, ACTION: ${ctx.ACTION}}`);
35
+ router.all(cfg_app.app_multi ? '/:APP?/:CONTROLLER?/:ACTION?' : '/:CONTROLLER?/:ACTION?', async (ctx, next) => {
36
+ ctx.APP = ctx.params.APP || cfg_app.default_app;
37
+ ctx.CONTROLLER = ctx.params.CONTROLLER || cfg_app.default_controller;
38
+ ctx.ACTION = ctx.params.ACTION || cfg_app.default_action;
39
+ delete ctx.params.APP;
40
+ delete ctx.params.CONTROLLER;
41
+ delete ctx.params.ACTION;
45
42
  await run(ctx, next);
46
43
  });
47
44
 
package/lib/run.js CHANGED
@@ -4,8 +4,13 @@ const {readFile} = require('./utils/fs');
4
4
  const {toHump} = require('./utils/str');
5
5
  const {app: cfg_app, view: cfg_view, db, page, log, cache, cookie, tpl} = require('./config');
6
6
  const compose = require('koa-compose');
7
+ const Logger = require('./logger');
7
8
 
8
9
  async function run(ctx, next, control_type=cfg_app.controller_folder) {
10
+ Logger.debug(ctx.request);
11
+ Logger.debug(`Router: {APP: '${ctx.APP}', CONTROLLER: '${ctx.CONTROLLER}', ACTION: '${ctx.ACTION}'}`);
12
+ Logger.debug('Params/Get/Post:', ctx.params, ctx.query, ctx.request.body);
13
+
9
14
  // 设置根加载器
10
15
  ctx.$ = loader('./', ctx);
11
16
  ctx._ = loader(cfg_app.base_dir, ctx);
@@ -95,7 +100,7 @@ async function run(ctx, next, control_type=cfg_app.controller_folder) {
95
100
  }
96
101
  const Middleware = ctx._[APP]['middleware'][MIDDLEWARE];
97
102
  if(!Middleware || typeof Middleware != 'function') {
98
- throw new Errorw(`中间件文件:${APP}/middleware/${MIDDLEWARE}不存在!`);
103
+ throw new Error(`中间件文件:${APP}/middleware/${MIDDLEWARE}不存在!`);
99
104
  }
100
105
  const $middleware = new Middleware(ctx, next);
101
106
  if(!$middleware[METHOD] || typeof $middleware[METHOD] != 'function') {
package/lib/upload.js CHANGED
@@ -32,13 +32,13 @@ class Upload extends Context
32
32
  savename = utils.date.format('YYYY/mmdd/') + utils.md5(new Date().getTime() + Math.random().toString(36).substr(2));
33
33
  }
34
34
  if(!pt.extname(savename)) {
35
- savename += pt.extname(this._file.name);
35
+ savename += pt.extname(this._file.originalFilename);
36
36
  }
37
37
 
38
38
  try {
39
39
  const filePath = pt.join(this.$config.app.base_dir, dir, savename);
40
40
  await utils.fs.mkdirs(pt.dirname(filePath));
41
- const reader = fs.createReadStream(this._file.path);
41
+ const reader = fs.createReadStream(this._file.filepath);
42
42
  const writer = fs.createWriteStream(filePath);
43
43
  reader.pipe(writer);
44
44
  await new Promise((resolve, reject) => {
@@ -54,9 +54,9 @@ class Upload extends Context
54
54
  extname: pt.extname(savename).slice(1),
55
55
  savename,
56
56
  filepath: filePath,
57
- name: this._file.name,
57
+ name: this._file.originalFilename,
58
58
  size: this._file.size,
59
- type: this._file.type,
59
+ mimetype: this._file.mimetype,
60
60
  hash: this._file.hash
61
61
  };
62
62
  } catch(e) {
@@ -108,7 +108,7 @@ class Upload extends Context
108
108
  if(typeof ext == 'string') {
109
109
  ext = ext.split(',');
110
110
  }
111
- if(!~ext.indexOf(pt.extname(this._file.name).slice(1).toLowerCase())) {
111
+ if(!~ext.indexOf(pt.extname(this._file.originalFilename).slice(1).toLowerCase())) {
112
112
  return false;
113
113
  }
114
114
  return true;
@@ -118,18 +118,17 @@ class Upload extends Context
118
118
  if(typeof type == 'string') {
119
119
  type = type.split(',');
120
120
  }
121
- if(!~type.indexOf(this._file.type.toLowerCase())) {
121
+ if(!~type.indexOf(this._file.mimetype.toLowerCase())) {
122
122
  return false;
123
123
  }
124
124
  return true;
125
125
  }
126
126
 
127
- // 此判断方法无效
128
127
  checkImg() {
129
- const fileExt = pt.extname(this._file.name).slice(1).toLowerCase();
130
- const fileType = this._file.type.toLowerCase().split('/');
131
- const exts = ['gif', 'png', 'jpeg', 'webp', 'ico', 'bmp', 'svg', 'jpg'];
132
- const mimes = ['gif', 'png', 'jpeg', 'webp', 'x-icon', 'bmp', 'svg+xml'];
128
+ const fileExt = pt.extname(this._file.originalFilename).slice(1).toLowerCase();
129
+ const fileType = this._file.mimetype.toLowerCase().split('/');
130
+ const exts = ['gif', 'png', 'jpeg', 'webp', 'ico', 'bmp', 'svg', 'jpg', 'tiff'];
131
+ const mimes = ['gif', 'png', 'jpeg', 'webp', 'x-icon', 'bmp', 'svg+xml', 'tiff'];
133
132
  if(~exts.indexOf(fileExt) && (fileType[0] != 'image' || !~mimes.indexOf(fileType[1]))) {
134
133
  return false;
135
134
  }
package/lib/view.js CHANGED
@@ -56,8 +56,10 @@ class View extends Context
56
56
  let view_file = template;
57
57
  if(view_file.indexOf('/') !== 0) {
58
58
  const temp = view_file.replace(/^\/|\/$/g, '').split('/').reverse().map(u => toLine(u));
59
+ const folder = this._folder;
60
+ const depr = this._depr;
59
61
  if(temp.length <= 3) {
60
- view_file = `${temp[2] || this.ctx.APP}/${cfg_view.view_folder}/${temp[1] || this.ctx.CONTROLLER}${cfg_view.view_depr}${temp[0]}`;
62
+ view_file = `${temp[2] || this.ctx.APP}/${folder || cfg_view.view_folder}/${temp[1] || this.ctx.CONTROLLER}${depr || cfg_view.view_depr}${temp[0]}`;
61
63
  }
62
64
  }
63
65
  path.extname(view_file) || (view_file += cfg_view.view_ext);
@@ -86,6 +88,18 @@ class View extends Context
86
88
  }
87
89
  return this;
88
90
  }
91
+
92
+ //设置模版目录
93
+ setFolder(view_folder) {
94
+ this._folder = view_folder;
95
+ return this;
96
+ }
97
+
98
+ //设置文件分割符
99
+ setDepr(view_depr) {
100
+ this._depr = view_depr;
101
+ return this;
102
+ }
89
103
  }
90
104
 
91
105
  module.exports = View;
package/package.json CHANGED
@@ -1,34 +1,34 @@
1
- {
2
- "name": "jj.js",
3
- "version": "0.7.5",
4
- "description": "A simple and lightweight MVC framework built on nodejs+koa2",
5
- "main": "jj.js",
6
- "scripts": {
7
- "test": "node test/test.js"
8
- },
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/yafoo/jj.js.git"
12
- },
13
- "keywords": [
14
- "mvc",
15
- "koa",
16
- "node"
17
- ],
18
- "author": "yafoo",
19
- "license": "MIT",
20
- "bugs": {
21
- "url": "https://github.com/yafoo/jj.js/issues"
22
- },
23
- "homepage": "https://github.com/yafoo/jj.js#readme",
24
- "dependencies": {
25
- "@koa/router": "^10.1.1",
26
- "art-template": "^4.13.2",
27
- "escape-html": "^1.0.3",
28
- "is-class": "0.0.9",
29
- "koa": "^2.13.4",
30
- "koa-body": "^5.0.0",
31
- "koa-static": "^5.0.0",
32
- "mysql": "^2.18.1"
33
- }
34
- }
1
+ {
2
+ "name": "jj.js",
3
+ "version": "0.8.1",
4
+ "description": "A simple and lightweight MVC framework built on nodejs+koa2",
5
+ "main": "jj.js",
6
+ "scripts": {
7
+ "test": "node test/test.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/yafoo/jj.js.git"
12
+ },
13
+ "keywords": [
14
+ "mvc",
15
+ "koa",
16
+ "node"
17
+ ],
18
+ "author": "yafoo",
19
+ "license": "MIT",
20
+ "bugs": {
21
+ "url": "https://github.com/yafoo/jj.js/issues"
22
+ },
23
+ "homepage": "https://github.com/yafoo/jj.js#readme",
24
+ "dependencies": {
25
+ "@koa/router": "^10.1.1",
26
+ "art-template": "^4.13.2",
27
+ "escape-html": "^1.0.3",
28
+ "is-class": "0.0.9",
29
+ "koa": "^2.13.4",
30
+ "koa-body": "^5.0.0",
31
+ "koa-static": "^5.0.0",
32
+ "mysql": "^2.18.1"
33
+ }
34
+ }