jj.js 0.18.0 → 0.20.0

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/app.js CHANGED
@@ -1,42 +1,79 @@
1
1
  const path = require('path');
2
2
  const Koa = require('koa');
3
3
  const {app: cfg_app, cookie: cfg_cookie} = require('./config');
4
+ const loader = require('./loader');
4
5
  const Logger = require('./logger');
5
6
  const Response = require('./response');
6
7
  const router = require('./router');
8
+ const mvc = require('./mvc');
7
9
  const storage = require('./storage');
8
10
  const pjson = require('../package.json');
9
11
 
12
+ /**
13
+ * @typedef {import('../types').AppOptions} AppOptions
14
+ * @typedef {import('../types').KoaMiddleware} KoaMiddleware
15
+ */
16
+
10
17
  /**
11
18
  * @extends Koa
12
19
  */
13
20
  class App extends Koa
14
21
  {
15
22
  /**
16
- * @override
17
- * @param {...any} args - listen(port, ip, callback(err){})
23
+ * @param {AppOptions|KoaMiddleware|KoaMiddleware[]} [options] Application options
18
24
  */
19
- listen(...args) {
25
+ constructor(options) {
26
+ if(typeof options == 'function') {
27
+ options = {
28
+ middleware: [options]
29
+ };
30
+ } else if(Array.isArray(options)) {
31
+ options = {
32
+ middleware: options
33
+ };
34
+ } else if(options && options.middleware && !Array.isArray(options.middleware)) {
35
+ options.middleware = [options.middleware]
36
+ }
37
+ super(options);
38
+ this._init(options);
39
+ }
40
+
41
+ /**
42
+ * @param {AppOptions} [options] - Application options
43
+ */
44
+ _init(options) {
20
45
  // storage
21
46
  this.use(async (ctx, next) => {
22
- await storage.run({}, async() => {
47
+ ctx.APP_TIME = Date.now();
48
+ ctx.APP_VERSION = pjson.version;
49
+ await storage.run({
50
+ $: loader(__dirname, ctx),
51
+ _: loader(cfg_app.base_dir, ctx)
52
+ }, async() => {
23
53
  await next();
24
54
  });
25
55
  });
26
56
 
57
+ // options.middleware
58
+ options && Array.isArray(options.middleware) && options.middleware.forEach(middleware => {
59
+ this.use(middleware);
60
+ });
61
+
27
62
  // exception
28
63
  this.use(async (ctx, next) => {
29
- ctx.APP_TIME = Date.now();
30
- ctx.APP_VERSION = pjson.version;
31
-
32
64
  try {
33
65
  await next();
34
66
  } catch (err) {
35
- Logger.error(...err.stack.split("\n"));
67
+ const errorMessage = err instanceof Error ? (err.stack || '') : String(err);
68
+ errorMessage.split("\n").forEach(line => {
69
+ Logger.error(line);
70
+ });
36
71
  if(cfg_app.app_debug) {
37
- new Response(ctx).exception(err);
72
+ new Response(ctx).exception(err instanceof Error ? err : new Error(errorMessage));
38
73
  } else {
39
- ctx.response.status = err.statusCode || err.status || 500;
74
+ // @ts-ignore
75
+ const errorStatus = typeof err === 'object' && err && (err.statusCode || err.status) || 500;
76
+ ctx.response.status = errorStatus;
40
77
  ctx.body = 'Internal Server Error';
41
78
  }
42
79
  }
@@ -54,18 +91,19 @@ class App extends Koa
54
91
  // router
55
92
  this.use(router.routes()).use(router.allowedMethods());
56
93
 
94
+ // mvc
95
+ this.use(mvc);
96
+
57
97
  // cookie
58
98
  cfg_cookie.keys && (this.keys = cfg_app.app_debug ? ['jj.js'] : cfg_cookie.keys);
59
99
 
60
- // types
100
+ // create types
61
101
  if(cfg_app.app_debug) {
62
102
  const {isFileSync} = require('./utils/fs');
63
103
  const jsconfigFile = path.join(cfg_app.base_dir, 'jsconfig.json');
64
- isFileSync(jsconfigFile) && require('./types')();
104
+ if(!isFileSync(jsconfigFile)) Logger.warning('jsconfig.json not found!');
105
+ require('./types').watch();
65
106
  }
66
-
67
- // server
68
- return super.listen(...args);
69
107
  }
70
108
  }
71
109
 
package/lib/cache.js CHANGED
@@ -2,22 +2,35 @@ const {cache: cfg_cache} = require('./config');
2
2
 
3
3
  class Cache
4
4
  {
5
+ static cache = new Map();
6
+ static timer = null;
7
+
5
8
  /**
6
9
  * Creat a new `Cache` class
7
10
  * @public
11
+ * @param {Map.<any, any>} [store] - 缓存存储实例
8
12
  */
9
- constructor() {
13
+ constructor(store) {
10
14
  if(new.target) {
11
15
  // @ts-ignore
12
- class ChildCache extends this.constructor {}
13
- ChildCache.cache = {};
14
- ChildCache.timer = null;
15
- // @ts-ignore
16
- ChildCache.setIntervalTime(cfg_cache.clear_time);
16
+ class ChildCache extends this.constructor {
17
+ static cache = store || Cache.cache;
18
+ }
19
+ // 只有独立 cache 时才声明独立的 timer
20
+ if(store) {
21
+ // @ts-ignore
22
+ ChildCache.timer = null;
23
+ // @ts-ignore
24
+ ChildCache.setIntervalTime(cfg_cache.clear_time);
25
+ }
17
26
  return ChildCache;
18
27
  }
19
28
  }
20
29
 
30
+ static {
31
+ this.setIntervalTime(cfg_cache.clear_time);
32
+ }
33
+
21
34
  /**
22
35
  * 获取缓存
23
36
  * @public
@@ -29,9 +42,9 @@ class Cache
29
42
  if(key === undefined) {
30
43
  return this.cache;
31
44
  }
32
- const now_time = Math.round(Date.now() / 1000);
33
- if(this.cache[key] && this.cache[key].time > now_time) {
34
- return this.cache[key].data;
45
+ const item = this.cache.get(key);
46
+ if(item && item.time > this.time()) {
47
+ return item.data;
35
48
  } else {
36
49
  this.delete(key);
37
50
  return undefined;
@@ -48,8 +61,7 @@ class Cache
48
61
  */
49
62
  static set(key, data, cache_time) {
50
63
  cache_time || (cache_time = cfg_cache.cache_time || 60 * 60 * 24 * 365 * 10);
51
- const now_time = this.time();
52
- this.cache[key] = {data: data, time: cache_time + now_time};
64
+ this.cache.set(key, {data: data, time: cache_time + this.time()});
53
65
  }
54
66
 
55
67
  /**
@@ -60,11 +72,69 @@ class Cache
60
72
  */
61
73
  static delete(key) {
62
74
  if(key) {
63
- delete this.cache[key];
75
+ this.cache.delete(key);
64
76
  } else {
65
77
  // @ts-ignore
66
- this.cache = {};
78
+ this.cache = new Map();
79
+ }
80
+ }
81
+
82
+ /**
83
+ * 检查缓存是否存在
84
+ * @public
85
+ * @static
86
+ * @param {string} key - 缓存键
87
+ * @returns {boolean}
88
+ */
89
+ static has(key) {
90
+ const item = this.cache.get(key);
91
+ if(item && item.time > this.time()) {
92
+ return true;
93
+ } else if(item) {
94
+ // 已过期,删除
95
+ this.delete(key);
96
+ }
97
+ return false;
98
+ }
99
+
100
+ /**
101
+ * 获取有效缓存数量(过滤已过期)
102
+ * @public
103
+ * @static
104
+ * @returns {number}
105
+ */
106
+ static size() {
107
+ let count = 0;
108
+ const now_time = this.time();
109
+ for(let [key, item] of this.cache) {
110
+ if(item.time > now_time) {
111
+ count++;
112
+ } else {
113
+ // 自动清理过期缓存
114
+ this.delete(key);
115
+ }
67
116
  }
117
+ return count;
118
+ }
119
+
120
+ /**
121
+ * 获取所有有效缓存的键(过滤已过期)
122
+ * @public
123
+ * @static
124
+ * @returns {string[]}
125
+ */
126
+ static keys() {
127
+ const keys = [];
128
+ const now_time = this.time();
129
+ for(let [key, item] of this.cache) {
130
+ if(item.time > now_time) {
131
+ keys.push(key);
132
+ } else {
133
+ // 自动清理过期缓存
134
+ this.delete(key);
135
+ }
136
+ }
137
+ return keys;
68
138
  }
69
139
 
70
140
  /**
@@ -79,8 +149,8 @@ class Cache
79
149
  // @ts-ignore
80
150
  this.timer = setInterval(() => {
81
151
  const now_time = this.time();
82
- for(let key in this.cache) {
83
- this.cache[key].time < now_time && this.delete(key);
152
+ for(let [key, item] of this.cache) {
153
+ item.time < now_time && this.delete(key);
84
154
  }
85
155
  }, time * 1000);
86
156
  } else {
@@ -90,6 +160,17 @@ class Cache
90
160
  }
91
161
  }
92
162
 
163
+ /**
164
+ * 设置缓存存储实例
165
+ * @public
166
+ * @static
167
+ * @param {Map.<any, any>} [store] - 缓存存储实例
168
+ */
169
+ static setStore(store) {
170
+ this.cache = store || new Map();
171
+ this.setIntervalTime(cfg_cache.clear_time);
172
+ }
173
+
93
174
  /**
94
175
  * 获取当前时间戳
95
176
  * @public
@@ -101,19 +182,4 @@ class Cache
101
182
  }
102
183
  }
103
184
 
104
- /**
105
- * 缓存store
106
- */
107
- // @ts-ignore
108
- Cache.cache = {};
109
- /**
110
- * 缓存自动清理定时器
111
- */
112
- // @ts-ignore
113
- Cache.timer = null;
114
- /**
115
- * 开启缓存自动清理
116
- */
117
- Cache.setIntervalTime(cfg_cache.clear_time);
118
-
119
185
  module.exports = Cache;
package/lib/config.js CHANGED
@@ -3,7 +3,7 @@ const loader = require('./loader');
3
3
  const {format} = require('./utils/date');
4
4
 
5
5
  const app = {
6
- app_debug: true, // 调试模式
6
+ app_debug: false, // 调试模式
7
7
  app_multi: false, // 是否开启多应用
8
8
 
9
9
  default_app: 'app', // 默认应用
@@ -34,19 +34,34 @@ const db = {
34
34
  user : 'root', // 数据库用户名
35
35
  password : '', // 数据库密码
36
36
  port : '', // 数据库连接端口
37
- charset : 'utf8', // 数据库编码默认采用utf8
37
+ charset : 'utf8mb4', // 数据库编码默认采用utf8mb4
38
38
  prefix : 'jj_' // 数据库表前缀
39
- }
39
+ },
40
+ sqlite: {
41
+ type : 'sqlite', // 数据库类型
42
+ database : ':memory:', // 数据库文件绝对地址,支持:memory:内存数据库
43
+ prefix : 'jj_' // 数据库表前缀
44
+ },
45
+ mongodb: {
46
+ type : 'mongodb', // 数据库类型
47
+ host : '127.0.0.1', // 服务器地址
48
+ database : 'jj', // 数据库名
49
+ user : 'root', // 数据库用户名
50
+ password : '', // 数据库密码
51
+ port : 27017, // 数据库连接端口
52
+ prefix : 'jj_' // 数据库表前缀
53
+ },
40
54
  }
41
55
 
42
56
  const log = {
43
57
  log_level: ['system', 'error'], // [system, error, warning, info, debug, http, sql]
58
+ // @ts-ignore
44
59
  log_handle: function(level, ...args) {console.log(`[${format('YY-mm-dd HH:ii:ss')}] [${level}]`, ...args.map(msg => typeof msg == 'object' ? JSON.stringify(msg) : String(msg)));}
45
60
  }
46
61
 
47
62
  const cache = {
48
63
  cache_time: 60 * 60 * 24, // 默认缓存时间(1天),为空或false则为10年
49
- clear_time: undefined // (undefined: 清理一次, 0: 关闭自动清理, >0: 为自动清理周期)
64
+ clear_time: undefined // (undefined: 清理一次, 0: 关闭自动清理, >0: 为自动清理周期
50
65
  }
51
66
 
52
67
  const cookie = {
@@ -83,8 +98,8 @@ const tpl = {
83
98
  jump: require('./tpl/jump'),
84
99
  exception: require('./tpl/exception')
85
100
  }
86
-
87
- const base_dir = path.dirname(module.parent.parent.parent.filename);
101
+ // @ts-ignore
102
+ const base_dir = require.main.path;
88
103
  const base_config = loader(path.join(base_dir, './config'));
89
104
 
90
105
  /**
package/lib/context.js CHANGED
@@ -1,23 +1,19 @@
1
- /**
2
- * 开发jj.js库本身时,可以设置为import('../types')
3
- * @type {typeof import('../../../types')}
4
- */
5
- const Ctx = require('./ctx');
6
-
7
- /**
8
- * @extends Ctx
9
- */
10
- class Context extends Ctx
11
- {
12
- /**
13
- * Initialize a new `Context`
14
- * @public
15
- * @param {import('../types').KoaCtx} [ctx]
16
- */
17
- constructor(ctx) {
18
- super();
19
- this.ctx = ctx;
20
- }
21
- }
22
-
1
+ const Ctx = require('./ctx');
2
+
3
+ /**
4
+ * @extends Ctx
5
+ */
6
+ class Context extends Ctx
7
+ {
8
+ /**
9
+ * Initialize a new `Context`
10
+ * @public
11
+ * @param {import('../types').KoaCtx} ctx
12
+ */
13
+ constructor(ctx) {
14
+ super();
15
+ this.ctx = ctx;
16
+ }
17
+ }
18
+
23
19
  module.exports = Context;
package/lib/controller.js CHANGED
@@ -1,5 +1,9 @@
1
1
  const Middleware = require('./middleware');
2
2
 
3
+ /**
4
+ * @typedef {import('../types').ViewInstance} ViewInstance
5
+ */
6
+
3
7
  /**
4
8
  * @extends Middleware
5
9
  */
@@ -13,7 +17,7 @@ class Controller extends Middleware
13
17
  * @returns {this}
14
18
  */
15
19
  $assign(name, value) {
16
- this.$view.assign(name, value);
20
+ this._$view.assign(name, value);
17
21
  return this;
18
22
  }
19
23
 
@@ -24,7 +28,7 @@ class Controller extends Middleware
24
28
  * @returns {object}
25
29
  */
26
30
  $data(name) {
27
- return this.$view.data(name);
31
+ return this._$view.data(name);
28
32
  }
29
33
 
30
34
  /**
@@ -33,7 +37,7 @@ class Controller extends Middleware
33
37
  * @param {string} [template]
34
38
  */
35
39
  async $load(template) {
36
- const content = await this.$view.load(template);
40
+ const content = await this._$view.load(template);
37
41
  this.$show(content);
38
42
  }
39
43
 
@@ -43,7 +47,7 @@ class Controller extends Middleware
43
47
  * @param {string} content
44
48
  */
45
49
  async $render(content) {
46
- const html = await this.$view.render(content);
50
+ const html = await this._$view.render(content);
47
51
  this.$show(html);
48
52
  }
49
53
 
@@ -53,9 +57,36 @@ class Controller extends Middleware
53
57
  * @param {string} [template]
54
58
  */
55
59
  async $fetch(template) {
56
- const content = await this.$view.fetch(template);
60
+ const content = await this._$view.fetch(template);
57
61
  this.$show(content);
58
62
  }
63
+
64
+ /**
65
+ * @type {ViewInstance} View实例
66
+ * @private
67
+ */
68
+ // @ts-ignore
69
+ __view = null;
70
+
71
+ /**
72
+ * @type {ViewInstance} View实例
73
+ */
74
+ get _$view() {
75
+ if(this.__view === null) {
76
+ if(this.$view && this.$view.__ISCLASS__) {
77
+ this.__view = this.$view;
78
+ } else if(this.$ && this.$.view) {
79
+ this.__view = this.$.view;
80
+ } else {
81
+ this.__view = new (require('./view'))(this.ctx);
82
+ }
83
+ }
84
+ return this.__view;
85
+ }
86
+
87
+ set _$view(view) {
88
+ this.__view = view;
89
+ }
59
90
  }
60
91
 
61
92
  module.exports = Controller;
package/lib/cookie.js CHANGED
@@ -22,39 +22,35 @@ class Cookie extends Context
22
22
  /**
23
23
  * 获取cookie
24
24
  * @public
25
- * @param {string} [key] - 为空,则获取所有cookie
25
+ * @param {string} key - cookie键名
26
26
  * @param {import('cookies').GetOption} [options]
27
- * @returns {string}
27
+ * @returns {any}
28
28
  */
29
29
  get(key, options) {
30
- if(key === undefined) {
31
- return this.all();
32
- }
33
- return this.ctx.cookies.get(key, {signed: cfg_cookie.signed, ...options});
30
+ return this.ctx.cookies.get(key, {signed: cfg_cookie.signed ? true : false, ...options});
34
31
  }
35
32
 
36
33
  /**
37
- * 删除或清理cookie
34
+ * 删除cookie
38
35
  * @public
39
- * @param {*} [key] - 为空则清理所有cookie
36
+ * @param {string} key - cookie键名
40
37
  * @param {import('cookies').GetOption} [options]
41
38
  * @returns {this}
42
39
  */
43
40
  delete(key, options) {
44
- if(key === undefined) {
45
- this.clear();
46
- } else {
47
- this.ctx.cookies.set(key, '', {...cfg_cookie, ...options, maxAge: 0});
48
- }
41
+ this.ctx.cookies.set(key, null, {...cfg_cookie, ...options, maxAge: 0});
49
42
  return this;
50
43
  }
51
44
 
52
45
  /**
53
46
  * 获取所有cookie
54
47
  * @public
55
- * @returns {object}
48
+ * @returns {Object.<string, string>}
56
49
  */
57
50
  all() {
51
+ /**
52
+ * @type {Object.<string, string>}
53
+ */
58
54
  const cookies = {};
59
55
  this.keys().forEach(key => cookies[key] = this.get(key));
60
56
  return cookies;
@@ -73,7 +69,7 @@ class Cookie extends Context
73
69
  /**
74
70
  * 获取所有cookie key
75
71
  * @public
76
- * @returns {array}
72
+ * @returns {Array.<string>}
77
73
  */
78
74
  keys() {
79
75
  const cookie_header = this.ctx.request.headers.cookie || '';
package/lib/ctx.js CHANGED
@@ -1,21 +1,22 @@
1
1
  // @ts-nocheck
2
2
  const storage = require('./storage');
3
+ // 允许$访问的系统类库
4
+ const libs = ['ctx', 'context', 'cache', 'cookie', 'db', 'logger', 'middleware', 'model', 'pagination', 'request', 'response', 'upload', 'url', 'view', 'utils', 'config'];
3
5
 
4
6
  /**
5
- * 开发jj.js库本身时,可以设置为import('../types')
7
+ * 开发jj.js库本身时,需改为import('../types')
8
+ * type {typeof import('../types')}
6
9
  * @type {typeof import('../../../types')}
7
10
  */
8
- const Ctx = new Proxy(class {}, {
11
+ const Ctx = new Proxy(class Ctx {}, {
9
12
  construct() {
10
13
  return new Proxy({__proto__: arguments[2].prototype}, {
11
14
  get: (target, $prop, receiver) => {
12
- if($prop in target || typeof $prop == 'symbol' || $prop == 'inspect') {
15
+ if($prop in target || typeof $prop == 'symbol' || $prop == 'inspect' || $prop == 'ctx' || $prop == '$next') {
13
16
  return Reflect.get(target, $prop, receiver);
14
17
  }
15
18
 
16
19
  const store = storage.getStore();
17
- const $ = store.$;
18
- const _ = store._;
19
20
  if($prop == '$' || $prop == '_') {
20
21
  return store[$prop];
21
22
  }
@@ -23,33 +24,32 @@ const Ctx = new Proxy(class {}, {
23
24
  return undefined;
24
25
  }
25
26
 
27
+ const {$, _, APP, COMMON} = store;
26
28
  const prop = $prop.slice(1);
27
- const APP = store.APP;
28
- const COMMON = store.COMMON;
29
29
 
30
- // 缓存
31
- if(_[APP][$prop] === undefined) {
32
- if(_[APP][prop] && _[COMMON] && _[COMMON][prop]) {
33
- _[APP][$prop] = new Proxy({}, {
34
- get: (...args) => {
35
- return _[APP][prop][args[1]] || _[COMMON][prop][args[1]];
36
- }
37
- });
38
- } else if(_[APP][prop]) {
39
- _[APP][$prop] = _[APP][prop];
40
- } else if(_[COMMON] && _[COMMON][prop]) {
41
- _[APP][$prop] = _[COMMON][prop];
42
- } else {
43
- _[APP][$prop] = null;
44
- }
45
- // lib内有调用,防止覆盖
46
- if(['logger', 'pagination', 'request', 'response', 'url', 'view'].includes(prop) && _[APP][$prop] && _[APP][$prop].__node && !_[APP][$prop].__node.isClass && $[prop]) {
47
- _[APP][$prop] = $[prop];
48
- }
30
+ if(!_[APP]) {
31
+ return libs.includes(prop) ? $[prop] : undefined;
32
+ }
33
+
34
+ if(!_[APP][prop] && !(_[COMMON] && _[COMMON][prop]) && !libs.includes(prop)) {
35
+ return undefined;
49
36
  }
50
37
 
51
- // 屏蔽部分lib
52
- return _[APP][$prop] || _[prop] || (!['app', 'loader', 'router', 'run', 'storage', 'types'].includes(prop) && $[prop]) || undefined;
38
+ return _[APP][$prop] = new Proxy(function() {}, {
39
+ construct(target, args, newTarget) {
40
+ const node = _[APP][prop] && _[APP][prop].__ISCLASS__ && _[APP][prop]
41
+ || _[COMMON] && _[COMMON][prop].__ISCLASS__ && _[COMMON][prop]
42
+ || libs.includes(prop) && $[prop].__ISCLASS__ && $[prop]
43
+ || undefined;
44
+ return node ? Reflect.construct(node, args, newTarget) : undefined;
45
+ },
46
+ get: (...args) => {
47
+ return res = _[APP][prop] && _[APP][prop][args[1]]
48
+ || _[COMMON] && _[COMMON][prop][args[1]]
49
+ || libs.includes(prop) && $[prop][args[1]]
50
+ || undefined;
51
+ }
52
+ });
53
53
  }
54
54
  });
55
55
  }