jj.js 0.19.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/upload.js CHANGED
@@ -61,11 +61,11 @@ class Upload extends Context
61
61
  /**
62
62
  * 保存文件到目录
63
63
  * @public
64
- * @param {string} [dir]
64
+ * @param {string} [dir] - 保存目录,默认为当前目录
65
65
  * @returns {Promise<(boolean|UploadData)>}
66
66
  */
67
- async save(dir) {
68
- if(!this.check()) {
67
+ async save(dir = './') {
68
+ if(!this.check() || !this._file) {
69
69
  return false;
70
70
  }
71
71
 
@@ -76,7 +76,7 @@ class Upload extends Context
76
76
  savename = utils.date.format('YYYY/mmdd/') + utils.md5(Date.now() + Math.random().toString(36).substr(2));
77
77
  }
78
78
  if(!pt.extname(savename)) {
79
- savename += pt.extname(this._file.originalFilename);
79
+ savename += pt.extname(this._file.originalFilename || '');
80
80
  }
81
81
 
82
82
  try {
@@ -98,7 +98,7 @@ class Upload extends Context
98
98
  extname: pt.extname(savename).slice(1),
99
99
  savename,
100
100
  filepath: filePath,
101
- name: this._file.originalFilename,
101
+ name: this._file.originalFilename || '',
102
102
  size: this._file.size,
103
103
  mimetype: this._file.mimetype,
104
104
  hash: this._file.hash
@@ -113,10 +113,10 @@ class Upload extends Context
113
113
  * 获取上传文件
114
114
  * @public
115
115
  * @param {string} [name] - 为空时,获取所有上传文件
116
- * @returns {(File|File[]|Files)}
116
+ * @returns {(File|File[]|Files|undefined)}
117
117
  */
118
118
  getFile(name) {
119
- return typeof name == 'string' ? this.ctx.request.files[name] : this.ctx.request.files;
119
+ return typeof name == 'string' ? this.ctx.request.files && this.ctx.request.files[name] : this.ctx.request.files;
120
120
  }
121
121
 
122
122
  /**
@@ -125,7 +125,7 @@ class Upload extends Context
125
125
  * @returns {string}
126
126
  */
127
127
  getError() {
128
- return this._error;
128
+ return this._error || '';
129
129
  }
130
130
 
131
131
  /**
@@ -135,7 +135,7 @@ class Upload extends Context
135
135
  */
136
136
  check() {
137
137
  if(!this._file) {
138
- this._error = '上传文件为找到!';
138
+ this._error = '上传文件未找到!';
139
139
  return false;
140
140
  }
141
141
 
@@ -167,14 +167,14 @@ class Upload extends Context
167
167
  /**
168
168
  * 检查上传文件后缀
169
169
  * @public
170
- * @param {(string|array)} ext
170
+ * @param {(string|string[])} ext
171
171
  * @returns {boolean}
172
172
  */
173
173
  checkExt(ext) {
174
174
  if(typeof ext == 'string') {
175
175
  ext = ext.split(',');
176
176
  }
177
- if(!~ext.indexOf(pt.extname(this._file.originalFilename).slice(1).toLowerCase())) {
177
+ if(!~ext.indexOf(pt.extname(this._file && this._file.originalFilename || '').slice(1).toLowerCase())) {
178
178
  return false;
179
179
  }
180
180
  return true;
@@ -183,14 +183,14 @@ class Upload extends Context
183
183
  /**
184
184
  * 检查上传文件mimetype
185
185
  * @public
186
- * @param {(string|array)} type
186
+ * @param {(string|string[])} type
187
187
  * @returns {boolean}
188
188
  */
189
189
  checkType(type) {
190
190
  if(typeof type == 'string') {
191
191
  type = type.split(',');
192
192
  }
193
- if(!~type.indexOf(this._file.mimetype.toLowerCase())) {
193
+ if(!~type.indexOf((this._file && this._file.mimetype || '').toLowerCase())) {
194
194
  return false;
195
195
  }
196
196
  return true;
@@ -202,8 +202,8 @@ class Upload extends Context
202
202
  * @returns {boolean}
203
203
  */
204
204
  checkImg() {
205
- const fileExt = pt.extname(this._file.originalFilename).slice(1).toLowerCase();
206
- const fileType = this._file.mimetype.toLowerCase().split('/');
205
+ const fileExt = pt.extname(this._file && this._file.originalFilename || '').slice(1).toLowerCase();
206
+ const fileType = (this._file && this._file.mimetype || '').toLowerCase().split('/');
207
207
  const exts = ['gif', 'png', 'jpeg', 'webp', 'ico', 'bmp', 'svg', 'jpg', 'tiff'];
208
208
  const mimes = ['gif', 'png', 'jpeg', 'webp', 'x-icon', 'bmp', 'svg+xml', 'tiff'];
209
209
  if(~exts.indexOf(fileExt) && (fileType[0] != 'image' || !~mimes.indexOf(fileType[1]))) {
package/lib/url.js CHANGED
@@ -1,6 +1,13 @@
1
1
  const {app: cfg_app, routes: cfg_routes} = require('./config');
2
+ /**
3
+ * @typedef {import('../types').RouteConfigItem} RouteConfigItem
4
+ * @type {Object.<string, RouteConfigItem>}
5
+ */
2
6
  const routes = (cfg_routes || []).reverse().reduce((routes, route) => {
3
- if(route.name){
7
+ if(route.name) {
8
+ // @ts-ignore
9
+ if(routes[route.name]) throw new Error(`RouteError: 重复的路由名字:${route.name}`);
10
+ // @ts-ignore
4
11
  routes[route.name] = route;
5
12
  }
6
13
  return routes;
@@ -30,7 +37,7 @@ class Url extends Context
30
37
  if(typeof vars != 'object') {
31
38
  [vars, ext, domain] = [{}, vars, ext];
32
39
  }
33
- if(ext === true || ext.slice(0, 4) == 'http') {
40
+ if(typeof ext == 'boolean' || ext.slice(0, 4) == 'http') {
34
41
  [ext, domain] = ['', ext];
35
42
  }
36
43
  if(domain === true) {
@@ -83,6 +90,7 @@ class Url extends Context
83
90
  url = routes[url].url;
84
91
 
85
92
  if(~url.indexOf(':')) {
93
+ // @ts-ignore
86
94
  url = url.replace(/\:([^/]+\(.*\)|[^/]+)/g, (match, key) => {
87
95
  key = key.split(/[(.]/);
88
96
  const k = key[0];
@@ -1,24 +1,28 @@
1
1
  const fs = require('./fs.js');
2
2
 
3
+ /**
4
+ * @typedef {import('../../types').ExceptionData} ExceptionData
5
+ */
6
+
3
7
  /**
4
8
  * @function - 解析Error数据
5
9
  * @param {Error} err - 错误对象
6
- * @returns {object} - 解析后数据
10
+ * @returns {ExceptionData} - 解析后数据
7
11
  */
8
12
  function parseError(err) {
9
13
  let msg = err.message;
10
- let stack = err.stack.replace(/(\r\n)/g, "\n").split("\n");
14
+ let stack = (err.stack || '').replace(/(\r\n)/g, "\n").split("\n");
11
15
 
12
16
  if(err.name == 'TemplateError') {
13
17
  msg = 'TemplateError: ' + stack.pop();
14
- stack = [stack.shift().replace('TemplateError:', "at")];
18
+ stack = [(stack.shift() || '').replace('TemplateError:', "at")];
15
19
  } else {
16
20
  stack = stack.filter(text => {return /^ at /.test(text);});
17
21
  }
18
22
 
19
23
  const file_info = stack[0].split(' ').slice(-1)[0].replace(/(\()|(\))/g, '').split(':');
20
- const column = parseInt(file_info.pop());
21
- const row = parseInt(file_info.pop());
24
+ const column = parseInt(file_info.pop() || '0');
25
+ const row = parseInt(file_info.pop() || '0');
22
26
  const file_path = file_info.join(':');
23
27
  let begin = row - 10;
24
28
  let end = row + 10;
@@ -7,9 +7,11 @@
7
7
  module.exports = new Proxy({}, {
8
8
  get: (target, prop) => {
9
9
  if(prop in target || typeof prop == 'symbol' || prop == 'inspect') {
10
+ // @ts-ignore
10
11
  return target[prop];
11
12
  }
12
13
  if(prop == 'type') {
14
+ // @ts-ignore
13
15
  return para => Object.prototype.toString.call(para).slice(8, -1);
14
16
  }
15
17
  return require('./' + prop);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Validate path name
3
+ * @param {string} name
4
+ * @returns {boolean}
5
+ */
6
+ function validatePath(name) {
7
+ if (!/^[a-zA-Z0-9_]+$/.test(name)) {
8
+ return false;
9
+ }
10
+ return true;
11
+ }
12
+
13
+ module.exports = { validatePath };
package/lib/view.js CHANGED
@@ -5,19 +5,36 @@ const {app: cfg_app, view: cfg_view} = require('./config');
5
5
  const {toLine} = require('./utils/str');
6
6
  const Context = require('./context');
7
7
 
8
+ /**
9
+ * @typedef {import('../types').KoaCtx} KoaCtx
10
+ * @typedef {import('../types').ViewEngine} ViewEngine
11
+ * @typedef {import('../types').ViewFilter} ViewFilter
12
+ * @typedef {import('../types').UrlInstance} UrlInstance
13
+ */
14
+
8
15
  /**
9
16
  * @extends Context
10
17
  */
11
18
  class View extends Context
12
19
  {
20
+ /**
21
+ * @type {Object.<string, any>}
22
+ */
23
+ _data = {};
24
+
25
+ /**
26
+ * @type {ViewEngine}
27
+ */
28
+ // @ts-ignore
29
+ _engine = null;
30
+
13
31
  /**
14
32
  * Initialize a new `View`
15
33
  * @public
16
- * @param {import('../types').KoaCtx} ctx
34
+ * @param {KoaCtx} ctx
17
35
  */
18
36
  constructor(ctx) {
19
37
  super(ctx);
20
- this._data = {};
21
38
  this.setEngine(cfg_view.view_engine);
22
39
  }
23
40
 
@@ -61,7 +78,8 @@ class View extends Context
61
78
  * @returns {this}
62
79
  */
63
80
  assign(name, value) {
64
- if(typeof name == 'string') {
81
+
82
+ if(typeof name == 'string') {console.log('assign', this, Object.keys(this));
65
83
  this._data[name] = value;
66
84
  } else {
67
85
  this._data = name;
@@ -88,11 +106,11 @@ class View extends Context
88
106
  * @param {string} [view_depr] - 模板文件分隔符
89
107
  * @returns {string}
90
108
  */
91
- parsePath(template, view_folder, view_depr) {
109
+ parsePath(template = '', view_folder, view_depr) {
92
110
  template = template || this.ctx.ACTION
93
111
  !view_folder && (view_folder = cfg_view.view_folder);
94
112
  !view_depr && (view_depr = cfg_view.view_depr);
95
- let view_file = template;
113
+ let view_file = template || '';
96
114
 
97
115
  if(view_file.indexOf('/') !== 0) {
98
116
  const temp = view_file.replace(/^\/|\/$/g, '').split('/').reverse().map(u => toLine(u));
@@ -110,7 +128,7 @@ class View extends Context
110
128
  * @param {string} [template] - 默认当前请求方法名,支持智能解析
111
129
  * @returns {string}
112
130
  */
113
- parseTplName(template) {
131
+ parseTplName(template = '') {
114
132
  let file_name = this.parsePath(template, this._folder, this._depr);
115
133
  let is_file = isFileSync(file_name);
116
134
  if(!is_file && (this._folder || this._depr)) {
@@ -133,8 +151,9 @@ class View extends Context
133
151
  setEngine(engine) {
134
152
  this._engine = typeof engine == 'string' ? require(engine) : engine;
135
153
  this._engine.defaults.debug = cfg_app.app_debug;
154
+ // @ts-ignore
136
155
  this.setFilter('url', (...args) => {
137
- return this.$url.build(...args);
156
+ return this._$url.build(...args);
138
157
  });
139
158
  this.setFilter(cfg_view.view_filter);
140
159
  return this;
@@ -143,7 +162,7 @@ class View extends Context
143
162
  /**
144
163
  * 设置模版函数
145
164
  * @public
146
- * @param {(string|object)} fun_obj - 函数名字或者包含多个函数的对象
165
+ * @param {(string|ViewFilter)} fun_obj - 函数名字或者包含多个函数的对象
147
166
  * @param {*} [fun] - 函数
148
167
  * @returns {this}
149
168
  */
@@ -179,6 +198,33 @@ class View extends Context
179
198
  this._depr = view_depr;
180
199
  return this;
181
200
  }
201
+
202
+ /**
203
+ * @type {UrlInstance} Url实例
204
+ * @private
205
+ */
206
+ // @ts-ignore
207
+ __url = null;
208
+
209
+ /**
210
+ * @type {UrlInstance} Url实例
211
+ */
212
+ get _$url() {
213
+ if(this.__url === null) {
214
+ if(this.$url && this.$url.__ISCLASS__) {
215
+ this.__url = this.$url;
216
+ } else if(this.$ && this.$.url) {
217
+ this.__url = this.$.url;
218
+ } else {
219
+ this.__url = new (require('./url'))(this.ctx);
220
+ }
221
+ }
222
+ return this.__url;
223
+ }
224
+
225
+ set _$url(url) {
226
+ this.__url = url;
227
+ }
182
228
  }
183
229
 
184
230
  module.exports = View;
package/logo.png ADDED
Binary file
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "jj.js",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "A super simple lightweight NodeJS MVC framework(一个超级简单轻量的NodeJS MVC框架)",
5
5
  "main": "jj.js",
6
6
  "scripts": {
7
- "test": "node ./test/server.js"
7
+ "demo": "node ./tests/_demo/server.js",
8
+ "test": "node --test"
8
9
  },
9
10
  "repository": {
10
11
  "type": "git",
@@ -29,11 +30,14 @@
29
30
  "koa": "^2.16.0",
30
31
  "koa-body": "^6.0.1",
31
32
  "koa-static": "^5.0.0",
33
+ "mongodb": "^6.0.0",
32
34
  "mysql": "^2.18.1",
33
- "sqlite3": "^5.1.7",
34
- "watch": "^1.0.2"
35
+ "sqlite3": "^5.1.7"
35
36
  },
36
37
  "engines": {
37
38
  "node": ">= 18"
39
+ },
40
+ "devDependencies": {
41
+ "supertest": "^7.2.2"
38
42
  }
39
43
  }