jj.js 0.10.0 → 0.11.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/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # v0.11.0 / 2024-02-26
2
+ 1. 基于`AsyncLocalStorage`重构核心类
3
+ 2. 空控制器和空方法名改为`_empty`
4
+
1
5
  # v0.10.0 / 2024-02-26
2
6
  1. 类型文件位置调整及优化
3
7
  2. 自动生成应用端类型文件(开启app_debug,并且根目录下存在jsconfig.json文件)
package/README.md CHANGED
@@ -6,15 +6,15 @@ A super simple lightweight NodeJS MVC framework(一个超级简单轻量的Nod
6
6
 
7
7
  ## 项目介绍
8
8
 
9
- 本框架依赖koa2、@koa/routerart-templatemysql,基于proxy实现了代码自动加载及懒加载技术,最低运行依赖仅仅为koa和koa-router,非常轻量。
9
+ 无论什么语言什么框架的程序,类库的使用都离不开requireimportinclude等导入模块,不用时,还得删除。而本框架基于proxy实现了类库自动加载、懒加载和Class自动实例化及单例化技术,所有的类库,想用直接调用,系统会自动导入。
10
10
 
11
11
  ### 项目特性
12
12
 
13
- 1. 系统架构模仿Thinkphp5,很容易上手
14
- 2. 系统类库、用户类库都支持自动加载、懒加载、自动生成单实例
13
+ 1. 系统架构为经典的MVC模式,模仿Thinkphp5,很容易上手
14
+ 2. 系统类库、用户类库都支持自动加载、懒加载、Class自动生成单实例
15
15
  3. 支持应用级、路由级、控制器级三级中间件,方便插件及二次开发
16
16
  4. 支持单应用和多应用两种运行模式
17
- 5. 基于jsdoc,提供完整的代码提示。支持自动生成应用端jsdoc文件
17
+ 5. 基于jsdoc,提供完整的代码提示,自动生成应用端types类型文件
18
18
 
19
19
  ### 项目地址
20
20
 
@@ -230,17 +230,6 @@ const app = {
230
230
  }
231
231
  module.exports = app;
232
232
  ```
233
- - 模板配置`./config/view.js`:
234
- ```javascript
235
- const view = {
236
- view_folder: 'view', // 模板目录名
237
- view_depr: '/', // 模版文件名分割符,'/'代表二级目录
238
- view_ext: '.htm', // 模版文件后缀
239
- view_engine: 'art-template', // 默认模版引擎,字符串或引擎类,咱不支持更换
240
- view_filter: {}, // 模版函数,配置注入模板的函数,默认会自动注入url函数
241
- }
242
- module.exports = view;
243
- ```
244
233
  - 数据库配置`./config/db.js`:可以配置多个,方便程序里切换使用。
245
234
  ```javascript
246
235
  const db = {
@@ -257,67 +246,7 @@ const db = {
257
246
  }
258
247
  module.exports = db;
259
248
  ```
260
- - 日志配置`./config/log.js`:log_handle可以自定义日志handle
261
- ```javascript
262
- const log = {
263
- log_level: [], // [error, warning, info, debug, http, sql]
264
- 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) {}
265
- }
266
- module.exports = log;
267
- ```
268
- - 缓存配置`./config/cache.js`:
269
- ```javascript
270
- const cache = {
271
- cache_time: 60 * 60 * 24, // 默认缓存时间(1天),为空或false则为10年
272
- clear_time: undefined // (undefined: 一天清理一次, 0: 关闭自动清理, >0: 为自动清理周期)
273
- }
274
- module.exports = cache;
275
- ```
276
- - 分页配置`./config/page.js`:
277
- ```javascript
278
- const page = {
279
- page_key : 'page', // 默认分页标识
280
- key_origin : 'query', // query 或 params
281
- page_size : 10, // 默认分页大小
282
- page_length : 5, // 默认分页长度,数字页码链接数量
283
-
284
- //网址规则,可为空,可为路由名字,可用参数:页码${page}
285
- //样例:':name'
286
- //样例:'/list_${page}.html'
287
- url_page : '',
288
- url_index : '',
289
-
290
- //模块样式 可用参数:网址${url},页码${page},总数${total_page},总页数${total_page}
291
- index_tpl : '<li class="index"><a href="${url}">首页</a></li>',
292
- end_tpl : '<li class="end"><a href="${url}">末页</a></li>',
293
- prev_tpl : '<li class="prev"><a href="${url}">上一页</a></li>',
294
- next_tpl : '<li class="next"><a href="${url}">下一页</a></li>',
295
- list_tpl : '<li><a href="${url}">${page}</a></li>',
296
- active_tpl : '<li class="active"><a href="${url}">${page}</a></li>',
297
- info_tpl : '<span class="info">共${total_page}页,${total}条记录</span>',
298
-
299
- //渲染模版
300
- template : '<div class="pagination"><ul class="page">${index}${prev}${list}${next}${end}</ul>${info}</div>'
301
- }
302
- module.exports = page;
303
- ```
304
- - 跳转模板配置`./config/tpl.js`:模板可以配置为自定义的
305
- ```javascript
306
- const tpl = {
307
- jump: require('./tpl/jump'), // 跳转模板
308
- exception: require('./tpl/exception') // 异常页面模板
309
- }
310
- module.exports = tpl;
311
- ```
312
- - 自定义配置`./config/self.js`:自定义配置同样可以直接通过`this.$config.self`使用。
313
- ```javascript
314
- const self = {
315
- option1: ''
316
- option2: ''
317
- ...
318
- }
319
- module.exports = self;
320
- ```
249
+
321
250
  - 路由配置`./config/routes.js`:
322
251
  路由功能基于`@koa/router`开发,关于url匹配规则可以参考官方文档:[文档地址](https://www.npmjs.com/package/@koa/router)
323
252
 
@@ -338,26 +267,22 @@ module.exports = routes;
338
267
  ```
339
268
  > 注意:本路由配置示例前两条规则为常规用法,后面的非常规用法,不建议使用。如果是单应用模式,可以去掉path参数里的app。
340
269
 
341
- ### 应用入口文件
342
-
343
- 应用入口文件放在项目的根目录,名字可以任意改。
344
-
345
- 示例文件 `./server.js`
270
+ - 自定义配置`./config/self.js`:自定义配置同样可以直接通过`this.$config.self`使用。
346
271
  ```javascript
347
- const {app, Logger} = require('jj.js');
348
-
349
- app.run(3000, '127.0.0.1', function(err){
350
- !err && Logger.info('http server is ready on 3000');
351
- });
272
+ const self = {
273
+ option1: ''
274
+ option2: ''
275
+ ...
276
+ }
277
+ module.exports = self;
352
278
  ```
353
- 其中`app`是一个koa实例,可以使用`use`方法添加应用级中间件,使用方法参考koa文档。`app`的`run`方法经过了封装,仅允许调用一次。其中`Logger`因为是一个静态类,所以可以直接调用`info`方法输出日志。
279
+ - 其他配置项,请参考系统配置文件`jj.js/lib/config.js`
280
+
281
+ ### 编码命名规范
354
282
 
355
- ## 总结
283
+ 类名使用大驼峰,方法名使用小驼峰,私有方法使用下划线前缀。
284
+ 控制器文件名使用小写下划线。
356
285
 
357
- 通过以上文档,可以看到:
358
- 1. jj.js是一个轻量的mvc框架,几乎所有文件不使用就不会调用。
359
- 2. 同时也是个功能强大的框架,支持应用级、路由级、控制器级三级中间件。
360
- 3. 类库自动加载,只要是继承自`Context`的类库,都可以在方法内使用包含`$`前缀的属性自动加载其他资源。
361
286
 
362
287
  ## 应用案例
363
288
 
package/jj.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * jj.js核心库<br/>
3
- * {App, Cache, Context, Controller, Cookie, Ctx, Db, Logger, Middleware, Model, Pagination, Response, Upload, Url, View, utils}
3
+ * {App, Cache, Context, Controller, Cookie, Ctx, Db, Logger, Middleware, Model, Pagination, Response, Upload, Url, View, utils, config}
4
4
  * @module core
5
5
  * @type {import('./types').Core}
6
6
  */
package/lib/app.js CHANGED
@@ -4,6 +4,7 @@ const {app: cfg_app} = require('./config');
4
4
  const Logger = require('./logger');
5
5
  const Response = require('./response');
6
6
  const router = require('./router');
7
+ const storage = require('./storage');
7
8
  const pjson = require('../package.json');
8
9
 
9
10
  /**
@@ -16,6 +17,13 @@ class App extends Koa
16
17
  * @param {...any} args - listen(port, ip, callback(err){})
17
18
  */
18
19
  listen(...args) {
20
+ // storage
21
+ this.use(async (ctx, next) => {
22
+ await storage.run({}, async() => {
23
+ await next();
24
+ });
25
+ });
26
+
19
27
  // exception
20
28
  this.use(async (ctx, next) => {
21
29
  ctx.APP_TIME = Date.now();
package/lib/ctx.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @ts-nocheck
2
- const loader = require('./loader');
2
+ const storage = require('./storage');
3
3
 
4
4
  /**
5
5
  * 开发jj.js库本身时,可以设置为import('../types')
@@ -8,46 +8,48 @@ const loader = require('./loader');
8
8
  const Ctx = new Proxy(class {}, {
9
9
  construct() {
10
10
  return new Proxy({__proto__: arguments[2].prototype}, {
11
- get: (target, prop, receiver) => {
12
- if(prop in target || typeof prop == 'symbol' || prop == 'inspect') {
13
- return Reflect.get(target, prop, receiver);
11
+ get: (target, $prop, receiver) => {
12
+ if($prop in target || typeof $prop == 'symbol' || $prop == 'inspect') {
13
+ return Reflect.get(target, $prop, receiver);
14
14
  }
15
- if(prop == '$' || prop == '_') {
16
- return target.ctx && target.ctx[prop]
17
- || prop == '$' && (target.$ || (target.$ = loader('../'))) && target.$
18
- || undefined;
15
+
16
+ const store = storage.getStore();
17
+ const $ = store.$;
18
+ const _ = store._;
19
+ if($prop == '$' || $prop == '_') {
20
+ return store[$prop];
19
21
  }
20
- if(prop.slice(0, 1) != '$') {
22
+ if($prop.slice(0, 1) != '$') {
21
23
  return undefined;
22
24
  }
23
- const pp = prop.slice(1);
24
- const ctx = target.ctx;
25
- if(ctx && ctx._) {
26
- const _ = ctx._;
27
- const APP = ctx.APP;
28
- const COMMON = _.config.app.common_app;
29
- if(_[APP][prop] === undefined) {
30
- if(_[APP][pp] && _[COMMON] && _[COMMON][pp]) {
31
- _[APP][prop] = new Proxy({}, {
32
- get: (...args) => {
33
- return _[APP][pp][args[1]] || _[COMMON][pp][args[1]];
34
- }
35
- });
36
- } else if(_[APP][pp]) {
37
- _[APP][prop] = _[APP][pp];
38
- } else if(_[COMMON] && _[COMMON][pp]) {
39
- _[APP][prop] = _[COMMON][pp];
40
- } else {
41
- _[APP][prop] = null;
42
- }
43
- if(~['view', 'response'].indexOf(pp) && _[APP][prop] && _[APP][prop].__node && !_[APP][prop].__node.isClass && ctx.$ && ctx.$[pp]) {
44
- _[APP][prop] = ctx.$[pp];
45
- }
25
+
26
+ const prop = $prop.slice(1);
27
+ const APP = store.APP;
28
+ const COMMON = store.COMMON;
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', 'response', 'url', 'view'].includes(prop) && _[APP][$prop] && _[APP][$prop].__node && !_[APP][$prop].__node.isClass && $[prop]) {
47
+ _[APP][$prop] = $[prop];
46
48
  }
47
- return _[APP][prop] || _[pp] || (ctx.$ && ctx.$[pp]) || undefined;
48
- } else {
49
- return (target.$ || (target.$ = loader('../'))) && target.$[pp];
50
49
  }
50
+
51
+ // 屏蔽部分lib
52
+ return _[APP][$prop] || _[prop] || (!['app', 'loader', 'router', 'run', 'storage', 'types'].includes(prop) && $[prop]) || undefined;
51
53
  }
52
54
  });
53
55
  }
package/lib/db.js CHANGED
@@ -699,9 +699,7 @@ class Db extends Context
699
699
  async pagination({page, page_size, pagination} = {}) {
700
700
  !page && (page = this._options.page.page);
701
701
  !page_size && (page_size = this._options.page.pageSize);
702
- if(!pagination) {
703
- pagination = this.$pagination.__node && this.$pagination.__node.isClass ? this.$pagination : this.$.pagination;
704
- }
702
+ !pagination && (pagination = this.$pagination);
705
703
 
706
704
  const options = {...this._options}; // 暂存options
707
705
  const total = await this.count();
package/lib/run.js CHANGED
@@ -2,38 +2,42 @@ const path = require('path');
2
2
  const loader = require('./loader');
3
3
  const {readFile} = require('fs').promises;
4
4
  const {toHump} = require('./utils/str');
5
- const {app: cfg_app, view: cfg_view, db, page, log, cache, cookie, tpl} = require('./config');
5
+ const config = require('./config');
6
+ const {app: cfg_app, view: cfg_view} = config;
6
7
  const compose = require('koa-compose');
7
8
  const Logger = require('./logger');
9
+ const storage = require('./storage');
8
10
 
9
11
  async function run(ctx, next, control_type=cfg_app.controller_folder) {
10
12
  if(cfg_app.app_debug) {
11
13
  Logger.http('Request:', ctx.request);
12
- Logger.http(`Router(${control_type}): {APP: '${ctx.APP}', CONTROLLER: '${ctx.CONTROLLER}', ACTION: '${ctx.ACTION}'}`);
14
+ Logger.http({Router: control_type, APP: ctx.APP, CONTROLLER: ctx.CONTROLLER, ACTION: ctx.ACTION});
13
15
  Object.keys(ctx.params).length && Logger.http('Params:', ctx.params);
14
16
  Object.keys(ctx.query).length && Logger.http('Get:', ctx.query);
15
17
  Object.keys(ctx.request.body || {}).length && Logger.http('Post:', ctx.request.body);
16
18
  }
17
19
 
20
+ // 设置store
21
+ const store = storage.getStore();
22
+ store.APP = ctx.APP;
23
+ store.CONTROLLER = ctx.CONTROLLER;
24
+ store.ACTION = ctx.ACTION;
25
+ store.COMMON = cfg_app.common_app;
18
26
  // 设置根加载器
19
- ctx.$ = loader('./', ctx);
20
- ctx._ = loader(cfg_app.base_dir, ctx);
21
- ctx._.config === undefined && (ctx._.config = {});
22
- ctx._.config.app = cfg_app;
23
- ctx._.config.view = cfg_view;
24
- ctx._.config.db = db;
25
- ctx._.config.page = page;
26
- ctx._.config.log = log;
27
- ctx._.config.cache = cache;
28
- ctx._.config.cookie = cookie;
29
- ctx._.config.tpl = tpl;
27
+ store.$ = loader('./', ctx);
28
+ store._ = loader(cfg_app.base_dir, ctx);
29
+ // 配置特殊处理
30
+ store._.config === undefined && (store._.config = {});
31
+ Object.entries(config).forEach(([key, value]) => {
32
+ store._.config[key] = value;
33
+ });
30
34
 
31
35
  // 应用、控制器、方法名字验证
32
36
  if(~ctx.APP.indexOf('.') || ~ctx.CONTROLLER.indexOf('.') || ~ctx.ACTION.indexOf('.')) {
33
37
  throw new Error(`RunError: 应用、控制器或方法名字不合法!`);
34
38
  }
35
39
 
36
- if(ctx.APP[0] == '_' || ctx.APP[0] == '$' || !ctx._[ctx.APP]) {
40
+ if(ctx.APP[0] == '_' || ctx.APP[0] == '$' || !store._[ctx.APP]) {
37
41
  if(cfg_app.app_debug) {
38
42
  throw new Error(`RunError: 应用:${ctx.APP}不存在!`);
39
43
  } else {
@@ -41,7 +45,7 @@ async function run(ctx, next, control_type=cfg_app.controller_folder) {
41
45
  }
42
46
  }
43
47
 
44
- if(!ctx._[ctx.APP][control_type]) {
48
+ if(!store._[ctx.APP][control_type]) {
45
49
  if(cfg_app.app_debug) {
46
50
  throw new Error(`RunError: 目录:${ctx.APP}/${control_type}不存在!`);
47
51
  } else {
@@ -51,7 +55,7 @@ async function run(ctx, next, control_type=cfg_app.controller_folder) {
51
55
 
52
56
  // 模版文件内容直接输出
53
57
  if(control_type == cfg_view.view_folder) {
54
- const content = await readFile(path.join(ctx._[ctx.APP][control_type].__node.path, ctx.CONTROLLER + cfg_view.view_depr + ctx.ACTION + cfg_view.view_ext));
58
+ const content = await readFile(path.join(cfg_app.base_dir, `${ctx.APP}/${control_type}`, ctx.CONTROLLER + cfg_view.view_depr + ctx.ACTION + cfg_view.view_ext));
55
59
  if(!content) {
56
60
  throw new Error(`RunError: 模板文件:${ctx.APP}/${control_type}/${ctx.CONTROLLER + cfg_view.view_depr + ctx.ACTION + cfg_view.view_ext}不存在!`);
57
61
  }
@@ -60,7 +64,7 @@ async function run(ctx, next, control_type=cfg_app.controller_folder) {
60
64
  }
61
65
 
62
66
  // 控制器
63
- const Controller = ctx._[ctx.APP][control_type][ctx.CONTROLLER] || ctx._[ctx.APP][control_type]['empty'];
67
+ const Controller = store._[ctx.APP][control_type][ctx.CONTROLLER] || store._[ctx.APP][control_type]['_empty'];
64
68
 
65
69
  if(ctx.CONTROLLER[0] == '_' || ctx.CONTROLLER[0] == '$' || typeof Controller != 'function') {
66
70
  if(cfg_app.app_debug) {
@@ -73,7 +77,7 @@ async function run(ctx, next, control_type=cfg_app.controller_folder) {
73
77
  // 控制器实例
74
78
  const $controller = new Controller(ctx, next);
75
79
  const humpAction = toHump(ctx.ACTION);
76
- const action = typeof $controller[humpAction] == 'function' ? humpAction : 'empty';
80
+ const action = typeof $controller[humpAction] == 'function' ? humpAction : '_empty';
77
81
 
78
82
  if(ctx.ACTION[0] == '_' || ctx.ACTION[0] == '$' || typeof $controller[action] != 'function') {
79
83
  if(cfg_app.app_debug) {
@@ -96,13 +100,13 @@ async function run(ctx, next, control_type=cfg_app.controller_folder) {
96
100
  }
97
101
  return stack.concat(async (ctx, next) => {
98
102
  const [METHOD = action, MIDDLEWARE = ctx.CONTROLLER, APP = ctx.APP] = middle.middleware.split('/').reverse();
99
- if(!ctx._[APP]) {
103
+ if(!store._[APP]) {
100
104
  throw new Error(`RunError: 中间件应用:${APP}不存在!`);
101
105
  }
102
- if(!ctx._[APP]['middleware']) {
106
+ if(!store._[APP]['middleware']) {
103
107
  throw new Error(`RunError: 中间件目录:${APP}/middleware不存在!`);
104
108
  }
105
- const Middleware = ctx._[APP]['middleware'][MIDDLEWARE];
109
+ const Middleware = store._[APP]['middleware'][MIDDLEWARE];
106
110
  if(!Middleware || typeof Middleware != 'function') {
107
111
  throw new Error(`RunError: 中间件文件:${APP}/middleware/${MIDDLEWARE}不存在!`);
108
112
  }
package/lib/storage.js ADDED
@@ -0,0 +1,5 @@
1
+ const {AsyncLocalStorage} = require('async_hooks');
2
+
3
+ const storage = new AsyncLocalStorage();
4
+
5
+ module.exports = storage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jj.js",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "A super simple lightweight NodeJS MVC framework(一个超级简单轻量的NodeJS MVC框架)",
5
5
  "main": "jj.js",
6
6
  "scripts": {},
package/types.js CHANGED
@@ -11,13 +11,13 @@
11
11
  * @typedef {typeof import('./lib/model')} Model
12
12
  * @typedef {typeof import('./lib/pagination')} Pagination
13
13
  * @typedef {typeof import('./lib/response')} Response
14
+ * @typedef {typeof import('./lib/storage')} Storage
14
15
  * @typedef {typeof import('./lib/upload')} Upload
15
16
  * @typedef {typeof import('./lib/url')} Url
16
17
  * @typedef {typeof import('./lib/view')} View
17
18
  */
18
19
 
19
20
  /**
20
- * @typedef {typeof import('./lib/app').prototype} AppInstance
21
21
  * @typedef {typeof import('./lib/context').prototype} ContextInstance
22
22
  * @typedef {typeof import('./lib/controller').prototype} ControllerInstance
23
23
  * @typedef {typeof import('./lib/cookie').prototype} CookieInstance
@@ -50,12 +50,14 @@
50
50
  * @property {Url} Url
51
51
  * @property {View} View
52
52
  * @property {Utils} utils
53
+ * @property {Config} config
54
+ * @property {Storage} storage
53
55
  */
54
56
 
55
57
  /**
56
58
  * @typedef {Object} $
57
- * @property {(App & AppInstance)} app
58
59
  * @property {Cache} cache
60
+ * @property {Config} config
59
61
  * @property {ContextInstance} context
60
62
  * @property {ControllerInstance} controller
61
63
  * @property {CookieInstance} cookie