diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..80da00465
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+# http://editorconfig.org/
+
+# Top-most EditorConfig file
+root = true
+
+[*.js]
+indent_style = tab
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = false
+max_line_length = 120
+
+[*.json]
+indent_style = space
+indent_size = 2
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..feed3f4a9
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,8 @@
+# These are supported funding model platforms
+
+patreon: totaljs
+open_collective: totalplatform
+ko_fi: totaljs
+liberapay: totaljs
+buy_me_a_coffee: totaljs
+custom: https://www.totaljs.com/support/
diff --git a/.gitignore b/.gitignore
index c8670d33f..0b4524423 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,9 @@
*.sublime-project
*.sublime-workspace
.DS_Store
-*.log
\ No newline at end of file
+*.log
+.idea
+test/tmp/*
+node_modules
+test/test-framework-debug.js.json
+test/test-framework-release.js.json
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
index cc3e6c53f..f823ea1f3 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,12 +1,19 @@
Makefile
empty-project/
examples/
+merged/
+tools/
+helpers/
test/
*.markdown
*.md
+.editorconfig
+.gitingore
.git*
+*.yml
*.DS_Store
*.sublime-project
*.sublime-workspace
+*.sh
changes.txt
roadmap.txt
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..14c12bc01
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,10 @@
+language: node_js
+cache:
+ - npm
+node_js:
+ - "8.9.1"
+install:
+ - npm install
+script:
+ - cd test
+ - bash run.sh
\ No newline at end of file
diff --git a/503.html b/503.html
new file mode 100644
index 000000000..9b82dfe69
--- /dev/null
+++ b/503.html
@@ -0,0 +1,65 @@
+
+
+
+ @(Please wait)
+
+
+
+
+
+
+
+
+
+
+
10
+
@(Please wait)
+
+ @(Application is starting …)
+
+
+ @{foreach m in model}
+
+ | @{m.key} |
+ ⧗ |
+
+ @{end}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/total b/bin/total
deleted file mode 100755
index 649bafc12..000000000
--- a/bin/total
+++ /dev/null
@@ -1,539 +0,0 @@
-var exec = require('child_process').exec;
-var fs = require('fs');
-var path = require('path');
-var os = require('os');
-
-var EOF = os.platform() === 'win32' ? '\r\n' : '\n';
-var $type = 1;
-
-// $type == 0 - full
-// $type == 1 - normal
-// $type == 2 - minimum
-// $type == 3 - angular
-
-function createDirectory(directory) {
- var arr = [];
-
- arr.push('controllers');
- arr.push('node_modules');
-
- if ($type < 2) {
-
- arr.push('views');
- arr.push('templates');
- arr.push('databases');
- arr.push('definitions');
-
- if ($type == 0) {
- arr.push('components');
- arr.push('workers');
- arr.push('tests');
- arr.push('contents');
- arr.push('logs');
- arr.push('modules');
- arr.push('source');
- }
-
- arr.push('resources');
- arr.push('public');
- arr.push('tmp');
- }
-
- arr.forEach(function(o) {
- var dir = path.join(directory, o);
- if (!fs.existsSync(dir))
- fs.mkdirSync(dir);
- });
-}
-
-function createDirectoryAngular(directory) {
- var arr = [];
-
- arr.push('app');
- arr.push('controllers');
- arr.push('node_modules');
- arr.push('views');
-
- arr.forEach(function(o) {
- var dir = path.join(directory, o);
- if (!fs.existsSync(dir))
- fs.mkdirSync(dir);
- });
-
- arr = [];
- arr.push('common');
- arr.push('controllers');
- arr.push('css');
- arr.push('directives');
- arr.push('filters');
- arr.push('resources');
- arr.push('services');
- arr.push('templates');
- arr.push('views');
-
- arr.forEach(function(o) {
- var dir = path.join(directory, 'app', o);
- if (!fs.existsSync(dir))
- fs.mkdirSync(dir);
- });
-}
-
-function createFiles(directory) {
- createFileIndex(directory);
- createFileConfig(directory);
-
- if ($type < 2) {
- createFileViews(directory);
- createFileResource(directory);
- createFilePublic(directory);
- }
-
- if ($type === 3) {
- createFileViews(directory);
- createFilePublicAngular(directory);
- createFileControllerAngular(directory);
- } else
- createFileController(directory);
-
- if ($type === 0) {
- createFileTest(directory);
- createFileModules(directory);
- createFileComponent(directory);
- }
-}
-
-function createFileIndex(directory) {
- fs.writeFileSync(path.join(directory, 'debug.js'), 'Ly8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQovLyBJTVBPUlRBTlQ6IG9ubHkgZm9yIGRldmVsb3BtZW50IG1vZGUNCi8vIHRvdGFsLmpzIC0gd2ViIGFwcGxpY2F0aW9uIGZyYW1ld29yayBmb3Igbm9kZS5qcw0KLy8gaHR0cDovL3d3dy50b3RhbGpzLmNvbQ0KLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCnZhciBmcyA9IHJlcXVpcmUoImZzIik7DQoNCnZhciBodHRwcyA9IGZhbHNlOw0KdmFyIG9wdGlvbnMgPSB7IGtleTogbnVsbCwgY2VydDogbnVsbCB9Ow0KDQp2YXIgaXNEZWJ1Z2dpbmc9cHJvY2Vzcy5hcmd2W3Byb2Nlc3MuYXJndi5sZW5ndGgtMV09PT0iZGVidWdnaW5nIjt2YXIgZGlyZWN0b3J5PXByb2Nlc3MuY3dkKCk7dmFyIHBhdGg9cmVxdWlyZSgicGF0aCIpO2Z1bmN0aW9uIGRlYnVnKCl7dmFyIGZyYW1ld29yaz1yZXF1aXJlKCJ0b3RhbC5qcyIpO3ZhciBwb3J0PXBhcnNlSW50KHByb2Nlc3MuYXJndlsyXSk7ZnJhbWV3b3JrLnJ1bihyZXF1aXJlKGh0dHBzPyJodHRwcyI6Imh0dHAiKSx0cnVlLHBvcnQsIiIsb3B0aW9ucyYmb3B0aW9ucy5rZXkmJm9wdGlvbnMuY2VydD9vcHRpb25zOm51bGwpfWZ1bmN0aW9uIGFwcCgpe3ZhciBmb3JrPXJlcXVpcmUoImNoaWxkX3Byb2Nlc3MiKS5mb3JrO3ZhciB1dGlscz1yZXF1aXJlKCJ0b3RhbC5qcy91dGlscyIpO3ZhciBkaXJlY3Rvcmllcz1bZGlyZWN0b3J5KyIvY29udHJvbGxlcnMiLGRpcmVjdG9yeSsiL2RlZmluaXRpb25zIixkaXJlY3RvcnkrIi9tb2R1bGVzIixkaXJlY3RvcnkrIi9yZXNvdXJjZXMiLGRpcmVjdG9yeSsiL2NvbXBvbmVudHMiLGRpcmVjdG9yeSsiL21vZGVscyIsZGlyZWN0b3J5KyIvc291cmNlIl07dmFyIGZpbGVzPXt9O3ZhciBmb3JjZT1mYWxzZTt2YXIgY2hhbmdlcz1bXTt2YXIgYXBwPW51bGw7dmFyIHN0YXR1cz0wO3ZhciBhc3luYz1uZXcgdXRpbHMuQXN5bmM7dmFyIHBpZD0iIjt2YXIgcGlkSW50ZXJ2YWw9bnVsbDt2YXIgcHJlZml4PSItLS0tLS0tLS0tLS0+ICI7dmFyIGlzTG9hZGVkPWZhbHNlO2Z1bmN0aW9uIG9uRmlsdGVyKHBhdGgsaXNEaXJlY3Rvcnkpe3JldHVybiBpc0RpcmVjdG9yeT90cnVlOnBhdGguaW5kZXhPZigiLmpzIikhPT0tMXx8cGF0aC5pbmRleE9mKCIucmVzb3VyY2UiKSE9PS0xfWZ1bmN0aW9uIG9uQ29tcGxldGUoKXt2YXIgc2VsZj10aGlzO2ZzLnJlYWRkaXIoZGlyZWN0b3J5LGZ1bmN0aW9uKGVycixhcnIpe3ZhciBsZW5ndGg9YXJyLmxlbmd0aDtmb3IodmFyIGk9MDtpPGxlbmd0aDtpKyspe3ZhciBuYW1lPWFycltpXTtpZihuYW1lPT09ImNvbmZpZyJ8fG5hbWU9PT0iY29uZmlnLWRlYnVnInx8bmFtZT09PSJjb25maWctcmVsZWFzZSJ8fG5hbWU9PT0idmVyc2lvbnMifHxuYW1lLmluZGV4T2YoIi5qcyIpIT09LTF8fG5hbWUuaW5kZXhPZigiLnJlc291cmNlIikhPT0tMSlzZWxmLmZpbGUucHVzaChuYW1lKX1sZW5ndGg9c2VsZi5maWxlLmxlbmd0aDtmb3IodmFyIGk9MDtpPGxlbmd0aDtpKyspe3ZhciBuYW1lPXNlbGYuZmlsZVtpXTtpZighZmlsZXNbbmFtZV0pZmlsZXNbbmFtZV09aXNMb2FkZWQ/MDpudWxsfXJlZnJlc2goKX0pfWZ1bmN0aW9uIHJlZnJlc2goKXt2YXIgZmlsZW5hbWVzPU9iamVjdC5rZXlzKGZpbGVzKTt2YXIgbGVuZ3RoPWZpbGVuYW1lcy5sZW5ndGg7Zm9yKHZhciBpPTA7aTxsZW5ndGg7aSsrKXt2YXIgZmlsZW5hbWU9ZmlsZW5hbWVzW2ldOyhmdW5jdGlvbihmaWxlbmFtZSl7YXN5bmMuYXdhaXQoZnVuY3Rpb24obmV4dCl7ZnMuc3RhdChmaWxlbmFtZSxmdW5jdGlvbihlcnIsc3RhdCl7aWYoIWVycil7dmFyIHRpY2tzPXN0YXQubXRpbWUuZ2V0VGltZSgpO2lmKGZpbGVzW2ZpbGVuYW1lXSE9PW51bGwmJmZpbGVzW2ZpbGVuYW1lXSE9PXRpY2tzKXtjaGFuZ2VzLnB1c2gocHJlZml4K2ZpbGVuYW1lLnJlcGxhY2UoZGlyZWN0b3J5LCIiKSsoZmlsZXNbZmlsZW5hbWVdPT09MD8iIChhZGRlZCkiOiIgKG1vZGlmaWVkKSIpKTtmb3JjZT10cnVlfWZpbGVzW2ZpbGVuYW1lXT10aWNrc31lbHNle2RlbGV0ZSBmaWxlc1tmaWxlbmFtZV07Y2hhbmdlcy5wdXNoKHByZWZpeCtmaWxlbmFtZS5yZXBsYWNlKGRpcmVjdG9yeSwiIikrIiAocmVtb3ZlZCkiKTtmb3JjZT10cnVlfW5leHQoKX0pfSl9KShmaWxlbmFtZSl9YXN5bmMuY29tcGxldGUoZnVuY3Rpb24oKXtpc0xvYWRlZD10cnVlO3NldFRpbWVvdXQocmVmcmVzaF9kaXJlY3RvcnksMmUzKTtpZihzdGF0dXMhPT0xKXJldHVybjtpZighZm9yY2UpcmV0dXJuO3Jlc3RhcnQoKTt2YXIgbGVuZ3RoPWNoYW5nZXMubGVuZ3RoO2Zvcih2YXIgaT0wO2k8bGVuZ3RoO2krKyljb25zb2xlLmxvZyhjaGFuZ2VzW2ldKTtjaGFuZ2VzPVtdO2ZvcmNlPWZhbHNlfSl9ZnVuY3Rpb24gcmVmcmVzaF9kaXJlY3RvcnkoKXt1dGlscy5scyhkaXJlY3RvcmllcyxvbkNvbXBsZXRlLG9uRmlsdGVyKX1mdW5jdGlvbiByZXN0YXJ0KCl7aWYoYXBwIT09bnVsbCl7dHJ5e3Byb2Nlc3Mua2lsbChhcHAucGlkKX1jYXRjaChlcnIpe31hcHA9bnVsbH12YXIgYXJyPXByb2Nlc3MuYXJndjthcnIucG9wKCk7YXJyLnB1c2goImRlYnVnZ2luZyIpO2FwcD1mb3JrKHBhdGguam9pbihkaXJlY3RvcnksImRlYnVnLmpzIiksYXJyKTthcHAub24oIm1lc3NhZ2UiLGZ1bmN0aW9uKG1zZyl7aWYobXNnLnN1YnN0cmluZygwLDUpPT09Im5hbWU6Iil7cHJvY2Vzcy50aXRsZT0iZGVidWc6ICIrbXNnLnN1YnN0cmluZyg2KTtyZXR1cm59fSk7YXBwLm9uKCJleGl0IixmdW5jdGlvbigpe2lmKHN0YXR1cyE9PTI1NSlyZXR1cm47YXBwPW51bGx9KTtpZihzdGF0dXM9PT0wKWFwcC5zZW5kKCJkZWJ1Z2dpbmciKTtzdGF0dXM9MX1wcm9jZXNzLm9uKCJTSUdURVJNIixlbmQpO3Byb2Nlc3Mub24oIlNJR0lOVCIsZW5kKTtwcm9jZXNzLm9uKCJleGl0IixlbmQpO2Z1bmN0aW9uIGVuZCgpe2lmKGFyZ3VtZW50cy5jYWxsZWUuaXNFbmQpcmV0dXJuO2FyZ3VtZW50cy5jYWxsZWUuaXNFbmQ9dHJ1ZTtmcy51bmxpbmsocGlkLG5vb3ApO2lmKGFwcD09PW51bGwpe3Byb2Nlc3MuZXhpdCgwKTtyZXR1cm59cHJvY2Vzcy5raWxsKGFwcC5waWQpO2FwcD1udWxsO3Byb2Nlc3MuZXhpdCgwKX1mdW5jdGlvbiBub29wKCl7fWlmKHByb2Nlc3MucGlkPjApe2NvbnNvbGUubG9nKHByZWZpeCsiUElEOiAiK3Byb2Nlc3MucGlkKTtwaWQ9cGF0aC5qb2luKGRpcmVjdG9yeSwiZGVidWcucGlkIik7ZnMud3JpdGVGaWxlU3luYyhwaWQscHJvY2Vzcy5waWQpO3BpZEludGVydmFsPXNldEludGVydmFsKGZ1bmN0aW9uKCl7ZnMuZXhpc3RzKHBpZCxmdW5jdGlvbihleGlzdCl7aWYoZXhpc3QpcmV0dXJuO2ZzLnVubGluayhwaWQsbm9vcCk7aWYoYXBwIT09bnVsbClwcm9jZXNzLmtpbGwoYXBwLnBpZCk7cHJvY2Vzcy5leGl0KDApfSl9LDJlMyl9cmVzdGFydCgpO3JlZnJlc2hfZGlyZWN0b3J5KCl9aWYoaXNEZWJ1Z2dpbmcpZGVidWcoKTtlbHNlIGlmKCFmcy5leGlzdHNTeW5jKHBhdGguam9pbihkaXJlY3RvcnksImRlYnVnLnBpZCIpKSlhcHAoKTs=', 'base64');
- fs.writeFileSync(path.join(directory, 'release.js'), 'dmFyIGZyYW1ld29yayA9IHJlcXVpcmUoJ3RvdGFsLmpzJyk7DQp2YXIgaHR0cCA9IHJlcXVpcmUoJ2h0dHAnKTsNCg0KZnJhbWV3b3JrLnJ1bihodHRwLCBmYWxzZSwgcGFyc2VJbnQocHJvY2Vzcy5hcmd2WzJdKSk7', 'base64');
- fs.writeFileSync(path.join(directory, 'test.js'), 'dmFyIGZyYW1ld29yayA9IHJlcXVpcmUoJ3RvdGFsLmpzJyk7DQp2YXIgaHR0cCA9IHJlcXVpcmUoJ2h0dHAnKTsNCg0KZnJhbWV3b3JrLnJ1bihodHRwLCB0cnVlLCBwYXJzZUludChwcm9jZXNzLmFyZ3ZbMl0pKTsNCg0KZnJhbWV3b3JrLnRlc3QodHJ1ZSwgZnVuY3Rpb24oKSB7DQoJY29uc29sZS5sb2coJycpOw0KCWNvbnNvbGUubG9nKCc9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Jyk7DQoJY29uc29sZS5sb2coJ0NvbmdyYXR1bGF0aW9ucywgdGhlIHRlc3Qgd2FzIHN1Y2Nlc3NmdWwhJyk7DQoJY29uc29sZS5sb2coJz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0nKTsNCgljb25zb2xlLmxvZygnJyk7DQp9KTs=', 'base64');
-}
-
-function createFileConfig(directory) {
- var buffer = [];
-
- buffer.push("name : My web site");
- buffer.push("author : Your company name");
- buffer.push("");
- buffer.push("// Mail settings");
- buffer.push("mail.smtp : smtp.yourwebsite.com");
- buffer.push('mail.smtp.options : "{"secure":false,"port":25,"user":"","password":"","timeout":10000}"');
- buffer.push("mail.address.from : your@yourwebsite.com");
- buffer.push("mail.address.reply : your@yourwebsite.com");
- buffer.push("mail.address.bcc : ");
-
- fs.writeFileSync(path.join(directory, 'config'), buffer.join(EOF));
- buffer = [];
-
- buffer.push("default-ip : 127.0.0.1");
- buffer.push("default-port : 8000");
- buffer.push("");
- buffer.push("secret : your-secret-key");
- buffer.push("version : 1.01");
-
- if ($type === 3) {
- buffer.push('');
- buffer.push('// CloudFlare cdnjs');
- buffer.push("angular-version : 1.2.16");
- buffer.push('');
- buffer.push('// CloudFlare cdnjs');
- buffer.push("angular-i18n-version : 1.2.15");
- }
-
- fs.writeFileSync(path.join(directory, 'config-release'), buffer.join(EOF));
- fs.writeFileSync(path.join(directory, 'config-debug'), buffer.join(EOF).replace(/true/g, 'false'));
-}
-
-function createFileViews(directory) {
- var buffer = [];
- var dir = path.join(directory, 'views');
-
- if (!fs.existsSync(dir))
- fs.mkdirSync(dir);
-
- buffer.push("@{meta('title', 'description', 'keywords')}");
-
- if ($type === 3) {
- buffer.push('');
- buffer.push('')
- buffer.push("@{ngStyle('app')}");
- buffer.push('');
- buffer.push('')
- buffer.push("@{ngLocale('en-us')}");
- buffer.push('');
- buffer.push('')
- buffer.push("@{ngController('home', 'user')}");
- buffer.push("");
- buffer.push('{{ name }}
');
- } else {
- buffer.push("");
- buffer.push('Hello world!
');
- }
-
- if ($type === 3)
- fs.writeFileSync(path.join(dir, 'app.html'), buffer.join('\n'));
- else
- fs.writeFileSync(path.join(dir, 'homepage.html'), buffer.join('\n'));
-
- buffer = [];
-
- if ($type === 3) {
- buffer.push("");
- buffer.push("@{ng('angular', 'resource', 'route')}");
- buffer.push("");
- }
-
- buffer.push('');
-
- if ($type === 3)
- buffer.push('');
- else
- buffer.push('');
-
- buffer.push('');
- buffer.push(' @{meta}');
- buffer.push(' ');
- buffer.push(' ');
- buffer.push(' ');
- buffer.push(' ');
- buffer.push(' ');
- buffer.push(' @{head}');
- if ($type !== 3) {
- buffer.push(' @{css(\'default.css\')}');
- buffer.push(' ');
- buffer.push(' @{js(\'default.js\')}');
- }
- buffer.push(' @{favicon(\'favicon.ico\')}');
- buffer.push('');
- buffer.push('');
- buffer.push('');
- buffer.push(' @{body}');
- buffer.push('');
- buffer.push('');
- buffer.push('');
-
- fs.writeFileSync(path.join(dir, '_layout.html'), buffer.join(EOF));
-}
-
-function createFileResource(directory) {
- var dir = path.join(directory, 'resources');
- fs.writeFileSync(path.join(dir, 'default.resource'), 'name : value' + EOF);
-}
-
-function createFilePublicAngular(directory) {
-
- var dir = path.join(directory, 'app');
- var css = path.join(dir, 'css');
- var controllers = path.join(dir, 'controllers');
- var buffer = [];
-
- buffer.push('User-agent: *');
- buffer.push('Allow: /');
- buffer.push('');
-
- fs.writeFileSync(path.join(dir, 'robots.txt'), buffer.join(EOF));
- fs.writeFileSync(path.join(css, 'app.css'), 'LyphdXRvKi8NCg0KLyoNCiAgICB2YXIgY29sb3IgPSAnY29sb3I6cmVkJzsNCiAgICB2YXIgd2lkdGggPSAnOTQwcHgnOw0KKi8NCg0KYm9keSB7IHBhZGRpbmc6MjBweDsgbWFyZ2luOjA7IGZvbnQ6bm9ybWFsIDEycHggQXJpYWw7IGNvbG9yOiM1MDUwNTA7IH0NCg0KLmNvbnRlbnQgeyBtYXJnaW46MCBhdXRvOyB3aWR0aDogJHdpZHRoOyBwYWRkaW5nOiAxMHB4OyBib3JkZXItcmFkaXVzOiA1cHg7ICRjb2xvcjsgYm94LXNoYWRvdzogMCAwIDIwcHggcmdiYSgwLDAsMCwwLjUpOyBhbmltYXRpb246IG15QW5pbWF0aW9uIDVzIGFsdGVybmF0ZTsgfQ0KDQoubGlzdCB7IGJvcmRlci1ib3R0b206MXB4IHNvbGlkICNFMEUwRTA7IHBhZGRpbmctYm90dG9tOjVweDsgbWFyZ2luLWJvdHRvbTo1cHg7IH0NCi5saXN0ID4gZGl2OmZpcnN0LWNoaWxkIHsgZm9udC1zaXplOiAxNXB4OyB9DQoNCkBrZXlmcmFtZXMgbXlBbmltYXRpb24NCnsNCgkwJSAgIHsgYmFja2dyb3VuZDogd2hpdGU7IH0NCgkyNSUgIHsgYmFja2dyb3VuZDogI0YwRjBGMDsgfQ0KCTUwJSAgeyBiYWNrZ3JvdW5kOiAjRDBEMEQwOyB9DQoJMTAwJSB7IGJhY2tncm91bmQ6ICNFMEUwRTA7IH0NCn0=', 'base64');
-
- buffer = [];
- buffer.push("var app = angular.module('app', []);");
- buffer.push('');
- fs.writeFileSync(path.join(dir, 'app.js'), buffer.join(EOF));
-
- buffer = [];
- buffer.push('function HomeCtrl($scope) {\n $scope.name = \'total.js + angular.js = awesome\';\n}');
- fs.writeFileSync(path.join(controllers, 'home.js'), buffer.join(EOF));
-
- buffer = [];
- buffer.push('function UserCtrl($scope) {\n // example\n}');
- fs.writeFileSync(path.join(controllers, 'user.js'), buffer.join(EOF));
-}
-
-function createFilePublic(directory) {
-
- var dir = path.join(directory, 'public');
- var css = path.join(dir, 'css');
- var js = path.join(dir, 'js');
- var img = path.join(dir, 'img');
-
- fs.writeFileSync(path.join(dir, 'favicon.ico'), 'AAABAAIAGBgAAAEAIAAoCQAAJgAAABAQAAABACAAKAQAAE4JAAAoAAAAGAAAADAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGsAAADxAAAA/wAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP8AAADwAAAAZAAAAAAAAAAAAAAAAAAAAPEAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA7wAAAAAAAAAAAAAAAAAAAP8AAAD/AAEB/wMJBv8ECwj/BAsI/wQLCP8ECwj/BAsI/wQLCP8ECwj/BAsI/wQLCP8ECwj/BAsI/wQLCP8ECwj/AwkG/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAP8AAAD/AwoH/yyGXf83qHT/NqRx/zakcf82pHH/NqRx/zakcf82pHH/NqRx/zakcf82pHH/NqRx/zakcf83qHT/LIVc/wMJBv8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAwI/ziodP9F0pL/Q82O/0TPj/9Ez4//RM+P/0TPj/9Ez4//RM+P/0TPj/9Ez4//RM+P/0PNjv9F0pL/N6h0/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9DzY7/QseK/0DChv8/wYX/P8GF/z/Ahf8/wIX/P8CF/z/Bhf8/v4P+QMKG/0LHiv9DzY7/NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9Ez4//P8CG/xU+K/8LIhf/DCUa/wwlGv8MJRr/DCUa/wwlGv8LIhf/FT8s/z/Bhv9Ez4//NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9Ez4//P8CE/wofFf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/CiAW/z/Ahf9Ez4//NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9Ez4//P8CE/wsiGP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/DCMY/z/Bhf9Ez4//NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9Ez4//P8CE/wsiGP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/DCMY/z/Bhf9Ez4//NqRx/wQLCP8AAAD+AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9Ez4//P8CF/wsiGP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/DCMY/z/Bhf9Ez4//NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9Ez4//P8CE/wsiGP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/DCMY/z/Bhf9Ez4//NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9Ez4//P8CE/wsiGP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/DCMY/z/Bhf9Ez4//NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9Ez4//P8CF/wsiGP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/DCMY/z/Bhf9Ez4//NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9Ez4//P8CE/wsiGP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/DCMY/z/Bhf9Ez4//NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9Ez4//P8CE/wsiGP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/DCMY/z/Bhf9Ez4//NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9Ez4//P8CE/wofFf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/CiAW/z/Bhf9Ez4//NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9Ez4//P8GF/xQ9Kv8LIRf/DCQZ/wwlGf8MJRn/DCUZ/wwkGf8LIRf/FT4r/z/Bhv9Ez4//NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAsI/zakcv9DzY7/QseK/0DChf8/wYT/P8GF/z/Bhf8/wYX/P8GF/z/Bhf8/wYT/QMKF/0LHiv9DzY7/NqRx/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/BAwI/ziodf9F0pL/Q82O/0TOj/9Ez4//RM+P/0TPj/9Ez4//RM+P/0TPj/9Ez4//RM6P/0PNjv9F0pL/N6h0/wQLCP8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/AwoH/yyHXv83qHT/NqRx/zakcf82pHH/NqVx/zalcf82pXH/NqVx/zalcf82pHH/NqRx/zakcf83qHX/LIZd/wMIBv8AAAD/AAAA/gAAAAAAAAAAAAAAAAAAAP8AAAD/AAEB/wMJBv8ECwj/BAsI/wQLCP8ECwj/BAsI/wQLCP8ECwj/BAsI/wQLCP8ECwj/BAsI/wQLCP8ECwj/AwkG/wAAAf8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAOMAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA6wAAAAAAAAAAAAAAAAAAAEcAAADgAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAADuAAAAWAAAAAAAAAAAKAAAABAAAAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAADPAAAA7QAAAO0AAADtAAAA7QAAAO0AAADtAAAA7QAAAO0AAADtAAAA7QAAAO0AAADlAAAAYgAAAAAAAABYAAAA/wEEA/8FDwv/BQ8L/wUPC/8FDwv/BQ8L/wUPC/8FDwv/BQ8L/wUQC/8DCQb/AAAA/wAAAK4AAAAAAAAAVQAAAP8QMiP/OKl1/zmtd/85rnj/Oa54/zmueP85rnj/Oa54/zisdv86sXv/IGFD/wAAAP8AAACqAAAAAAAAAFUAAAD/FT4r/0XTkv9E0JD/QMSH/0DEh/9AxIj/QMSI/0DChf5Dyov/Sd2Z/yh6Vf8AAAD/AAAAqQAAAAAAAABVAAAA/xQ7Kf9G1JP/M5pr/wshFv8LIRf/CyIY/wsiGP8JHRT/ImZI/0jcmP8ndlH/AAAA/wAAAKkAAAAAAAAAVQAAAP8UOyn/RtaU/y+QYv8AAAD/AAAA/wAAAP8AAAD/AAAA/xpROP9J3pn/J3ZR/wAAAP8AAACpAAAAAAAAAFUAAAD/FDsp/0bWlP8wkmT/AAAA/wAAAP8AAAD/AAAA/wAAAP8cVjv/Sd6Z/yd2Uf8AAAD/AAAAqQAAAAAAAABVAAAA/xQ7Kf9G1pT/MJJk/wAAAP8AAAD/AAAA/wAAAP8AAAD/HFY7/0nemf8ndlH/AAAA/wAAAKkAAAAAAAAAVQAAAP8UOyn/RtaU/zCSZP8AAAD/AAAA/wAAAP8AAAD/AAAA/xxWO/9J3pn/J3ZR/wAAAP8AAACpAAAAAAAAAFUAAAD/FDsp/0bWlP8wkmT/AAAA/wAAAP8AAAD/AAAA/wAAAP8cVjv/Sd6Z/yd2Uf8AAAD/AAAAqQAAAAAAAABVAAAA/xQ7Kf9G1pT/L5Bi/wAAAP8AAAD/AAAA/wAAAP8AAAD/GlI4/0nemf8ndlH/AAAA/wAAAKkAAAAAAAAAVQAAAP8UOyn/RtST/zOaav8LIBb/CyEW/wsjGP8LIhj/CRwT/yJnR/9I3Jj/J3ZR/wAAAP8AAACpAAAAAAAAAFUAAAD/FT4r/0XTk/9E0JD/QMSH/0DEh/9AxIf/QMSH/0DDhv9Dyov/Sd2Z/yh6VP8AAAD/AAAAqQAAAAAAAABWAAAA/xAyI/84qXb/Oa13/zmueP85rnj/Oa54/zmueP85rnj/OKx2/zqxe/8gYUT/AAAA/wAAAKoAAAAAAAAAVgAAAP8BBAP/BQ8L/wUPC/8FDwv/BQ8L/wUPC/8FDwv/BQ8L/wUPC/8FEAv/AwkG/wAAAP8AAACuAAAAAAAAACAAAADAAAAA7QAAAO0AAADtAAAA7QAAAO0AAADtAAAA7QAAAO0AAADtAAAA7QAAAO0AAADkAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'base64');
-
- if ($type === 3)
- return;
-
- if (!fs.existsSync(js))
- fs.mkdirSync(js);
-
- if (!fs.existsSync(css))
- fs.mkdirSync(css);
-
- if (!fs.existsSync(img))
- fs.mkdirSync(img);
-
- var buffer = [];
- buffer.push('User-agent: *');
- buffer.push('Allow: /');
- buffer.push('');
-
- fs.writeFileSync(path.join(dir, 'robots.txt'), buffer.join(EOF));
- fs.writeFileSync(path.join(css, 'default.css'), 'LyphdXRvKi8NCg0KLyoNCiAgICB2YXIgY29sb3IgPSAnY29sb3I6cmVkJzsNCiAgICB2YXIgd2lkdGggPSAnOTQwcHgnOw0KKi8NCg0KYm9keSB7IHBhZGRpbmc6MjBweDsgbWFyZ2luOjA7IGZvbnQ6bm9ybWFsIDEycHggQXJpYWw7IGNvbG9yOiM1MDUwNTA7IH0NCg0KLmNvbnRlbnQgeyBtYXJnaW46MCBhdXRvOyB3aWR0aDogJHdpZHRoOyBwYWRkaW5nOiAxMHB4OyBib3JkZXItcmFkaXVzOiA1cHg7ICRjb2xvcjsgYm94LXNoYWRvdzogMCAwIDIwcHggcmdiYSgwLDAsMCwwLjUpOyBhbmltYXRpb246IG15QW5pbWF0aW9uIDVzIGFsdGVybmF0ZTsgfQ0KDQoubGlzdCB7IGJvcmRlci1ib3R0b206MXB4IHNvbGlkICNFMEUwRTA7IHBhZGRpbmctYm90dG9tOjVweDsgbWFyZ2luLWJvdHRvbTo1cHg7IH0NCi5saXN0ID4gZGl2OmZpcnN0LWNoaWxkIHsgZm9udC1zaXplOiAxNXB4OyB9DQoNCkBrZXlmcmFtZXMgbXlBbmltYXRpb24NCnsNCgkwJSAgIHsgYmFja2dyb3VuZDogd2hpdGU7IH0NCgkyNSUgIHsgYmFja2dyb3VuZDogI0YwRjBGMDsgfQ0KCTUwJSAgeyBiYWNrZ3JvdW5kOiAjRDBEMEQwOyB9DQoJMTAwJSB7IGJhY2tncm91bmQ6ICNFMEUwRTA7IH0NCn0=', 'base64');
-
- buffer = [];
- buffer.push('$(document).ready(function() {');
- buffer.push('');
- buffer.push('});');
-
- fs.writeFileSync(path.join(js, 'default.js'), buffer.join(EOF));
-}
-
-function createFileModules(directory) {
-
- var dir = path.join(directory, 'modules');
-
- var buffer = [];
- buffer.push('exports.yourcode = function () {');
- buffer.push(' return \'Hello World\';');
- buffer.push('};');
-
- fs.writeFileSync(path.join(dir, 'example.js'), buffer.join(EOF));
-}
-
-function createFileControllerAngular(directory) {
-
- var dir = path.join(directory, 'controllers');
- var buffer = [];
-
- buffer.push('exports.install = function(framework) {');
- buffer.push(' framework.route(\'/*\', view_app);');
- buffer.push('};');
- buffer.push('');
- buffer.push('function view_app() {');
- buffer.push(' var self = this;');
- buffer.push(' self.view(\'app\');');
- buffer.push('}');
-
- fs.writeFileSync(path.join(dir, 'default.js'), buffer.join(EOF));
-}
-
-function createFileController(directory) {
-
- var dir = path.join(directory, 'controllers');
- var buffer = [];
-
- buffer.push('exports.install = function(framework) {');
- buffer.push(' framework.route(\'/\', view_homepage);');
- buffer.push(' framework.route(\'#400\', error400);');
- buffer.push(' framework.route(\'#401\', error401);');
- buffer.push(' framework.route(\'#403\', error403);');
- buffer.push(' framework.route(\'#404\', error404);');
- buffer.push(' framework.route(\'#408\', error408);');
- buffer.push(' framework.route(\'#431\', error431);');
- buffer.push(' framework.route(\'#500\', error500);');
- buffer.push('};');
- buffer.push('');
- buffer.push('// Bad Request');
- buffer.push('function error400() {');
- buffer.push(' var self = this;');
- buffer.push(' self.status = 400;');
- buffer.push(' self.plain(utils.httpStatus(self.status));');
- buffer.push('}');
- buffer.push('');
- buffer.push('// Unauthorized');
- buffer.push('function error401() {');
- buffer.push(' var self = this;');
- buffer.push(' self.status = 401;');
- buffer.push(' self.plain(utils.httpStatus(self.status));');
- buffer.push('}');
- buffer.push('');
- buffer.push('// Forbidden');
- buffer.push('function error403() {');
- buffer.push(' var self = this;');
- buffer.push(' self.status = 403;');
- buffer.push(' self.plain(utils.httpStatus(self.status));');
- buffer.push('}');
- buffer.push('');
- buffer.push('// Not Found');
- buffer.push('function error404() {');
- buffer.push(' var self = this;');
- buffer.push(' self.status = 404;');
- buffer.push(' self.plain(utils.httpStatus(self.status));');
- buffer.push('}');
- buffer.push('');
- buffer.push('// Request Timeout');
- buffer.push('function error408() {');
- buffer.push(' var self = this;');
- buffer.push(' self.status = 408;');
- buffer.push(' self.plain(utils.httpStatus(self.status));');
- buffer.push('}');
- buffer.push('');
- buffer.push('// Request Header Fields Too Large');
- buffer.push('function error431() {');
- buffer.push(' var self = this;');
- buffer.push(' self.status = 431;');
- buffer.push(' self.plain(utils.httpStatus(self.status));');
- buffer.push('}');
- buffer.push('');
- buffer.push('// Internal Server Error');
- buffer.push('function error500() {');
- buffer.push(' var self = this;');
- buffer.push(' self.status = 500;');
- buffer.push(' self.plain(utils.httpStatus(self.status));');
- buffer.push('}');
- buffer.push('');
- buffer.push('function view_homepage() {');
- buffer.push(' var self = this;');
-
- if ($type !== 2)
- buffer.push(' self.view(\'homepage\');');
- else
- buffer.push(' self.plain(\'homepage\');');
-
- buffer.push('}');
-
- fs.writeFileSync(path.join(dir, 'default.js'), buffer.join(EOF));
-}
-
-function createFileTest(directory) {
- var dir = path.join(directory, 'tests');
- var buffer = [];
-
- buffer.push("var assert = require('assert');");
- buffer.push("");
- buffer.push("exports.run = function(framework) {");
- buffer.push("");
- buffer.push(" framework.assert('Number validation', function(next, name) {");
- buffer.push(" assert.ok('1' === '1', name);");
- buffer.push(" next();");
- buffer.push(" });");
- buffer.push("");
- buffer.push(" framework.assert('Homepage', '/1/', ['xhr', 'json'], function(error, data, code, headers, cookies, name) {");
- buffer.push(" assert.ok(code === 200, name);");
- buffer.push(" }, { name: 'total.js (optional)' }, { cookie: 'value (optional)' }, { 'X-My-Header': 'optional' });");
- buffer.push("");
- buffer.push("};");
-
- fs.writeFileSync(path.join(dir, 'default.js'), buffer.join(EOF));
-}
-
-function createFileComponent(directory) {
- var dir = path.join(directory, 'components');
- var buffer = [];
-
- buffer.push("// optional");
- buffer.push("exports.install = function(framework) {");
- buffer.push(" // component doesn't support routing");
- buffer.push(" console.log('OK');");
- buffer.push("};");
- buffer.push("");
- buffer.push("// optional");
- buffer.push("exports.usage = function(isDetailed) {");
- buffer.push(" return '';");
- buffer.push("};");
- buffer.push("");
- buffer.push("// optional");
- buffer.push("exports.configure = function(setup) {");
- buffer.push("");
- buffer.push("};");
- buffer.push("");
- buffer.push("// REQUIRED");
- buffer.push("exports.render = function(data) {");
- buffer.push(" // this === controller or this === framework");
- buffer.push(" return '';");
- buffer.push("}; ");
-
- fs.writeFileSync(path.join(dir, 'example.js'), buffer.join(EOF));
-}
-
-function install(directory) {
- exec('npm install total.js', { cwd: directory }, function (error, stdout, stderr) {
-
- }).on('exit', function() {
- console.log('total.js: success');
- });
-}
-
-function display_help() {
- console.log('');
- console.log('-m or -minimal = minimal');
- console.log('-n or -normal = normal (default)');
- console.log('-f or -full = full');
- console.log('-a or -angular = for angular.js application');
- console.log('-v or -version = total.js version');
- console.log('/path/ = target (default current directory)');
- console.log('');
-}
-
-function main() {
- var dir = process.cwd();
-
- for (var i = 2; i < process.argv.length; i++) {
- var arg = process.argv[i];
- var cmd = arg.toLowerCase();
-
- if (cmd === '-v' || cmd === '-version') {
- console.log(require('total.js').version);
- return;
- }
-
- if (cmd === '-f' || cmd === '-full' || cmd === 'full')
- continue;
-
- if (cmd === '-a' || cmd === '-angular' || cmd === 'angular') {
- $type = 3;
- continue;
- }
-
- if (cmd === '-m' || cmd === '-minimal' || cmd === '-minimum' || cmd === 'minimum') {
- $type = 2;
- continue;
- }
-
- if (cmd === '-n' || cmd === '-normal' || cmd === 'normal') {
- $type = 1;
- continue;
- }
-
- if (cmd === '-h' || cmd === '-help' || cmd === '--help' || cmd === 'help') {
- display_help();
- return;
- }
-
- dir = arg;
- break;
- }
-
- if (!fs.existsSync(dir)) {
- console.log('total.js: error / directory not exists');
- return;
- }
-
- var files = fs.readdirSync(dir);
- if (files.length > 0) {
-
- var can = true;
- for (var i = 0; i < files.length; i++) {
- var name = files[i];
- if (name[0] === '.')
- continue;
- can = false;
- }
-
- if (!can) {
- console.log('total.js: error / directory is not empty');
- return;
- }
- }
-
- console.log('total.js: creating directories');
-
- if ($type !== 3)
- createDirectory(dir);
- else
- createDirectoryAngular(dir);
-
- console.log('total.js: creating files');
- createFiles(dir);
-
- console.log('total.js: installing current version total.js');
- install(dir);
-}
-
-main();
\ No newline at end of file
diff --git a/bin/totaljs b/bin/totaljs
new file mode 100755
index 000000000..8ce0e9ab4
--- /dev/null
+++ b/bin/totaljs
@@ -0,0 +1,971 @@
+#! /usr/bin/env node
+
+var exec = require('child_process').exec;
+var fs = require('fs');
+var path = require('path');
+var os = require('os');
+var Utils = require('total.js/utils');
+var Internal = require('total.js/internal');
+var $type = 0;
+var isDirectory = false;
+
+function display_help() {
+ console.log('--------------------------------------------------------');
+ console.log('TEMPLATES');
+ console.log('--------------------------------------------------------');
+ console.log('');
+ console.log('without arguments : creates emptyproject');
+ console.log('flow : creates emptyproject-flow');
+ console.log('dashboard : creates emptyproject-dashboard');
+ console.log('flowboard : creates emptyproject-flowboard');
+ console.log('spa : creates emptyproject-jcomponent');
+ console.log('pwa : creates emptyproject-pwa');
+ console.log('rest : creates emptyproject-restservice');
+ console.log('cms : downloads Total.js CMS');
+ console.log('eshop : downloads Total.js Eshop');
+ console.log('superadmin : downloads Total.js SuperAdmin');
+ console.log('openplatform : downloads Total.js OpenPlatform');
+ console.log('helpdesk : downloads Total.js HelpDesk');
+ console.log('');
+ console.log('--------------------------------------------------------');
+ console.log('TOOLS');
+ console.log('--------------------------------------------------------');
+ console.log('');
+ console.log('-translate : creates a resource file with the localized text from views');
+ console.log('-translate "TEXT" : creates an identificator for the resource');
+ console.log('-translate filename : parses and creates a resource file from the text file');
+ console.log('-translatecsv : parses and creates CSV with localization in the current directory');
+ console.log('-csv filename : parses CSV and creates resources from CSV file');
+ console.log('-diff source target : creates differences between two resources "-diff source target"');
+ console.log('-merge source target : merges first resource into the second "-merge source target"');
+ console.log('-clean source : cleans a resource file "-clean source"');
+ console.log('-minify filename : minifies .js, .css or .html file into filename.min.[extension]');
+ console.log('-bundle filename : makes a bundle from the current directory');
+ console.log('-package filename : makes a package from the current directory');
+ console.log('-install : run "totaljs -install help" to see what can be installed');
+ console.log('8000 : starts a server');
+ console.log('');
+}
+
+function translateFile(a) {
+
+ if (!fs.existsSync(a))
+ return false;
+
+ var arr = fs.readFileSync(a).toString('utf8').split('\n');
+ var builder = [];
+ var count = 0;
+
+ for (var i = 0, length = arr.length; i < length; i++) {
+ var line = arr[i].trim();
+ if (line.length === 0)
+ continue;
+ builder.push('T' + line.hash().padRight(17, ' ') + ': ' + line);
+ count++;
+ }
+
+ fs.writeFileSync('translate.resource', '// Total.js translation file\n// Created: ' + new Date().format('yyyy-MM-dd HH:mm') + '\n' + builder.join('\n'));
+ console.log('Total.js: the translation was created (' + count + ' texts)');
+ return true;
+}
+
+function diff(a, b) {
+
+ if (!fs.existsSync(a)) {
+ console.log('The translation file does not exist: ' + a);
+ return;
+ }
+
+ if (!fs.existsSync(b)) {
+ console.log('The translation file does not exist: ' + b);
+ return;
+ }
+
+ var ba = fs.readFileSync(a).toString('utf8');
+ var bb = fs.readFileSync(b).toString('utf8');
+ var ca = ba.parseConfig();
+ var cb = bb.parseConfig();
+ var ka = Object.keys(ca);
+ var kb = Object.keys(cb);
+
+ ba = ba.split('\n');
+ bb = bb.split('\n');
+
+ var output = '';
+ var items = [];
+ var add = 0;
+ var rem = 0;
+ var padding = 0;
+
+ for (var i = 0, length = ba.length; i < length; i++) {
+ if (ba[i].indexOf(ka[0]) !== -1) {
+ padding = ba[i].indexOf(':');
+ break;
+ }
+ }
+
+ if (padding <= 0)
+ padding = 17;
+
+ function find_comment(arr, id) {
+ var comment = '';
+ for (var i = 0, length = arr.length; i < length; i++) {
+ if (arr[i].indexOf(id) !== -1)
+ return comment;
+ var line = arr[i];
+ if (line[0] !== '/' && line[1] !== '/')
+ continue;
+ comment = line;
+ }
+ return '';
+ }
+
+ var comment = '';
+ var prev = '';
+
+ for (var i = 0, length = ka.length; i < length; i++) {
+ var key = ka[i];
+
+ if (cb[key] !== undefined)
+ continue;
+
+ comment = find_comment(ba, key);
+
+ if (comment) {
+ if (items[items.length - 1] !== '')
+ items.push('');
+ items.push(comment);
+ }
+
+ var empty = comment === prev;
+
+ prev = comment;
+ items.push(key.padRight(padding) + ': ' + ca[key]);
+
+ if (!empty)
+ items.push('');
+
+ add++;
+ }
+
+ if (items.length > 0) {
+ output += '\n';
+ output += 'Add to "' + b + '" these:\n';
+ output += '\n';
+ output += items.join('\n');
+ output += '\n';
+ }
+
+ items = [];
+
+ for (var i = 0, length = kb.length; i < length; i++) {
+ var key = kb[i];
+
+ if (ca[key] !== undefined)
+ continue;
+
+ comment = find_comment(bb, key);
+
+ if (comment) {
+ if (items[items.length - 1] !== '')
+ items.push('');
+ items.push(comment);
+ }
+ else if (prev !== '')
+ items.push('');
+
+ var empty = comment === prev;
+
+ prev = comment;
+ items.push(key.padRight(padding) + ': ' + cb[key]);
+
+ if (!empty)
+ items.push('');
+
+ rem++;
+ }
+
+ if (items.length) {
+ output += '\n';
+ output += 'Remove from "' + b + '" these:\n';
+ output += '\n';
+ output += items.join('\n');
+ output += '\n';
+ }
+
+ var filename = path.join(path.dirname(b), path.basename(b, '.resource') + '-diff.txt');
+ fs.writeFileSync(filename, '// Total.js diff file\n// Created: ' + new Date().format('yyyy-MM-dd HH:mm') + '\n' + clean_resource(output));
+ console.log('========================================');
+ console.log('Translation files differences:');
+ console.log('========================================');
+ console.log('');
+ console.log('added : ' + add);
+ console.log('removed : ' + rem);
+ console.log('output : ' + filename);
+ console.log('');
+}
+
+function merge(a, b) {
+ if (!fs.existsSync(a)) {
+ console.log('The translation file does not exist: ' + a);
+ return;
+ }
+
+ if (!fs.existsSync(b)) {
+ console.log('The translation file does not exist: ' + b);
+ return;
+ }
+
+ var ba = fs.readFileSync(b).toString('utf8');
+ var bb = fs.readFileSync(a).toString('utf8');
+ var arr = ba.split('\n');
+ var output = [];
+ var cb = bb.parseConfig();
+ var upd = 0;
+
+ for (var i = 0, length = arr.length; i < length; i++) {
+
+ var line = arr[i];
+ if (!line || line[0] === '#' || line.startsWith('//')) {
+ output.push(line);
+ continue;
+ }
+
+ var index = line.indexOf(' :');
+ if (index === -1) {
+ index = line.indexOf('\t:');
+ if (index === -1) {
+ output.push(line);
+ continue;
+ }
+ }
+
+ var key = line.substring(0, index).trim();
+ var val = cb[key];
+ if (!val) {
+ output.push(line);
+ continue;
+ }
+
+ upd++;
+ output.push(key.padRight(index) + ' : ' + val);
+ }
+
+ var filename = path.join(path.dirname(b), path.basename(b, '.resource') + '-merged.txt');
+ fs.writeFileSync(filename, '// Total.js merged file\n// Created: ' + new Date().format('yyyy-MM-dd HH:mm') + '\n' + clean_resource(output.join('\n')));
+ console.log('========================================');
+ console.log('Merged result:');
+ console.log('========================================');
+ console.log('');
+ console.log('merged : ' + upd);
+ console.log('output : ' + filename);
+ console.log('');
+}
+
+function clean_resource(content) {
+ var lines = content.split('\n');
+ var output = [];
+ var max = 0;
+
+ for (var i = 0, length = lines.length; i < length; i++) {
+ var line = lines[i];
+ if (!line || line[0] === '#' || line.startsWith('//'))
+ continue;
+
+ var index = line.indexOf(' :');
+ if (index === -1) {
+ index = line.indexOf('\t:');
+ if (index === -1)
+ continue;
+ }
+
+ max = Math.max(max, index);
+ }
+
+ for (var i = 0, length = lines.length; i < length; i++) {
+ var line = lines[i];
+ if (!line || line[0] === '#' || line.startsWith('//')) {
+ output.push(line);
+ continue;
+ }
+
+ var index = line.indexOf(' :');
+ if (index === -1) {
+ index = line.indexOf('\t:');
+ if (index === -1) {
+ output.push(line);
+ continue;
+ }
+ }
+
+ var key = line.substring(0, index).trim();
+ output.push(key.padRight(max, ' ') + ' : ' + line.substring(index + 2).trim());
+ }
+
+ return output.join('\n');
+}
+
+function parse_csv(content) {
+
+ var output = {};
+ var max = 0;
+ var csv = content.parseCSV(';');
+
+ for (var i = 1; i < csv.length; i++) {
+ var line = csv[i];
+ var key = line.a || '';
+ var val = line.b || '';
+ if (key) {
+ max = Math.max(key.length, max);
+ output[key] = val;
+ }
+ }
+
+ var builder = [];
+ max += 10;
+
+ Object.keys(output).forEach(function(key) {
+ builder.push('{0}: {1}'.format(key.padRight(max, ' '), output[key]));
+ });
+
+ return '\n' + builder.join('\n');
+}
+
+function main() {
+
+ console.log('');
+ console.log('|==================================================|');
+ console.log('| Total.js - www.totaljs.com |');
+ console.log('| Version: v' + require('total.js').version_header.padRight(39) + '|');
+ console.log('|==================================================|');
+ console.log('');
+
+ var dir = process.cwd();
+ for (var i = 2; i < process.argv.length; i++) {
+ var arg = process.argv[i];
+ var cmd = arg.toLowerCase();
+
+ if (cmd.substring(0, 2) === '--')
+ cmd = cmd.substring(1);
+
+ if (i === 2) {
+ var port = cmd.parseInt();
+ if (port) {
+
+ CONF.directory_temp = '~' + path.join(os.tmpdir(), 'totaljs' + dir.hash());
+ CONF.directory_public = '~' + dir;
+ CONF.allow_compile_html = false;
+ CONF.allow_compile_script = false;
+ CONF.allow_compile_style = false;
+
+ F.accept('.less', 'text/less');
+
+ F.http('debug', { port: port, directory: dir });
+
+ F.route('/*', function() {
+
+ var self = this;
+ var dir = F.path.public(self.url.substring(1));
+ var filename = path.join(self.url, 'index.html').substring(1);
+
+ F.path.exists(filename, function(e) {
+
+ if (e)
+ return self.file(filename, '');
+
+ fs.readdir(dir, function(err, items) {
+
+ var render = function(controller, directories, files) {
+ controller.content('Directory listing: {0}{1}
{2}
'.format(controller.url, directories.join(''), files.join('')), 'text/html');
+ };
+
+ var directories = [];
+ var files = [];
+
+ if (self.url !== '/')
+ directories.push('..');
+
+ if (err)
+ return render(self, directories, files);
+
+ items.wait(function(item, next) {
+ var filename = path.join(dir, item);
+ fs.stat(filename, function(err, info) {
+
+ if (info.isFile())
+ files.push('{0}{2}'.format(item, self.url + item, info.size.filesize()));
+ else
+ directories.push('{0}'.format(item, self.url + item));
+
+ next();
+ });
+ }, () => render(self, directories, files));
+ });
+
+ });
+ });
+
+ F.route('/proxy/', function() {
+ var self = this;
+ var method = self.req.method;
+ U.request(self.query.url, [self.req.method], method === 'POST' || method === 'PUT' || method === 'DELETE' ? self.body : null, (err, response, status, headers) => self.content(response, headers ? headers['content-type'] : 'text/plain'));
+ }, ['get', 'post', 'put', 'delete'], 5120);
+
+ return;
+ }
+ }
+
+ if (!$type && (cmd === '-v' || cmd === '-version'))
+ return;
+
+ if (!$type && (cmd === '-t' || cmd === '-translate')) {
+ $type = 4;
+ continue;
+ }
+
+ if (!$type && cmd === '-merge') {
+ merge(process.argv[i + 1] || '', process.argv[i + 2] || '');
+ return;
+ }
+
+ if (!$type && (cmd === '-translate-csv' || cmd === '-translatecsv' || cmd === '-c')) {
+ $type = 6;
+ continue;
+ }
+
+ if (!$type && cmd === '-csv') {
+ var tmp = process.argv[i + 1] || '';
+ var tt = path.join(path.dirname(tmp), path.basename(tmp, '.csv') + '.resource');
+ fs.writeFileSync(tt, '// Total.js resource file\n// Created: ' + new Date().format('yyyy-MM-dd HH:mm') + '\n' + parse_csv(fs.readFileSync(tmp).toString('utf8')));
+ console.log('========================================');
+ console.log('Parsed CSV:');
+ console.log('========================================');
+ console.log('');
+ console.log('output : ' + tt);
+ console.log('');
+ continue;
+ }
+
+ if (!$type && (cmd === '-i' || cmd === '-install')) {
+
+ var libs = ['jc', 'jc.min', 'jcta', 'jcta.min', 'jctajr', 'jctajr.min', 'ta', 'jr', 'jr.jc', 'spa', 'spa.min'];
+ var tmp = process.argv[i + 1] || '';
+
+ if (tmp === 'help') {
+ return console.log('Following libs can be installed: jc, jc.min, jcta.min, jctajr.min, ta, jr, jr.jc, spa');
+ }
+
+ if (!tmp || libs.indexOf(tmp) < 0)
+ return console.log('Unknown library: "' + tmp + '"');
+
+ console.log('');
+ console.log('Installing: ' + tmp);
+
+ var url = '';
+ switch(tmp) {
+ case 'jc':
+ url = 'https://rawgit.com/totaljs/components/master/0dependencies/jc.js';
+ break;
+ case 'jc.min':
+ url = 'https://rawgit.com/totaljs/components/master/0dependencies/jc.min.js';
+ break;
+ case 'jcta.min':
+ url = 'https://rawgit.com/totaljs/components/master/0dependencies/jcta.min.js';
+ break;
+ case 'jctajr.min':
+ url = 'https://rawgit.com/totaljs/components/master/0dependencies/jctajr.min.js';
+ break;
+ case 'spa':
+ case 'spa.min':
+ url = 'https://rawgit.com/totaljs/components/master/0dependencies/spa.min.js';
+ break;
+ case 'spa@14':
+ case 'spa.min@14':
+ url = 'https://rawgit.com/totaljs/components/master/0dependencies/spa.min@14.js';
+ break;
+ case 'spa@15':
+ case 'spa.min@15':
+ url = 'https://rawgit.com/totaljs/components/master/0dependencies/spa.min@15.js';
+ break;
+ case 'ta':
+ url = 'https://rawgit.com/totaljs/Tangular/master/Tangular.js';
+ break;
+ case 'jr':
+ url = 'https://rawgit.com/totaljs/jRouting/master/jrouting.js';
+ break;
+ case 'jr.jc':
+ url = 'https://rawgit.com/totaljs/jRouting/master/jrouting.jcomponent.js';
+ break;
+ }
+
+ U.download(url, [], function callback(err,response) {
+ var target = fs.createWriteStream(path.join(dir, './' + tmp + '.js'));
+ response.pipe(target);
+ console.log('Done!');
+ });
+ return;
+ }
+
+ if (cmd === '-minify' || cmd === '-compress' || cmd === '-compile') {
+ $type = 5;
+ break;
+ }
+
+ if (!$type && (cmd === '-clean')) {
+ var tmp = process.argv[i + 1] || '';
+ var tt = path.join(path.dirname(tmp), path.basename(tmp, '.resource') + '-cleaned.txt');
+ fs.writeFileSync(tt, '// Total.js cleaned file\n// Created: ' + new Date().format('yyyy-MM-dd HH:mm') + '\n' + clean_resource(fs.readFileSync(tmp).toString('utf8')));
+ console.log('========================================');
+ console.log('Cleaned result:');
+ console.log('========================================');
+ console.log('');
+ console.log('output : ' + tt);
+ console.log('');
+ return;
+ }
+
+ if (!$type && (cmd === '-cms' || cmd === 'cms')) {
+ git(dir, 'cms');
+ return;
+ }
+
+ if (!$type && (cmd === '-eshop' || cmd === 'eshop')) {
+ git(dir, 'eshop');
+ return;
+ }
+
+ if (!$type && (cmd === '-superadmin' || cmd === 'superadmin')) {
+ git(dir, 'superadmin');
+ return;
+ }
+
+ if (!$type && (cmd === '-messenger' || cmd === 'messenger')) {
+ git(dir, 'messenger');
+ return;
+ }
+
+ if (!$type && (cmd === '-helpdesk' || cmd === 'helpdesk')) {
+ git(dir, 'helpdesk');
+ return;
+ }
+
+ if (!$type && (cmd === '-openplatform' || cmd === 'openplatform')) {
+ git(dir, 'openplatform');
+ return;
+ }
+
+ if (!$type && (cmd === '-flow' || cmd === 'flow')) {
+ git(dir, 'emptyproject-flow');
+ return;
+ }
+
+ if (!$type && (cmd === '-dashboard' || cmd === 'dashboard')) {
+ git(dir, 'emptyproject-dashboard');
+ return;
+ }
+
+ if (!$type && (cmd === '-flowboard' || cmd === 'flowboard')) {
+ git(dir, 'emptyproject-flowboard');
+ return;
+ }
+
+ if (!$type && (cmd === '-bundle' || cmd === 'bundle')) {
+ makebundle(dir, process.argv[i + 1] || '');
+ return;
+ }
+
+ if (!$type && (cmd === '-package' || cmd === 'package')) {
+ makepackage(dir, process.argv[i + 1] || '');
+ return;
+ }
+
+ if (!$type && (cmd === '-pwa' || cmd === 'pwa')) {
+ git(dir, 'emptyproject-pwa');
+ return;
+ }
+
+ if (!$type && (cmd === '-spa' || cmd === 'spa')) {
+ git(dir, 'emptyproject-jcomponent');
+ return;
+ }
+
+ if (!$type && (cmd === '-rest' || cmd === 'rest')) {
+ git(dir, 'emptyproject-restservice');
+ return;
+ }
+
+ if (!$type && cmd === '-diff') {
+ diff(process.argv[i + 1] || '', process.argv[i + 2] || '');
+ return;
+ }
+
+ if (cmd === '-a' || cmd === '-angular' || cmd === 'angular') {
+ $type = 3;
+ continue;
+ }
+
+ if (!$type && (cmd === '-m' || cmd === '-minimal' || cmd === '-minimum' || cmd === 'minimum')) {
+ $type = 2;
+ continue;
+ }
+
+ if (!$type && (cmd === '-n' || cmd === '-normal' || cmd === 'normal')) {
+ $type = 1;
+ continue;
+ }
+
+ if (!$type && (cmd === '-h' || cmd === '-help' || cmd === '--help' || cmd === 'help')) {
+ display_help();
+ return;
+ }
+
+ dir = arg;
+ isDirectory = true;
+ }
+
+ if (!$type)
+ $type = 1;
+
+ if (dir === '.')
+ dir = process.cwd();
+
+ if ($type === 5) {
+
+ if (!fs.existsSync(dir)) {
+ console.log('ERROR: file not found');
+ console.log('');
+ return;
+ }
+
+ var content = fs.readFileSync(dir).toString('utf8');
+ var extension = U.getExtension(dir);
+ var filename = dir.replace('.' + extension, '.min.' + extension);
+
+ switch (extension.toLowerCase()) {
+ case 'html':
+ fs.writeFileSync(filename, Utils.minifyHTML(content));
+ break;
+ case 'js':
+ fs.writeFileSync(filename, Utils.minifyScript(content));
+ break;
+ case 'css':
+ fs.writeFileSync(filename, Utils.minifyStyle(content));
+ break;
+ }
+
+ console.log('Minified: ' + filename);
+ return;
+ }
+
+ if ($type !== 4) {
+ if (!fs.existsSync(dir)) {
+ console.log('ERROR: directory does not exist');
+ console.log('');
+ return;
+ }
+ }
+
+ if ($type === 4) {
+
+ if (isDirectory) {
+ if (translateFile(dir))
+ return;
+ console.log('T' + dir.hash().padRight(17, ' ') + ': ' + dir);
+ return;
+ }
+
+ console.log('Total.js: creating translation');
+ Utils.ls(dir, function(files) {
+
+ var resource = {};
+ var texts = {};
+ var max = 0;
+ var count = 0;
+ var key;
+ var file;
+
+ for (var i = 0, length = files.length; i < length; i++) {
+ var filename = files[i];
+ var ext = Utils.getExtension(filename);
+
+ if (filename.indexOf('sitemap') === -1 && ext !== 'html' && ext !== 'js')
+ continue;
+
+ var content = fs.readFileSync(filename).toString('utf8');
+ var command = Internal.findLocalization(content, 0);
+ while (command !== null) {
+
+ // Skip for direct reading
+ if (command.command[0] === '#' && command.command[1] !== ' ') {
+ command = Internal.findLocalization(content, command.end);
+ continue;
+ }
+
+ key = 'T' + command.command.hash();
+ file = filename.substring(dir.length + 1);
+
+ texts[key] = command.command;
+
+ if (resource[key]) {
+ if (resource[key].indexOf(file) === -1)
+ resource[key] += ', ' + file;
+ } else
+ resource[key] = file;
+
+ count++;
+ max = Math.max(max, key.length);
+ command = Internal.findLocalization(content, command.end);
+ }
+
+ if (ext === 'js') {
+ // ErrorBuilder
+ var tmp = content.match(/\$\.invalid\('[a-z-0-9]+'\)/gi);
+ if (tmp) {
+ for (var j = 0; j < tmp.length; j++) {
+ var m = (tmp[j] + '');
+ m = m.substring(11, m.length - 2);
+ key = m;
+ file = filename.substring(dir.length + 1);
+ texts[key] = m;
+ if (resource[key]) {
+ if (resource[key].indexOf(file) === -1)
+ resource[key] += ', ' + file;
+ } else
+ resource[key] = file;
+ count++;
+ max = Math.max(max, key.length);
+ }
+ }
+
+ // DBMS
+ tmp = content.match(/\.(error|err)\('[a-z-0-9]+'/gi);
+ if (tmp) {
+ for (var j = 0; j < tmp.length; j++) {
+ var m = (tmp[j] + '');
+ m = m.substring(m.indexOf('(') + 2, m.length - 1);
+ key = m;
+ file = filename.substring(dir.length + 1);
+ texts[key] = m;
+ if (resource[key]) {
+ if (resource[key].indexOf(file) === -1)
+ resource[key] += ', ' + file;
+ } else
+ resource[key] = file;
+ count++;
+ max = Math.max(max, key.length);
+ }
+ }
+ }
+ }
+
+ var keys = Object.keys(resource);
+ var builder = [];
+ var output = {};
+
+ for (var i = 0, length = keys.length; i < length; i++) {
+ if (!output[resource[keys[i]]])
+ output[resource[keys[i]]] = [];
+ output[resource[keys[i]]].push(keys[i].padRight(max + 5, ' ') + ': ' + texts[keys[i]]);
+ }
+
+ keys = Object.keys(output);
+ for (var i = 0, length = keys.length; i < length; i++)
+ builder.push('\n// ' + keys[i] + '\n' + output[keys[i]].join('\n'));
+
+ fs.writeFileSync('translate.resource', '// Total.js translation file\n// Created: ' + new Date().format('yyyy-MM-dd HH:mm') + '\n' + builder.join('\n'));
+ console.log('Total.js: the translation was created (' + count + ' texts)');
+ }, (path, dir) => dir ? (path.endsWith('/node_modules') || path.endsWith('/tmp') || path.endsWith('/.git')) ? false : true : true);
+ return;
+ }
+
+ if ($type === 6) {
+ console.log('Total.js: creating translation to CSV');
+ Utils.ls(dir, function(files) {
+
+ var resource = {};
+ var texts = {};
+ var count = 0;
+ var output = ['Hash;Text;Translation'];
+
+ for (var i = 0, length = files.length; i < length; i++) {
+ var filename = files[i];
+ var ext = Utils.getExtension(filename);
+
+ if (ext !== 'html' && ext !== 'js')
+ continue;
+
+ var content = fs.readFileSync(filename).toString('utf8');
+ var command = Internal.findLocalization(content, 0);
+ while (command !== null) {
+
+ // Skip for direct reading
+ if (command.command[0] === '#' && command.command[1] !== ' ') {
+ command = Internal.findLocalization(content, command.end);
+ continue;
+ }
+
+ var key = 'T' + command.command.hash();
+
+ texts[key] = command.command;
+
+ if (!resource[key]) {
+ output.push(key + ';"' + command.command.replace(/"/g, '""') + '";');
+ resource[key] = true;
+ count++;
+ }
+
+ command = Internal.findLocalization(content, command.end);
+ }
+ }
+
+ fs.writeFileSync('translate.csv', output.join('\n'));
+ console.log('Total.js: the translation was created (' + count + ' texts)');
+ }, (path, dir) => dir ? (path.endsWith('/node_modules') && path.endsWith('/tmp') && path.endsWith('/.git')) ? false : true : true);
+ return;
+ }
+
+ var files = fs.readdirSync(dir);
+ if (files.length > 0) {
+
+ var can = true;
+ for (var i = 0; i < files.length; i++) {
+ var name = files[i];
+ if (name[0] !== '.')
+ can = false;
+ }
+
+ if (!can) {
+ console.log('ERROR: directory is not empty');
+ console.log('');
+ return;
+ }
+ }
+
+ git(dir, 'emptyproject');
+}
+
+function git(dir, type) {
+
+ var done = function() {
+ console.log('Installed: {0}'.format(type));
+ console.log();
+ };
+
+ U.ls(dir, function(fol, fil) {
+
+ if (fol.length || fil.length) {
+ console.log('Directory "{0}"" is not empty.'.format(dir));
+ console.log();
+ return;
+ }
+
+ F.path.mkdir(dir);
+ exec('git clone https://github.com/totaljs/{0}.git {1}'.format(type, dir), function() {
+ F.path.mkdir(path.join(dir, '/node_modules/'));
+ F.rmdir(path.join(dir, '.git'), function() {
+ F.unlink(path.join(dir, '.gitignore'), function() {
+ F.path.exists(path.join(dir, 'package.json'), function(e) {
+ if (e)
+ exec('npm install total.js --save', done);
+ else
+ exec('npm install', done);
+ });
+ });
+ });
+ });
+ });
+}
+
+function makebundle(dir, filename) {
+
+ if (!filename)
+ filename = 'app.bundle';
+
+ var blacklist = {};
+ blacklist['/bundle.json'] = 1;
+ blacklist['/debug.js'] = 1;
+ blacklist['/release.js'] = 1;
+ blacklist['/debug.pid'] = 1;
+ blacklist['/package.json'] = 1;
+ blacklist['/readme.md'] = 1;
+ blacklist['/license.txt'] = 1;
+ blacklist['/bundles/'] = 1;
+ blacklist['/tmp/'] = 1;
+
+ if (filename[0] !== '/')
+ blacklist['/' + filename] = 1;
+ else
+ blacklist[filename] = 1;
+
+ blacklist['/.git/'] = 1;
+
+ if (filename.toLowerCase().lastIndexOf('.bundle') === -1)
+ filename += '.bundle';
+
+ blacklist[filename] = 1;
+
+ console.log('--- CREATE BUNDLE PACKAGE --');
+ console.log('');
+ console.log('Directory :', dir);
+ console.log('Filename :', filename);
+
+ F.backup(filename, U.path(dir), function(err, path) {
+
+ if (err)
+ throw err;
+
+ console.log('Success :', path.files.pluralize('# files', '# file', '# files', '# files') + ' (' + path.size.filesize() + ')');
+ console.log('');
+
+ }, function(path) {
+ return blacklist[path] == null;
+ });
+}
+
+function makepackage(dir, filename) {
+
+ if (!filename)
+ filename = 'noname.package';
+
+ var blacklist = {};
+ blacklist['/bundle.json'] = 1;
+ blacklist['/debug.js'] = 1;
+ blacklist['/release.js'] = 1;
+ blacklist['/debug.pid'] = 1;
+ blacklist['/package.json'] = 1;
+ blacklist['/readme.md'] = 1;
+ blacklist['/license.txt'] = 1;
+ blacklist['/bundles/'] = 1;
+ blacklist['/tmp/'] = 1;
+
+ if (filename[0] !== '/')
+ blacklist['/' + filename] = 1;
+ else
+ blacklist[filename] = 1;
+
+ blacklist['/.git/'] = 1;
+
+ if (filename.toLowerCase().lastIndexOf('.package') === -1)
+ filename += '.package';
+
+ blacklist[filename] = 1;
+
+ console.log('--- CREATE PACKAGE --');
+ console.log('');
+ console.log('Directory :', dir);
+ console.log('Filename :', filename);
+
+ F.backup(filename, U.path(dir), function(err, path) {
+
+ if (err)
+ throw err;
+
+ console.log('Success :', path.files.pluralize('# files', '# file', '# files', '# files') + ' (' + path.size.filesize() + ')');
+ console.log('');
+
+ }, function(path) {
+ return blacklist[path] == null;
+ });
+}
+
+main();
\ No newline at end of file
diff --git a/bin/tpm b/bin/tpm
index d671e18c1..a55e7e928 100755
--- a/bin/tpm
+++ b/bin/tpm
@@ -1,922 +1,1060 @@
+#! /usr/bin/env node
+
'use strict';
-var VERSION = 'v1.5.1';
-
-var fs = require('fs');
-var ph = require('path');
-var parser = require('url');
-var zlib = require('zlib');
-var http = require('http');
-var https = require('https');
-var padding = 120;
-var exec = require('child_process').exec;
-var os = require('os');
-var EOF = os.platform() === 'win32' ? '\r\n' : '\n';
+const VERSION = 'v4.0.0';
+const PADDING = 120;
+
+const Fs = require('fs');
+const Path = require('path');
+const parser = require('url');
+const Zlib = require('zlib');
+const Http = require('http');
+const Https = require('https');
+const Os = require('os');
+const REG_EMPTY = /\s/g;
+const isWindows = Os.platform().substring(0, 3).toLowerCase() === 'win';
+const NODEVERSION = parseFloat(process.version.toString().replace('v', '').replace(/\./g, ''));
+
var $type = 0;
var settings = {};
-var colors = { reset: '\x1b[0m', underscore: '\x1b[4m', dim: '\x1b[2m', reverse: '\x1b[7m', white: '\x1b[37m', red: '\x1b[31m', bgRed: '\x1b[41m', bgGreen: '\x1b[42m' };
+var colors = { reset: '\x1b[0m', underscore: '\x1b[4m', dim: '\x1b[33m', reverse: '\x1b[7m', white: '\x1b[37m', red: '\x1b[31m', bgRed: '\x1b[41m', bgGreen: '\x1b[42m' };
var current_repository = 'default';
var current_package = '';
+if (NODEVERSION > 699) {
+ global.createBufferSize = (size) => Buffer.alloc(size || 0);
+ global.createBuffer = (val, type) => Buffer.from(val || '', type);
+} else {
+ global.createBufferSize = (size) => new Buffer(size || 0);
+ global.createBuffer = (val, type) => new Buffer(val || '', type);
+}
+
+
String.prototype.padRight = function(max, c) {
- var self = this;
- return self + new Array(Math.max(0, max - self.length + 1)).join(c || ' ');
+ var self = this;
+ return self + new Array(Math.max(0, max - self.length + 1)).join(c || ' ');
};
function Backup() {
- this.file = [];
- this.directory = [];
- this.path = '';
- this.fileName = '';
-
- this.read = { key: '', value: '', status: 0 };
-
- this.complete = function() {};
- this.filter = function(path) {
- return true;
- };
+ this.file = [];
+ this.directory = [];
+ this.path = '';
+ this.filename = '';
+ this.pending = 0;
+
+ this.read = { key: createBufferSize(), value: createBufferSize(), status: 0 };
+ this.bufKey = createBuffer(':');
+ this.bufNew = createBuffer('\n');
+
+ this.complete = function() {};
+ this.filter = () => true;
}
function Walker() {
- this.pending = [];
- this.pendingDirectory = [];
- this.directory = [];
- this.file = [];
- this.options = { sort: true, addEmptyDirectory: false };
- this.onComplete = null;
- this.onFilter = null;
+ this.pending = [];
+ this.pendingDirectory = [];
+ this.directory = [];
+ this.file = [];
+ this.options = { sort: true, addEmptyDirectory: false };
+ this.onComplete = null;
+ this.onFilter = null;
}
Walker.prototype.reset = function() {
- var self = this;
- self.file = [];
- self.directory = [];
- self.pendingDirectory = [];
+ var self = this;
+ self.file = [];
+ self.directory = [];
+ self.pendingDirectory = [];
};
Walker.prototype.walk = function(path) {
- var self = this;
+ var self = this;
- if (path instanceof Array) {
- var length = path.length;
+ if (path instanceof Array) {
+ var length = path.length;
- for (var i = 0; i < length; i++)
- self.pendingDirectory.push(path[i]);
+ for (var i = 0; i < length; i++)
+ self.pendingDirectory.push(path[i]);
- self.next();
- return;
- }
+ self.next();
+ return;
+ }
- fs.readdir(path, function(err, arr) {
+ Fs.readdir(path, function(err, arr) {
- if (err)
- return self.next();
+ if (err)
+ return self.next();
- if (arr.length === 0 || self.options.addEmptyDirectory) {
- if (self.onFilter === null || self.onFilter(path))
- self.directory.push(path);
- }
+ if (arr.length === 0 || self.options.addEmptyDirectory) {
+ if (self.onFilter === null || self.onFilter(path))
+ self.directory.push(path);
+ }
- var length = arr.length;
- for (var i = 0; i < length; i++)
- self.pending.push(ph.join(path, arr[i]));
+ var length = arr.length;
+ for (var i = 0; i < length; i++)
+ self.pending.push(Path.join(path, arr[i]));
- self.next();
- });
+ self.next();
+ });
};
Walker.prototype.stat = function(path) {
- var self = this;
+ var self = this;
- fs.stat(path, function(err, stats) {
+ Fs.stat(path, function(err, stats) {
- if (err)
- return self.next();
+ if (err)
+ return self.next();
- if (stats.isDirectory())
- self.pendingDirectory.push(path);
- else if (self.onFilter === null || self.onFilter(path))
- self.file.push(path);
+ if (stats.isDirectory())
+ self.pendingDirectory.push(path);
+ else if (!self.onFilter || self.onFilter(path))
+ self.file.push(path);
- self.next();
- });
+ self.next();
+ });
};
Walker.prototype.next = function() {
- var self = this;
-
- if (self.pending.length > 0) {
- var item = self.pending.shift();
- self.stat(item);
- return;
- }
-
- if (self.pendingDirectory.length > 0) {
- var directory = self.pendingDirectory.shift();
- self.walk(directory);
- return;
- }
-
- if (self.options.sort) {
- self.file.sort(function(a, b) {
- return a.localeCompare(b);
- });
- }
-
- self.onComplete(self.directory, self.file);
-};
-
-Backup.prototype.backup = function(path, fileName, callback, filter) {
-
- if (fs.existsSync(fileName))
- fs.unlinkSync(fileName);
+ var self = this;
- var walker = new Walker();
- var self = this;
+ if (self.pending.length) {
+ var item = self.pending.shift();
+ self.stat(item);
+ return;
+ }
- self.fileName = fileName;
- self.path = path;
+ if (self.pendingDirectory.length) {
+ var directory = self.pendingDirectory.shift();
+ self.walk(directory);
+ return;
+ }
- if (callback)
- self.complete = callback;
+ self.options.sort && self.file.sort(function(a, b) {
+ return a.localeCompare(b);
+ });
- if (filter)
- self.filter = filter;
-
- walker.onComplete = function(directory, files) {
- self.directory = directory;
- self.file = files;
- self.$compress();
- };
-
- walker.walk(path);
+ self.onComplete(self.directory, self.file);
};
+Backup.prototype.backup = function(path, filename, callback, filter) {
+
+ Fs.existsSync(filename) && Fs.unlinkSync(filename);
+
+ var walker = new Walker();
+ var self = this;
+
+ self.filename = filename;
+ self.path = path;
+
+ if (callback)
+ self.complete = callback;
+
+ if (filter)
+ self.filter = filter;
+
+ walker.onComplete = function(directory, files) {
+ self.directory = directory;
+
+ var ignore = [];
+ var index = 0;
+
+ while (true) {
+ var file = files[index++];
+ if (file === undefined)
+ break;
+ if (file.indexOf('.tpmignore') === -1)
+ continue;
+ index--;
+ ignore.push(file);
+ files.splice(index, 1);
+ }
+
+ var skip = ['.DS_Store', '.git', 'thumbs.db'];
+ for (var i = 0, length = ignore.length; i < length; i++)
+ skip = skip.concat(Fs.readFileSync(ignore[i]).toString('utf8').split('\n'));
+
+ index = 0;
+ while (true) {
+ var file = directory[index++];
+ if (file === undefined)
+ break;
+ for (var i = 0, length = skip.length; i < length; i++) {
+ if (!skip[i])
+ continue;
+ if (file.indexOf(skip[i]) === -1)
+ continue;
+ index--;
+ directory.splice(index, 1);
+ break;
+ }
+ }
+
+ index = 0;
+ while (true) {
+ var file = files[index++];
+ if (file === undefined)
+ break;
+ for (var i = 0, length = skip.length; i < length; i++) {
+ if (!skip[i] || (file.indexOf(skip[i]) === -1 && file.substring(self.path.length).indexOf('/tmp/') !== 0))
+ continue;
+ index--;
+ files.splice(index, 1);
+ break;
+ }
+ }
+
+ self.file = files;
+ self.$compress();
+ };
+
+ walker.walk(path);
+};
Backup.prototype.$compress = function() {
- var self = this;
- var length = self.path.length;
- var len = 0;
+ var self = this;
+ var length = self.path.length;
+ var len = 0;
- if (self.directory.length > 0) {
+ if (self.directory.length) {
- len = self.directory.length;
+ len = self.directory.length;
- for (var i = 0; i < len; i++) {
- var o = self.directory[i];
- if (self.filter(o.substring(length)))
- fs.appendFileSync(self.fileName, (o.replace(self.path, '').replace(/\\/g, '/') + '/').padRight(padding) + ':#\n');
- }
+ for (var i = 0; i < len; i++) {
+ var o = self.directory[i];
+ self.filter(o.substring(length)) && Fs.appendFileSync(self.filename, (o.replace(self.path, '').replace(/\\/g, '/') + '/').padRight(PADDING) + ':#\n');
+ }
- self.directory = [];
- }
+ self.directory = [];
+ }
- var fileName = self.file.shift();
+ var filename = self.file.shift();
- if (typeof(fileName) === 'undefined') {
- self.complete(null, self.fileName);
- return;
- }
+ if (!filename) {
+ self.complete(null, self.filename);
+ return;
+ }
- if (!self.filter(fileName.substring(length))) {
- self.$compress();
- return;
- }
+ if (!self.filter(filename.substring(length))) {
+ self.$compress();
+ return;
+ }
- var buffer = '';
+ Fs.readFile(filename, function(err, data) {
+ Zlib.gzip(data, function(err, data) {
- fs.readFile(fileName, function(err, data) {
- zlib.gzip(data, function(err, data) {
+ if (err)
+ return;
- if (err)
- return;
-
- var name = fileName.replace(self.path, '').replace(/\\/g, '/');
- fs.appendFile(self.fileName, name.padRight(padding) + ':' + data.toString('base64') + '\n', function(err) {
- self.$compress();
- });
- });
- });
+ var name = filename.replace(self.path, '').replace(/\\/g, '/');
+ Fs.appendFile(self.filename, name.padRight(PADDING) + ':' + data.toString('base64') + '\n', () => self.$compress());
+ });
+ });
};
Backup.prototype.restoreKey = function(data) {
- var self = this;
- var read = self.read;
-
- if (read.status === 1) {
- self.restoreValue(data);
- return;
- }
-
- var index = data.indexOf(':');
-
- if (index === -1) {
- read.key += data;
- return;
- }
-
- read.status = 1;
- read.key = data.substring(0, index);
- self.restoreValue(data.substring(index + 1));
+ var self = this;
+ var read = self.read;
+
+ if (read.status === 1) {
+ self.restoreValue(data);
+ return;
+ }
+
+ var index = -1;
+ var tmp = data;
+
+ if (read.status === 2) {
+ tmp = Buffer.concat([read.key, tmp]);
+ index = tmp.indexOf(self.bufKey);
+ } else
+ index = tmp.indexOf(self.bufKey);
+
+ if (index === -1) {
+ read.key = Buffer.concat([read.key, data]);
+ read.status = 2;
+ return;
+ }
+
+ read.status = 1;
+ read.key = tmp.slice(0, index);
+ self.restoreValue(tmp.slice(index + 1));
+ tmp = null;
};
Backup.prototype.restoreValue = function(data) {
- var self = this;
- var read = self.read;
+ var self = this;
+ var read = self.read;
- if (read.status !== 1) {
- self.restoreKey(data);
- return;
- }
+ if (read.status !== 1) {
+ self.restoreKey(data);
+ return;
+ }
- var index = data.indexOf('\n');
- if (index === -1) {
- read.value += data;
- return;
- }
+ var index = data.indexOf(self.bufNew);
+ if (index === -1) {
+ read.value = Buffer.concat([read.value, data]);
+ return;
+ }
- read.value += data.substring(0, index);
- self.restoreFile(read.key.replace(/\s/g, ''), read.value.replace(/\s/g, ''));
+ read.value = Buffer.concat([read.value, data.slice(0, index)]);
+ self.restoreFile(read.key.toString('utf8').replace(REG_EMPTY, ''), read.value.toString('utf8').replace(REG_EMPTY, ''));
- read.status = 0;
- read.value = '';
- read.key = '';
+ read.status = 0;
+ read.value = createBufferSize();
+ read.key = createBufferSize();
- self.restoreKey(data.substring(index + 1));
+ self.restoreKey(data.slice(index + 1));
};
Backup.prototype.restore = function(filename, path, callback, filter) {
- if (!fs.existsSync(filename)) {
- if (callback)
- callback(new Error('Backup file not found.'), path);
- return;
- }
-
- var self = this;
- self.filter = filter;
- self.createDirectory(path, true);
+ if (!Fs.existsSync(filename)) {
+ callback && callback(new Error('Package not found.'), path);
+ return;
+ }
- var stream = fs.createReadStream(filename);
- var key = '';
- var value = '';
- var status = 0;
+ var self = this;
- self.path = path;
+ self.filter = filter;
+ self.cache = {};
+ self.createDirectory(path, true);
+ self.path = path;
- stream.on('data', function(buffer) {
+ var stream = Fs.createReadStream(filename);
+ stream.on('data', buffer => self.restoreKey(buffer));
- var data = buffer.toString('utf8');
- self.restoreKey(data);
+ if (!callback) {
+ stream.resume();
+ return;
+ }
- });
+ callback.path = path;
- if (callback) {
- stream.on('end', function() {
- callback(null, path);
- stream = null;
- });
- }
+ stream.on('end', function() {
+ self.callback(callback);
+ stream = null;
+ });
- stream.resume();
+ stream.resume();
};
-Backup.prototype.restoreFile = function(key, value) {
- var self = this;
-
- if (typeof(self.filter) === 'function' && !self.filter(key))
- return;
-
- if (value === '#') {
- self.createDirectory(key);
- return;
- }
-
- var path = key;
- var index = key.lastIndexOf('/');
-
- if (index !== -1) {
- path = key.substring(0, index).trim();
- if (path.length > 0)
- self.createDirectory(path);
- }
-
- var buffer = new Buffer(value, 'base64');
- zlib.gunzip(buffer, function(err, data) {
- fs.writeFileSync(ph.join(self.path, key), data);
- buffer = null;
- });
+Backup.prototype.callback = function(cb) {
+ var self = this;
+ if (self.pending <= 0)
+ return cb(null, cb.path);
+ setTimeout(() => self.callback(cb), 100);
};
-Backup.prototype.createDirectory = function(path, root) {
-
- if (path[0] === '/')
- path = path.substring(1);
-
- if (path[path.length - 1] === '/')
- path = path.substring(0, path.length - 1);
+Backup.prototype.restoreFile = function(key, value) {
+ var self = this;
- var arr = path.split('/');
- var directory = '';
- var self = this;
- var length = arr.length;
+ if (typeof(self.filter) === 'function' && !self.filter(key))
+ return;
- for (var i = 0; i < length; i++) {
+ if (value === '#') {
+ self.createDirectory(key);
+ return;
+ }
- var name = arr[i];
- directory += (directory.length > 0 ? '/' : '') + name;
- var dir = ph.join(self.path, directory);
+ var p = key;
+ var index = key.lastIndexOf('/');
- if (root)
- dir = '/' + dir;
+ if (index !== -1) {
+ p = key.substring(0, index).trim();
+ p && self.createDirectory(p);
+ }
- if (fs.existsSync(dir))
- continue;
+ var buffer = createBuffer(value, 'base64');
+ self.pending++;
- fs.mkdirSync(dir);
- }
+ Zlib.gunzip(buffer, function(err, data) {
+ Fs.writeFile(Path.join(self.path, key), data, () => self.pending--);
+ buffer = null;
+ });
};
-Backup.prototype.clear = function(path, callback, filter) {
-
- var self = this;
- var walker = new Walker();
- walker.options.addEmptyDirectory = true;
-
- if (callback)
- self.complete = callback;
+Backup.prototype.createDirectory = function(p, root) {
- if (filter)
- self.filter = filter;
+ var self = this;
- walker.onComplete = function(directory, files) {
+ if (p[0] === '/')
+ p = p.substring(1);
- self.file = [];
- self.directory = [];
+ var is = isWindows;
- if (typeof(filter) !== 'function')
- filter = function(o) { return true; };
+ if (is) {
+ if (p[p.length - 1] === '\\')
+ p = p.substring(0, p.length - 1);
+ } else {
+ if (p[p.length - 1] === '/')
+ p = p.substring(0, p.length - 1);
+ }
- var length = files.length;
+ var arr = is ? p.replace(/\//g, '\\').split('\\') : p.split('/');
+ var directory = '';
- for (var i = 0; i < length; i++) {
- var o = files[i];
- if (filter(o))
- self.file.push(o);
- }
+ if (is && arr[0].indexOf(':') !== -1)
+ arr.shift();
- length = directory.length;
- for (var i = 0; i < length; i++) {
+ var length = arr.length;
- var o = files[i];
+ for (var i = 0; i < length; i++) {
- if (o === path)
- return;
+ var name = arr[i];
- if (filter(o))
- self.directory.push(o);
- }
+ if (is)
+ directory += (directory.length > 0 ? '\\' : '') + name;
+ else
+ directory += (directory.length > 0 ? '/' : '') + name;
- self.directory.sort(function(a, b) {
- if (a.length < b.length)
- return 1;
- else
- return -1;
- });
+ var dir = Path.join(self.path, directory);
+ if (root)
+ dir = (is ? '\\' : '/') + dir;
- self.removeFile();
- };
-
- walker.walk(path);
+ !Fs.existsSync(dir) && Fs.mkdirSync(dir);
+ }
};
-Backup.prototype.removeFile = function() {
+Backup.prototype.clear = function(path, callback, filter) {
- var self = this;
- var filename = self.file.shift();
+ var self = this;
+ var walker = new Walker();
+ walker.options.addEmptyDirectory = true;
- if (typeof(filename) === 'undefined') {
- self.removeDirectory();
- return;
- }
+ if (callback)
+ self.complete = callback;
- fs.unlink(filename, function() {
- self.removeFile();
- });
-};
+ if (filter)
+ self.filter = filter;
-Backup.prototype.removeDirectory = function() {
+ walker.onComplete = function(directory, files) {
- var self = this;
- var directory = self.directory.shift();
+ self.file = [];
+ self.directory = [];
- if (typeof(directory) === 'undefined') {
- self.complete();
- return;
- }
+ if (typeof(filter) !== 'function')
+ filter = function() { return true; };
- fs.rmdir(directory, function() {
- self.removeDirectory();
- });
-};
+ var length = files.length;
-function download(url, callback) {
+ for (var i = 0; i < length; i++) {
+ var o = files[i];
+ filter(o) && self.file.push(o);
+ }
- var uri = parser.parse(url);
- var h = {};
- var encoding = 'utf8';
+ length = directory.length;
+ for (var i = 0; i < length; i++) {
+ var o = files[i];
+ if (o === path)
+ return;
+ filter(o) && self.directory.push(o);
+ }
- h['X-Powered-By'] = 'total.js package manager';
+ self.directory.sort(function(a, b) {
+ if (a.length < b.length)
+ return 1;
+ else
+ return -1;
+ });
- var options = { protocol: uri.protocol, auth: uri.auth, method: 'GET', hostname: uri.hostname, port: uri.port, path: uri.path, agent: false, headers: h };
+ self.removeFile();
+ };
- var response = function(res) {
- callback(null, res);
- };
+ walker.walk(path);
+};
- var con = options.protocol === 'https:' ? https : http;
+Backup.prototype.removeFile = function() {
- try
- {
+ var self = this;
+ var filename = self.file.shift();
+ if (filename)
+ Fs.unlink(filename, () => self.removeFile());
+ else
+ self.removeDirectory();
+};
- var req = con.get(options, response);
+Backup.prototype.removeDirectory = function() {
+ var self = this;
+ var directory = self.directory.shift();
+ if (directory)
+ Fs.rmdir(directory, () => self.removeDirectory());
+ else
+ self.complete();
+};
- if (callback) {
- req.on('error', function(error) {
- callback(error, null);
- });
- }
+function download(url, callback) {
+ var uri = parser.parse(url);
+ var h = {};
- req.end();
+ h['X-Powered-By'] = 'Total.js Package Manager';
- } catch (ex) {
- if (callback)
- callback(ex, null);
- return false;
- }
+ var options = { protocol: uri.protocol, auth: uri.auth, method: 'GET', hostname: uri.hostname, port: uri.port, path: uri.path, agent: false, headers: h };
+ var con = options.protocol === 'https:' ? Https : Http;
+ var req = con.get(options, res => callback(null, res));
- return true;
+ req.on('error', error => callback(error, null));
+ req.end();
+ return true;
}
function display_help() {
- log(colors.reset);
- log('====== Total.js Package Manager ' + VERSION +' ======');
- log('');
- log('Documentation: http://docs.totaljs.com/total-package-manager/');
- log('');
- log(colors.underscore + '===== INSTALL PACKAGE TO THE CURRENT PATH' + colors.reset);
- log('');
- log(colors.red + 'tpm install [optional: package_name] [optional: repository_name]' + colors.reset);
- log('');
- log('EXAMPLE: tpm install ddos');
- log('EXAMPLE: tpm install ddos local-repository');
- log('');
- log(colors.dim + 'Install all total.js packages from package.json:' + colors.reset);
- log('EXAMPLE: tpm install');
- log('');
- log(colors.underscore + '===== UNINSTALL PACKAGE FROM THE CURRENT PATH' + colors.reset);
- log('');
- log(colors.red + 'tpm uninstall [optional: package_name] [optional: repository_name]' + colors.reset);
- log('');
- log('EXAMPLE: tpm uninstall ddos');
- log('EXAMPLE: tpm uninstall ddos local-repository');
- log('');
- log(colors.dim + 'Uninstall all total.js packages from package.json:' + colors.reset);
- log('EXAMPLE: tpm uninstall');
- log('');
- log(colors.underscore + '===== CREATE PACKAGE FROM THE CURRENT PATH CREATE' + colors.reset);
- log('');
- log(colors.red + 'tpm create [important: package_name]' + colors.reset);
- log('');
- log('EXAMPLE: tpm create my-project-template');
- log('EXAMPLE: tpm create my-module');
- log('');
- log(colors.underscore + '===== ADD A NEW PACKAGE REPOSITORY' + colors.reset);
- log('');
- log(colors.red + 'tpm repository [repository_name] [repository_url]' + colors.reset);
- log('');
- log('EXAMPLE: tpm repository local http://127.0.0.1:8000/');
- log('EXAMPLE: tpm repository enterprise http://repository.yourcompany.com/packages/totaljs/');
- log('');
- log(colors.underscore + '===== SHOW ALL REGISTERED REPOSITORIES' + colors.reset);
- log('');
- log(colors.red + 'tpm repositories' + colors.reset);
- log('');
+ log(colors.reset);
+ log('====== Total.js Package Manager ' + VERSION +' ======');
+ log('');
+ log(colors.red + '$ tpm install [optional: package_name] [optional: repository_name]' + colors.reset);
+ log('');
+ log(colors.dim + 'EXAMPLE: tpm install ddos');
+ log('EXAMPLE: tpm install ddos local-repository' + colors.reset);
+ log('');
+ log('Install all Total.js packages from "package.json":');
+ log(colors.dim + 'EXAMPLE: tpm install' + colors.reset);
+ log('');
+ log('--- --- --- --- ---');
+ log('');
+ log(colors.red + '$ tpm uninstall [optional: package_name] [optional: repository_name]' + colors.reset);
+ log('');
+ log(colors.dim + 'EXAMPLE: tpm uninstall ddos');
+ log('EXAMPLE: tpm uninstall ddos local-repository' + colors.reset);
+ log('');
+ log('Uninstall all Total.js packages from package.json:');
+ log(colors.dim + 'EXAMPLE: tpm uninstall' + colors.reset);
+ log('');
+ log('--- --- --- --- ---');
+ log('');
+ log('Creating packages:');
+ log(colors.red + '$ tpm create [important: package_name] [optional: package_directory_to_pack]' + colors.reset);
+ log('');
+ log(colors.dim + 'EXAMPLE: tpm create my-project-template');
+ log('EXAMPLE: tpm create my-module');
+ log('EXAMPLE: tpm create my-module /users/packages/my-package/' + colors.reset);
+ log('');
+ log('--- --- --- --- ---');
+ log('');
+ log('Creating bundles:');
+ log(colors.red + '$ tpm bundle [important: bundle_name] [optional: bundle_directory_to_pack]' + colors.reset);
+ log('');
+ log(colors.dim + 'EXAMPLE: tpm bundle my-project-template');
+ log('EXAMPLE: tpm bundle my-module');
+ log('EXAMPLE: tpm bundle my-module /users/bundles/my-package/' + colors.reset);
+ log('');
+ log('--- --- --- --- ---');
+ log('');
+ log(colors.red + '$ tpm repository [repository_name] [repository_url]' + colors.reset);
+ log('');
+ log(colors.dim + 'EXAMPLE: tpm repository local http://127.0.0.1:8000/');
+ log('EXAMPLE: tpm repository enterprise http://repository.yourcompany.com/packages/totaljs/' + colors.reset);
+ log('');
+ log('--- --- --- --- ---');
+ log('');
+ log(colors.red + '$ tpm repositories' + colors.reset);
+ log('');
+ log('--- --- --- --- ---');
+ log('');
+ log(colors.red + '$ tpm unpack [package_name] [optional: target_directory]' + colors.reset);
+ log('');
}
function display_repositories() {
- log('');
- log('--- REPOSITORIES ---');
- log('');
- log('Filename: ' + ph.join(ph.dirname(process.argv[1]), 'tpm.json'));
- log('');
- Object.keys(settings).forEach(function(key) {
- log(key.padRight(33) + ': ' + settings[key]);
- });
- log('');
+ log('');
+ log('--- REPOSITORIES ---');
+ log('');
+ log('Filename: ' + Path.join(Path.dirname(process.argv[1]), 'tpm.json'));
+ log('');
+ Object.keys(settings).forEach(function(key) {
+ log(key.padRight(33) + ': ' + settings[key]);
+ });
+ log('');
}
function repository_add() {
- if (current_package.length === 0 || current_repository.length === 0) {
- display_repositories();
- return;
- }
+ if (!current_package.length || !current_repository.length) {
+ display_repositories();
+ return;
+ }
- var filename = ph.join(ph.dirname(process.argv[1]), 'tpm.json');
+ var filename = Path.join(Path.dirname(process.argv[1]), 'tpm.json');
+ var last = current_repository[current_repository.length - 1];
+ if (last !== '=' && last !== '?' && last !== '/')
+ current_repository += '/';
- var last = current_repository[current_repository.length - 1];
- if (last !== '=' && last !== '?' && last !== '/')
- current_repository += '/';
-
- settings[current_package] = current_repository;
- fs.writeFileSync(filename, JSON.stringify(settings));
-
- display_repositories();
+ settings[current_package] = current_repository;
+ Fs.writeFileSync(filename, JSON.stringify(settings));
+ display_repositories();
}
function install(repository, pkgname, cb, append) {
- var name = pkgname;
+ var name = pkgname;
+ if (name.toLowerCase().lastIndexOf('.package') === -1)
+ name += '.package';
- if (name.toLowerCase().lastIndexOf('.package') === -1)
- name += '.package';
+ var h = name.substring(0, 6);
+ if (h === 'http:/' || h === 'https:') {
+ var n = Path.basename(name);
+ pkgname = n;
+ repository = name.substring(0, name.length - n.length);
+ name = n;
+ }
- log('');
- log(colors.red + '--- INSTALL PACKAGE --' + colors.reset);
- log('');
- log('Package :', pkgname);
- log('Directory :', process.cwd());
- log('Download :', repository + name);
+ log('');
+ log(colors.red + '--- INSTALL PACKAGE --' + colors.reset);
+ log('');
+ log('Package :', pkgname);
+ log('Directory :', process.cwd());
+ log('Download :', repository + name);
- download(repository + name, function (err, response) {
+ download(repository + name, function (err, response) {
- if (err) {
+ if (err) {
- log(colors.bgRed + colors.white + 'Error :', err + colors.reset);
+ log(colors.bgRed + colors.white + 'Error :', err + colors.reset);
- if (cb)
- cb();
+ if (cb)
+ cb();
- return;
- }
+ return;
+ }
- if (response.statusCode !== 200) {
- log(colors.bgRed + colors.white + 'Error :', response.statusCode + colors.reset);
- log('');
+ if (response.statusCode !== 200) {
+ log(colors.bgRed + colors.white + 'Error :', response.statusCode + colors.reset);
+ log('');
- if (cb)
- cb();
+ if (cb)
+ cb();
- return;
- }
+ return;
+ }
- var filename = ph.join(process.cwd(), name);
- var stream = fs.createWriteStream(filename);
+ var filename = Path.join(process.cwd(), name);
+ var stream = Fs.createWriteStream(filename);
- response.on('end', function () {
+ response.on('end', function () {
- log('Installing :', filename);
+ log('Installing :', filename);
- var backup = new Backup();
- backup.restore(filename, ph.dirname(filename), function() {
- log('Removing :', filename);
- fs.unlinkSync(filename);
- log(colors.bgGreen + colors.white + 'Status :', 'Success' + colors.reset);
- log('');
- if (append) {
- filename = ph.join(process.cwd(), 'package.json');
+ var backup = new Backup();
+ backup.restore(filename, Path.dirname(filename), function() {
+ log('Removing :', filename);
+ Fs.unlinkSync(filename);
+ log(colors.bgGreen + colors.white + 'Status :', 'Success' + colors.reset);
+ log('');
+ if (append) {
+ filename = Path.join(process.cwd(), 'package.json');
- if (fs.existsSync(filename)) {
- var obj = JSON.parse(fs.readFileSync(filename).toString('utf8'));
- if (!obj.tpm)
- obj.tpm = {};
- obj.tpm[pkgname] = repository;
- } else {
- obj = { tpm: {} };
- obj.tpm[pkgname] = repository;
- }
+ if (Fs.existsSync(filename)) {
+ var obj = JSON.parse(Fs.readFileSync(filename).toString('utf8'));
+ if (!obj.tpm)
+ obj.tpm = {};
+ obj.tpm[pkgname] = repository;
+ } else {
+ obj = { tpm: {} };
+ obj.tpm[pkgname] = repository;
+ }
- fs.writeFileSync(filename, JSON.stringify(obj, null, 4));
- }
+ Fs.writeFileSync(filename, JSON.stringify(obj, null, 4));
+ }
- if (cb)
- cb();
+ if (cb)
+ cb();
- });
+ });
- });
+ });
- response.pipe(stream);
-
- });
+ response.pipe(stream);
+ });
+}
+function unpack(filename, target) {
+ var backup = new Backup();
+ backup.restore(filename, target ? target : Path.dirname(filename), function() {
+ log(colors.bgGreen + colors.white + 'Status :', 'Success' + colors.reset);
+ log('');
+ });
}
function uninstall(repository, pkgname, cb, remove) {
- var name = pkgname;
+ var name = pkgname;
- if (name.toLowerCase().lastIndexOf('.package') === -1)
- name += '.package';
+ if (name.toLowerCase().lastIndexOf('.package') === -1)
+ name += '.package';
- log('');
- log(colors.red + '--- UNINSTALL PACKAGE --' + colors.reset);
- log('');
- log('Package :', pkgname);
- log('Directory :', process.cwd());
- log('Download :', repository + name);
+ log('');
+ log(colors.red + '--- UNINSTALL PACKAGE --' + colors.reset);
+ log('');
+ log('Package :', pkgname);
+ log('Directory :', process.cwd());
+ log('Download :', repository + name);
- download(repository + name, function (err, response) {
+ download(repository + name, function (err, response) {
- if (err) {
+ if (err) {
- log(colors.bgRed + colors.white + 'Error :', err + colors.reset);
+ log(colors.bgRed + colors.white + 'Error :', err + colors.reset);
- if (cb)
- cb();
+ if (cb)
+ cb();
- return;
- }
+ return;
+ }
- if (response.statusCode !== 200) {
- log(colors.bgRed + colors.white + 'Error :', response.statusCode + colors.reset);
- log('');
+ if (response.statusCode !== 200) {
+ log(colors.bgRed + colors.white + 'Error :', response.statusCode + colors.reset);
+ log('');
- if (cb)
- cb();
+ if (cb)
+ cb();
- return;
- }
+ return;
+ }
- var filename = ph.join(process.cwd(), name);
- var stream = fs.createWriteStream(filename);
- var arr = [];
+ var filename = Path.join(process.cwd(), name);
+ var stream = Fs.createWriteStream(filename);
+ var arr = [];
- response.on('end', function () {
+ response.on('end', function () {
- log('Reading :', filename);
+ log('Reading :', filename);
- var backup = new Backup();
- backup.restore(filename, ph.dirname(filename), function() {
+ var backup = new Backup();
+ backup.restore(filename, Path.dirname(filename), function() {
- arr.sort(function(a, b) {
- if (a.length > b.length)
- return -1;
- if (a.length < b.length)
- return 1;
- return a.localeCompare(b);
- });
+ arr.sort(function(a, b) {
+ if (a.length > b.length)
+ return -1;
+ if (a.length < b.length)
+ return 1;
+ return a.localeCompare(b);
+ });
- arr.forEach(function(path) {
- try
- {
- log('Removing :', path);
- unlink(path);
- } catch (e) {}
- });
+ arr.forEach(function(path) {
+ try
+ {
+ log('Removing :', path);
+ unlink(path);
+ } catch (e) {}
+ });
- log('Removing :', filename);
- fs.unlinkSync(filename);
- log(colors.bgGreen + colors.white + 'Status :', 'Success' + colors.reset);
- log('');
+ log('Removing :', filename);
+ Fs.unlinkSync(filename);
+ log(colors.bgGreen + colors.white + 'Status :', 'Success' + colors.reset);
+ log('');
- if (remove) {
- filename = ph.join(process.cwd(), 'package.json');
+ if (remove) {
+ filename = Path.join(process.cwd(), 'package.json');
- if (fs.existsSync(filename)) {
- var obj = JSON.parse(fs.readFileSync(filename).toString('utf8'));
- if (obj.tpm)
- delete obj.tpm[pkgname];
- fs.writeFileSync(filename, JSON.stringify(obj, null, 4));
- }
+ if (Fs.existsSync(filename)) {
+ var obj = JSON.parse(Fs.readFileSync(filename).toString('utf8'));
+ if (obj.tpm)
+ obj.tpm[pkgname] = undefined;
+ Fs.writeFileSync(filename, JSON.stringify(obj, null, 4));
+ }
- }
+ }
- if (cb)
- cb();
+ cb && cb();
- }, function(path) {
- arr.push(path);
- return false;
- });
+ }, function(path) {
+ arr.push(path);
+ return false;
+ });
- });
+ });
- response.pipe(stream);
-
- });
+ response.pipe(stream);
+ });
}
function create() {
- var name = current_package;
+ var target = process.cwd();
+ var name = current_package;
+
+ var length = process.argv.length;
+
+ if (length === 5) {
+ target = process.argv[4];
+ name = process.argv[3];
+ } else if (length === 4) {
+ current_package = process.argv[3];
+ name = Path.join(process.cwd(), current_package);
+ }
- if (name.toLowerCase().lastIndexOf('.package') === -1)
- name += '.package';
+ if (name.toLowerCase().lastIndexOf('.package') === -1)
+ name += '.package';
- log('');
- log('--- CREATE PACKAGE --');
- log('');
- log('Package :', current_package);
- log('Directory :', process.cwd());
+ log('');
+ log('--- CREATE PACKAGE --');
+ log('');
+ log('Package :', current_package);
+ log('Directory :', target);
- var backup = new Backup();
- backup.backup(process.cwd(), ph.join(process.cwd(), name), function(err, path) {
+ if (!isWindows) {
+ if (name[0] !== '/')
+ name = Path.join(process.cwd(), name);
+ }
- if (err) {
- throw err;
- return;
- }
+ var backup = new Backup();
- log('Success :', path);
- log('');
+ backup.backup(target, name, function(err, path) {
- }, function(path) {
- return path.lastIndexOf('.package') === -1;
- });
+ if (err)
+ throw err;
+
+ log('Success :', path);
+ log('');
+
+ }, () => true);
+}
+
+function createbundle() {
+
+ var target = process.cwd();
+ var name = current_package;
+ var length = process.argv.length;
+ var blacklist = {};
+
+ blacklist['/bundle.json'] = 1;
+ blacklist['/debug.js'] = 1;
+ blacklist['/release.js'] = 1;
+ blacklist['/debug.pid'] = 1;
+ blacklist['/package.json'] = 1;
+ blacklist['/readme.md'] = 1;
+ blacklist['/license.txt'] = 1;
+ blacklist['/bundles/'] = 1;
+ blacklist['/tmp/'] = 1;
+ blacklist['/.git/'] = 1;
+
+ if (length === 5) {
+ target = process.argv[4];
+ name = process.argv[3];
+ } else if (length === 4) {
+ current_package = process.argv[3];
+ name = Path.join(process.cwd(), current_package);
+ }
+
+ if (name.toLowerCase().lastIndexOf('.bundle') === -1)
+ name += '.bundle';
+
+ log('');
+ log('--- CREATE BUNDLE PACKAGE --');
+ log('');
+ log('Package :', current_package);
+ log('Directory :', target);
+
+ if (!isWindows) {
+ if (name[0] !== '/')
+ name = Path.join(process.cwd(), name);
+ }
+
+ var backup = new Backup();
+
+ backup.backup(target, name, function(err, path) {
+
+ if (err)
+ throw err;
+
+ log('Success :', path);
+ log('');
+
+ }, path => blacklist[path] == null);
}
function unlink(path) {
- var p = ph.join(process.cwd(), path);
+ var p = Path.join(process.cwd(), path);
- if (!fs.existsSync(p))
- return;
+ if (!Fs.existsSync(p))
+ return;
- var stats = fs.statSync(p);
+ var stats = Fs.statSync(p);
- if (stats.isFile()) {
- fs.unlinkSync(p);
- unlink(ph.dirname(path));
- return;
- }
+ if (stats.isFile()) {
+ Fs.unlinkSync(p);
+ unlink(Path.dirname(path));
+ return;
+ }
- if (fs.readdirSync(p).length === 0)
- fs.rmdirSync(p);
+ !Fs.readdirSync(p).length && Fs.rmdirSync(p);
}
function main() {
- var filename = ph.join(ph.dirname(process.argv[1]), 'tpm.json');
- var dir = process.cwd();
- settings = { 'default': 'http://packages.totaljs.com/' + VERSION.replace(/\.+/g, '').replace(/\s+/g, '') + '/' };
+ var filename = Path.join(Path.dirname(process.argv[1]), 'tpm.json');
+ settings = { 'default': 'https://modules.totaljs.com/packages/' };
+
+ if (Fs.existsSync(filename))
+ settings = JSON.parse(Fs.readFileSync(filename).toString('utf8'));
+
+ if (process.argv.length === 2) {
+ display_help();
+ return;
+ }
- if (fs.existsSync(filename))
- settings = JSON.parse(fs.readFileSync(filename).toString('utf8'));
+ for (var i = 2; i < process.argv.length; i++) {
+ var arg = process.argv[i];
+ var cmd = arg.toLowerCase();
- if (process.argv.length === 2) {
- display_help();
- return;
- }
+ if (cmd === '-v' || cmd === '-version') {
+ log(VERSION);
+ return;
+ }
- for (var i = 2; i < process.argv.length; i++) {
- var arg = process.argv[i];
- var cmd = arg.toLowerCase();
+ if (cmd === 'install') {
+ $type = 1;
+ continue;
+ }
- if (cmd === '-v' || cmd === '-version') {
- log(VERSION);
- return;
- }
+ if (cmd === 'uninstall') {
+ $type = 4;
+ continue;
+ }
- if (cmd === 'install') {
- $type = 1;
- continue;
- }
+ if (cmd === 'create') {
+ $type = 2;
+ continue;
+ }
- if (cmd === 'uninstall') {
- $type = 4;
- continue;
- }
+ if (cmd === 'bundle') {
+ $type = 7;
+ continue;
+ }
- if (cmd === 'create') {
- $type = 2;
- continue;
- }
+ if (cmd === 'repository') {
+ $type = 3;
+ continue;
+ }
- if (cmd === 'repository') {
- $type = 3;
- continue;
- }
+ if (cmd === 'repositories') {
+ display_repositories();
+ return;
+ }
- if (cmd === 'repositories') {
- display_repositories();
- return;
- }
+ if (cmd === 'unpack') {
+ current_repository = '';
+ $type = 6;
+ continue;
+ }
- if (cmd === 'packages' || cmd === 'list') {
- $type = 5;
- continue;
- }
+ if (cmd === 'packages' || cmd === 'list') {
+ $type = 5;
+ continue;
+ }
- if (cmd === '-h' || cmd === '-help' || cmd === '--help' || cmd === 'help') {
- display_help();
- return;
- }
+ if (cmd === '-h' || cmd === '-help' || cmd === '--help' || cmd === 'help') {
+ display_help();
+ return;
+ }
- if (current_package.length > 0)
- current_repository = arg;
- else
- current_package = arg;
+ if (current_package.length > 0)
+ current_repository = arg;
+ else
+ current_package = arg;
- continue;
- }
+ continue;
+ }
- switch ($type) {
+ switch ($type) {
- case 1:
+ case 1:
- if (current_package.length !== 0) {
- install(settings[current_repository], current_package, null, true);
- return;
- }
+ if (current_package.length) {
+ install(settings[current_repository], current_package, null, true);
+ return;
+ }
- var filename = ph.join(process.cwd(), 'package.json');
- var packagejson = {};
+ var filename = Path.join(process.cwd(), 'package.json');
+ var packagejson = {};
- if (!fs.existsSync(filename))
- return;
+ if (!Fs.existsSync(filename))
+ return;
- packagejson = JSON.parse(fs.readFileSync(filename).toString('utf8'));
+ packagejson = JSON.parse(Fs.readFileSync(filename).toString('utf8'));
- if (!packagejson.tpm)
- return;
+ if (!packagejson.tpm)
+ return;
- var index = 0;
- var fn = function() {
- var key = Object.keys(packagejson.tpm)[index++];
+ var index = 0;
+ var fn = function() {
+ var key = Object.keys(packagejson.tpm)[index++];
- if (typeof(key) === 'undefined')
- return;
+ if (typeof(key) === 'undefined')
+ return;
- var url = packagejson.tpm[key];
+ var url = packagejson.tpm[key];
+ install(url, key, () => fn());
+ };
- install(url, key, function() {
- fn();
- });
- };
+ fn();
- fn();
+ break;
- break;
+ case 2:
+ create();
+ break;
- case 2:
- create();
- break;
+ case 7:
+ createbundle();
+ break;
- case 3:
- repository_add();
- break;
+ case 3:
+ repository_add();
+ break;
- case 4:
+ case 4:
- if (current_package.length !== 0) {
- uninstall(settings[current_repository], current_package, null, true);
- return;
- }
+ if (current_package.length) {
+ uninstall(settings[current_repository], current_package, null, true);
+ return;
+ }
- var filename = ph.join(process.cwd(), 'package.json');
- var packagejson = {};
+ var filename = Path.join(process.cwd(), 'package.json');
+ var packagejson = {};
- if (!fs.existsSync(filename))
- return;
+ if (!Fs.existsSync(filename))
+ return;
- packagejson = JSON.parse(fs.readFileSync(filename).toString('utf8'));
+ packagejson = JSON.parse(Fs.readFileSync(filename).toString('utf8'));
- if (!packagejson.tpm)
- return;
+ if (!packagejson.tpm)
+ return;
- var index = 0;
- var fn = function() {
- var key = Object.keys(packagejson.tpm)[index++];
+ var index = 0;
+ var fn = function() {
+ var key = Object.keys(packagejson.tpm)[index++];
- if (typeof(key) === 'undefined')
- return;
+ if (typeof(key) === 'undefined')
+ return;
- var url = packagejson.tpm[key];
+ var url = packagejson.tpm[key];
+ uninstall(url, key, () => fn(), true);
+ };
- uninstall(url, key, function() {
- fn();
- }, true);
- };
+ fn();
+ break;
- fn();
- break;
+ case 6:
+ unpack(current_package, current_repository);
+ break;
- default:
- display_help();
- break;
- }
+ default:
+ display_help();
+ break;
+ }
}
function log() {
- console.log.apply(console, arguments);
+ console.log.apply(console, arguments);
}
-main();
\ No newline at end of file
+main();
diff --git a/builders.js b/builders.js
index 6e2a73782..ef1c17963 100755
--- a/builders.js
+++ b/builders.js
@@ -1,1078 +1,6729 @@
+// Copyright 2012-2020 (c) Peter Širka
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
/**
* @module FrameworkBuilders
- * @author Peter Širka
- * @copyright Peter Širka 2012-2014
- * @version 1.5.0
+ * @version 3.4.4
*/
'use strict';
-var utils = require('./utils');
-
-var schema = {};
-var schemaValidation = {};
-var schemaValidator = {};
-var schemaDefaults = {};
-
-var UNDEFINED = 'undefined';
-var FUNCTION = 'function';
-var OBJECT = 'object';
-var STRING = 'string';
-var NUMBER = 'number';
-var BOOLEAN = 'boolean';
-var REQUIRED = 'The field "@" is required.';
-
-/*
- @onResource {Function} :: function(name, key) return {String}
-*/
-/**
- * ErrorBuilder
- * @class
- * @classdesc Object validation.
- * @param {ErrorBuilderOnResource} onResource Resource handler.
- * @property {Number} count Count of errors.
- */
-function ErrorBuilder(onResource) {
-
- this.errors = [];
- this.onResource = onResource;
- this.resourceName = '';
- this.resourcePrefix = '';
- this.count = 0;
- this.replacer = [];
- this.isPrepared = false;
-
- if (typeof(onResource) === UNDEFINED)
- this._resource();
+const REQUIRED = 'The field "@" is invalid.';
+const DEFAULT_SCHEMA = 'default';
+const SKIP = { $$schema: 1, $$async: 1, $$repository: 1, $$controller: 1, $$workflow: 1, $$parent: 1, $$keys: 1 };
+const REGEXP_CLEAN_EMAIL = /\s/g;
+const REGEXP_CLEAN_PHONE = /\s|\.|-|\(|\)/g;
+const REGEXP_NEWOPERATION = /^(async\s)?function(\s)?\([a-zA-Z$\s]+\)|^function anonymous\(\$|^\([a-zA-Z$\s]+\)|^function\*\(\$|^\([a-zA-Z$\s]+\)/;
+const hasOwnProperty = Object.prototype.hasOwnProperty;
+const Qs = require('querystring');
+const MSG_OBSOLETE_NEW = 'You used older declaration of this delegate and you must rewrite it. Read more in docs.';
+const BOOL = { true: 1, on: 1, '1': 1 };
+const REGEXP_FILTER = /[a-z0-9-_]+:(\s)?(\[)?(String|Number|Boolean|Date)(\])?/i;
+
+var schemas = {};
+var schemasall = {};
+var schemacache = {};
+var operations = {};
+var tasks = {};
+var transforms = { pagination: {}, error: {}, restbuilder: {} };
+var restbuilderupgrades = [];
+
+function SchemaBuilder(name) {
+ this.name = name;
+ this.collection = {};
}
-/**
- * @callback ErrorBuilderOnResource
- * @param {String} name Filename of resource.
- * @param {String} key Resource key.
- * @return {String}
- */
+const SchemaBuilderProto = SchemaBuilder.prototype;
-/**
- * UrlBuilder
- * @class
- * @classdesc CRUD parameters in URL.
- */
-function UrlBuilder() {
- this.builder = {};
+function SchemaOptions(error, model, options, callback, controller, name, schema) {
+ this.error = error;
+ this.value = this.model = model;
+ this.options = options || EMPTYOBJECT;
+ this.callback = this.next = callback;
+ this.controller = (controller instanceof SchemaOptions || controller instanceof OperationOptions) ? controller.controller : controller;
+ this.name = name;
+ this.schema = schema;
}
-/**
- * Pagination
- * @class
- * @param {Number} items Count of items.
- * @param {Number} page Current page.
- * @param {Number} max Max items on page.
- * @param {String} format URL format for links (next, back, go to). Example: ?page={0} --- {0} = page, {1} = items count, {2} = page count
- * @property {Number} isNext Is next page?
- * @property {Number} isPrev Is previous page?
- * @property {Number} count Page count.
- * @property {Boolean} visible Is more than one page?
- * @property {String} format Format URL. Example: ?page={0} --- {0} = page, {1} = items count, {2} = page count
- */
-function Pagination(items, page, max, format) {
- this.isNext = false;
- this.isPrev = false;
- this.items = items;
- this.count = 0;
- this.skip = 0;
- this.take = 0;
- this.page = 0;
- this.max = 0;
- this.visible = false;
- this.format = format || '?page={0}';
- this.refresh(items, page, max);
+function TaskBuilder($) {
+ var t = this;
+ t.value = {};
+ t.tasks = {};
+ if ($ instanceof SchemaOptions || $ instanceof OperationOptions) {
+ t.error = $.error;
+ t.controller = $.controller;
+ } else {
+ if ($ instanceof Controller || $ instanceof WebSocketClient)
+ t.controller = $;
+ else if ($ instanceof ErrorBuilder)
+ t.error = $;
+ }
}
+TaskBuilder.prototype = {
-/**
- * Create schema
- * @param {String} name chema name.
- * @param {Object} obj Schema definition.
- * @param {SchemaDefaults} defaults Schema defaults.
- * @param {SchemaValidator} validator Schema validator.
- * @return {Object}
- */
-exports.schema = function(name, obj, defaults, validator) {
-
- if (typeof(obj) === UNDEFINED)
- return schema[name] || null;
+ get user() {
+ return this.controller ? this.controller.user : null;
+ },
- if (typeof(defaults) === FUNCTION)
- schemaDefaults[name] = defaults;
+ get session() {
+ return this.controller ? this.controller.session : null;
+ },
- if (typeof(validator) === FUNCTION)
- schemaValidator[name] = validator;
+ get sessionid() {
+ return this.controller && this.controller ? this.controller.req.sessionid : null;
+ },
- schema[name] = obj;
- return obj;
-};
+ get language() {
+ return (this.controller ? this.controller.language : '') || '';
+ },
-/**
- * Default handler
- * @callback SchemaDefaults
- * @param {String} name Property name.
- * @param {Booelan} isDefault Is default (true) or prepare (false)?
- * @return {Object} Property value.
- */
+ get ip() {
+ return this.controller ? this.controller.ip : null;
+ },
-/**
- * Validator handler
- * @callback SchemaValidator
- * @param {String} name Property name.
- * @param {Object} value Property value.
- * @return {Boolean} Is valid?
- */
+ get id() {
+ return this.controller ? this.controller.id : null;
+ },
-/**
- * Check if property value is joined to other class
- * @private
- * @param {String} value Property value from Schema definition.
- * @return {Boolean}
- */
-exports.isJoin = function(value) {
- if (!value)
- return false;
- if (value[0] === '[')
- return true;
- return typeof(schema[value]) !== UNDEFINED;
-};
+ get req() {
+ return this.controller ? this.controller.req : null;
+ },
-/**
- * Create validation
- * @param {String} name Schema name.
- * @param {Function or Array} fn Validator Handler or Property names as array for validating.
- * @return {Function or Array}
- */
-exports.validation = function(name, fn) {
+ get res() {
+ return this.controller ? this.controller.res : null;
+ },
- if (typeof(fn) === FUNCTION) {
- schemaValidator[name] = fn;
- return true;
- }
+ get params() {
+ return this.controller ? this.controller.params : null;
+ },
- if (typeof(fn) === UNDEFINED)
- return schemaValidation[name] || [];
+ get files() {
+ return this.controller ? this.controller.files : null;
+ },
- schemaValidation[name] = fn;
- return fn;
-};
+ get body() {
+ return this.controller ? this.controller.body : null;
+ },
+ get query() {
+ return this.controller ? this.controller.query : null;
+ },
-/**
- * Validate model
- * @param {String} name Schema name.
- * @param {Object} model Object for validating.
- * @return {ErrorBuilder}
- */
-exports.validate = function(name, model) {
+ get model() {
+ return this.value;
+ },
- var fn = schemaValidator[name];
- var builder = new ErrorBuilder();
+ set model(val) {
+ this.value = val;
+ },
- if (typeof(fn) === UNDEFINED)
- return builder;
+ get headers() {
+ return this.controller && this.controller.req ? this.controller.req.headers : null;
+ },
- return utils.validate.call(this, model, Object.keys(schema[name]), fn, builder);
-};
+ get ua() {
+ return this.controller && this.controller.req ? this.controller.req.ua : null;
+ },
-/**
- * Create default object according to schema
- * @param {String} name Schema name.
- * @return {Object}
- */
-exports.create = function(name) {
- return exports.defaults(name);
+ get filter() {
+ var ctrl = this.controller;
+ if (ctrl && !ctrl.$filter)
+ ctrl.$filter = ctrl.$filterschema ? CONVERT(ctrl.query, ctrl.$filterschema) : ctrl.query;
+ return ctrl ? ctrl.$filter : EMPTYOBJECT;
+ }
};
-/**
- * Create default object according to schema
- * @param {String} name Schema name.
- * @return {Object}
- */
-exports.defaults = function(name) {
+const TaskBuilderProto = TaskBuilder.prototype;
- var obj = exports.schema(name);
+SchemaOptions.prototype = {
- if (obj === null)
- return null;
-
- var defaults = schemaDefaults[name];
- var item = utils.extend({}, obj, true);
- var properties = Object.keys(item);
- var length = properties.length;
-
- for (var i = 0; i < length; i++) {
-
- var property = properties[i];
- var value = item[property];
- var type = typeof(value);
-
- if (defaults) {
- var def = defaults(property, true);
- if (typeof(def) !== UNDEFINED) {
- item[property] = def;
- continue;
- }
- }
-
- if (type === FUNCTION) {
-
- if (value === Number) {
- item[property] = 0;
- continue;
- }
-
- if (value === Boolean) {
- item[property] = false;
- continue;
- }
+ get user() {
+ return this.controller ? this.controller.user : null;
+ },
- if (value === String) {
- item[property] = '';
- continue;
- }
+ get session() {
+ return this.controller ? this.controller.session : null;
+ },
- if (value === Date) {
- item[property] = new Date();
- continue;
- }
+ get keys() {
+ return this.model.$$keys;
+ },
- if (value === Object) {
- item[property] = {};
- continue;
- }
+ get parent() {
+ return this.model.$$parent;
+ },
- if (value === Array) {
- item[property] = [];
- continue;
- }
+ get repo() {
+ if (this.controller)
+ return this.controller.repository;
+ if (!this.model.$$repository)
+ this.model.$$repository = {};
+ return this.model.$$repository;
+ },
- item[property] = value();
- continue;
- }
+ get sessionid() {
+ return this.controller && this.controller ? this.controller.req.sessionid : null;
+ },
- if (type === NUMBER) {
- item[property] = 0;
- continue;
- }
+ get language() {
+ return (this.controller ? this.controller.language : '') || '';
+ },
- if (type === BOOLEAN) {
- item[property] = false;
- continue;
- }
+ get ip() {
+ return this.controller ? this.controller.ip : null;
+ },
- if (type === OBJECT) {
- item[property] = value instanceof Array ? [] : {};
- continue;
- }
+ get id() {
+ return this.controller ? this.controller.id : null;
+ },
- if (type !== STRING) {
- item[property] = null;
- continue;
- }
+ get req() {
+ return this.controller ? this.controller.req : null;
+ },
- var isArray = value[0] === '[';
+ get res() {
+ return this.controller ? this.controller.res : null;
+ },
- if (isArray)
- value = value.substring(1, value.length - 1);
+ get params() {
+ return this.controller ? this.controller.params : null;
+ },
- if (isArray) {
- item[property] = [];
- continue;
- }
+ get files() {
+ return this.controller ? this.controller.files : null;
+ },
- var lower = value.toLowerCase();
+ get body() {
+ return this.controller ? this.controller.body : null;
+ },
- if (lower.contains([STRING, 'text', 'varchar', 'nvarchar', 'binary', 'data', 'base64'])) {
- item[property] = '';
- continue;
- }
+ get query() {
+ return this.controller ? this.controller.query : null;
+ },
- if (lower.contains(['int', NUMBER, 'decimal', 'byte', 'float', 'double'])) {
- item[property] = 0;
- continue;
- }
+ get headers() {
+ return this.controller && this.controller.req ? this.controller.req.headers : null;
+ },
- if (lower.contains('bool')) {
- item[property] = false;
- continue;
- }
-
- if (lower.contains(['date', 'time'])) {
- item[property] = new Date();
- continue;
- }
+ get ua() {
+ return this.controller && this.controller.req ? this.controller.req.ua : null;
+ },
- if (lower.contains(['object'])) {
- item[property] = {};
- continue;
- }
-
- if (lower.contains(['array'])) {
- item[property] = [];
- continue;
- }
+ get filter() {
+ var ctrl = this.controller;
+ if (ctrl && !ctrl.$filter)
+ ctrl.$filter = ctrl.$filterschema ? CONVERT(ctrl.query, ctrl.$filterschema) : ctrl.query;
+ return ctrl ? ctrl.$filter : EMPTYOBJECT;
+ }
+};
- if (lower.contains(['binary', 'data', 'base64'])) {
- item[property] = null;
- continue;
- }
-
- item[property] = exports.defaults(value);
- }
+var SchemaOptionsProto = SchemaOptions.prototype;
- return item;
+SchemaOptionsProto.cancel = function() {
+ var self = this;
+ self.callback = self.next = null;
+ self.error = null;
+ self.controller = null;
+ self.model = null;
+ self.options = null;
+ return self;
};
-/**
- * Prepare object according to schema
- * @param {String} name Schema name.
- * @param {Object} model Object to prepare.
- * @return {Object} Prepared object.
- */
-exports.prepare = function(name, model) {
-
- var obj = exports.schema(name);
+SchemaOptionsProto.extend = function(data) {
+ var self = this;
+ var ext = self.schema.extensions[self.name];
+ if (ext) {
+ for (var i = 0; i < ext.length; i++)
+ ext[i](self, data);
+ return true;
+ }
+};
- if (obj === null)
- return null;
+SchemaOptionsProto.redirect = function(url) {
+ this.callback(new F.callback_redirect(url));
+ return this;
+};
- if (model === null)
- return exports.defaults(name);
+SchemaOptionsProto.clean = function() {
+ return this.model.$clean();
+};
- var tmp;
- var item = utils.extend({}, obj, true);
- var properties = Object.keys(item);
- var defaults = schemaDefaults[name];
- var length = properties.length;
+SchemaOptionsProto.$async = function(callback, index) {
+ return this.model.$async(callback, index);
+};
- for (var i = 0; i < length; i++) {
+SchemaOptionsProto.$workflow = function(name, helper, callback, async) {
+ return this.model.$workflow(name, helper, callback, async);
+};
- var property = properties[i];
- var val = model[property];
+SchemaOptionsProto.$transform = function(name, helper, callback, async) {
+ return this.model.$transform(name, helper, callback, async);
+};
- if (typeof(val) === UNDEFINED && defaults)
- val = defaults(property, false);
+SchemaOptionsProto.$operation = function(name, helper, callback, async) {
+ return this.model.$operation(name, helper, callback, async);
+};
- if (typeof(val) === UNDEFINED)
- val = '';
+SchemaOptionsProto.$hook = function(name, helper, callback, async) {
+ return this.model.$hook(name, helper, callback, async);
+};
- var value = item[property];
- var type = typeof(value);
- var typeval = typeof(val);
+SchemaOptionsProto.$save = function(helper, callback, async) {
+ return this.model.$save(helper, callback, async);
+};
- if (typeval === FUNCTION)
- val = val();
+SchemaOptionsProto.$insert = function(helper, callback, async) {
+ return this.model.$insert(helper, callback, async);
+};
- if (type === FUNCTION) {
+SchemaOptionsProto.$update = function(helper, callback, async) {
+ return this.model.$update(helper, callback, async);
+};
- if (value === Number) {
- item[property] = utils.parseFloat(val);
- continue;
- }
+SchemaOptionsProto.$patch = function(helper, callback, async) {
+ return this.model.$patch(helper, callback, async);
+};
- if (value === Boolean) {
- tmp = val.toString();
- item[property] = tmp === 'true' || tmp === '1';
- continue;
- }
-
- if (value === String) {
- item[property] = val.toString();
- continue;
- }
-
- if (value === Date) {
-
- tmp = null;
-
- switch (typeval) {
- case OBJECT:
- if (utils.isDate(val))
- tmp = val;
- else
- tmp = null;
- break;
-
- case NUMBER:
- tmp = new Date(val);
- break;
-
- case STRING:
- if (val === '')
- tmp = null;
- else
- tmp = val.parseDate();
- break;
- }
-
- if (tmp !== null && typeof(tmp) === OBJECT && tmp.toString() === 'Invalid Date')
- tmp = null;
-
- item[property] = tmp || (defaults ? isUndefined(defaults(property), null) : null);
- continue;
- }
-
- item[property] = isUndefined(defaults(property), null);
- continue;
- }
-
- if (type === OBJECT) {
- item[property] = typeval === OBJECT ? val : null;
- continue;
- }
-
- if (type === NUMBER) {
- item[property] = utils.parseFloat(val);
- continue;
- }
-
- if (val === null || typeval === UNDEFINED)
- tmp = '';
- else
- tmp = val.toString();
-
- if (type === BOOLEAN) {
- item[property] = tmp === 'true' || tmp === '1';
- continue;
- }
-
- if (type !== STRING) {
- item[property] = tmp;
- continue;
- }
-
- var isArray = value[0] === '[' || value === 'array';
-
- if (isArray) {
-
- if (value[0] === '[')
- value = value.substring(1, value.length - 1);
- else
- value = null;
-
- if (!(val instanceof Array)) {
- item[property] = (defaults ? isUndefined(defaults(property, false), []) : []);
- continue;
- }
-
- item[property] = [];
- var sublength = val.length;
- for (var j = 0; j < sublength; j++) {
-
- if (value === null) {
- item[property].push(model[property][j]);
- continue;
- }
-
- var tmp = model[property][j];
-
- switch (value) {
- case 'string':
- case 'varchar':
- case 'text':
- item[property].push((tmp || '').toString());
- break;
- case 'bool':
- case 'boolean':
- tmp = (tmp || '').toString().toLowerCase();
- item[property].push(tmp === 'true' || tmp === '1');
- break;
- case 'int':
- case 'integer':
- item[property].push(utils.parseInt(tmp));
- break;
- case 'number':
- item[property].push(utils.parseFloat(tmp));
- break;
- default:
- item[property][j] = exports.prepare(value, model[property][j]);
- break;
- }
- }
-
- continue;
- }
-
- var lower = value.toLowerCase();
-
- if (lower.contains([STRING, 'text', 'varchar', 'nvarchar'])) {
-
- var beg = lower.indexOf('(');
- if (beg === -1) {
- item[property] = tmp;
- continue;
- }
-
- var size = lower.substring(beg + 1, lower.length - 1).parseInt();
- item[property] = tmp.max(size, '...');
- continue;
- }
-
- if (lower.contains(['int', 'byte'])) {
- item[property] = utils.parseInt(val);
- continue;
- }
-
- if (lower.contains(['decimal', NUMBER, 'float', 'double'])) {
- item[property] = utils.parseFloat(val);
- continue;
- }
-
- if (lower.contains('bool', BOOLEAN)) {
- item[property] = tmp === 'true' || tmp === '1';
- continue;
- }
-
- if (lower.contains(['date', 'time'])) {
-
- if (typeval === 'date') {
- item[property] = val;
- continue;
- }
-
- if (typeval === STRING) {
- item[property] = val.parseDate();
- continue;
- }
-
- if (typeval === NUMBER) {
- item[property] = new Date(val);
- continue;
- }
-
- item[property] = isUndefined(defaults(property));
- continue;
- }
-
- item[property] = exports.prepare(value, model[property]);
- }
-
- return item;
+SchemaOptionsProto.$query = function(helper, callback, async) {
+ return this.model.$query(helper, callback, async);
};
-function isUndefined(value, def) {
- if (typeof(value) === UNDEFINED)
- return def;
- return value;
-}
+SchemaOptionsProto.$delete = SchemaOptionsProto.$remove = function(helper, callback, async) {
+ return this.model.$remove(helper, callback, async);
+};
-// ======================================================
-// PROTOTYPES
-// ======================================================
+SchemaOptionsProto.$get = SchemaOptionsProto.$read = function(helper, callback, async) {
+ return this.model.$get(helper, callback, async);
+};
-/**
- * Resource setting
- * @param {String} name Resource name.
- * @param {String} prefix Resource prefix.
- * @return {ErrorBuilder}
- */
-ErrorBuilder.prototype.resource = function(name, prefix) {
- var self = this;
- self.resourceName = name;
- self.resourcePrefix = prefix || '';
- return self._resource();
+SchemaOptionsProto.push = function(type, name, helper, first) {
+ return this.model.$push(type, name, helper, first);
};
-/**
- * Internal: Resource wrapper
- * @private
- * @return {ErrorBuilder}
- */
-ErrorBuilder.prototype._resource = function() {
- var self = this;
- self.onResource = function(name) {
- var self = this;
- return framework.resource(self.resourceName, self.resourcePrefix + name);
- };
- return self;
+SchemaOptionsProto.next = function(type, name, helper) {
+ return this.model.$next(type, name, helper);
};
-/**
- * Add an error
- * @param {String} name Property name.
- * @param {String} error Error message.
- * @param {String} path Current path (in object).
- * @return {ErrorBuilder}
- */
-ErrorBuilder.prototype.add = function(name, error, path) {
- var self = this;
- self.isPrepared = false;
+SchemaOptionsProto.output = function() {
+ return this.model.$output();
+};
- if (name instanceof ErrorBuilder) {
+SchemaOptionsProto.stop = function() {
+ return this.model.$stop();
+};
- name.errors.forEach(function(o) {
- self.errors.push(o);
- });
+SchemaOptionsProto.response = function(index) {
+ return this.model.$response(index);
+};
- self.count = self.errors.length;
- return self;
- }
+SchemaOptionsProto.DB = function() {
+ return F.database(this.error);
+};
- self.errors.push({
- name: name,
- error: error || '@',
- path: path
- });
- self.count = self.errors.length;
- return self;
+SchemaOptionsProto.successful = function(callback) {
+ var self = this;
+ return function(err, a, b, c) {
+ if (err)
+ self.invalid(err);
+ else
+ callback.call(self, a, b, c);
+ };
};
-/**
- * Remove error
- * @param {String} name Property name.
- * @return {ErrorBuilder}
- */
-ErrorBuilder.prototype.remove = function(name) {
- var self = this;
+SchemaOptionsProto.success = function(a, b) {
- self.errors = self.errors.remove(function(o) {
- return o.name === name;
- });
+ if (a && b === undefined && typeof(a) !== 'boolean') {
+ b = a;
+ a = true;
+ }
- self.count = self.errors.length;
- return self;
+ this.callback(SUCCESS(a === undefined ? true : a, b));
+ return this;
};
-/**
- * Has error?
- * @param {String} name Property name (optional).
- * @return {Boolean}
- */
-ErrorBuilder.prototype.hasError = function(name) {
- var self = this;
+SchemaOptionsProto.done = function(arg) {
+ var self = this;
+ return function(err, response) {
+ if (err) {
- if (name) {
- return self.errors.find(function(o) {
- return o.name === name;
- }) !== null;
- }
+ if (self.error !== err)
+ self.error.push(err);
- return self.errors.length > 0;
+ self.callback();
+ } else if (arg)
+ self.callback(SUCCESS(err == null, arg === true ? response : arg));
+ else
+ self.callback(SUCCESS(err == null));
+ };
};
-/**
- * Read an error
- * @param {String} name Property name.
- * @return {String}
- */
-ErrorBuilder.prototype.read = function(name) {
-
- var self = this;
-
- if (!self.isPrepared)
- self.prepare();
+SchemaOptionsProto.invalid = function(name, error, path, index) {
+ var self = this;
- var error = self.errors.find(function(o) {
- return o.name === name;
- });
+ if (arguments.length) {
+ self.error.push(name, error, path, index);
+ self.callback();
+ return self;
+ }
- if (error !== null)
- return error.error;
+ return function(err) {
+ self.error.push(err);
+ self.callback();
+ };
+};
- return null;
+SchemaOptionsProto.repository = function(name, value) {
+ return this.model && this.model.$repository ? this.model.$repository(name, value) : value;
};
-/**
- * Clear error collection
- * @return {ErrorBuilder}
- */
-ErrorBuilder.prototype.clear = function() {
- var self = this;
- self.errors = [];
- self.count = 0;
- return self;
+SchemaOptionsProto.noop = function() {
+ this.callback(NoOp);
+ return this;
};
/**
- * Replace text in message
- * @param {String} search Text to search.
- * @param {String} newvalue Text to replace.
- * @return {ErrorBuilder}
+ *
+ * Get a schema
+ * @param {String} name
+ * @return {Object}
*/
-ErrorBuilder.prototype.replace = function(search, newvalue) {
- var self = this;
- self.isPrepared = false;
- self.replacer[search] = newvalue;
- return self;
+SchemaBuilderProto.get = function(name) {
+ return this.collection[name];
};
-/*
- Serialize ErrorBuilder to JSON format
- return {String}
-*/
/**
- * Serialize ErrorBuilder to JSON
- * @param {Boolean} beautify Beautify JSON.
- * @return {String}
+ * Create a new schema
+ * @alias
+ * @return {SchemaBuilderEntity}
*/
-ErrorBuilder.prototype.json = function(beautify) {
- if (beautify)
- return JSON.stringify(this.prepare().errors, null, '\t');
- return JSON.stringify(this.prepare().errors);
+SchemaBuilderProto.create = function(name) {
+ this.collection[name] = new SchemaBuilderEntity(this, name);
+ return this.collection[name];
};
/**
- * Serialize ErrorBuilder to JSON
- * @param {Boolean} beautify Beautify JSON.
- * @return {String}
+ * Removes an existing schema or group of schemas
+ * @param {String} name Schema name, optional.
+ * @return {SchemaBuilder}
*/
-ErrorBuilder.prototype.JSON = function(beautify) {
- if (beautify)
- return JSON.stringify(this.prepare().errors, null, '\t');
- return JSON.stringify(this.prepare().errors);
+SchemaBuilderProto.remove = function(name) {
+ if (name) {
+ var schema = this.collection[name];
+ schema && schema.destroy();
+ schema = null;
+ delete schemasall[name.toLowerCase()];
+ delete this.collection[name];
+ } else {
+ exports.remove(this.name);
+ this.collection = null;
+ }
};
-/**
- * Internal: Prepare error messages with onResource()
- * @private
- * @return {ErrorBuidler}
- */
-ErrorBuilder.prototype._prepare = function() {
- var self = this;
+SchemaBuilderProto.destroy = function(name) {
+ return this.remove(name);
+};
- if (self.onResource === null)
- return self;
+function SchemaBuilderEntity(parent, name) {
+ this.parent = parent;
+ this.name = name;
+ this.primary;
+ this.trim = true;
+ this.schema = {};
+ this.meta = {};
+ this.properties = [];
+ this.inherits = [];
+ this.verifications = null;
+ this.resourcePrefix;
+ this.extensions = {};
+ this.resourceName;
+ this.transforms;
+ this.workflows;
+ this.hooks;
+ this.operations;
+ this.constants;
+ this.onPrepare;
+ this.$onPrepare; // Array of functions for inherits
+ this.onDefault;
+ this.$onDefault; // Array of functions for inherits
+ this.onValidate = F.onValidate;
+ this.onSave;
+ this.onInsert;
+ this.onUpdate;
+ this.onGet;
+ this.onRemove;
+ this.onQuery;
+ this.onError;
+ this.gcache = {};
+ this.dependencies;
+ this.fields;
+ this.fields_allow;
+ this.CurrentSchemaInstance = function(){};
+ this.CurrentSchemaInstance.prototype = new SchemaInstance();
+ this.CurrentSchemaInstance.prototype.$$schema = this;
+}
- var errors = self.errors;
- var length = errors.length;
+const SchemaBuilderEntityProto = SchemaBuilderEntity.prototype;
- for (var i = 0; i < length; i++) {
+SchemaBuilderEntityProto.allow = function() {
+ var self = this;
- var o = errors[i];
+ if (!self.fields_allow)
+ self.fields_allow = [];
- if (o.error[0] !== '@')
- continue;
+ var arr = arguments;
- if (o.error.length === 1)
- o.error = self.onResource(o.name);
- else
- o.error = self.onResource(o.error.substring(1));
+ if (arr.length === 1)
+ arr = arr[0].split(',').trim();
- if (typeof(o.error) === UNDEFINED)
- o.error = REQUIRED.replace('@', o.name);
- }
+ for (var i = 0, length = arr.length; i < length; i++) {
+ if (arr[i] instanceof Array)
+ arr[i].forEach(item => self.fields_allow.push(item));
+ else
+ self.fields_allow.push(arr[i]);
+ }
+ return self;
+};
- return self;
+SchemaBuilderEntityProto.before = function(name, fn) {
+ var self = this;
+ if (!self.preparation)
+ self.preparation = {};
+ self.preparation[name] = fn;
+ return self;
};
-/**
- * To string
- * @return {String}
- */
-ErrorBuilder.prototype.toString = function() {
+SchemaBuilderEntityProto.required = function(name, fn) {
+
+ var self = this;
- var self = this;
+ if (!name)
+ return self;
- if (!self.isPrepared)
- self.prepare();
+ if (name.indexOf(',') !== -1) {
+ var arr = name.split(',');
+ for (var i = 0; i < arr.length; i++)
+ self.required(arr[i].trim(), fn);
+ return self;
+ }
- var errors = self.errors;
- var length = errors.length;
- var builder = [];
+ if (fn === false) {
+ self.properties && (self.properties = self.properties.remove(name));
+ return self;
+ }
- for (var i = 0; i < length; i++)
- builder.push(errors[i].error || errors[i].name);
+ var prop = self.schema[name];
+ if (!prop)
+ throw new Error('Property "{0}" doesn\'t exist in "{1}" schema.'.format(name, self.name));
- return builder.join('\n');
+ prop.can = typeof(fn) === 'function' ? fn : null;
+ if (!prop.required) {
+ prop.required = true;
+ if (self.properties) {
+ self.properties.indexOf(name) === -1 && self.properties.push(name);
+ } else
+ self.properties = [name];
+ }
+
+ return self;
};
-/**
- * Internal: Prepare error messages with onResource()
- * @private
- * @return {ErrorBuidler}
- */
-ErrorBuilder.prototype._prepareReplace = function() {
+SchemaBuilderEntityProto.clear = function() {
+ var self = this;
+
+ self.schema = {};
+ self.properties = [];
+ self.fields = [];
+ self.verifications = null;
- var self = this;
- var errors = self.errors;
- var lengthBuilder = errors.length;
- var keys = Object.keys(self.replacer);
- var lengthKeys = keys.length;
+ if (self.preparation)
+ self.preparation = null;
- if (lengthBuilder === 0 || lengthKeys === 0)
- return self;
+ if (self.dependencies)
+ self.dependencies = null;
- for (var i = 0; i < lengthBuilder; i++) {
- var o = errors[i];
- for (var j = 0; j < lengthKeys; j++) {
- var key = keys[j];
- o.error = o.error.replace(key, self.replacer[key]);
- }
- }
+ if (self.fields_allow)
+ self.fields_allow = null;
- return self;
+ return self;
};
-/**
- * Internal: Prepare error messages with onResource()
- * @private
- * @return {ErrorBuidler}
- */
-ErrorBuilder.prototype.prepare = function() {
- var self = this;
+SchemaBuilderEntityProto.middleware = function(fn) {
+ var self = this;
+ if (!self.middlewares)
+ self.middlewares = [];
+ self.middlewares.push(fn);
+ return self;
+};
- if (self.isPrepared)
- return self;
+function runmiddleware(opt, schema, callback, index, processor) {
- self._prepare()._prepareReplace();
- self.isPrepared = true;
+ if (!index)
+ index = 0;
- return self;
-};
+ var fn = schema.middlewares[index];
+
+ if (fn == null) {
+ callback.call(schema, opt);
+ return;
+ }
+
+ if (processor) {
+ fn(opt, processor);
+ return;
+ }
+
+ processor = function(stop) {
+ if (!stop)
+ runmiddleware(opt, schema, callback, index + 1, processor);
+ };
+
+ fn(opt, processor);
+}
/**
- * Refresh pagination
- * @param {Number} items Count of items.
- * @param {Number} page Current page.
- * @param {Number} max Max items on page.
- * @return {Pagination}
+ * Define type in schema
+ * @param {String|String[]} name
+ * @param {Object/String} type
+ * @param {Boolean} [required=false] Is required? Default: false.
+ * @param {Number|String} [custom] Custom tag for search.
+ * @return {SchemaBuilder}
*/
-Pagination.prototype.refresh = function(items, page, max) {
- var self = this;
+SchemaBuilderEntityProto.define = function(name, type, required, custom) {
+
+ if (name instanceof Array) {
+ for (var i = 0, length = name.length; i < length; i++)
+ this.define(name[i], type, required, custom);
+ return this;
+ }
+
+ var rt = typeof(required);
+
+ if (required !== undefined && rt === 'string') {
+ custom = required;
+ required = false;
+ }
+
+ if (type == null) {
+ // remove
+ delete this.schema[name];
+ this.properties = this.properties.remove(name);
+ if (this.dependencies)
+ this.dependencies = this.dependencies.remove(name);
+ this.fields = Object.keys(this.schema);
+ return this;
+ }
+
+ if (type instanceof SchemaBuilderEntity)
+ type = type.name;
+
+ var a = this.schema[name] = this.$parse(name, type, required, custom);
+ switch (this.schema[name].type) {
+ case 7:
+ if (this.dependencies)
+ this.dependencies.push(name);
+ else
+ this.dependencies = [name];
+ break;
+ }
+
+ this.fields = Object.keys(this.schema);
+
+ if (a.type === 7)
+ required = true;
+
+ if (required)
+ this.properties.indexOf(name) === -1 && this.properties.push(name);
+ else
+ this.properties = this.properties.remove(name);
+
+ return function(val) {
+ a.def = val;
+ return this;
+ };
+};
+
+SchemaBuilderEntityProto.verify = function(name, fn, cache) {
+ var self = this;
- self.count = Math.floor(items / max) + (items % max > 0 ? 1 : 0);
- self.page = page - 1;
+ if (!self.verifications)
+ self.verifications = [];
- if (self.page < 0)
- self.page = 0;
+ var cachekey;
- self.items = items;
- self.skip = self.page * max;
- self.take = max;
- self.max = max;
- self.isPrev = self.page > 0;
- self.isNext = self.page < self.count - 1;
- self.visible = self.count > 1;
- self.page++;
+ if (cache)
+ cachekey = self.name + '_verify_' + name + '_';
- return self;
+ self.verifications.push({ name: name, fn: fn, cache: cache, cachekey: cachekey });
+ return self;
};
-/**
- * Get previous page
- * @param {String} format Custom format (optional).
- * @return {Object} Example: { url: String, page: Number, selected: Boolean }
- */
-Pagination.prototype.prev = function(format) {
- var self = this;
- var page = 0;
+SchemaBuilderEntityProto.inherit = function(group, name) {
- format = format || self.format;
+ if (!name) {
+ name = group;
+ group = DEFAULT_SCHEMA;
+ }
- if (self.isPrev)
- page = self.page - 1;
- else
- page = self.count;
+ var self = this;
- return {
- url: format.format(page, self.items, self.count),
- page: page,
- selected: false
- };
-};
+ exports.getschema(group, name, function(err, schema) {
-/**
- * Get next page
- * @param {String} format Custom format (optional).
- * @return {Object} Example: { url: String, page: Number, selected: Boolean }
- */
-Pagination.prototype.next = function(format) {
- var self = this;
- var page = 0;
+ if (err)
+ throw err;
- format = format || self.format;
+ self.primary = schema.primary;
+ self.inherits.push(schema);
- if (self.isNext)
- page = self.page + 1;
- else
- page = 1;
+ if (!self.resourceName && schema.resourceName)
+ self.resourceName = schema.resourceName;
- return {
- url: format.format(page, self.items, self.count),
- page: page,
- selected: false
- };
-};
+ if (!self.resourcePrefix && schema.resourcePrefix)
+ self.resourcePrefix = schema.resourcePrefix;
-/**
- * Create pagination
- * @param {Number} max Max pages in collection (optional).
- * @param {String} format Custom format (optional).
- * @return {Object Array} Example: [{ url: String, page: Number, selected: Boolean }]
- */
-Pagination.prototype.render = function(max, format) {
+ copy_inherit(self, 'schema', schema.schema);
+ copy_inherit(self, 'meta', schema.meta);
+ copy_inherit(self, 'transforms', schema.transforms);
+ copy_inherit(self, 'workflows', schema.workflows);
+ copy_inherit(self, 'hooks', schema.hooks);
+ copy_inherit(self, 'operations', schema.operations);
+ copy_inherit(self, 'constants', schema.constants);
- var self = this;
- var builder = [];
- format = format || self.format;
+ if (schema.middlewares) {
+ self.middlewares = [];
+ for (var i = 0; i < schema.middlewares.length; i++)
+ self.middlewares.push(schema.middlewares[i]);
+ }
- if (typeof(max) === STRING) {
- var tmp = format;
- format = max;
- max = format;
- }
+ if (schema.verifications) {
+ self.verifications = [];
+ for (var i = 0; i < schema.verifications.length; i++)
+ self.verifications.push(schema.verifications[i]);
+ }
- if (typeof(max) === UNDEFINED || max === null) {
- for (var i = 1; i < self.count + 1; i++)
- builder.push({
- url: format.format(i, self.items, self.count),
- page: i,
- selected: i === self.page
- });
- return builder;
- }
+ schema.properties.forEach(function(item) {
+ if (self.properties.indexOf(item) === -1)
+ self.properties.push(item);
+ });
- var half = Math.floor(max / 2);
- var pages = self.count;
+ if (schema.preparation) {
+ self.preparation = {};
+ Object.keys(schema.preparation).forEach(function(key) {
+ self.preparation[key] = schema.preparation[key];
+ });
+ }
- var pageFrom = self.page - half;
- var pageTo = self.page + half;
- var plus = 0;
+ if (schema.onPrepare) {
+ if (!self.$onPrepare)
+ self.$onPrepare = [];
+ self.$onPrepare.push(schema.onPrepare);
+ }
- if (pageFrom <= 0) {
- plus = Math.abs(pageFrom);
- pageFrom = 1;
- pageTo += plus;
- }
+ if (schema.onDefault) {
+ if (!self.$onDefault)
+ self.$onDefault = [];
+ self.$onDefault.push(schema.onDefault);
+ }
- if (pageTo >= pages) {
- pageTo = pages;
- pageFrom = pages - max;
- }
+ if (self.onValidate === F.onValidate && self.onValidate !== schema.onValidate)
+ self.onValidate = schema.onValidate;
- if (pageFrom < 0)
- pageFrom = 1;
+ if (!self.onSave && schema.onSave)
+ self.onSave = schema.onSave;
- for (var i = pageFrom; i < pageTo + 1; i++)
- builder.push({
- url: format.format(i, self.items, self.count),
- page: i,
- selected: i === self.page
- });
+ if (!self.onInsert && schema.onInsert)
+ self.onInsert = schema.onInsert;
- return builder;
-};
+ if (!self.onUpdate && schema.onUpdate)
+ self.onUpdate = schema.onUpdate;
-/**
- * Add parameter
- * @param {String} name
- * @param {Object} value
- * return {UrlBuilder}
- */
-UrlBuilder.prototype.add = function(name, value) {
- var self = this;
+ if (!self.onGet && schema.onGet)
+ self.onGet = schema.onGet;
+
+ if (!self.onRemove && schema.onRemove)
+ self.onRemove = schema.onRemove;
- if (typeof(name) === 'object') {
- Object.keys(name).forEach(function(o) {
- self.builder[o] = name[o];
- });
- return;
- }
+ if (!self.onQuery && schema.onQuery)
+ self.onQuery = schema.onQuery;
- self.builder[name] = value;
- return self;
+ if (!self.onError && schema.onError)
+ self.onError = schema.onError;
+
+ self.fields = Object.keys(self.schema);
+ });
+
+ return self;
};
+function copy_inherit(schema, field, value) {
+
+ if (!value)
+ return;
+
+ if (value && !schema[field]) {
+ schema[field] = framework_utils.clone(value);
+ return;
+ }
+
+ Object.keys(value).forEach(function(key) {
+ if (schema[field][key] === undefined)
+ schema[field][key] = framework_utils.clone(value[key]);
+ });
+}
+
/**
- * Remove parameter
- * @param {String} name
- * @return {UrlBuilder}
+ * Set primary key
+ * @param {String} name
*/
-UrlBuilder.prototype.remove = function(name) {
- var self = this;
- delete self.builder[name];
- return self;
+SchemaBuilderEntityProto.setPrimary = function(name) {
+ this.primary = name;
+ return this;
};
/**
- * Read value
- * @param {String} name
- * @return {Object}
+ * Filters current names of the schema via custom attribute
+ * @param {Number/String} custom
+ * @param {Object} model Optional
+ * @param {Boolean} reverse Reverse results.
+ * @return {Array|Object} Returns Array (with property names) if the model is undefined otherwise returns Object Name/Value.
*/
-UrlBuilder.prototype.read = function(name) {
- return this.builder[name] || null;
+SchemaBuilderEntityProto.filter = function(custom, model, reverse) {
+
+ if (typeof(model) === 'boolean') {
+ var tmp = reverse;
+ reverse = model;
+ model = tmp;
+ }
+
+ var output = model === undefined ? [] : {};
+ var type = typeof(custom);
+ var isSearch = type === 'string' ? custom[0] === '*' || custom[0] === '%' : false;
+ var isReg = false;
+
+ if (isSearch)
+ custom = custom.substring(1);
+ else if (type === 'object')
+ isReg = framework_utils.isRegExp(custom);
+
+ for (var prop in this.schema) {
+
+ var schema = this.schema[prop];
+ if (!schema)
+ continue;
+
+ var tv = typeof(schema.custom);
+
+ if (isSearch) {
+ if (tv === 'string') {
+ if (schema.custom.indexOf(custom) === -1) {
+ if (!reverse)
+ continue;
+ } else if (reverse)
+ continue;
+ } else
+ continue;
+ } else if (isReg) {
+ if (tv === 'string') {
+ if (!custom.test(schema.current)) {
+ if (!reverse)
+ continue;
+ } else if (reverse)
+ continue;
+ } else
+ continue;
+ } else if (schema.custom !== custom) {
+ if (!reverse)
+ continue;
+ } else if (reverse)
+ continue;
+
+ if (model === undefined)
+ output.push(prop);
+ else
+ output[prop] = model[prop];
+ }
+
+ return output;
};
-/**
- * Clear parameter collection
- * @return {UrlBuilder}
- */
-UrlBuilder.prototype.clear = function() {
- var self = this;
- self.builder = {};
- return self;
+function parseLength(lower, result) {
+ result.raw = 'string';
+ var beg = lower.indexOf('(');
+ if (beg !== -1) {
+ result.length = lower.substring(beg + 1, lower.length - 1).parseInt();
+ result.raw = lower.substring(0, beg);
+ }
+ return result;
+}
+
+SchemaBuilderEntityProto.$parse = function(name, value, required, custom) {
+
+ var type = typeof(value);
+ var result = {};
+
+ result.raw = value;
+ result.type = 0;
+ result.length = 0;
+ result.required = required ? true : false;
+ result.validate = typeof(required) === 'function' ? required : null;
+ result.can = null;
+ result.isArray = false;
+ result.custom = custom || '';
+
+ // 0 = undefined
+ // 1 = integer
+ // 2 = float
+ // 3 = string
+ // 4 = boolean
+ // 5 = date
+ // 6 = object
+ // 7 = custom object
+ // 8 = enum
+ // 9 = keyvalue
+ // 10 = custom object type
+ // 11 = number2
+ // 12 = object as filter
+
+ if (value === null)
+ return result;
+
+ if (value === '[]') {
+ result.isArray = true;
+ return result;
+ }
+
+ if (type === 'function') {
+
+ if (value === UID) {
+ result.type = 3;
+ result.length = 20;
+ result.raw = 'string';
+ result.subtype = 'uid';
+ return result;
+ }
+
+ if (value === Number) {
+ result.type = 2;
+ return result;
+ }
+
+ if (value === String) {
+ result.type = 3;
+ return result;
+ }
+
+ if (value === Boolean) {
+ result.type = 4;
+ return result;
+ }
+
+ if (value === Date) {
+ result.type = 5;
+ return result;
+ }
+
+ if (value === Array) {
+ result.isArray = true;
+ return result;
+ }
+
+ if (value === Object) {
+ result.type = 6;
+ return result;
+ }
+
+ if (value instanceof SchemaBuilderEntity)
+ result.type = 7;
+ else {
+ result.type = 10;
+ if (!this.asyncfields)
+ this.asyncfields = [];
+ this.asyncfields.push(name);
+ }
+
+ return result;
+ }
+
+ if (type === 'object') {
+ if (value instanceof Array) {
+ result.type = 8; // enum
+ result.subtype = typeof(value[0]);
+ } else
+ result.type = 9; // keyvalue
+ return result;
+ }
+
+ if (value[0] === '[') {
+ value = value.substring(1, value.length - 1);
+ result.isArray = true;
+ result.raw = value;
+ }
+
+ var lower = value.toLowerCase();
+
+ if (lower === 'object') {
+ result.type = 6;
+ return result;
+ }
+
+ if (lower === 'array') {
+ result.isArray = true;
+ return result;
+ }
+
+ if (value.indexOf(',') !== -1) {
+ // multiple
+ result.type = 12;
+ return result;
+ }
+
+ if ((/^(string|text)+(\(\d+\))?$/).test(lower)) {
+ result.type = 3;
+ return parseLength(lower, result);
+ }
+
+ if ((/^(capitalize2)+(\(\d+\))?$/).test(lower)) {
+ result.type = 3;
+ result.subtype = 'capitalize2';
+ return parseLength(lower, result);
+ }
+
+ if ((/^(capitalize|camelcase|camelize)+(\(\d+\))?$/).test(lower)) {
+ result.type = 3;
+ result.subtype = 'capitalize';
+ return parseLength(lower, result);
+ }
+
+ if ((/^(lower|lowercase)+(\(\d+\))?$/).test(lower)) {
+ result.subtype = 'lowercase';
+ result.type = 3;
+ return parseLength(lower, result);
+ }
+
+ if (lower.indexOf('base64') !== -1) {
+ result.type = 3;
+ result.raw = 'string';
+ result.subtype = 'base64';
+ return result;
+ }
+
+ if ((/^(upper|uppercase)+(\(\d+\))?$/).test(lower)) {
+ result.subtype = 'uppercase';
+ result.type = 3;
+ return parseLength(lower, result);
+ }
+
+ if (lower === 'uid') {
+ result.type = 3;
+ result.length = 20;
+ result.raw = 'string';
+ result.subtype = 'uid';
+ return result;
+ }
+
+ if (lower === 'email') {
+ result.type = 3;
+ result.length = 120;
+ result.raw = 'string';
+ result.subtype = 'email';
+ return result;
+ }
+
+ if (lower === 'json') {
+ result.type = 3;
+ result.raw = 'string';
+ result.subtype = 'json';
+ return result;
+ }
+
+ if (lower === 'url') {
+ result.type = 3;
+ result.length = 500;
+ result.raw = 'string';
+ result.subtype = 'url';
+ return result;
+ }
+
+ if (lower === 'zip') {
+ result.type = 3;
+ result.length = 10;
+ result.raw = 'string';
+ result.subtype = 'zip';
+ return result;
+ }
+
+ if (lower === 'phone') {
+ result.type = 3;
+ result.length = 20;
+ result.raw = 'string';
+ result.subtype = 'phone';
+ return result;
+ }
+
+ if (lower === 'number2') {
+ result.type = 11;
+ return result;
+ }
+
+ if (['int', 'integer', 'byte'].indexOf(lower) !== -1) {
+ result.type = 1;
+ return result;
+ }
+
+ if (['decimal', 'number', 'float', 'double'].indexOf(lower) !== -1) {
+ result.type = 2;
+ return result;
+ }
+
+ if (['bool', 'boolean'].indexOf(lower) !== -1) {
+ result.type = 4;
+ return result;
+ }
+
+ if (['date', 'time', 'datetime'].indexOf(lower) !== -1) {
+ result.type = 5;
+ return result;
+ }
+
+ result.type = 7;
+ return result;
};
-/**
- * Create URL
- * @return {String}
- */
-UrlBuilder.prototype.toString = function() {
+SchemaBuilderEntityProto.getDependencies = function() {
+ var dependencies = [];
+
+ for (var name in this.schema) {
- var self = this;
- var builder = [];
+ var type = this.schema[name];
+ if (typeof(type) !== 'string')
+ continue;
- Object.keys(self.builder).forEach(function(o) {
- builder.push(o + '=' + encodeURIComponent(self.builder[o] || ''));
- });
+ var isArray = type[0] === ']';
+ if (isArray)
+ type = type.substring(1, type.length - 1);
- return builder.join('&');
+ var m = this.parent.get(type);
+ m && dependencies.push({ name: name, isArray: isArray, schema: m });
+ }
+
+ return dependencies;
};
/**
- * Has these parameters?
- * @param {String Array} keys Keys.
- * @return {Boolean}
+ * Set schema validation
+ * @param {String|Array} properties Properties to validate, optional.
+ * @param {Function(propertyName, value, path, entityName, model)} fn A validation function.
+ * @return {SchemaBuilderEntity}
*/
-UrlBuilder.prototype.hasValue = function(keys) {
+SchemaBuilderEntityProto.setValidate = function(properties, fn) {
- if (typeof(keys) === UNDEFINED)
- return false;
+ if (fn === undefined && properties instanceof Array) {
+ this.properties = properties;
+ return this;
+ }
- var self = this;
+ if (typeof(properties) !== 'function') {
+ this.properties = properties;
+ this.onValidate = fn;
+ } else
+ this.onValidate = properties;
- if (typeof(keys) === 'string')
- keys = [keys];
+ return this;
+};
- for (var i = 0; i < keys.length; i++) {
- var val = self.builder[keys[i]];
- if (typeof(val) === UNDEFINED || val === null)
- return false;
- }
+SchemaBuilderEntityProto.setPrefix = function(prefix) {
+ this.resourcePrefix = prefix;
+ return this;
+};
- return true;
+SchemaBuilderEntityProto.setResource = function(name) {
+ this.resourceName = name;
+ return this;
};
/**
- * Render paramerters
- * @param {String Array} keys Keys.
- * @param {String} delimiter Delimiter (default &).
- * @return {String}
+ * Set the default values for schema
+ * @param {Function(propertyName, isntPreparing, entityName)} fn
+ * @return {SchemaBuilderEntity}
*/
-UrlBuilder.prototype.toOne = function(keys, delimiter) {
-
- var self = this;
- var builder = [];
+SchemaBuilderEntityProto.setDefault = function(fn) {
+ this.onDefault = fn;
+ return this;
+};
- keys.forEach(function(o) {
- builder.push(self.builder[o] || '');
- });
+/**
+ * Set the prepare
+ * @param {Function(name, value)} fn Must return a new value.
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.setPrepare = function(fn) {
+ this.onPrepare = fn;
+ return this;
+};
+
+/**
+ * Set save handler
+ * @param {Function(error, model, helper, next(value), controller)} fn
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.setSave = function(fn, description, filter) {
+
+ if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) {
+ filter = description;
+ description = null;
+ }
+
+ fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString());
+ this.onSave = fn;
+ this.meta.save = description || null;
+ this.meta.savefilter = filter;
+ !fn.$newversion && OBSOLETE('Schema("{0}").setSave()'.format(this.name), MSG_OBSOLETE_NEW);
+ return this;
+};
+
+SchemaBuilderEntityProto.setSaveExtension = function(fn) {
+ var key = 'save';
+ if (this.extensions[key])
+ this.extensions[key].push(fn);
+ else
+ this.extensions[key] = [fn];
+ return this;
+};
+
+/**
+ * Set insert handler
+ * @param {Function(error, model, helper, next(value), controller)} fn
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.setInsert = function(fn, description, filter) {
+
+ if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) {
+ filter = description;
+ description = null;
+ }
+
+ fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString());
+ this.onInsert = fn;
+ this.meta.insert = description || null;
+ this.meta.insertfilter = filter;
+ !fn.$newversion && OBSOLETE('Schema("{0}").setInsert()'.format(this.name), MSG_OBSOLETE_NEW);
+ return this;
+};
+
+SchemaBuilderEntityProto.setInsertExtension = function(fn) {
+ var key = 'insert';
+ if (this.extensions[key])
+ this.extensions[key].push(fn);
+ else
+ this.extensions[key] = [fn];
+ return this;
+};
+
+/**
+ * Set update handler
+ * @param {Function(error, model, helper, next(value), controller)} fn
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.setUpdate = function(fn, description, filter) {
+
+ if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) {
+ filter = description;
+ description = null;
+ }
+
+ fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString());
+ this.onUpdate = fn;
+ this.meta.update = description || null;
+ this.meta.updatefilter = filter;
+ !fn.$newversion && OBSOLETE('Schema("{0}").setUpdate()'.format(this.name), MSG_OBSOLETE_NEW);
+ return this;
+};
+
+SchemaBuilderEntityProto.setUpdateExtension = function(fn) {
+ var key = 'update';
+ if (this.extensions[key])
+ this.extensions[key].push(fn);
+ else
+ this.extensions[key] = [fn];
+ return this;
+};
+
+/**
+ * Set patch handler
+ * @param {Function(error, model, helper, next(value), controller)} fn
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.setPatch = function(fn, description, filter) {
+
+ if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) {
+ filter = description;
+ description = null;
+ }
+ fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString());
+ this.onPatch = fn;
+ this.meta.patch = description || null;
+ this.meta.patchfilter = filter;
+ !fn.$newversion && OBSOLETE('Schema("{0}").setPatch()'.format(this.name), MSG_OBSOLETE_NEW);
+ return this;
+};
+
+SchemaBuilderEntityProto.setPatchExtension = function(fn) {
+ var key = 'patch';
+ if (this.extensions[key])
+ this.extensions[key].push(fn);
+ else
+ this.extensions[key] = [fn];
+ return this;
+};
+
+/**
+ * Set error handler
+ * @param {Function(error)} fn
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.setError = function(fn) {
+ this.onError = fn;
+ return this;
+};
+
+/**
+ * Set getter handler
+ * @param {Function(error, model, helper, next(value), controller)} fn
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.setGet = SchemaBuilderEntityProto.setRead = function(fn, description, filter) {
+
+ if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) {
+ filter = description;
+ description = null;
+ }
+
+ fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString());
+ this.onGet = fn;
+ this.meta.get = this.meta.read = description || null;
+ this.meta.getfilter = this.meta.readfilter = filter;
+ !fn.$newversion && OBSOLETE('Schema("{0}").setGet()'.format(this.name), MSG_OBSOLETE_NEW);
+ return this;
+};
+
+SchemaBuilderEntityProto.setGetExtension = SchemaBuilderEntityProto.setReadExtension = function(fn) {
+ var key = 'read';
+ if (this.extensions[key])
+ this.extensions[key].push(fn);
+ else
+ this.extensions[key] = [fn];
+ return this;
+};
+
+/**
+ * Set query handler
+ * @param {Function(error, helper, next(value), controller)} fn
+ * @param {String} description Optional.
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.setQuery = function(fn, description, filter) {
+
+ if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) {
+ filter = description;
+ description = null;
+ }
+
+ fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString());
+ this.onQuery = fn;
+ this.meta.query = description || null;
+ this.meta.queryfilter = filter;
+
+ !fn.$newversion && OBSOLETE('Schema("{0}").setQuery()'.format(this.name), MSG_OBSOLETE_NEW);
+ return this;
+};
+
+SchemaBuilderEntityProto.setQueryExtension = function(fn) {
+ var key = 'query';
+ if (this.extensions[key])
+ this.extensions[key].push(fn);
+ else
+ this.extensions[key] = [fn];
+ return this;
+};
+
+/**
+ * Set remove handler
+ * @param {Function(error, helper, next(value), controller)} fn
+ * @param {String} description Optional.
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.setRemove = function(fn, description, filter) {
+
+ if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) {
+ filter = description;
+ description = null;
+ }
+
+ fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString());
+ this.onRemove = fn;
+ this.meta.remove = description || null;
+ this.meta.removefilter = filter;
+ !fn.$newversion && OBSOLETE('Schema("{0}").setRemove()'.format(this.name), MSG_OBSOLETE_NEW);
+ return this;
+};
+
+SchemaBuilderEntityProto.setRemoveExtension = function(fn) {
+ var key = 'remove';
+ if (this.extensions[key])
+ this.extensions[key].push(fn);
+ else
+ this.extensions[key] = [fn];
+ return this;
+};
+
+/**
+ * Add a new constant for the schema
+ * @param {String} name Constant name, optional.
+ * @param {Object} value
+ * @param {String} description Optional.
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.constant = function(name, value, description) {
+
+ OBSOLETE('Constants will be removed from schemas.');
+
+ if (value === undefined)
+ return this.constants ? this.constants[name] : undefined;
+
+ !this.constants && (this.constants = {});
+ this.constants[name] = value;
+ this.meta['constant#' + name] = description || null;
+ return this;
+};
+
+/**
+ * Add a new transformation for the entity
+ * @param {String} name Transform name, optional.
+ * @param {Function(errorBuilder, model, helper, next([output]), controller)} fn
+ * @param {String} description Optional.
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.addTransform = function(name, fn, description, filter) {
+
+ if (typeof(name) === 'function') {
+ fn = name;
+ name = 'default';
+ }
+
+ if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) {
+ filter = description;
+ description = null;
+ }
+
+ fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString());
+ !this.transforms && (this.transforms = {});
+ this.transforms[name] = fn;
+ this.meta['transform#' + name] = description || null;
+ this.meta['transformfilter#' + name] = filter;
+ !fn.$newversion && OBSOLETE('Schema("{0}").addTransform("{1}")'.format(this.name, name), MSG_OBSOLETE_NEW);
+ return this;
+};
+
+SchemaBuilderEntityProto.addTransformExtension = function(name, fn) {
+ var key = 'transform.' + name;
+ if (this.extensions[key])
+ this.extensions[key].push(fn);
+ else
+ this.extensions[key] = [fn];
+ return this;
+};
+
+/**
+ * Add a new operation for the entity
+ * @param {String} name Operation name, optional.
+ * @param {Function(errorBuilder, [model], helper, next([output]), controller)} fn
+ * @param {String} description Optional.
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.addOperation = function(name, fn, description, filter) {
+
+ if (typeof(name) === 'function') {
+ fn = name;
+ name = 'default';
+ }
+
+ if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) {
+ filter = description;
+ description = null;
+ }
+
+ fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString());
+ !this.operations && (this.operations = {});
+ this.operations[name] = fn;
+ this.meta['operation#' + name] = description || null;
+ this.meta['operationfilter#' + name] = filter;
+ !fn.$newversion && OBSOLETE('Schema("{0}").addOperation("{1}")'.format(this.name, name), MSG_OBSOLETE_NEW);
+ return this;
+};
+
+SchemaBuilderEntityProto.addOperationExtension = function(name, fn) {
+ var key = 'operation.' + name;
+ if (this.extensions[key])
+ this.extensions[key].push(fn);
+ else
+ this.extensions[key] = [fn];
+ return this;
+};
+
+/**
+ * Add a new workflow for the entity
+ * @param {String} name Workflow name, optional.
+ * @param {Function(errorBuilder, model, helper, next([output]), controller)} fn
+ * @param {String} description Optional.
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.addWorkflow = function(name, fn, description, filter) {
+
+ if (typeof(name) === 'function') {
+ fn = name;
+ name = 'default';
+ }
+
+ if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) {
+ filter = description;
+ description = null;
+ }
+
+ fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString());
+ !this.workflows && (this.workflows = {});
+ this.workflows[name] = fn;
+ this.meta['workflow#' + name] = description || null;
+ this.meta['workflowfilter#' + name] = filter;
+ !fn.$newversion && OBSOLETE('Schema("{0}").addWorkflow("{1}")'.format(this.name, name), MSG_OBSOLETE_NEW);
+ return this;
+};
+
+SchemaBuilderEntityProto.addWorkflowExtension = function(name, fn) {
+ var key = 'workflow.' + name;
+ if (this.extensions[key])
+ this.extensions[key].push(fn);
+ else
+ this.extensions[key] = [fn];
+ return this;
+};
+
+SchemaBuilderEntityProto.addHook = function(name, fn, description, filter) {
+
+ if (!this.hooks)
+ this.hooks = {};
+
+ if (typeof(description) === 'string' && REGEXP_FILTER.test(description)) {
+ filter = description;
+ description = null;
+ }
+
+ fn.$newversion = REGEXP_NEWOPERATION.test(fn.toString());
+ !this.hooks[name] && (this.hooks[name] = []);
+ this.hooks[name].push({ owner: F.$owner(), fn: fn });
+ this.meta['hook#' + name] = description || null;
+ this.meta['hookfilter#' + name] = filter;
+ !fn.$newversion && OBSOLETE('Schema("{0}").addHook("{1}")'.format(this.name, name), MSG_OBSOLETE_NEW);
+ return this;
+};
+
+SchemaBuilderEntityProto.addHookExtension = function(name, fn) {
+ var key = 'hook.' + name;
+ if (this.extensions[key])
+ this.extensions[key].push(fn);
+ else
+ this.extensions[key] = [fn];
+ return this;
+};
+
+/**
+ * Find an entity in current group
+ * @param {String} name
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.find = function(name) {
+ return this.parent.get(name);
+};
+
+/**
+ * Destroys current entity
+ */
+SchemaBuilderEntityProto.destroy = function() {
+ delete this.parent.collection[this.name];
+ delete this.properties;
+ delete this.schema;
+ delete this.onDefault;
+ delete this.$onDefault;
+ delete this.onValidate;
+ delete this.onSave;
+ delete this.onInsert;
+ delete this.onUpdate;
+ delete this.onRead;
+ delete this.onGet;
+ delete this.onRemove;
+ delete this.onQuery;
+ delete this.workflows;
+ delete this.operations;
+ delete this.transforms;
+ delete this.meta;
+ delete this.newversion;
+ delete this.properties;
+ delete this.hooks;
+ delete this.constants;
+ delete this.onPrepare;
+ delete this.$onPrepare;
+ delete this.onError;
+ delete this.gcache;
+ delete this.dependencies;
+ delete this.fields;
+ delete this.fields_allow;
+};
+
+/**
+ * Execute onSave delegate
+ * @param {Object} model
+ * @param {Object} options Custom options object, optional
+ * @param {Function(err, result)} callback
+ * @param {Controller} controller
+ * @param {Boolean} skip Skips preparing and validation, optional
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.save = function(model, options, callback, controller, skip) {
+ return this.execute('onSave', model, options, callback, controller, skip);
+};
+
+/**
+ * Execute onInsert delegate
+ * @param {Object} model
+ * @param {Object} options Custom options object, optional
+ * @param {Function(err, result)} callback
+ * @param {Controller} controller
+ * @param {Boolean} skip Skips preparing and validation, optional
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.insert = function(model, options, callback, controller, skip) {
+ return this.execute('onInsert', model, options, callback, controller, skip);
+};
+
+/**
+ * Execute onUpdate delegate
+ * @param {Object} model
+ * @param {Object} options Custom options object, optional
+ * @param {Function(err, result)} callback
+ * @param {Controller} controller
+ * @param {Boolean} skip Skips preparing and validation, optional
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.update = function(model, options, callback, controller, skip) {
+ return this.execute('onUpdate', model, options, callback, controller, skip);
+};
+
+SchemaBuilderEntityProto.patch = function(model, options, callback, controller, skip) {
+ return this.execute('onPatch', model, options, callback, controller, skip);
+};
+
+SchemaBuilderEntityProto.execute = function(TYPE, model, options, callback, controller, skip) {
+
+ if (typeof(callback) === 'boolean') {
+ skip = callback;
+ callback = options;
+ options = undefined;
+ } else if (typeof(options) === 'function') {
+ callback = options;
+ options = undefined;
+ }
+
+ if (typeof(controller) === 'boolean') {
+ var tmp = skip;
+ skip = controller;
+ controller = tmp;
+ }
+
+ if (typeof(callback) !== 'function')
+ callback = function(){};
+
+ var self = this;
+ var $type;
+
+ switch (TYPE) {
+ case 'onInsert':
+ $type = 'insert';
+ break;
+ case 'onUpdate':
+ $type = 'update';
+ break;
+ case 'onPatch':
+ $type = 'patch';
+ break;
+ default:
+ $type = 'save';
+ break;
+ }
+
+ if (!self[TYPE])
+ return callback(new Error('Operation "{0}/{1}" not found'.format(self.name, $type)));
+
+ self.$prepare(model, function(err, model) {
+
+ if (err) {
+ callback(err, model);
+ return;
+ }
+
+ if (controller instanceof SchemaOptions || controller instanceof OperationOptions)
+ controller = controller.controller;
+
+ if (model && !controller && model.$$controller)
+ controller = model.$$controller;
+
+ var builder = new ErrorBuilder();
+ var $now;
+
+ if (CONF.logger)
+ $now = Date.now();
+
+ self.resourceName && builder.setResource(self.resourceName);
+ self.resourcePrefix && builder.setPrefix(self.resourcePrefix);
+
+ if (!isGenerator(self, $type, self[TYPE])) {
+ if (self[TYPE].$newversion) {
+ var opt = new SchemaOptions(builder, model, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+ self.$process(arguments, model, $type, undefined, builder, res, callback, controller);
+ }, controller, $type, self);
+
+ if (self.middlewares && self.middlewares.length)
+ runmiddleware(opt, self, self[TYPE]);
+ else
+ self[TYPE](opt);
+
+ } else
+ self[TYPE](builder, model, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+ self.$process(arguments, model, $type, undefined, builder, res, callback, controller);
+ }, controller, skip !== true);
+ return self;
+ }
+
+ callback.success = false;
+
+ var onError = function(err) {
+ if (!err || callback.success)
+ return;
+
+ callback.success = true;
+
+ if (builder !== err)
+ builder.push(err);
+
+ self.onError && self.onError(builder, model, $type);
+ callback(builder);
+ };
+
+ var onCallback = function(res) {
+
+ CONF.logger && F.ilogger(self.getLoggerName($type, name), controller, $now);
+
+ if (callback.success)
+ return;
+
+ if (arguments.length === 2 || (res instanceof Error || res instanceof ErrorBuilder)) {
+ if ((res instanceof Error || res instanceof ErrorBuilder) && builder !== res)
+ builder.push(res);
+ res = arguments[1];
+ }
+
+ var has = builder.is;
+ has && self.onError && self.onError(builder, model, $type);
+ callback.success = true;
+ callback(has ? builder : null, res === undefined ? model : res);
+ };
+
+ if (self[TYPE].$newversion) {
+ var opt = new SchemaOptions(builder, model, options, onCallback, controller, $type, self);
+ if (self.middlewares && self.middlewares.length)
+ runmiddleware(opt, self, () => async.call(self, self[TYPE])(onError, opt));
+ else
+ async.call(self, self[TYPE])(onError, opt);
+ } else
+ async.call(self, self[TYPE])(onError, builder, model, options, onCallback, controller, skip !== true);
+
+ }, controller ? controller.req : null);
+
+ return self;
+};
+
+
+function isGenerator(obj, name, fn) {
+ return obj.gcache[name] ? obj.gcache[name] : obj.gcache[name] = fn.toString().substring(0, 9) === 'function*';
+}
+
+/**
+ * Execute onGet delegate
+ * @param {Object} options Custom options object, optional
+ * @param {Function(err, result)} callback
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.get = SchemaBuilderEntityProto.read = function(options, callback, controller) {
+
+ if (typeof(options) === 'function') {
+ callback = options;
+ options = undefined;
+ }
+
+ if (typeof(callback) !== 'function')
+ callback = function(){};
+
+ var self = this;
+ var builder = new ErrorBuilder();
+ var $now;
+
+ self.resourceName && builder.setResource(self.resourceName);
+ self.resourcePrefix && builder.setPrefix(self.resourcePrefix);
+
+ if (controller instanceof SchemaOptions || controller instanceof OperationOptions)
+ controller = controller.controller;
+
+ if (self.meta.getfilter && controller) {
+ controller.$filterschema = self.meta.getfilter;
+ controller.$filter = null;
+ }
+
+ if (CONF.logger)
+ $now = Date.now();
+
+ var output = self.default();
+ var $type = 'get';
+
+ if (!isGenerator(self, $type, self.onGet)) {
+ if (self.onGet.$newversion) {
+ var opt = new SchemaOptions(builder, output, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+ self.$process(arguments, output, $type, undefined, builder, res, callback, controller);
+ }, controller, $type, self);
+
+ if (self.middlewares && self.middlewares.length)
+ runmiddleware(opt, self, self.onGet);
+ else
+ self.onGet(opt);
+ } else
+ self.onGet(builder, output, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+ self.$process(arguments, output, $type, undefined, builder, res, callback, controller);
+ }, controller);
+ return self;
+ }
+
+ callback.success = false;
+
+ var onError = function(err) {
+ if (!err || callback.success)
+ return;
+ callback.success = true;
+
+ if (builder !== err)
+ builder.push(err);
+
+ self.onError && self.onError(builder, output, $type);
+ callback(builder);
+ };
+
+ var onCallback = function(res) {
+
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+
+ if (callback.success)
+ return;
+
+ if (arguments.length === 2 || (res instanceof Error || res instanceof ErrorBuilder)) {
+ if ((res instanceof Error || res instanceof ErrorBuilder) && builder !== res)
+ builder.push(res);
+ res = arguments[1];
+ }
+
+ callback.success = true;
+ var has = builder.is;
+ has && self.onError && self.onError(builder, output, $type);
+ callback(has ? builder : null, res === undefined ? output : res);
+ };
+
+ if (self.onGet.$newversion) {
+ var opt = new SchemaOptions(builder, output, options, onCallback, controller, $type, self);
+ if (self.middlewares && self.middlewares.length)
+ runmiddleware(opt, self, () => async.call(self, self.onGet)(onError, opt));
+ else
+ async.call(self, self.onGet)(onError, opt);
+ } else
+ async.call(self, self.onGet)(onError, builder, output, options, onCallback, controller);
+
+ return self;
+};
+
+/**
+ * Execute onRemove delegate
+ * @param {Object} options Custom options object, optional
+ * @param {Function(err, result)} callback
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.remove = function(options, callback, controller) {
+
+ if (typeof(options) === 'function') {
+ callback = options;
+ options = undefined;
+ }
+
+ var self = this;
+ var builder = new ErrorBuilder();
+ var $type = 'remove';
+ var $now;
+
+ if (!self.onRemove)
+ return callback(new Error('Operation "{0}/{1}" not found'.format(self.name, $type)));
+
+ if (controller instanceof SchemaOptions || controller instanceof OperationOptions)
+ controller = controller.controller;
+
+ if (self.meta.removefilter && controller) {
+ controller.$filterschema = self.meta.removefilter;
+ controller.$filter = null;
+ }
+
+ if (CONF.logger)
+ $now = Date.now();
+
+ self.resourceName && builder.setResource(self.resourceName);
+ self.resourcePrefix && builder.setPrefix(self.resourcePrefix);
+
+ if (!isGenerator(self, $type, self.onRemove)) {
+ if (self.onRemove.$newversion) {
+
+ var opt = new SchemaOptions(builder, controller ? controller.body : undefined, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+ self.$process(arguments, undefined, $type, undefined, builder, res, callback, controller);
+ }, controller, $type, self);
+
+ if (self.middlewares && self.middlewares.length)
+ runmiddleware(opt, self, self.onRemove);
+ else
+ self.onRemove(opt);
+ } else
+ self.onRemove(builder, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+ self.$process(arguments, undefined, $type, undefined, builder, res, callback, controller);
+ }, controller);
+ return self;
+ }
+
+ callback.success = false;
+
+ var onError = function(err) {
+ if (!err || callback.success)
+ return;
+ callback.success = true;
+
+ if (builder !== err)
+ builder.push(err);
+
+ self.onError && self.onError(builder, EMPTYOBJECT, $type);
+ callback(builder);
+ };
+
+ var onCallback = function(res) {
+
+ CONF.logger && F.ilogger(self.getLoggerName($type, name), controller, $now);
+
+ if (callback.success)
+ return;
+
+ if (arguments.length === 2 || (res instanceof Error || res instanceof ErrorBuilder)) {
+ if ((res instanceof Error || res instanceof ErrorBuilder) && builder !== res)
+ builder.push(res);
+ res = arguments[1];
+ }
+
+ var has = builder.is;
+ has && self.onError && self.onError(builder, EMPTYOBJECT, $type);
+ callback.success = true;
+ callback(has ? builder : null, res === undefined ? options : res);
+ };
+
+ if (self.onRemove.$newversion) {
+ var opt = new SchemaOptions(builder, undefined, options, onCallback, controller, $type, self);
+ if (self.middlewares && self.middlewares.length)
+ runmiddleware(opt, self, () => async.call(self, self.onRemove)(onError, opt));
+ else
+ async.call(self, self.onRemove)(onError, opt);
+ } else
+ async.call(self, self.onRemove)(onError, builder, options, onCallback, controller);
+
+ return self;
+};
+
+/**
+ * Execute onQuery delegate
+ * @param {Object} options Custom options object, optional
+ * @param {Function(err, result)} callback
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.query = function(options, callback, controller) {
+
+ if (typeof(options) === 'function') {
+ callback = options;
+ options = undefined;
+ }
+
+ if (controller instanceof SchemaOptions || controller instanceof OperationOptions)
+ controller = controller.controller;
+
+ var self = this;
+ var builder = new ErrorBuilder();
+ var $type = 'query';
+ var $now;
+
+ if (self.meta.queryfilter && controller) {
+ controller.$filterschema = self.meta.queryfilter;
+ controller.$filter = null;
+ }
+
+ self.resourceName && builder.setResource(self.resourceName);
+ self.resourcePrefix && builder.setPrefix(self.resourcePrefix);
+
+ if (CONF.logger)
+ $now = Date.now();
+
+ if (!isGenerator(self, $type, self.onQuery)) {
+ if (self.onQuery.$newversion) {
+ var opt = new SchemaOptions(builder, undefined, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+ self.$process(arguments, undefined, $type, undefined, builder, res, callback, controller);
+ }, controller, $type, self);
+
+ if (self.middlewares && self.middlewares.length)
+ runmiddleware(opt, self, self.onQuery);
+ else
+ self.onQuery(opt);
+
+ } else
+ self.onQuery(builder, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+ self.$process(arguments, undefined, $type, undefined, builder, res, callback, controller);
+ }, controller);
+ return self;
+ }
+
+ callback.success = false;
+
+ var onError = function(err) {
+ if (!err || callback.success)
+ return;
+ callback.success = true;
+
+ if (builder !== err)
+ builder.push(err);
+
+ self.onError && self.onError(builder, EMPTYOBJECT, $type);
+ callback(builder);
+ };
+
+ var onCallback = function(res) {
+
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+
+ if (callback.success)
+ return;
+
+ if (arguments.length === 2 || (res instanceof Error || res instanceof ErrorBuilder)) {
+ if ((res instanceof Error || res instanceof ErrorBuilder) && builder !== res)
+ builder.push(res);
+ res = arguments[1];
+ }
+
+ var has = builder.is;
+ has && self.onError && self.onError(builder, EMPTYOBJECT, $type);
+ callback.success = true;
+ callback(builder.is ? builder : null, res);
+ };
+
+ if (self.onQuery.$newversion) {
+ var opt = new SchemaOptions(builder, undefined, options, onCallback, controller, $type, self);
+ if (self.middlewares && self.middlewares.length)
+ runmiddleware(opt, self, () => async.call(self, self.onQuery)(onError, opt));
+ else
+ async.call(self, self.onQuery)(onError, opt);
+ } else
+ async.call(self, self.onQuery)(onError, builder, options, onCallback, controller);
+
+ return self;
+};
+
+/**
+ * Validate a schema
+ * @param {Object} model Object to validate.
+ * @param {String} resourcePrefix Prefix for resource key.
+ * @param {String} resourceName Resource filename.
+ * @param {ErrorBuilder} builder ErrorBuilder, INTERNAL.
+ * @return {ErrorBuilder}
+ */
+SchemaBuilderEntityProto.validate = function(model, resourcePrefix, resourceName, builder, filter, path, index) {
+
+ var self = this;
+
+ if (builder === undefined) {
+ builder = new ErrorBuilder();
+ self.resourceName && builder.setResource(self.resourceName);
+ self.resourcePrefix && builder.setPrefix(self.resourcePrefix);
+ }
+
+ if (self.resourcePrefix)
+ builder.resourcePrefix = self.resourcePrefix;
+
+ if (self.resourceName)
+ builder.resourceName = self.resourceName;
+
+ if (resourceName)
+ builder.resourceName = resourceName;
+
+ if (resourcePrefix)
+ builder.resourcePrefix = resourcePrefix;
+
+ if (filter)
+ filter = self.filter(filter);
+
+ if (path)
+ path += '.';
+ else
+ path = '';
+
+ framework_utils.validate_builder.call(self, model, builder, self, '', index, filter, path);
+ return builder;
+};
+
+/**
+ * Create a default object according the schema
+ * @alias SchemaBuilderEntity.default()
+ * @return {Object}
+ */
+SchemaBuilderEntityProto.create = function() {
+ return this.default();
+};
+
+SchemaBuilderEntityProto.Create = function() {
+ return this.default();
+};
+
+/**
+ * Makes extensible object
+ * @param {Object} obj
+ * @return {Object}
+ */
+SchemaBuilderEntityProto.$make = function(obj) {
+ return obj;
+};
+
+SchemaBuilderEntityProto.$prepare = function(obj, callback) {
+ if (obj && typeof(obj.$save) === 'function')
+ callback(null, obj);
+ else
+ this.make(obj, (err, model) => callback(err, model));
+ return this;
+};
+
+/**
+ * Create a default object according the schema
+ * @return {SchemaInstance}
+ */
+SchemaBuilderEntityProto.default = function() {
+
+ var obj = this.schema;
+ if (obj === null)
+ return null;
+
+ var item = new this.CurrentSchemaInstance();
+ var defaults = this.onDefault || this.$onDefault ? true : false;
+
+ for (var property in obj) {
+
+ var type = obj[property];
+
+ if (defaults) {
+ var def = this.$ondefault(property, true, this.name);
+ if (def !== undefined) {
+ item[property] = def;
+ continue;
+ }
+ }
+
+ if (type.def !== undefined) {
+ item[property] = typeof(type.def) === 'function' ? type.def() : type.def;
+ continue;
+ }
+
+ switch (type.type) {
+ // undefined
+ // object
+ // object: convertor
+ case 0:
+ case 6:
+ case 12:
+ item[property] = type.isArray ? [] : null;
+ break;
+ // numbers: integer, float
+ case 1:
+ case 2:
+ item[property] = type.isArray ? [] : 0;
+ break;
+ // numbers: default "null"
+ case 10:
+ item[property] = type.isArray ? [] : null;
+ break;
+ // string
+ case 3:
+ item[property] = type.isArray ? [] : type.subtype === 'email' ? '@' : '';
+ break;
+ // boolean
+ case 4:
+ item[property] = type.isArray ? [] : false;
+ break;
+ // date
+ case 5:
+ item[property] = type.isArray ? [] : NOW;
+ break;
+ // schema
+ case 7:
+
+ if (type.isArray) {
+ item[property] = [];
+ } else {
+ var tmp = this.parent.collection[type.raw] || GETSCHEMA(type.raw);
+ if (tmp) {
+ item[property] = tmp.default();
+ } else {
+ F.error(new Error('Schema: "' + property + '.' + type.raw + '" not found in "' + this.parent.name + '".'));
+ item[property] = null;
+ }
+ }
+ break;
+
+ // enum + keyvalue
+ case 8:
+ case 9:
+ item[property] = undefined;
+ break;
+ }
+ }
+
+ return item;
+};
+
+function SchemaOptionsVerify(controller, builder) {
+ var t = this;
+ t.controller = (controller instanceof SchemaOptions || controller instanceof OperationOptions) ? controller.controller : controller;
+ t.callback = t.next = t.success = function(value) {
+ if (value !== undefined)
+ t.model[t.name] = value;
+ t.cache && CACHE(t.cachekey, { value: t.model[t.name] }, t.cache);
+ t.$next();
+ };
+ t.invalid = function(err) {
+ if (err) {
+ builder.push(err);
+ t.cache && CACHE(t.cachekey, { error: err }, t.cache);
+ }
+ t.model[t.name] = null;
+ t.$next();
+ };
+}
+
+SchemaOptionsVerify.prototype = {
+
+ get user() {
+ return this.controller ? this.controller.user : null;
+ },
+
+ get session() {
+ return this.controller ? this.controller.session : null;
+ },
+
+ get sessionid() {
+ return this.controller && this.controller ? this.controller.req.sessionid : null;
+ },
+
+ get language() {
+ return (this.controller ? this.controller.language : '') || '';
+ },
+
+ get ip() {
+ return this.controller ? this.controller.ip : null;
+ },
+
+ get id() {
+ return this.controller ? this.controller.id : null;
+ },
+
+ get req() {
+ return this.controller ? this.controller.req : null;
+ },
+
+ get res() {
+ return this.controller ? this.controller.res : null;
+ },
+
+ get params() {
+ return this.controller ? this.controller.params : null;
+ },
+
+ get files() {
+ return this.controller ? this.controller.files : null;
+ },
+
+ get body() {
+ return this.controller ? this.controller.body : null;
+ },
+
+ get query() {
+ return this.controller ? this.controller.query : null;
+ },
+
+ get headers() {
+ return this.controller && this.controller.req ? this.controller.req.headers : null;
+ },
+
+ get ua() {
+ return this.controller && this.controller.req ? this.controller.req.ua : null;
+ }
+};
+
+/**
+ * Create schema instance
+ * @param {function|object} model
+ * @param [filter]
+ * @param [callback]
+ * @returns {SchemaInstance}
+ */
+SchemaBuilderEntityProto.make = function(model, filter, callback, argument, novalidate, workflow, req) {
+
+ var self = this;
+
+ if (typeof(model) === 'function') {
+ model.call(self, self);
+ return self;
+ }
+
+ if (typeof(filter) === 'function') {
+ var tmp = callback;
+ callback = filter;
+ filter = tmp;
+ }
+
+ var verifications = [];
+ var output = self.prepare(model, null, req, verifications);
+
+ if (workflow)
+ output.$$workflow = workflow;
+
+ if (novalidate) {
+ callback && callback(null, output, argument);
+ return output;
+ }
+
+ var builder = self.validate(output, undefined, undefined, undefined, filter);
+
+ if (builder.is) {
+ self.onError && self.onError(builder, model, 'make');
+ callback && callback(builder, null, argument);
+ return output;
+ } else {
+
+ if (self.verifications)
+ verifications.unshift({ model: output, entity: self });
+
+ if (!verifications.length) {
+ callback && callback(null, output, argument);
+ return output;
+ }
+
+ var options = new SchemaOptionsVerify(req, builder);
+
+ verifications.wait(function(item, next) {
+
+ item.entity.verifications.wait(function(verify, resume) {
+
+ options.value = item.model[verify.name];
+
+ // Empty values are skipped
+ if (options.value == null || options.value === '') {
+ resume();
+ return;
+ }
+
+ var cachekey = verify.cachekey;
+
+ if (cachekey) {
+ cachekey += options.value + '';
+ var cachevalue = F.cache.get2(cachekey);
+ if (cachevalue) {
+ if (cachevalue.error)
+ builder.push(cachevalue.error);
+ else
+ item.model[verify.name] = cachevalue.value;
+ resume();
+ return;
+ }
+ }
+
+ options.cache = verify.cache;
+ options.cachekey = cachekey;
+ options.entity = item.entity;
+ options.model = item.model;
+ options.name = verify.name;
+ options.$next = resume;
+ verify.fn(options);
+
+ }, next, 3); // "3" means count of imaginary "threads" - we will see how it will work
+
+ }, function() {
+ if (builder.is) {
+ self.onError && self.onError(builder, model, 'make');
+ callback && callback(builder, null, argument);
+ } else
+ callback && callback(null, output, argument);
+ });
+
+ }
+};
+
+SchemaBuilderEntityProto.load = SchemaBuilderEntityProto.make; // Because JSDoc doesn't work with double asserting
+
+function autotrim(context, value) {
+ return context.trim ? value.trim() : value;
+}
+
+SchemaBuilderEntityProto.$onprepare = function(name, value, index, model, req) {
+
+ var val = value;
+
+ if (this.$onPrepare) {
+ for (var i = 0, length = this.$onPrepare.length; i < length; i++) {
+ var tmp = this.$onPrepare[i](name, val, index, model, req);
+ if (tmp !== undefined)
+ val = tmp;
+ }
+ }
+
+ if (this.onPrepare)
+ val = this.onPrepare(name, val, index, model, req);
+
+ if (this.preparation && this.preparation[name])
+ val = this.preparation[name](val, model, index, req);
+
+ return val === undefined ? value : val;
+};
+
+SchemaBuilderEntityProto.$ondefault = function(property, create, entity) {
+
+ var val;
+
+ if (this.onDefault) {
+ val = this.onDefault(property, create, entity);
+ if (val !== undefined)
+ return val;
+ }
+
+ if (this.$onDefault) {
+ for (var i = 0, length = this.$onDefault.length; i < length; i++) {
+ val = this.$onDefault[i](property, create, entity);
+ if (val !== undefined)
+ return val;
+ }
+ }
+};
+
+/**
+ * Prepare model according to schema
+ * @param {Object} model
+ * @param {String|Array} [dependencies] INTERNAL.
+ * @return {SchemaInstance}
+ */
+SchemaBuilderEntityProto.prepare = function(model, dependencies, req, verifications) {
+
+ var self = this;
+ var obj = self.schema;
+
+ if (obj === null)
+ return null;
+
+ if (model == null)
+ return self.default();
+
+ var tmp;
+ var entity;
+ var item = new self.CurrentSchemaInstance();
+ var defaults = self.onDefault || self.$onDefault ? true : false;
+ var keys = req && req.$patch ? [] : null;
+
+ for (var property in obj) {
+
+ var val = model[property];
+
+ if (req && req.$patch && val === undefined) {
+ delete item[property];
+ continue;
+ }
+
+ var type = obj[property];
+ keys && keys.push(property);
+
+ // IS PROTOTYPE? The problem was in e.g. "search" property, because search is in String prototypes.
+ if (!hasOwnProperty.call(model, property))
+ val = undefined;
+
+ var def = type.def && typeof(type.def) === 'function';
+
+ if (val === undefined) {
+ if (type.def !== undefined)
+ val = def ? type.def() : type.def;
+ else if (defaults)
+ val = self.$ondefault(property, false, self.name);
+ }
+
+ if (val === undefined)
+ val = '';
+
+ var typeval = typeof(val);
+
+ if (typeval === 'function')
+ val = val();
+
+ if (!type.isArray) {
+
+ switch (type.type) {
+ // undefined
+ case 0:
+ break;
+ // number: integer
+ case 1:
+ item[property] = self.$onprepare(property, framework_utils.parseInt(val, def ? type.def() : type.def), undefined, model, req);
+ break;
+ // number: float
+ case 2:
+ item[property] = self.$onprepare(property, framework_utils.parseFloat(val, def ? type.def() : type.def), undefined, model, req);
+ break;
+
+ // string
+ case 3:
+
+ var tv = typeof(val);
+
+ if (val == null || tv === 'object')
+ tmp = '';
+ else if (tv === 'string')
+ tmp = autotrim(self, val);
+ else
+ tmp = autotrim(self, val.toString());
+
+ if (type.length && type.length < tmp.length)
+ tmp = tmp.substring(0, type.length);
+
+ switch (type.subtype) {
+ case 'uid':
+ if (tmp && !type.required && !tmp.isUID())
+ tmp = '';
+ break;
+ case 'email':
+ tmp = tmp.toLowerCase().replace(REGEXP_CLEAN_EMAIL, '');
+ if (tmp && !type.required && !tmp.isEmail())
+ tmp = '';
+ break;
+ case 'url':
+ if (tmp && !type.required && !tmp.isURL())
+ tmp = '';
+ break;
+ case 'zip':
+ tmp = tmp.replace(REGEXP_CLEAN_EMAIL, '');
+ if (tmp && !type.required && !tmp.isZIP())
+ tmp = '';
+ break;
+ case 'phone':
+ tmp = tmp.replace(REGEXP_CLEAN_PHONE, '');
+ if (tmp && !type.required && !tmp.isPhone())
+ tmp = '';
+ break;
+ case 'capitalize':
+ tmp = tmp.capitalize();
+ break;
+ case 'capitalize2':
+ tmp = tmp.capitalize(true);
+ break;
+ case 'lowercase':
+ tmp = tmp.toLowerCase();
+ break;
+ case 'uppercase':
+ tmp = tmp.toUpperCase();
+ break;
+ case 'json':
+ if (tmp && !type.required && !tmp.isJSON())
+ tmp = '';
+ break;
+ case 'base64':
+ if (tmp && !type.required && !tmp.isBase64())
+ tmp = '';
+ break;
+ }
+
+ if (!tmp && type.def !== undefined)
+ tmp = def ? type.def() : type.def;
+
+ item[property] = self.$onprepare(property, tmp, undefined, model, req);
+ break;
+
+ // boolean
+ case 4:
+ tmp = val ? val.toString().toLowerCase() : null;
+ if (type.def && (tmp == null || tmp === ''))
+ tmp = def ? type.def() : type.def;
+ item[property] = self.$onprepare(property, typeof(tmp) === 'string' ? !!BOOL[tmp] : tmp == null ? false : tmp, undefined, model, req);
+ break;
+
+ // date
+ case 5:
+
+ tmp = null;
+
+ if (typeval === 'string') {
+ if (val)
+ tmp = val.trim().parseDate();
+ } else if (typeval === 'number')
+ tmp = new Date(val);
+ else
+ tmp = val;
+
+ if (framework_utils.isDate(tmp))
+ tmp = self.$onprepare(property, tmp, undefined, model, req);
+ else {
+ if (type.def !== undefined)
+ tmp = def ? type.def() : type.def;
+ else
+ tmp = (defaults ? isUndefined(self.$ondefault(property, false, self.name), null) : null);
+ }
+
+ item[property] = tmp;
+ break;
+
+ // object
+ case 6:
+ // item[property] = self.$onprepare(property, model[property], undefined, model, req);
+ item[property] = self.$onprepare(property, val, undefined, model, req);
+ if (item[property] === undefined)
+ item[property] = null;
+ break;
+
+ // enum
+ case 8:
+ // tmp = self.$onprepare(property, model[property], undefined, model, req);
+ tmp = self.$onprepare(property, val, undefined, model, req);
+ if (type.subtype === 'number' && typeof(tmp) === 'string')
+ tmp = tmp.parseFloat(null);
+ item[property] = tmp != null && type.raw.indexOf(tmp) !== -1 ? tmp : undefined;
+ if (item[property] == null && type.def)
+ item[property] = type.def;
+ break;
+
+ // keyvalue
+ case 9:
+ // tmp = self.$onprepare(property, model[property], undefined, model, req);
+ tmp = self.$onprepare(property, val, undefined, model, req);
+ item[property] = tmp != null ? type.raw[tmp] : undefined;
+ if (item[property] == null && type.def)
+ item[property] = type.def;
+ break;
+
+ // schema
+ case 7:
+
+ if (!val) {
+ val = (type.def === undefined ? defaults ? isUndefined(self.$ondefault(property, false, self.name), null) : null : (def ? type.def() : type.def));
+ if (val === null) {
+ item[property] = null;
+ break;
+ }
+ }
+
+ if (val && typeof(val.$schema) === 'function') {
+ tmp = val.$schema();
+ if (tmp && tmp.name && tmp.name === type.raw) {
+ item[property] = val;
+ break;
+ }
+ }
+
+ entity = GETSCHEMA(type.raw);
+ if (entity) {
+
+ item[property] = entity.prepare(val, undefined, req, verifications);
+ item[property].$$parent = item;
+ item[property].$$controller = req;
+
+ if (entity.verifications)
+ verifications.push({ model: item[property], entity: entity });
+
+ dependencies && dependencies.push({ name: type.raw, value: self.$onprepare(property, item[property], undefined, model, req) });
+ } else
+ item[property] = null;
+
+ break;
+
+ case 10:
+ item[property] = type.raw(val == null ? '' : val.toString());
+ if (item[property] === undefined)
+ item[property] = null;
+ break;
+
+ // number: nullable
+ case 11:
+ item[property] = self.$onprepare(property, typeval === 'number' ? val : typeval === 'string' ? parseNumber(val) : null, undefined, model, req);
+ break;
+
+ // object: convertor
+ case 12:
+ item[property] = self.$onprepare(property, val && typeval === 'object' && !(val instanceof Array) ? CONVERT(val, type.raw) : null, undefined, model, req);
+ break;
+
+ }
+ continue;
+ }
+
+ // ARRAY:
+ if (!(val instanceof Array)) {
+ item[property] = (type.def === undefined ? defaults ? isUndefined(self.$ondefault(property, false, self.name), EMPTYARRAY) : [] : (def ? type.def() : type.def));
+ continue;
+ }
+
+ item[property] = [];
+ for (var j = 0, sublength = val.length; j < sublength; j++) {
+
+ // tmp = model[property][j];
+ tmp = val[j];
+ typeval = typeof(tmp);
+
+ switch (type.type) {
+ case 0:
+ tmp = self.$onprepare(property, tmp, j, model, req);
+ break;
+
+ case 1:
+ tmp = self.$onprepare(property, framework_utils.parseInt(tmp), j, model, req);
+ break;
+
+ case 2:
+ tmp = self.$onprepare(property, framework_utils.parseFloat(tmp), j, model, req);
+ break;
+
+ case 3:
+
+ tmp = tmp == null ? '' : autotrim(self, tmp.toString());
+ if (type.length && tmp.length < tmp.length)
+ tmp = tmp.substring(0, type.length);
+
+ switch (type.subtype) {
+ case 'uid':
+ if (tmp && !type.required && !tmp.isUID())
+ continue;
+ break;
+ case 'url':
+ if (tmp && !type.required && !tmp.isURL())
+ continue;
+ break;
+ case 'email':
+ tmp = tmp.toLowerCase().replace(REGEXP_CLEAN_EMAIL, '');
+ if (tmp && !type.required && !tmp.isEmail())
+ continue;
+ break;
+ case 'phone':
+ tmp = tmp.replace(REGEXP_CLEAN_PHONE, '');
+ if (tmp && !type.required && !tmp.isPhone())
+ continue;
+ break;
+ case 'capitalize':
+ tmp = tmp.capitalize();
+ break;
+ case 'capitalize2':
+ tmp = tmp.capitalize(true);
+ break;
+ case 'lowercase':
+ tmp = tmp.toLowerCase();
+ break;
+ case 'uppercase':
+ tmp = tmp.toUpperCase();
+ break;
+ case 'json':
+ if (tmp && !type.required && !tmp.isJSON())
+ continue;
+ break;
+ case 'base64':
+ if (tmp && !type.required && !tmp.isBase64())
+ continue;
+ break;
+ }
+
+ tmp = self.$onprepare(property, tmp, j, model, req);
+ break;
+
+ case 4:
+ if (tmp)
+ tmp = tmp.toString().toLowerCase();
+ tmp = self.$onprepare(property, BOOL[tmp], j, model, req);
+ break;
+
+ case 5:
+
+ if (typeval === 'string') {
+ if (tmp)
+ tmp = tmp.trim().parseDate();
+ } else if (typeval === 'number')
+ tmp = new Date(tmp);
+
+ if (framework_utils.isDate(tmp))
+ tmp = self.$onprepare(property, tmp, j, model, req);
+ else
+ tmp = undefined;
+
+ break;
+
+ case 6:
+ tmp = self.$onprepare(property, tmp, j, model, req);
+ break;
+
+ case 7:
+
+ entity = self.parent.collection[type.raw] || GETSCHEMA(type.raw);
+
+ if (entity) {
+ tmp = entity.prepare(tmp, dependencies, req, verifications);
+ tmp.$$parent = item;
+ tmp.$$controller = req;
+ dependencies && dependencies.push({ name: type.raw, value: self.$onprepare(property, tmp, j, model, req) });
+ } else
+ throw new Error('Schema "{0}" not found'.format(type.raw));
+
+ tmp = self.$onprepare(property, tmp, j, model, req);
+
+ if (entity.verifications && tmp)
+ verifications.push({ model: tmp, entity: entity });
+
+ break;
+
+ case 11:
+ tmp = self.$onprepare(property, typeval === 'number' ? tmp : typeval === 'string' ? parseNumber(tmp) : null, j, model, req);
+ if (tmp == null)
+ continue;
+ break;
+
+ case 12:
+ tmp = self.$onprepare(property, tmp ? CONVERT(tmp, type.raw) : null, j, model, req);
+ if (tmp == null)
+ continue;
+ break;
+ }
+
+ if (tmp !== undefined)
+ item[property].push(tmp);
+ }
+ }
+
+ if (self.fields_allow) {
+ for (var i = 0, length = self.fields_allow.length; i < length; i++) {
+ var name = self.fields_allow[i];
+ var val = model[name];
+ if (val !== undefined) {
+ item[name] = val;
+ keys && keys.push(name);
+ }
+ }
+ }
+
+ if (keys)
+ item.$$keys = keys;
+
+ return item;
+};
+
+function parseNumber(str) {
+ if (!str)
+ return null;
+ if (str.indexOf(',') !== -1)
+ str = str.replace(',', '.');
+ var num = +str;
+ return isNaN(num) ? null : num;
+}
+
+/**
+ * Transform an object
+ * @param {String} name
+ * @param {Object} model
+ * @param {Object} options Custom options object, optional.
+ * @param {Function(errorBuilder, output, model)} callback
+ * @param {Boolean} skip Skips preparing and validation, optional.
+ * @param {Object} controller Optional
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.transform = function(name, model, options, callback, skip, controller) {
+ return this.$execute('transform', name, model, options, callback, skip, controller);
+};
+
+SchemaBuilderEntityProto.transform2 = function(name, options, callback, controller) {
+
+ if (typeof(options) === 'function') {
+ controller = callback;
+ callback = options;
+ options = undefined;
+ }
+
+ !callback && (callback = function(){});
+ return this.transform(name, this.create(), options, callback, true, controller);
+};
+
+SchemaBuilderEntityProto.$process = function(arg, model, type, name, builder, response, callback, controller) {
+
+ var self = this;
+
+ if (arg.length > 1 || (response instanceof Error || response instanceof ErrorBuilder)) {
+ if ((response instanceof Error || response instanceof ErrorBuilder || typeof(response) === 'string') && builder !== response)
+ builder.push(response);
+ response = arg[1];
+ }
+
+ var has = builder.is;
+ has && self.onError && self.onError(builder, model, type, name);
+
+ if (response !== NoOp) {
+ if (controller && response instanceof SchemaInstance && !response.$$controller)
+ response.$$controller = controller;
+ callback(has ? builder : null, response === undefined ? model : response, model);
+ } else
+ callback = null;
+
+ return self;
+};
+
+SchemaBuilderEntityProto.$process_hook = function(model, type, name, builder, result, callback) {
+ var self = this;
+ var has = builder.is;
+ has && self.onError && self.onError(builder, model, type, name);
+ callback(has ? builder : null, model, result);
+ return self;
+};
+
+/**
+ * Run a workflow
+ * @param {String} name
+ * @param {Object} model
+ * @param {Object} options Custom options object, optional.
+ * @param {Function(errorBuilder, output, model)} callback
+ * @param {Boolean} skip Skips preparing and validation, optional.
+ * @param {Object} controller Optional
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.workflow = function(name, model, options, callback, skip, controller) {
+ return this.$execute('workflow', name, model, options, callback, skip, controller);
+};
+
+SchemaBuilderEntityProto.workflow2 = function(name, options, callback, controller) {
+
+ if (typeof(options) === 'function') {
+ controller = callback;
+ callback = options;
+ options = undefined;
+ }
+
+ !callback && (callback = function(){});
+ return this.workflow(name, this.create(), options, callback, true, controller);
+};
+
+/**
+ * Run hooks
+ * @param {String} name
+ * @param {Object} model
+ * @param {Object} helper A helper object, optional.
+ * @param {Function(errorBuilder, output, model)} callback
+ * @param {Boolean} skip Skips preparing and validation, optional.
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.hook = function(name, model, options, callback, skip, controller) {
+
+ var self = this;
+
+ if (typeof(name) !== 'string') {
+ callback = options;
+ options = model;
+ model = name;
+ name = 'default';
+ }
+
+ if (typeof(callback) === 'boolean') {
+ skip = callback;
+ callback = options;
+ options = undefined;
+ } else if (typeof(options) === 'function') {
+ callback = options;
+ options = undefined;
+ }
+
+ if (typeof(callback) !== 'function')
+ callback = function(){};
+
+ var hook = self.hooks ? self.hooks[name] : undefined;
+
+ if (!hook || !hook.length) {
+ callback(null, model, EMPTYARRAY);
+ return self;
+ }
+
+ if (controller instanceof SchemaOptions || controller instanceof OperationOptions)
+ controller = controller.controller;
+
+ if (model && !controller && model.$$controller)
+ controller = model.$$controller;
+
+ var $type = 'hook';
+
+ if (skip === true || model instanceof SchemaInstance) {
+
+ var builder = new ErrorBuilder();
+ self.resourceName && builder.setResource(self.resourceName);
+ self.resourcePrefix && builder.setPrefix(self.resourcePrefix);
+
+ var output = [];
+ var $now;
+
+ if (CONF.logger)
+ $now = Date.now();
+
+ async_wait(hook, function(item, next) {
+ if (item.fn.$newversion) {
+
+ var opt = new SchemaOptions(builder, model, options, function(result) {
+ output.push(result == undefined ? model : result);
+ next();
+ }, controller, 'hook.' + name, self);
+
+ if (self.middlewares && self.middlewares.length)
+ runmiddleware(opt, self, item.fn);
+ else
+ item.fn.call(self, opt);
+
+ } else
+ item.fn.call(self, builder, model, options, function(result) {
+ output.push(result == undefined ? model : result);
+ next();
+ }, controller, skip !== true);
+ }, function() {
+ CONF.logger && F.ilogger(self.getLoggerName($type, name), controller, $now);
+ self.$process_hook(model, $type, name, builder, output, callback);
+ }, 0);
+
+ return self;
+ }
+
+ self.$prepare(model, function(err, model) {
+
+ if (err) {
+ callback(err, model);
+ return;
+ }
+
+ var builder = new ErrorBuilder();
+ var output = [];
+ var $now;
+
+ self.resourceName && builder.setResource(self.resourceName);
+ self.resourcePrefix && builder.setPrefix(self.resourcePrefix);
+
+ if (CONF.logger)
+ $now = Date.now();
+
+ async_wait(hook, function(item, next, index) {
+
+ if (!isGenerator(self, 'hook.' + name + '.' + index, item.fn)) {
+ if (item.fn.$newversion) {
+ item.fn.call(self, new SchemaOptions(builder, model, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type, name), controller, $now);
+ output.push(res === undefined ? model : res);
+ next();
+ }, controller, 'hook.' + name, self));
+ } else {
+ item.fn.call(self, builder, model, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type, name), controller, $now);
+ output.push(res === undefined ? model : res);
+ next();
+ }, controller, skip !== true);
+ }
+ return;
+ }
+
+ callback.success = false;
+
+ if (item.fn.$newversion) {
+ var opt = new SchemaOptions(builder, model, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type, name), controller, $now);
+ output.push(res == undefined ? model : res);
+ next();
+ }, controller, 'hook.' + name, self);
+
+ async.call(self, item.fn)(function(err) {
+ if (!err)
+ return;
+ if (builder !== err)
+ builder.push(err);
+ next();
+ }, opt);
+
+ } else {
+ async.call(self, item.fn)(function(err) {
+ if (!err)
+ return;
+ if (builder !== err)
+ builder.push(err);
+ next();
+ }, builder, model, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type, name), controller, $now);
+ output.push(res == undefined ? model : res);
+ next();
+ }, controller, skip !== true);
+ }
+
+ }, () => self.$process_hook(model, $type, name, builder, output, callback), 0);
+ }, controller ? controller.req : null);
+
+ return self;
+};
+
+SchemaBuilderEntityProto.hook2 = function(name, options, callback, controller) {
+
+ if (typeof(options) === 'function') {
+ controller = callback;
+ callback = options;
+ options = undefined;
+ }
+
+ if (!callback)
+ callback = function(){};
+
+ return this.hook(name, this.create(), options, callback, true, controller);
+};
+
+SchemaBuilderEntityProto.$execute = function(type, name, model, options, callback, skip, controller) {
+ var self = this;
+
+ if (typeof(name) !== 'string') {
+ callback = options;
+ options = model;
+ model = name;
+ name = 'default';
+ }
+
+ if (typeof(callback) === 'boolean') {
+ skip = callback;
+ callback = options;
+ options = undefined;
+ } else if (typeof(options) === 'function') {
+ callback = options;
+ options = undefined;
+ }
+
+ if (typeof(callback) !== 'function')
+ callback = function(){};
+
+ var ref = self[type + 's'];
+ var item = ref ? ref[name] : undefined;
+ var $now;
+
+ if (!item) {
+ callback(new ErrorBuilder().push('', type.capitalize() + ' "{0}" not found.'.format(name)));
+ return self;
+ }
+
+ if (CONF.logger)
+ $now = Date.now();
+
+ if (controller instanceof SchemaOptions || controller instanceof OperationOptions)
+ controller = controller.controller;
+
+ if (model && !controller && model.$$controller)
+ controller = model.$$controller;
+
+ var opfilter = self.meta[type + 'filter#' + name];
+ if (opfilter && controller) {
+ controller.$filterschema = opfilter;
+ controller.$filter = null;
+ }
+
+ var key = type + '.' + name;
+
+ if (skip === true || model instanceof SchemaInstance) {
+ var builder = new ErrorBuilder();
+ self.resourceName && builder.setResource(self.resourceName);
+ self.resourcePrefix && builder.setPrefix(self.resourcePrefix);
+ if (item.$newversion) {
+
+ var opt = new SchemaOptions(builder, model, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName(type, name), controller, $now);
+ self.$process(arguments, model, type, name, builder, res, callback, controller);
+ }, controller, key, self);
+
+ if (self.middlewares && self.middlewares.length)
+ runmiddleware(opt, self, item);
+ else
+ item.call(self, opt);
+
+ } else
+ item.call(self, builder, model, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName(type, name), controller, $now);
+ self.$process(arguments, model, type, name, builder, res, callback, controller);
+ }, controller, skip !== true);
+ return self;
+ }
+
+ self.$prepare(model, function(err, model) {
+
+ if (err) {
+ callback(err, model);
+ return;
+ }
+
+ if (controller && model instanceof SchemaInstance && !model.$$controller)
+ model.$$controller = controller;
+
+ var builder = new ErrorBuilder();
+
+ self.resourceName && builder.setResource(self.resourceName);
+ self.resourcePrefix && builder.setPrefix(self.resourcePrefix);
+
+ var key = type + '.' + name;
+
+ if (!isGenerator(self, key, item)) {
+ if (item.$newversion) {
+ var opt = new SchemaOptions(builder, model, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName(type, name), controller, $now);
+ self.$process(arguments, model, type, name, builder, res, callback, controller);
+ }, controller, key, self);
+
+ if (self.middlewares && self.middlewares.length)
+ runmiddleware(opt, self, item);
+ else
+ item.call(self, opt);
+
+ } else
+ item.call(self, builder, model, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName(type, name), controller, $now);
+ self.$process(arguments, model, type, name, builder, res, callback, controller);
+ }, controller);
+ return;
+ }
+
+ callback.success = false;
+
+ var onError = function(err) {
+ if (!err || callback.success)
+ return;
+ callback.success = true;
+ if (builder !== err)
+ builder.push(err);
+ self.onError && self.onError(builder, model, type, name);
+ callback(builder);
+ };
+
+ var onCallback = function(res) {
+
+ CONF.logger && F.ilogger(self.getLoggerName(type, name), controller, $now);
+
+ if (callback.success)
+ return;
+
+ if (arguments.length === 2 || (res instanceof Error || res instanceof ErrorBuilder)) {
+ if ((res instanceof Error || res instanceof ErrorBuilder) && builder !== res)
+ builder.push(res);
+ res = arguments[1];
+ }
+
+ var has = builder.is;
+ has && self.onError && self.onError(builder, model, type, name);
+ callback.success = true;
+ callback(has ? builder : null, res === undefined ? model : res);
+ };
+
+ if (item.$newversion) {
+ var opt = new SchemaOptions(builder, model, options, onCallback, controller, key, self);
+ if (self.middlewares && self.middlewares.length)
+ runmiddleware(opt, self, () => async.call(self, item)(onError, opt));
+ else
+ async.call(self, item)(onError, opt);
+ } else
+ async.call(self, item)(onError, builder, model, options, onCallback, controller);
+ }, controller ? controller.req : null);
+
+ return self;
+};
+
+SchemaBuilderEntityProto.getLoggerName = function(type, name) {
+ return this.name + '.' + type + (name ? ('(\'' + name + '\')') : '()');
+};
+
+/**
+ * Run a workflow
+ * @param {String} name
+ * @param {Object} model
+ * @param {Object} options Custom options object, optional.
+ * @param {Function(errorBuilder, output, model)} callback
+ * @param {Boolean} skip Skips preparing and validation, optional.
+ * @param {Object} controller Optional
+ * @return {SchemaBuilderEntity}
+ */
+SchemaBuilderEntityProto.operation = function(name, model, options, callback, skip, controller) {
+
+ var self = this;
+
+ var th = typeof(options);
+ var tc = typeof(callback);
+
+ if (tc === 'undefined') {
+ if (th === 'function') {
+ callback = options;
+ options = model;
+ model = undefined;
+ } else if (th === 'undefined') {
+ options = model;
+ model = undefined;
+ }
+ } else if (th === 'undefined') {
+ options = model;
+ model = undefined;
+ } else if (tc === 'boolean') {
+ skip = callback;
+ callback = options;
+ options = undefined;
+ }
+
+ if (typeof(options) === 'function') {
+ callback = options;
+ options = undefined;
+ }
+
+ if (typeof(callback) !== 'function')
+ callback = function(){};
+
+ var operation = self.operations ? self.operations[name] : undefined;
+
+ if (!operation) {
+ callback(new ErrorBuilder().push('', 'Operation "{0}" not found.'.format(name)));
+ return self;
+ }
+
+ var builder = new ErrorBuilder();
+ var $type = 'operation';
+ var $now;
+
+ self.resourceName && builder.setResource(self.resourceName);
+ self.resourcePrefix && builder.setPrefix(self.resourcePrefix);
+
+ if (controller instanceof SchemaOptions || controller instanceof OperationOptions)
+ controller = controller.controller;
+
+ if (model && !controller && model.$$controller)
+ controller = model.$$controller;
+
+ if (CONF.logger)
+ $now = Date.now();
+
+ var key = $type + '.' + name;
+
+ if (!isGenerator(self, key, operation)) {
+ if (operation.$newversion) {
+ operation.call(self, new SchemaOptions(builder, model, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type, name), controller, $now);
+ self.$process(arguments, model, $type, name, builder, res, callback, controller);
+ }, controller, key, self));
+ } else
+ operation.call(self, builder, model, options, function(res) {
+ CONF.logger && F.ilogger(self.getLoggerName($type, name), controller, $now);
+ self.$process(arguments, model, $type, name, builder, res, callback, controller);
+ }, controller, skip !== true);
+ return self;
+ }
+
+ callback.success = false;
+
+ var onError = function(err) {
+ if (!err || callback.success)
+ return;
+ callback.success = true;
+ if (builder !== err)
+ builder.push(err);
+ self.onError && self.onError(builder, model, $type, name);
+ callback(builder);
+ };
+
+ var onCallback = function(res) {
+
+ CONF.logger && F.ilogger(self.getLoggerName($type, name), controller, $now);
+
+ if (callback.success)
+ return;
+
+ if (arguments.length === 2 || (res instanceof Error || res instanceof ErrorBuilder)) {
+ if ((res instanceof Error || res instanceof ErrorBuilder) && builder !== res)
+ builder.push(res);
+ res = arguments[1];
+ }
+
+ var has = builder.is;
+ has && self.onError && self.onError(builder, model, $type, name);
+ callback.success = true;
+ callback(has ? builder : null, res);
+ };
+
+ if (operation.$newversion)
+ async.call(self, operation)(onError, new SchemaOptions(builder, model, options, onCallback, controller, key, self));
+ else
+ async.call(self, operation)(onError, builder, model, options, onCallback, controller, skip !== true);
+
+ return self;
+};
+
+SchemaBuilderEntityProto.operation2 = function(name, options, callback, controller) {
+
+ if (typeof(options) === 'function') {
+ controller = callback;
+ callback = options;
+ options = undefined;
+ }
+
+ !callback && (callback = function(){});
+ return this.operation(name, this.create(), options, callback, true, controller);
+};
+
+/**
+ * Clean model (remove state of all schemas in model).
+ * @param {Object} m Model.
+ * @param {Boolean} isCopied Internal argument.
+ * @return {Object}
+ */
+SchemaBuilderEntityProto.clean = function(m) {
+ return clone(m);
+};
+
+// For async operations, because SUCCESS() returns singleton instance everytime
+function copy(obj) {
+ return F.isSuccess(obj) ? { success: obj.success, value: obj.value } : obj;
+}
+
+function clone(obj) {
+
+ if (!obj)
+ return obj;
+
+ var type = typeof(obj);
+ if (type !== 'object' || obj instanceof Date)
+ return obj;
+
+ var length;
+ var o;
+
+ if (obj instanceof Array) {
+
+ length = obj.length;
+ o = new Array(length);
+
+ for (var i = 0; i < length; i++) {
+ type = typeof(obj[i]);
+ if (type !== 'object' || obj[i] instanceof Date) {
+ if (type !== 'function')
+ o[i] = obj[i];
+ continue;
+ }
+ if (obj[i] instanceof SchemaInstance)
+ o[i] = obj[i].$clean();
+ else
+ o[i] = clone(obj[i]);
+ }
+
+ return o;
+ }
+
+ o = {};
+
+ for (var m in obj) {
+
+ if (SKIP[m])
+ continue;
+
+ var val = obj[m];
+
+ if (val instanceof Array) {
+ o[m] = clone(val);
+ continue;
+ }
+
+ if (val instanceof SchemaInstance) {
+ o[m] = val.$clean();
+ continue;
+ }
+
+ var type = typeof(val);
+ if (type !== 'object' || val instanceof Date) {
+ if (type !== 'function')
+ o[m] = val;
+ continue;
+ }
+
+ // Because here can be a problem with MongoDB.ObjectID
+ // I assume plain/simple model
+ if (val && val.constructor === Object)
+ o[m] = clone(obj[m]);
+ else
+ o[m] = val;
+ }
+
+ return o;
+}
+
+/**
+ * Returns prototype of instances
+ * @returns {Object}
+ */
+SchemaBuilderEntityProto.instancePrototype = function() {
+ return this.CurrentSchemaInstance.prototype;
+};
+
+SchemaBuilderEntityProto.cl = function(name, value) {
+ var o = this.schema[name];
+ if (o && (o.type === 8 || o.type === 9)) {
+ if (value)
+ o.raw = value;
+ return o.raw;
+ }
+};
+
+SchemaBuilderEntityProto.props = function() {
+
+ var self = this;
+ var keys = Object.keys(self.schema);
+ var prop = {};
+
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var meta = self.schema[key];
+ var obj = {};
+
+ if (meta.required)
+ obj.required = meta.required;
+
+ if (meta.length)
+ obj.length = meta.length;
+
+ if (meta.isArray)
+ meta.array = true;
+
+ switch (meta.type) {
+ case 1:
+ case 2:
+ case 11:
+ obj.type = 'number';
+ break;
+ case 3:
+ obj.type = 'string';
+ switch (meta.subtype) {
+ case 'uid':
+ obj.type = 'uid';
+ delete obj.length;
+ break;
+ default:
+ obj.subtype = meta.subtype;
+ break;
+ }
+ break;
+
+ case 4:
+ obj.type = 'boolean';
+ break;
+ case 5:
+ obj.type = 'date';
+ break;
+ case 7:
+ obj.type = 'schema';
+ obj.name = meta.raw;
+ break;
+ case 8:
+ obj.type = 'enum';
+ obj.items = meta.raw;
+ break;
+ case 9:
+ // obj.type = 'keyvalue';
+ obj.type = 'enum'; // because it returns keys only
+ obj.items = Object.keys(meta.raw);
+ break;
+ // case 6:
+ // case 0:
+ // case 10:
+ default:
+ obj.type = 'object';
+ break;
+ }
+
+ prop[key] = obj;
+ }
+
+ return prop;
+};
+
+/**
+ * SchemaInstance
+ * @constructor
+ */
+function SchemaInstance() {
+}
+
+/**
+ * @type {SchemaBuilderEntity}
+ */
+SchemaInstance.prototype.$$schema = null;
+
+SchemaInstance.prototype.$async = function(callback, index) {
+ var self = this;
+ !callback && (callback = function(){});
+
+ var a = self.$$async = {};
+
+ a.callback = callback;
+ a.index = index;
+ a.indexer = 0;
+ a.response = [];
+ a.fn = [];
+ a.op = [];
+ a.pending = 0;
+
+ a.next = function() {
+ a.running = true;
+ var fn = a.fn ? a.fn.shift() : null;
+ if (fn) {
+ a.pending++;
+ fn.fn(a.done, a.indexer++);
+ fn.async && a.next();
+ }
+ };
+
+ a.done = function() {
+ a.running = false;
+ a.pending--;
+ if (a.fn.length)
+ setImmediate(a.next);
+ else if (!a.pending && a.callback)
+ a.callback(null, a.index != null ? a.response[a.index] : a.response);
+ };
+
+ setImmediate(a.next);
+ return self;
+};
+
+function async_wait(arr, onItem, onCallback, index) {
+ var item = arr[index];
+ if (item)
+ onItem(item, () => async_wait(arr, onItem, onCallback, index + 1), index);
+ else
+ onCallback();
+}
+
+Object.defineProperty(SchemaInstance.prototype, '$parent', {
+ get: function() {
+ return this.$$parent;
+ },
+ set: function(value) {
+ this.$$parent = value;
+ }
+});
+
+SchemaInstance.prototype.$response = function(index) {
+ var a = this.$$async;
+ if (a) {
+
+ if (index == null)
+ return a.response;
+
+ if (typeof(index) === 'string') {
+
+ if (index === 'prev')
+ return a.response[a.response.length - 1];
+
+ index = a.op.indexOf(index);
+
+ if (index !== -1)
+ return a.response[index];
+
+ } else
+ return a.response[index];
+ }
+};
+
+SchemaInstance.prototype.$repository = function(name, value) {
+
+ if (this.$$repository === undefined) {
+ if (value === undefined)
+ return undefined;
+ this.$$repository = {};
+ }
+
+ if (value !== undefined) {
+ this.$$repository[name] = value;
+ return value;
+ }
+
+ return this.$$repository[name];
+};
+
+SchemaInstance.prototype.$index = function(index) {
+ var a = this.$$async;
+ if (a) {
+ if (typeof(index) === 'string')
+ a.index = (a.index || 0).add(index);
+ a.index = index;
+ }
+ return this;
+};
+
+SchemaInstance.prototype.$callback = function(callback) {
+ var a = this.$$async;
+ if (a)
+ a.callback = callback;
+ return this;
+};
+
+SchemaInstance.prototype.$output = function() {
+ var a = this.$$async;
+ if (a)
+ a.index = true;
+ return this;
+};
+
+SchemaInstance.prototype.$stop = function() {
+ this.async.length = 0;
+ return this;
+};
+
+const PUSHTYPE1 = { save: 1, insert: 1, update: 1, patch: 1 };
+const PUSHTYPE2 = { query: 1, get: 1, read: 1, remove: 1 };
+
+SchemaInstance.prototype.$push = function(type, name, helper, first, async, callback) {
+
+ var self = this;
+ var fn;
+
+ if (PUSHTYPE1[type]) {
+ fn = function(next, indexer) {
+ self.$$schema[type](self, helper, function(err, result) {
+ var a = self.$$async;
+ a.response && (a.response[indexer] = err ? null : copy(result));
+ if (a.index === true)
+ a.index = indexer;
+ callback && callback(err, a.response[indexer]);
+ if (!err)
+ return next();
+ next = null;
+ a.callback(err, a.response);
+ }, self.$$controller);
+ };
+
+ } else if (PUSHTYPE2[type]) {
+ fn = function(next, indexer) {
+ self.$$schema[type](helper, function(err, result) {
+ var a = self.$$async;
+ a.response && (a.response[indexer] = err ? null : copy(result));
+ if (a.index === true)
+ a.index = indexer;
+ callback && callback(err, a.response[indexer]);
+ if (!err)
+ return next();
+ next = null;
+ a.callback(err, a.response);
+ }, self.$$controller);
+ };
+ } else {
+ fn = function(next, indexer) {
+ self.$$schema[type](name, self, helper, function(err, result) {
+ var a = self.$$async;
+ a.response && (a.response[indexer] = err ? null : copy(result));
+ if (a.index === true)
+ a.index = indexer;
+ callback && callback(err, a.response[indexer]);
+ if (!err)
+ return next();
+ next = null;
+ a.callback(err, a.response);
+ }, self.$$controller);
+ };
+ }
+
+ var a = self.$$async;
+ var obj = { fn: fn, async: async, index: a.length };
+ var key = type === 'workflow' || type === 'transform' || type === 'operation' || type === 'hook' ? (type + '.' + name) : type;
+
+ if (first) {
+ a.fn.unshift(obj);
+ a.op.unshift(key);
+ } else {
+ a.fn.push(obj);
+ a.op.push(key);
+ }
+
+ return self;
+};
+
+SchemaInstance.prototype.$next = function(type, name, helper, async) {
+ return this.$push(type, name, helper, true, async);
+};
+
+SchemaInstance.prototype.$exec = function(name, helper, callback) {
+
+ if (typeof(helper) === 'function') {
+ callback = helper;
+ helper = undefined;
+ }
+
+ var group = this.$$schema.parent.name;
+ var key = group !== 'default' ? group + '/' + this.$$schema.name : this.$$schema.name;
+ var workflow = F.workflows[key + '#' + name] || F.workflows[name];
+
+ if (workflow)
+ workflow(this, helper, callback || NOOP);
+ else
+ callback && callback(new ErrorBuilder().push('Workflow "' + name + '" not found in workflows.'));
+
+ return this;
+};
+
+SchemaInstance.prototype.$controller = function(controller) {
+ this.$$controller = controller;
+ return this;
+};
+
+SchemaInstance.prototype.$save = function(helper, callback, async) {
+
+ if (this.$$async && !this.$$async.running) {
+ if (typeof(helper) === 'function') {
+ async = callback;
+ callback = helper;
+ helper = null;
+ } else if (callback === true) {
+ var a = async;
+ async = true;
+ callback = a;
+ }
+
+ this.$push('save', null, helper, null, async, callback);
+
+ } else
+ this.$$schema.save(this, helper, callback, this.$$controller);
+ return this;
+};
+
+SchemaInstance.prototype.$insert = function(helper, callback, async) {
+
+ if (this.$$async && !this.$$async.running) {
+
+ if (typeof(helper) === 'function') {
+ async = callback;
+ callback = helper;
+ helper = null;
+ } else if (callback === true) {
+ var a = async;
+ async = true;
+ callback = a;
+ }
+
+ this.$push('insert', null, helper, null, async, callback);
+
+ } else
+ this.$$schema.insert(this, helper, callback, this.$$controller);
+ return this;
+};
+
+SchemaInstance.prototype.$update = function(helper, callback, async) {
+ if (this.$$async && !this.$$async.running) {
+ if (typeof(helper) === 'function') {
+ async = callback;
+ callback = helper;
+ helper = null;
+ } else if (callback === true) {
+ var a = async;
+ async = true;
+ callback = a;
+ }
+ this.$push('update', null, helper, null, async, callback);
+ } else
+ this.$$schema.update(this, helper, callback, this.$$controller);
+ return this;
+};
+
+SchemaInstance.prototype.$patch = function(helper, callback, async) {
+ if (this.$$async && !this.$$async.running) {
+ if (typeof(helper) === 'function') {
+ async = callback;
+ callback = helper;
+ helper = null;
+ } else if (callback === true) {
+ var a = async;
+ async = true;
+ callback = a;
+ }
+ this.$push('patch', null, helper, null, async, callback);
+ } else
+ this.$$schema.patch(this, helper, callback, this.$$controller);
+ return this;
+};
+
+SchemaInstance.prototype.$query = function(helper, callback, async) {
+
+ if (this.$$async && !this.$$async.running) {
+
+ if (typeof(helper) === 'function') {
+ async = callback;
+ callback = helper;
+ helper = null;
+ } else if (callback === true) {
+ var a = async;
+ async = true;
+ callback = a;
+ }
+
+ this.$push('query', null, helper, null, async, callback);
+ } else
+ this.$$schema.query(this, helper, callback, this.$$controller);
+
+ return this;
+};
+
+SchemaInstance.prototype.$read = SchemaInstance.prototype.$get = function(helper, callback, async) {
+
+ if (this.$$async && !this.$$async.running) {
+
+ if (typeof(helper) === 'function') {
+ async = callback;
+ callback = helper;
+ helper = null;
+ } else if (callback === true) {
+ var a = async;
+ async = true;
+ callback = a;
+ }
+
+ this.$push('get', null, helper, null, async, callback);
+ } else
+ this.$$schema.get(this, helper, callback, this.$$controller);
+
+ return this;
+};
+
+SchemaInstance.prototype.$delete = SchemaInstance.prototype.$remove = function(helper, callback, async) {
+
+ if (this.$$async && !this.$$async.running) {
+
+ if (typeof(helper) === 'function') {
+ async = callback;
+ callback = helper;
+ helper = null;
+ } else if (callback === true) {
+ var a = async;
+ async = true;
+ callback = a;
+ }
+
+ this.$push('remove', null, helper, null, async, callback);
+
+ } else
+ this.$$schema.remove(helper, callback, this.$$controller);
+
+ return this;
+};
+
+SchemaInstance.prototype.$default = function() {
+ return this.$$schema.default();
+};
+
+SchemaInstance.prototype.$destroy = function() {
+ return this.$$schema.destroy();
+};
+
+SchemaInstance.prototype.$transform = function(name, helper, callback, async) {
+
+ if (this.$$async && !this.$$async.running) {
+
+ if (typeof(helper) === 'function') {
+ async = callback;
+ callback = helper;
+ helper = null;
+ } else if (callback === true) {
+ var a = async;
+ async = true;
+ callback = a;
+ }
+
+ this.$push('transform', name, helper, null, async, callback);
+
+ } else
+ this.$$schema.transform(name, this, helper, callback, undefined, this.$$controller);
+
+ return this;
+};
+
+SchemaInstance.prototype.$workflow = function(name, helper, callback, async) {
+
+ if (this.$$async && !this.$$async.running) {
+
+ if (typeof(helper) === 'function') {
+ async = callback;
+ callback = helper;
+ helper = null;
+ } else if (callback === true) {
+ var a = async;
+ async = true;
+ callback = a;
+ }
+
+ this.$push('workflow', name, helper, null, async, callback);
+
+ } else
+ this.$$schema.workflow(name, this, helper, callback, undefined, this.$$controller);
+
+ return this;
+};
+
+SchemaInstance.prototype.$hook = function(name, helper, callback, async) {
+
+ if (this.$$async && !this.$$async.running) {
+
+ if (typeof(helper) === 'function') {
+ async = callback;
+ callback = helper;
+ helper = null;
+ } else if (callback === true) {
+ var a = async;
+ async = true;
+ callback = a;
+ }
+
+ this.$push('hook', name, helper, null, async, callback);
+
+ } else
+ this.$$schema.hook(name, this, helper, callback, undefined, this.$$controller);
+
+ return this;
+};
+
+SchemaInstance.prototype.$operation = function(name, helper, callback, async) {
+
+ if (this.$$async && !this.$$async.running) {
+
+ if (typeof(helper) === 'function') {
+ async = callback;
+ callback = helper;
+ helper = null;
+ } else if (callback === true) {
+ var a = async;
+ async = true;
+ callback = a;
+ }
+
+ this.$push('operation', name, helper, null, async, callback);
+ } else
+ this.$$schema.operation(name, this, helper, callback, undefined, this.$$controller);
+
+ return this;
+};
+
+SchemaInstance.prototype.$clean = SchemaInstance.prototype.$plain = function() {
+ return this.$$schema.clean(this);
+};
+
+SchemaInstance.prototype.$clone = function() {
+ return framework_utils.extend(new this.$$schema.CurrentSchemaInstance(), this, true);
+};
+
+SchemaInstance.prototype.$prepare = function() {
+ return this.$$schema.prepare(this);
+};
+
+SchemaInstance.prototype.$schema = function() {
+ return this.$$schema;
+};
+
+SchemaInstance.prototype.$validate = function(resourcePrefix, resourceName, builder) {
+ return this.$$schema.validate(this, resourcePrefix, resourceName, builder);
+};
+
+SchemaInstance.prototype.$constant = function(name) {
+ return this.$$schema.constant(name);
+};
+
+/**
+ * ErrorBuilder
+ * @class
+ * @classdesc Object validation.
+ * @param {ErrorBuilderOnResource} onResource Resource handler.
+ * @property {Number} count Count of errors.
+ */
+function ErrorBuilder(onResource) {
+
+ this.items = [];
+ this.transformName = transforms.error_default;
+ this.onResource = onResource;
+ this.resourceName = CONF.default_errorbuilder_resource_name;
+ this.resourcePrefix = CONF.default_errorbuilder_resource_prefix || '';
+ this.isResourceCustom = false;
+ this.count = 0;
+ this.replacer = [];
+ this.isPrepared = false;
+ this.contentType = 'application/json';
+ this.status = CONF.default_errorbuilder_status || 200;
+
+ // Hidden: when the .push() contains a classic Error instance
+ // this.unexpected;
+
+ // A default path for .push()
+ // this.path;
+
+ !onResource && this._resource();
+}
+
+/**
+ * @callback ErrorBuilderOnResource
+ * @param {String} name Filename of resource.
+ * @param {String} key Resource key.
+ * @return {String}
+ */
+
+/**
+ * UrlBuilder
+ * @class
+ * @classdesc CRUD parameters in URL.
+ */
+function UrlBuilder() {
+ this.builder = {};
+}
+
+exports.isSchema = function(obj) {
+ return obj instanceof SchemaInstance;
+};
+
+global.EACHSCHEMA = exports.eachschema = function(group, fn) {
+
+ if (fn === undefined) {
+ fn = group;
+ group = undefined;
+ }
+
+ var groups = group ? [group] : Object.keys(schemas);
+ for (var i = 0, length = groups.length; i < length; i++) {
+ var schema = schemas[groups[i]];
+ if (!schema)
+ continue;
+ var collection = Object.keys(schema.collection);
+ for (var j = 0, jl = collection.length; j < jl; j++)
+ fn(schema.name, schema.collection[collection[j]].name, schema.collection[collection[j]]);
+ }
+};
+
+global.$$$ = global.GETSCHEMA = exports.getschema = function(group, name, fn, timeout) {
+
+ if (!name || typeof(name) === 'function') {
+ timeout = fn;
+ fn = name;
+ } else
+ group = group + '/' + name;
+
+ if (schemacache[group])
+ group = schemacache[group];
+ else {
+ if (group.indexOf('/') === -1)
+ group = DEFAULT_SCHEMA + '/' + group;
+ group = schemacache[group] = group.toLowerCase();
+ }
+
+ if (fn)
+ framework_utils.wait(() => !!schemasall[group], err => fn(err, schemasall[group]), timeout || 20000);
+ else
+ return schemasall[group];
+};
+
+exports.findschema = function(groupname) {
+ return schemasall[groupname.toLowerCase()];
+};
+
+exports.newschema = function(group, name) {
+
+ if (!group)
+ group = DEFAULT_SCHEMA;
+
+ if (!schemas[group])
+ schemas[group] = new SchemaBuilder(group);
+
+ var o = schemas[group].create(name);
+ var key = group + '/' + name;
+
+ o.owner = F.$owner();
+ schemasall[key.toLowerCase()] = o;
+
+ return o;
+};
+
+/**
+ * Remove a schema
+ * @param {String} group Optional
+ * @param {String} name
+ */
+exports.remove = function(group, name) {
+ if (name) {
+
+ var g = schemas[group || DEFAULT_SCHEMA];
+ g && g.remove(name);
+ var key = ((group || DEFAULT_SCHEMA) + '/' + name).toLowerCase();
+ delete schemasall[key];
+
+ } else {
+
+ delete schemas[group];
+
+ var lower = group.toLowerCase();
+
+ Object.keys(schemasall).forEach(function(key) {
+ if (key.substring(0, group.length) === lower)
+ delete schemasall[key];
+ });
+ }
+};
+
+global.EACHOPERATION = function(fn) {
+ var keys = Object.keys(operations);
+ for (var i = 0, length = keys.length; i < length; i++)
+ fn(keys[i]);
+};
+
+/**
+ * Check if property value is joined to other class
+ * @private
+ * @param {String} value Property value from Schema definition.
+ * @return {Boolean}
+ */
+exports.isJoin = function(collection, value) {
+ if (!value)
+ return false;
+ if (value[0] === '[')
+ return true;
+ if (collection === undefined)
+ return false;
+ return collection[value] !== undefined;
+};
+
+/**
+ * Create validation
+ * @param {String} name Schema name.
+ * @param {Function|Array} fn Validator Handler or Property names as array for validating.
+ * @param {String|Array} properties Valid only these properties, optional.
+ * @return {Function|Array}
+ */
+exports.validation = function(name, properties, fn) {
+
+ if (schemas[DEFAULT_SCHEMA] === undefined)
+ return EMPTYARRAY;
+
+ var schema = schemas[DEFAULT_SCHEMA].get(name);
+ if (schema === undefined)
+ return EMPTYARRAY;
+
+ if (fn instanceof Array && typeof(properties) === 'function') {
+ var tmp = fn;
+ fn = properties;
+ properties = tmp;
+ }
+
+ if (typeof(fn) === 'function') {
+ schema.onValidate = fn;
+ if (properties)
+ schema.properties = properties;
+ else
+ schema.properties = Object.keys(schema.schema);
+ return true;
+ }
+
+ if (!fn) {
+ var validator = schema.properties;
+ if (validator === undefined)
+ return Object.keys(schema.schema);
+ return validator || [];
+ }
+
+ schema.onValidate = fn;
+ return fn;
+};
+
+/**
+ * Validate model
+ * @param {String} name Schema name.
+ * @param {Object} model Object for validating.
+ * @return {ErrorBuilder}
+ */
+exports.validate = function(name, model, resourcePrefix, resourceName) {
+ var schema = schemas[DEFAULT_SCHEMA];
+ if (schema === undefined)
+ return null;
+ schema = schema.get(name);
+ model = schema.prepare(model);
+ return schema === undefined ? null : schema.validate(model, resourcePrefix, resourceName);
+};
+
+/**
+ * Create default object according to schema
+ * @param {String} name Schema name.
+ * @return {Object}
+ */
+exports.create = function(name) {
+ return exports.defaults(name);
+};
+
+/**
+ * Create default object according to schema
+ * @param {String} name Schema name.
+ * @return {Object}
+ */
+exports.defaults = function(name) {
+ if (schemas[DEFAULT_SCHEMA] === undefined)
+ return null;
+ var schema = schemas[DEFAULT_SCHEMA].get(name);
+ return schema === undefined ? null : schema.default();
+};
+
+/**
+ * Prepare object according to schema
+ * @param {String} name Schema name.
+ * @param {Object} model Object to prepare.
+ * @return {Object} Prepared object.
+ */
+exports.prepare = function(name, model) {
+ if (schemas[DEFAULT_SCHEMA] === undefined)
+ return null;
+ var schema = schemas[DEFAULT_SCHEMA].get(name);
+ return schema === undefined ? null : schema.prepare(model);
+};
+
+function isUndefined(value, def) {
+ return value === undefined ? (def === EMPTYARRAY ? [] : def) : value;
+}
+
+// ======================================================
+// PROTOTYPES
+// ======================================================
+
+ErrorBuilder.prototype = {
+
+ get errors() {
+ var self = this;
+ !self.isPrepared && self.prepare();
+ return self._transform();
+ },
+
+ get error() {
+ var self = this;
+ !self.isPrepared && self.prepare();
+ return self._transform();
+ },
+
+ get is() {
+ return this.items.length > 0;
+ },
+
+ get length() {
+ return this.items.length;
+ }
+};
+
+/**
+ * Resource setting
+ * @param {String} name Resource name.
+ * @param {String} prefix Resource prefix.
+ * @return {ErrorBuilder}
+ */
+ErrorBuilder.prototype.resource = function(name, prefix) {
+ var self = this;
+ self.isResourceCustom = true;
+ self.resourceName = name;
+ self.resourcePrefix = prefix || '';
+ return self._resource();
+};
+
+ErrorBuilder.prototype.setContentType = function(type) {
+ this.contentType = type;
+ return this;
+};
+
+ErrorBuilder.prototype.setResource = function(name) {
+ var self = this;
+ self.isResourceCustom = true;
+ self.resourceName = name;
+ return self._resource();
+};
+
+ErrorBuilder.prototype.setPrefix = function(name) {
+ var self = this;
+ self.resourcePrefix = name || '';
+ return self._resource();
+};
+
+/**
+ * Internal: Resource wrapper
+ * @private
+ * @return {ErrorBuilder}
+ */
+ErrorBuilder.prototype._resource = function() {
+ var self = this;
+ self.onResource = self._resource_handler;
+ return self;
+};
+
+ErrorBuilder.prototype._resource_handler = function(name) {
+ var self = this;
+ return global.F ? F.resource(self.resourceName || 'default', name) : '';
+};
+
+ErrorBuilder.prototype.exception = function(message) {
+ this.items.push({ name: '', error: message });
+ return this;
+};
+
+/**
+ * Add an error
+ * @param {String} name Property name.
+ * @param {String|Error} error Error message.
+ * @param {String} path Current path (in object).
+ * @param {Number} index Array Index, optional.
+ * @return {ErrorBuilder}
+ */
+ErrorBuilder.prototype.add = function(name, error, path, index) {
+ return this.push(name, error, path, index);
+};
+
+const ERRORBUILDERWHITE = { ' ': 1, ':': 1, ',': 1 };
+
+/**
+ * Add an error (@alias for add)
+ * @param {String} name Property name.
+ * @param {String or Error} error Error message.
+ * @param {String} path Current path (in object).
+ * @param {Number} index Array Index, optional.
+ * @return {ErrorBuilder}
+ */
+ErrorBuilder.prototype.push = function(name, error, path, index, prefix) {
+
+ this.isPrepared = false;
+
+ if (name instanceof ErrorBuilder) {
+ if (name !== this && name.is) {
+ for (var i = 0, length = name.items.length; i < length; i++)
+ this.items.push(name.items[i]);
+ this.count = this.items.length;
+ }
+ return this;
+ }
+
+ if (name instanceof Array) {
+ for (var i = 0, length = name.length; i < length; i++)
+ this.push(name[i], undefined, path, index, prefix);
+ return this;
+ }
+
+ if (error instanceof Array) {
+ for (var i = 0, length = error.length; i < length; i++)
+ this.push(name, error[i], path, index, prefix);
+ return this;
+ }
+
+ if (typeof(name) === 'object') {
+ path = error;
+ error = name;
+ name = '';
+ }
+
+ if (error === null || (!name && !error))
+ return this;
+
+ // Status code
+ if (error > 0) {
+ this.status = error;
+ error = '@';
+ } else if (path > 0) {
+ this.status = path;
+ path = undefined;
+ }
+
+ if (this.path && !path)
+ path = this.path;
+
+ if (!error && typeof(name) === 'string') {
+ var m = name.length;
+ if (m > 15)
+ m = 15;
+
+ error = '@';
+
+ for (var i = 0; i < m; i++) {
+ if (ERRORBUILDERWHITE[name[i]]) {
+ error = name;
+ name = '';
+ break;
+ }
+ }
+ }
+
+ if (error instanceof Error) {
+ // Why? The answer is in controller.callback(); It's a reason for throwing 500 - internal server error
+ this.unexpected = true;
+ error = error.toString();
+ }
+
+ this.items.push({ name: name, error: typeof(error) === 'string' ? error : error.toString(), path: path, index: index, prefix: prefix });
+ this.count = this.items.length;
+ return this;
+};
+
+ErrorBuilder.assign = function(arr) {
+ var builder = new ErrorBuilder();
+ for (var i = 0; i < arr.length; i++) {
+ if (arr[i].error)
+ builder.items.push(arr[i]);
+ }
+ builder.count = builder.items.length;
+ return builder.count ? builder : null;
+};
+
+/**
+ * Remove error
+ * @param {String} name Property name.
+ * @return {ErrorBuilder}
+ */
+ErrorBuilder.prototype.remove = function(name) {
+ this.items = this.items.remove('name', name);
+ this.count = this.items.length;
+ return this;
+};
+
+/**
+ * Has error?
+ * @param {String} name Property name (optional).
+ * @return {Boolean}
+ */
+ErrorBuilder.prototype.hasError = function(name) {
+ return name ? this.items.findIndex('name', name) !== -1 : this.items.length > 0;
+};
+
+/**
+ * Read an error
+ * @param {String} name Property name.
+ * @return {String}
+ */
+ErrorBuilder.prototype.read = function(name) {
+ !this.isPrepared && this.prepare();
+ var error = this.items.findItem('name', name);
+ return error ? error.error : null;
+};
+
+/**
+ * Clear error collection
+ * @return {ErrorBuilder}
+ */
+ErrorBuilder.prototype.clear = function() {
+ this.items = [];
+ this.count = 0;
+ return this;
+};
+
+/**
+ * Replace text in message
+ * @param {String} search Text to search.
+ * @param {String} newvalue Text to replace.
+ * @return {ErrorBuilder}
+ */
+ErrorBuilder.prototype.replace = function(search, newvalue) {
+ this.isPrepared = false;
+ this.replacer[search] = newvalue;
+ return this;
+};
+
+/**
+ * Serialize ErrorBuilder to JSON
+ * @param {Boolean} beautify Beautify JSON.
+ * @param {Function(key, value)} replacer JSON replacer.
+ * @return {String}
+ */
+ErrorBuilder.prototype.json = function(beautify, replacer) {
+ var items = this.prepare().items;
+ return beautify ? JSON.stringify(items, replacer, '\t') : JSON.stringify(items, replacer);
+};
+
+ErrorBuilder.prototype.plain = function() {
+ var items = this.prepare().items;
+ var output = '';
+ for (var i = 0, length = items.length; i < length; i++)
+ output += (output ? ', ' : '') + items[i].error;
+ return output;
+};
+
+/**
+ * Serialize ErrorBuilder to JSON
+ * @param {Boolean} beautify Beautify JSON.
+ * @return {String}
+ */
+ErrorBuilder.prototype.JSON = function(beautify, replacer) {
+ return this.json(beautify, replacer);
+};
+
+/**
+ * Internal: Prepare error messages with onResource()
+ * @private
+ * @return {ErrorBuilder}
+ */
+ErrorBuilder.prototype._prepare = function() {
+
+ if (!this.onResource)
+ return this;
+
+ var arr = this.items;
+ for (var i = 0, length = arr.length; i < length; i++) {
+
+ var o = arr[i];
+
+ if (o.error[0] !== '@')
+ continue;
+
+ if (o.error.length === 1)
+ o.error = this.onResource(o.prefix ? o.prefix : (this.resourcePrefix + o.name));
+ else
+ o.error = this.onResource(o.error.substring(1));
+
+ if (!o.error)
+ o.error = REQUIRED.replace('@', o.name);
+ }
+
+ return this;
+};
+
+/**
+ * Execute a transform
+ * @private
+ * @return {Object}
+ */
+ErrorBuilder.prototype._transform = function(name) {
+ var transformName = name || this.transformName;
+ if (transformName) {
+ var current = transforms['error'][transformName];
+ return current ? current.call(this) : this.items;
+ }
+ return this.items;
+};
+
+ErrorBuilder.prototype.output = function(isResponse) {
+
+ if (!this.transformName)
+ return isResponse ? this.json() : this.items;
+
+ var current = transforms['error'][this.transformName];
+ if (current) {
+ this.prepare();
+ return current.call(this, isResponse);
+ }
+
+ return isResponse ? this.json() : this.items;
+};
+
+/**
+ * To string
+ * @return {String}
+ */
+ErrorBuilder.prototype.toString = function() {
+
+ !this.isPrepared && this.prepare();
+
+ var errors = this.items;
+ var length = errors.length;
+ var builder = [];
+
+ for (var i = 0; i < length; i++)
+ builder.push(errors[i].error || errors[i].name);
+
+ return builder.join('\n');
+
+};
+
+/**
+ * Set transformation for current ErrorBuilder
+ * @param {String} name
+ * @return {ErrorBuilder}
+ */
+ErrorBuilder.prototype.setTransform = function(name) {
+ this.transformName = name;
+ return this;
+};
+
+/**
+ * Transform
+ * @param {String} name
+ * @return {Object}
+ */
+ErrorBuilder.prototype.transform = function(name) {
+ return this.prepare()._transform(name);
+};
+
+/**
+ * Internal: Prepare error messages with onResource()
+ * @private
+ * @return {ErrorBuidler}
+ */
+ErrorBuilder.prototype._prepareReplace = function() {
+
+ var self = this;
+ var errors = self.items;
+ var lengthBuilder = errors.length;
+ var keys = Object.keys(self.replacer);
+ var lengthKeys = keys.length;
+
+ if (!lengthBuilder || !lengthKeys)
+ return self;
+
+ for (var i = 0; i < lengthBuilder; i++) {
+ var o = errors[i];
+ for (var j = 0; j < lengthKeys; j++) {
+ var key = keys[j];
+ o.error = o.error.replace(key, self.replacer[key]);
+ }
+ }
+
+ return self;
+};
+
+/**
+ * Internal: Prepare error messages with onResource()
+ * @private
+ * @return {ErrorBuilder}
+ */
+ErrorBuilder.prototype.prepare = function() {
+ if (this.isPrepared)
+ return this;
+ this._prepare()._prepareReplace();
+ this.isPrepared = true;
+ return this;
+};
+
+/**
+ * STATIC: Create transformation
+ * @param {String} name
+ * @param {Function(ErrorBuilder)} fn
+ * @param {Boolean} isDefault Default transformation for all error builders.
+ */
+ErrorBuilder.addTransform = function(name, fn, isDefault) {
+ transforms['error'][name] = fn;
+ isDefault && ErrorBuilder.setDefaultTransform(name);
+};
+
+/**
+ * STATIC: Remove transformation
+ * @param {String} name
+ */
+ErrorBuilder.removeTransform = function(name) {
+ delete transforms['error'][name];
+};
+
+/**
+ * STATIC: Create transformation
+ * @param {String} name
+ * @param {Function(errorBuilder)} fn
+ */
+ErrorBuilder.setDefaultTransform = function(name) {
+ if (name)
+ transforms['error_default'] = name;
+ else
+ delete transforms['error_default'];
+};
+
+/**
+ * Pagination
+ * @class
+ * @param {Number} items Count of items.
+ * @param {Number} page Current page.
+ * @param {Number} max Max items on page.
+ * @param {String} format URL format for links (next, back, go to). Example: ?page={0} --- {0} = page, {1} = items count, {2} = page count
+ * @property {Number} isNext Is next page?
+ * @property {Number} isPrev Is previous page?
+ * @property {Number} count Page count.
+ * @property {Boolean} visible Is more than one page?
+ * @property {String} format Format URL. Example: ?page={0} --- {0} = page, {1} = items count, {2} = page count
+ */
+function Pagination(items, page, max, format) {
+ this.isNext = false;
+ this.isPrev = false;
+ this.isFirst = false;
+ this.isLast = false;
+ this.nextPage = 0;
+ this.prevPage = 0;
+ this.lastPage = 0;
+ this.firstPage = 0;
+ this.items = Math.max(0, +items);
+ this.count = 0;
+ this.skip = 0;
+ this.take = 0;
+ this.page = 0;
+ this.max = 0;
+ this.visible = false;
+ this.format = format || '?page={0}';
+ this.refresh(items, page, max);
+ this.transformName = transforms['pagination_default'];
+}
+
+function Page(url, page, selected, enabled) {
+ this.url = url;
+ this.page = page;
+ this.selected = selected;
+ this.enabled = enabled;
+}
+
+Page.prototype.html = function(body, cls) {
+ var classname = cls ? cls : '';
+ if (this.selected)
+ classname += (classname ? ' ' : '') + 'selected';
+ return '' + (body || this.page) + '';
+};
+
+/**
+ * STATIC: Create transformation
+ * @param {String} name
+ * @param {Function(pagination)} fn
+ * @param {Boolean} isDefault Default transformation for all paginations.
+ */
+Pagination.addTransform = function(name, fn, isDefault) {
+ transforms['pagination'][name] = fn;
+ isDefault && Pagination.setDefaultTransform(name);
+};
+
+/**
+ * STATIC: Create transformation
+ * @param {String} name
+ * @param {Function(pagination)} fn
+ */
+Pagination.setDefaultTransform = function(name) {
+ if (name)
+ transforms['pagination_default'] = name;
+ else
+ delete transforms['pagination_default'];
+};
+
+/**
+ * STATIC: Remove transformation
+ * @param {String} name
+ */
+Pagination.removeTransform = function(name) {
+ delete transforms['pagination'][name];
+};
+
+/**
+ * Refresh pagination
+ * @param {Number} items Count of items.
+ * @param {Number} page Current page.
+ * @param {Number} max Max items on page.
+ * @return {Pagination}
+ */
+Pagination.prototype.refresh = function(items, page, max) {
+
+ this.page = Math.max(1, +page) - 1;
+
+ if (this.page <= 0)
+ this.page = 0;
+
+ this.items = Math.max(0, +items);
+ this.max = Math.max(1, +max);
+ this.skip = this.page * this.max;
+ this.count = Math.ceil(this.items / this.max);
+ this.take = Math.min(this.max, (this.items - this.skip));
+
+ this.lastPage = this.count;
+ this.firstPage = 1;
+ this.prevPage = this.page ? this.page : 1;
+ this.nextPage = this.page + 2 < this.count - 1 ? this.page + 2 : this.count;
+
+ this.isPrev = this.page > 0;
+ this.isNext = this.page < this.count - 1;
+
+ this.isFirst = this.page === 0;
+ this.isLast = this.page === this.count - 1;
+
+ this.visible = this.count > 1;
+ this.page++;
+
+ return this;
+};
+
+/**
+ * Set transformation for current Pagination
+ * @param {String} name
+ * @return {Pagination}
+ */
+Pagination.prototype.setTransform = function(name) {
+ this._transform = name;
+ return this;
+};
+
+/**
+ * Execute a transform
+ * @private
+ * @param {String} name A transformation name.
+ * @param {Object} argument1 Optional.
+ * @param {Object} argument2 Optional.
+ * @param {Object} argument3 Optional.
+ * @param {Object} argument4 Optional.
+ * @param {Object} argument..n Optional.
+ * @return {Object}
+ */
+Pagination.prototype.transform = function(name) {
+
+ var transformName = name || this.transformName;
+ if (!transformName)
+ throw new Error('A transformation of Pagination not found.');
+
+ var current = transforms['pagination'][transformName];
+ if (!current)
+ return this.render();
+
+ var param = [];
+ for (var i = 1; i < arguments.length; i++)
+ param.push(arguments[i]);
+
+ return current.apply(this, param);
+};
+
+/**
+ * Get a previous page
+ * @param {String} format Custom format (optional).
+ * @return {Object} Example: { url: String, page: Number, selected: Boolean }
+ */
+Pagination.prototype.prev = function(format) {
+ var page = 0;
+
+ format = format || this.format;
+
+ if (this.isPrev)
+ page = this.page - 1;
+ else
+ page = this.count;
+
+ return new Page(format.format(page, this.items, this.count), page, false, this.isPrev);
+};
+
+/**
+ * Get a next page
+ * @param {String} format Custom format (optional).
+ * @return {Object} Example: { url: String, page: Number, selected: Boolean }
+ */
+Pagination.prototype.next = function(format) {
+ var page = 0;
+
+ format = format || this.format;
+
+ if (this.isNext)
+ page = this.page + 1;
+ else
+ page = 1;
+
+ return new Page(format.format(page, this.items, this.count), page, false, this.isNext);
+};
+
+/**
+ * Get a last page
+ * @param {String} format Custom format (optional).
+ * @return {Object} Example: { url: String, page: Number, selected: Boolean }
+ */
+Pagination.prototype.last = function(format) {
+ var page = this.count;
+ format = format || this.format;
+ return new Page(format.format(page, this.items, this.count), page, false, this.count > 0);
+};
+
+/**
+ * Get a first page
+ * @param {String} format Custom format (optional).
+ * @return {Object} Example: { url: String, page: Number, selected: Boolean }
+ */
+Pagination.prototype.first = function(format) {
+ var page = 1;
+ format = format || this.format;
+ return new Page(format.format(page, this.items, this.count), page, false, this.count > 0);
+};
+
+/**
+ * Create a pagination object
+ * @param {Number} max Max pages in collection (optional).
+ * @param {String} format Custom format (optional).
+ * @return {Object Array} Example: [{ url: String, page: Number, selected: Boolean }]
+ */
+Pagination.prototype.prepare = function(max, format, type) {
+
+ var self = this;
+
+ if (self.transformName)
+ return transforms['pagination'][self.transformName].apply(self, arguments);
+
+ var builder = [];
+ format = format || self.format;
+
+ if (typeof(max) === 'string') {
+ var tmp = format;
+ format = max;
+ max = tmp;
+ }
+
+ var isHTML = type === 'html';
+
+ if (max == null) {
+ for (var i = 1; i < self.count + 1; i++) {
+ var page = new Page(format.format(i, self.items, self.count), i, i === self.page, true);
+ builder.push(isHTML ? page.html() : page);
+ }
+ return builder;
+ }
+
+ var half = Math.floor(max / 2);
+ var pages = self.count;
+
+ var pageFrom = self.page - half;
+ var pageTo = self.page + half;
+ var plus = 0;
+
+ if (pageFrom <= 0) {
+ plus = Math.abs(pageFrom);
+ pageFrom = 1;
+ pageTo += plus;
+ }
+
+ if (pageTo >= pages) {
+ pageTo = pages;
+ pageFrom = pages - max;
+ if (pageFrom <= 0)
+ pageFrom = 1;
+ }
+
+ for (var i = pageFrom; i < pageTo + 1; i++) {
+ var page = new Page(format.format(i, self.items, self.count), i, i === self.page, true);
+ builder.push(isHTML ? page.html() : page);
+ }
+
+ return builder;
+};
+
+Pagination.prototype.render = function(max, format) {
+ return this.prepare(max, format);
+};
+
+Pagination.prototype.html = function(max, format) {
+ return this.prepare(max, format, 'html').join('');
+};
+
+Pagination.prototype.json = function(max, format) {
+ return JSON.stringify(this.prepare(max, format));
+};
+
+UrlBuilder.make = function(fn) {
+ var b = new UrlBuilder();
+ fn.call(b, b);
+ return b;
+};
+
+/**
+ * Add parameter
+ * @param {String} name
+ * @param {Object} value
+ * return {UrlBuilder}
+ */
+UrlBuilder.prototype.add = function(name, value) {
+
+ if (typeof(name) !== 'object') {
+ this.builder[name] = value;
+ return this;
+ }
+
+ var arr = Object.keys(name);
+
+ for (var i = 0, length = arr.length; i < length; i++)
+ this.builder[arr[i]] = name[arr[i]];
+
+ return this;
+};
+
+/**
+ * Remove parameter
+ * @param {String} name
+ * @return {UrlBuilder}
+ */
+UrlBuilder.prototype.remove = function(name) {
+ delete this.builder[name];
+ return this;
+};
+
+/**
+ * Read value
+ * @param {String} name
+ * @return {Object}
+ */
+UrlBuilder.prototype.read = function(name) {
+ return this.builder[name] || null;
+};
+
+/**
+ * Clear parameter collection
+ * @return {UrlBuilder}
+ */
+UrlBuilder.prototype.clear = function() {
+ this.builder = {};
+ return this;
+};
+
+/**
+ * Create URL
+ * @return {String}
+ */
+UrlBuilder.prototype.toString = function(url, skipEmpty) {
+
+ if (typeof(url) === 'boolean') {
+ var tmp = skipEmpty;
+ skipEmpty = url;
+ url = tmp;
+ }
+
+ var self = this;
+ var builder = [];
+
+ Object.keys(self.builder).forEach(function(o) {
+
+ var value = self.builder[o];
+ if (value == null)
+ value = '';
+ else
+ value = value.toString();
+
+ if (skipEmpty && value === '')
+ return;
+
+ builder.push(o + '=' + encodeURIComponent(value));
+ });
+
+ if (typeof(url) === 'string') {
+ if (url[url.length - 1] !== '?')
+ url += '?';
+ } else
+ url = '';
+
+ return url + builder.join('&');
+};
+
+/**
+ * Has these parameters?
+ * @param {String Array} keys Keys.
+ * @return {Boolean}
+ */
+UrlBuilder.prototype.hasValue = function(keys) {
+
+ if (keys === undefined)
+ return false;
+
+ if (typeof(keys) === 'string')
+ keys = [keys];
+
+ for (var i = 0; i < keys.length; i++) {
+ var val = this.builder[keys[i]];
+ if (val == null)
+ return false;
+ }
+
+ return true;
+};
+
+/**
+ * Render parameters
+ * @param {String Array} keys Keys.
+ * @param {String} delimiter Delimiter (default &).
+ * @return {String}
+ */
+UrlBuilder.prototype.toOne = function(keys, delimiter) {
+ var self = this;
+ var builder = [];
+ keys.forEach(key => builder.push(self.builder[key] || ''));
+ return builder.join(delimiter || '&');
+};
+
+function RESTBuilder(url) {
+
+ this.$url = url;
+ this.$headers = { 'user-agent': 'Total.js/v' + F.version_header, accept: 'application/json, text/plain, text/plain, text/xml' };
+ this.$method = 'get';
+ this.$timeout = 10000;
+ this.$type = 0; // 0 = query, 1 = json, 2 = urlencode, 3 = raw
+ this.$schema;
+ this.$length = 0;
+ this.$transform = transforms['restbuilder_default'];
+ this.$files = null;
+ this.$persistentcookies = false;
+
+ // this.$flags;
+ // this.$data = {};
+ // this.$nodnscache = true;
+ // this.$cache_expire;
+ // this.$cache_nocache;
+ // this.$redirect
+
+ // Auto Total.js Error Handling
+ this.$errorbuilderhandling = true;
+}
+
+RESTBuilder.make = function(fn) {
+ var instance = new RESTBuilder();
+ fn && fn(instance);
+ return instance;
+};
+
+RESTBuilder.url = function(url) {
+ return new RESTBuilder(url);
+};
+
+RESTBuilder.GET = function(url, data) {
+ var builder = new RESTBuilder(url);
+ data && builder.raw(data);
+ return builder;
+};
+
+RESTBuilder.POST = function(url, data) {
+ var builder = new RESTBuilder(url);
+ builder.$method = 'post';
+ builder.$type = 1;
+ data && builder.raw(data);
+ return builder;
+};
+
+RESTBuilder.PUT = function(url, data) {
+ var builder = new RESTBuilder(url);
+ builder.$method = 'put';
+ builder.$type = 1;
+ builder.put(data);
+ return builder;
+};
+
+RESTBuilder.DELETE = function(url, data) {
+ var builder = new RESTBuilder(url);
+ builder.$method = 'delete';
+ builder.$type = 1;
+ data && builder.raw(data);
+ return builder;
+};
+
+RESTBuilder.PATCH = function(url, data) {
+ var builder = new RESTBuilder(url);
+ builder.$method = 'patch';
+ builder.$type = 1;
+ data && builder.raw(data);
+ return builder;
+};
+
+RESTBuilder.HEAD = function(url) {
+ var builder = new RESTBuilder(url);
+ builder.$method = 'head';
+ return builder;
+};
+
+RESTBuilder.upgrade = function(fn) {
+ restbuilderupgrades.push(fn);
+};
+
+/**
+ * STATIC: Creates a transformation
+ * @param {String} name
+ * @param {Function} fn
+ * @param {Boolean} isDefault Default transformation for all RESTBuilders.
+ */
+RESTBuilder.addTransform = function(name, fn, isDefault) {
+ transforms['restbuilder'][name] = fn;
+ isDefault && RESTBuilder.setDefaultTransform(name);
+};
+
+RESTBuilder.setDefaultTransform = function(name) {
+ if (name)
+ transforms['restbuilder_default'] = name;
+ else
+ delete transforms['restbuilder_default'];
+};
+
+var RESTP = RESTBuilder.prototype;
+
+RESTP.promise = function(fn) {
+ var self = this;
+ return new Promise(function(resolve, reject) {
+ self.exec(function(err, result) {
+ if (err)
+ reject(err);
+ else
+ resolve(fn == null ? result : fn(result));
+ });
+ });
+};
+
+RESTP.proxy = function(value) {
+ this.$proxy = value;
+ return this;
+};
+
+RESTP.setTransform = function(name) {
+ this.$transform = name;
+ return this;
+};
- return builder.join(delimiter || '&');
+RESTP.url = function(url) {
+ if (url === undefined)
+ return this.$url;
+ this.$url = url;
+ return this;
};
+RESTP.file = function(name, filename, buffer) {
+ var obj = { name: name, filename: filename, buffer: buffer };
+ if (this.$files)
+ this.$files.push(obj);
+ else
+ this.$files = [obj];
+ return this;
+};
+
+RESTP.maketransform = function(obj, data) {
+ if (this.$transform) {
+ var fn = transforms['restbuilder'][this.$transform];
+ return fn ? fn(obj, data) : obj;
+ }
+ return obj;
+};
+
+RESTP.timeout = function(number) {
+ this.$timeout = number;
+ return this;
+};
+
+RESTP.maxlength = function(number) {
+ this.$length = number;
+ this.$flags = null;
+ return this;
+};
+
+RESTP.auth = function(user, password) {
+ this.$headers['authorization'] = 'Basic ' + Buffer.from(user + ':' + password).toString('base64');
+ return this;
+};
+
+RESTP.convert = function(convert) {
+ this.$convert = convert;
+ return this;
+};
+
+RESTP.schema = function(group, name) {
+ this.$schema = exports.getschema(group, name);
+ if (!this.$schema)
+ throw Error('RESTBuilder: Schema "{0}" not found.'.format(name ? (group + '/' + name) : group));
+ return this;
+};
+
+RESTP.noDnsCache = function() {
+ this.$nodnscache = true;
+ this.$flags = null;
+ return this;
+};
+
+RESTP.noCache = function() {
+ this.$nocache = true;
+ return this;
+};
+
+RESTP.make = function(fn) {
+ fn.call(this, this);
+ return this;
+};
+
+RESTP.xhr = function() {
+ this.$headers['X-Requested-With'] = 'XMLHttpRequest';
+ return this;
+};
+
+RESTP.method = function(method, data) {
+ this.$method = method.charCodeAt(0) < 97 ? method.toLowerCase() : method;
+ this.$flags = null;
+ data && this.raw(data);
+ return this;
+};
+
+RESTP.referer = RESTP.referrer = function(value) {
+ this.$headers['Referer'] = value;
+ return this;
+};
+
+RESTP.origin = function(value) {
+ this.$headers['Origin'] = value;
+ return this;
+};
+
+RESTP.robot = function() {
+ if (this.$headers['User-Agent'])
+ this.$headers['User-Agent'] += ' Bot';
+ else
+ this.$headers['User-Agent'] = 'Bot';
+ return this;
+};
+
+RESTP.mobile = function() {
+ if (this.$headers['User-Agent'])
+ this.$headers['User-Agent'] += ' iPhone';
+ else
+ this.$headers['User-Agent'] = 'iPhone';
+ return this;
+};
+
+RESTP.put = RESTP.PUT = function(data) {
+ if (this.$method !== 'put') {
+ this.$flags = null;
+ this.$method = 'put';
+ this.$type = 1;
+ }
+ data && this.raw(data);
+ return this;
+};
+
+RESTP.delete = RESTP.DELETE = function(data) {
+ if (this.$method !== 'delete') {
+ this.$flags = null;
+ this.$method = 'delete';
+ this.$type = 1;
+ }
+ data && this.raw(data);
+ return this;
+};
+
+RESTP.get = RESTP.GET = function(data) {
+ if (this.$method !== 'get') {
+ this.$flags = null;
+ this.$method = 'get';
+ }
+ data && this.raw(data);
+ return this;
+};
+
+RESTP.post = RESTP.POST = function(data) {
+ if (this.$method !== 'post') {
+ this.$flags = null;
+ this.$method = 'post';
+ this.$type = 1;
+ }
+ data && this.raw(data);
+ return this;
+};
+
+RESTP.head = RESTP.HEAD = function() {
+ if (this.$method !== 'head') {
+ this.$flags = null;
+ this.$method = 'head';
+ }
+ return this;
+};
+
+RESTP.patch = RESTP.PATCH = function(data) {
+ if (this.$method !== 'patch') {
+ this.$flags = null;
+ this.$method = 'patch';
+ this.$type = 1;
+ }
+ data && this.raw(data);
+ return this;
+};
+
+RESTP.json = function(data) {
+
+ if (this.$type !== 1)
+ this.$flags = null;
+
+ data && this.raw(data);
+ this.$type = 1;
+
+ if (this.$method === 'get')
+ this.$method = 'post';
+
+ return this;
+};
+
+RESTP.urlencoded = function(data) {
+
+ if (this.$type !== 2)
+ this.$flags = null;
+
+ if (this.$method === 'get')
+ this.$method = 'post';
+
+ this.$type = 2;
+ data && this.raw(data);
+ return this;
+};
+
+RESTP.accept = function(ext) {
+
+ var type;
+
+ if (ext.length > 8)
+ type = ext;
+ else
+ type = framework_utils.getContentType(ext);
+
+ if (this.$headers.Accept !== type)
+ this.$flags = null;
+
+ this.$flags = null;
+ this.$headers.Accept = type;
+
+ return this;
+};
+
+RESTP.xml = function(data, replace) {
+
+ if (this.$type !== 3)
+ this.$flags = null;
+
+ if (this.$method === 'get')
+ this.$method = 'post';
+
+ this.$type = 3;
+
+ if (replace)
+ this.$replace = true;
+
+ data && this.raw(data);
+ return this;
+};
+
+RESTP.redirect = function(value) {
+ this.$redirect = value;
+ return this;
+};
+
+RESTP.raw = function(value) {
+ this.$data = value && value.$clean ? value.$clean() : value;
+ return this;
+};
+
+RESTP.plain = function() {
+ this.$plain = true;
+ return this;
+};
+
+RESTP.cook = function(value) {
+ this.$flags = null;
+ this.$persistentcookies = value !== false;
+ return this;
+};
+
+RESTP.cookies = function(obj) {
+ this.$cookies = obj;
+ return this;
+};
+
+RESTP.cookie = function(name, value) {
+ !this.$cookies && (this.$cookies = {});
+ this.$cookies[name] = value;
+ return this;
+};
+
+RESTP.header = function(name, value) {
+ this.$headers[name] = value;
+ return this;
+};
+
+RESTP.type = function(value) {
+ this.$headers['Content-Type'] = value;
+ return this;
+};
+
+function execrestbuilder(instance, callback) {
+ instance.exec(callback);
+}
+
+RESTP.callback = function(callback) {
+ setImmediate(execrestbuilder, this, callback);
+ return this;
+};
+
+RESTP.cache = function(expire) {
+ this.$cache_expire = expire;
+ return this;
+};
+
+RESTP.insecure = function() {
+ this.$insecure = true;
+ return this;
+};
+
+RESTP.set = function(name, value) {
+ if (!this.$data)
+ this.$data = {};
+ if (typeof(name) !== 'object') {
+ this.$data[name] = value;
+ } else {
+ var arr = Object.keys(name);
+ for (var i = 0, length = arr.length; i < length; i++)
+ this.$data[arr[i]] = name[arr[i]];
+ }
+ return this;
+};
+
+RESTP.rem = function(name) {
+ if (this.$data && this.$data[name])
+ this.$data[name] = undefined;
+ return this;
+};
+
+RESTP.stream = function(callback) {
+ var self = this;
+ var flags = self.$flags ? self.$flags : [self.$method];
+
+ if (!self.$flags) {
+ !self.$nodnscache && flags.push('dnscache');
+ self.$persistentcookies && flags.push('cookies');
+ switch (self.$type) {
+ case 1:
+ flags.push('json');
+ break;
+ case 3:
+ flags.push('xml');
+ break;
+ }
+ self.$flags = flags;
+ }
+
+ return U.download(self.$url, flags, self.$data, callback, self.$cookies, self.$headers, undefined, self.$timeout);
+};
+
+RESTP.keepalive = function() {
+ var self = this;
+ self.$keepalive = true;
+ return self;
+};
+
+RESTP.flags = function() {
+ var self = this;
+ !self.$flags && (self.$flags = []);
+ for (var i = 0; i < arguments.length; i++)
+ self.$flags(arguments[i]);
+ return self;
+};
+
+RESTP.exec = function(callback) {
+
+ if (!callback)
+ callback = NOOP;
+
+ var self = this;
+
+ if (self.$files && self.$method === 'get')
+ self.$method = 'post';
+
+ self.$callback = callback;
+
+ if (restbuilderupgrades.length) {
+ for (var i = 0; i < restbuilderupgrades.length; i++)
+ restbuilderupgrades[i](self);
+ }
+
+ var flags = self.$flags ? self.$flags : [self.$method];
+ var key;
+
+ if (!self.$flags) {
+
+ !self.$nodnscache && flags.push('dnscache');
+ self.$persistentcookies && flags.push('cookies');
+ self.$length && flags.push('<' + self.$length);
+ self.$redirect === false && flags.push('noredirect');
+ self.$proxy && flags.push('proxy ' + self.$proxy);
+ self.$keepalive && flags.push('keepalive');
+ self.$insecure && flags.push('insecure');
+
+ if (self.$files) {
+ flags.push('upload');
+ } else {
+ switch (self.$type) {
+ case 1:
+ flags.push('json');
+ break;
+ case 3:
+ flags.push('xml');
+ break;
+ }
+ }
+
+ self.$flags = flags;
+ }
+
+ if (self.$cache_expire && !self.$nocache) {
+ key = '$rest_' + (self.$url + flags.join(',') + (self.$data ? Qs.stringify(self.$data) : '')).hash();
+ var data = F.cache.read2(key);
+ if (data) {
+ var evt = new framework_utils.EventEmitter2();
+ setImmediate(exec_removelisteners, evt);
+ callback(null, self.maketransform(this.$schema ? this.$schema.make(data.value) : data.value, data), data);
+ return evt;
+ }
+ }
+
+ self.$callback_key = key;
+ return U.request(self.$url, flags, self.$data, exec_callback, self.$cookies, self.$headers, undefined, self.$timeout, self.$files, self);
+};
+
+function exec_callback(err, response, status, headers, hostname, cookies, self) {
+
+ var callback = self.$callback;
+ var key = self.$callback_key;
+ var type = err ? '' : headers['content-type'] || '';
+ var output = new RESTBuilderResponse();
+
+ if (type) {
+ var index = type.lastIndexOf(';');
+ if (index !== -1)
+ type = type.substring(0, index).trim();
+ }
+
+ var ishead = response === headers;
+
+ if (ishead)
+ response = '';
+
+ if (ishead) {
+ output.value = status < 400;
+ } else if (self.$plain) {
+ output.value = response;
+ } else {
+ switch (type.toLowerCase()) {
+ case 'text/xml':
+ case 'application/xml':
+ output.value = response ? response.parseXML(self.$replace ? true : false) : {};
+ break;
+ case 'application/x-www-form-urlencoded':
+ output.value = response ? F.onParseQuery(response) : {};
+ break;
+ case 'application/json':
+ case 'text/json':
+ output.value = response ? response.parseJSON(true) : null;
+ break;
+ default:
+ output.value = response && response.isJSON() ? response.parseJSON(true) : null;
+ break;
+ }
+ }
+
+ if (output.value == null)
+ output.value = EMPTYOBJECT;
+
+ output.response = response;
+ output.status = status;
+ output.headers = headers;
+ output.hostname = hostname;
+ output.cache = false;
+ output.datetime = NOW;
+
+ var val;
+
+ if (self.$schema) {
+
+ if (err)
+ return callback(err, EMPTYOBJECT, output);
+
+ val = self.maketransform(output.value, output);
+
+ if (self.$errorbuilderhandling) {
+ // Is the response Total.js ErrorBuilder?
+ if (val instanceof Array && val.length && val[0] && val[0].error) {
+ err = ErrorBuilder.assign(val);
+ if (err)
+ val = EMPTYOBJECT;
+ if (err) {
+ callback(err, EMPTYOBJECT, output);
+ return;
+ }
+ }
+ }
+
+ self.$schema.make(val, function(err, model) {
+ !err && key && output.status === 200 && F.cache.add(key, output, self.$cache_expire);
+ callback(err, err ? EMPTYOBJECT : model, output);
+ output.cache = true;
+ });
+
+ } else {
+ !err && key && output.status === 200 && F.cache.add(key, output, self.$cache_expire);
+
+ val = self.maketransform(output.value, output);
+
+ if (self.$errorbuilderhandling) {
+ // Is the response Total.js ErrorBuilder?
+ if (val instanceof Array && val.length && val[0] && val[0].error) {
+ err = ErrorBuilder.assign(val);
+ if (err)
+ val = EMPTYOBJECT;
+ }
+ }
+
+ if (self.$convert && val && val !== EMPTYOBJECT)
+ val = CONVERT(val, self.$convert);
+
+ callback(err, val, output);
+ output.cache = true;
+ }
+}
+
+function exec_removelisteners(evt) {
+ evt.removeAllListeners();
+}
+
+function RESTBuilderResponse() {}
+
+RESTBuilderResponse.prototype.cookie = function(name) {
+ var self = this;
+
+ if (self.cookies)
+ return $decodeURIComponent(self.cookies[name] || '');
+
+ var cookie = self.headers['cookie'];
+ if (!cookie)
+ return '';
+
+ self.cookies = {};
+
+ var arr = cookie.split(';');
+
+ for (var i = 0, length = arr.length; i < length; i++) {
+ var line = arr[i].trim();
+ var index = line.indexOf('=');
+ if (index !== -1)
+ self.cookies[line.substring(0, index)] = line.substring(index + 1);
+ }
+
+ return $decodeURIComponent(self.cookies[name] || '');
+};
+
+// Handle errors of decodeURIComponent
+function $decodeURIComponent(value) {
+ try
+ {
+ return decodeURIComponent(value);
+ } catch (e) {
+ return value;
+ }
+}
+
+global.NEWTASK = function(name, fn, filter) {
+ if (fn == null) {
+ delete tasks[name];
+ } else {
+ tasks[name] = {};
+ tasks[name].$owner = F.$owner();
+ tasks[name].$filter = filter;
+ var append = function(key, fn) {
+ tasks[name][key] = fn;
+ };
+ fn(append);
+ }
+};
+
+function taskrunner(obj, name, callback) {
+ obj.exec(name, callback);
+}
+
+global.TASK = function(taskname, name, callback, options) {
+ var obj = new TaskBuilder(options);
+ obj.taskname = taskname;
+
+ if (obj.controller) {
+ obj.controller.$filterschema = null;
+ obj.controller.$filter = null;
+ }
+
+ name && setImmediate(taskrunner, obj, name, callback);
+ return obj;
+};
+
+global.NEWOPERATION = function(name, fn, repeat, stop, binderror, filter) {
+
+ if (typeof(repeat) === 'string') {
+ filter = repeat;
+ repeat = null;
+ }
+
+ if (typeof(stop) === 'string') {
+ filter = stop;
+ stop = null;
+ }
+
+ if (typeof(binderror) === 'string') {
+ filter = binderror;
+ binderror = null;
+ }
+
+ // @repeat {Number} How many times will be the operation repeated after error?
+ // @stop {Boolean} Stop when the error is thrown
+ // @binderror {Boolean} Binds error when chaining of operations
+ // @filter {Object}
+
+ // Remove operation
+ if (fn == null) {
+ delete operations[name];
+ } else {
+ operations[name] = fn;
+ operations[name].$owner = F.$owner();
+ operations[name].$newversion = REGEXP_NEWOPERATION.test(fn.toString());
+ operations[name].$repeat = repeat;
+ operations[name].$stop = stop !== false;
+ operations[name].$binderror = binderror === true;
+ operations[name].$filter = filter;
+ if (!operations[name].$newversion)
+ OBSOLETE('NEWOPERATION("{0}")'.format(name), MSG_OBSOLETE_NEW);
+ }
+};
+
+function getLoggerNameOperation(name) {
+ return 'OPERATION(\'' + name + '\')';
+}
+
+function NoOp() {}
+
+global.OPERATION = function(name, value, callback, param, controller) {
+
+ if (typeof(value) === 'function') {
+ controller = param;
+ param = callback;
+ callback = value;
+ value = EMPTYOBJECT;
+ }
+
+ if (param instanceof Controller || param instanceof OperationOptions || param instanceof SchemaOptions || param instanceof TaskBuilder || param instanceof AuthOptions || param instanceof WebSocketClient) {
+ controller = param;
+ param = undefined;
+ }
+
+ if (controller && controller.controller)
+ controller = controller.controller;
+
+ var fn = operations[name];
+
+ var error = new ErrorBuilder();
+ var $now;
+
+ if (CONF.logger)
+ $now = Date.now();
+
+ if (fn) {
+
+ if (fn.$filter && controller) {
+ controller.$filterschema = fn.$filter;
+ controller.$filter = null;
+ }
+
+ if (fn.$newversion) {
+ var self = new OperationOptions(error, value, param, controller);
+ self.$repeat = fn.$repeat;
+ self.callback = function(value) {
+
+ CONF.logger && F.ilogger(getLoggerNameOperation(name), controller, $now);
+ if (arguments.length > 1) {
+ if (value instanceof Error || (value instanceof ErrorBuilder && value.is)) {
+ self.error.push(value);
+ value = EMPTYOBJECT;
+ } else
+ value = arguments[1];
+ } else if (value instanceof Error || (value instanceof ErrorBuilder && value.is)) {
+ self.error.push(value);
+ value = EMPTYOBJECT;
+ }
+
+ if (self.error.items.length && self.$repeat) {
+ self.error.clear();
+ self.$repeat--;
+ fn(self);
+ } else {
+ if (callback) {
+ if (value === NoOp)
+ callback = null;
+ else
+ callback(self.error.is ? self.error : null, value, self.options);
+ }
+ }
+ return self;
+ };
+ fn(self);
+ } else
+ fn(error, value, function(value) {
+ CONF.logger && F.ilogger(getLoggerNameOperation(name), controller, $now);
+ if (callback) {
+ if (value instanceof Error) {
+ error.push(value);
+ value = EMPTYOBJECT;
+ }
+ if (value !== NoOp)
+ callback(error.is ? error : null, value, param);
+ }
+ });
+ } else {
+ error.push('Operation "{0}" not found.'.format(name));
+ callback && callback(error, EMPTYOBJECT, param);
+ }
+};
+
+global.RUN = function(name, value, callback, param, controller, result) {
+
+ if (typeof(value) === 'function') {
+ result = controller;
+ controller = param;
+ param = callback;
+ callback = value;
+ value = EMPTYOBJECT;
+ }
+
+ if (param instanceof global.Controller || (param && param.isWebSocket)) {
+ result = controller;
+ controller = param;
+ param = EMPTYOBJECT;
+ } else if (param instanceof OperationOptions || param instanceof SchemaOptions || param instanceof TaskBuilder || param instanceof AuthOptions) {
+ result = controller;
+ controller = param.controller;
+ }
+
+ if (!result) {
+ if (typeof(param) === 'string') {
+ result = param;
+ param = EMPTYOBJECT;
+ } else if (typeof(controller) === 'string') {
+ result = controller;
+ controller = null;
+ }
+ }
+
+ if (typeof(name) === 'string')
+ name = name.split(',').trim();
+
+ var error = new ErrorBuilder();
+ var opt = new OperationOptions(error, value, param, controller);
+
+ opt.meta = {};
+ opt.meta.items = name;
+ opt.response = {};
+ opt.errors = error;
+
+ opt.callback = function(value) {
+
+ CONF.logger && F.ilogger(getLoggerNameOperation(opt.name), controller, opt.duration);
+
+ if (arguments.length > 1) {
+ if (value instanceof Error || (value instanceof ErrorBuilder && value.is)) {
+ opt.error.push(value);
+ value = EMPTYOBJECT;
+ } else
+ value = arguments[1];
+ } else if (value instanceof Error || (value instanceof ErrorBuilder && value.is)) {
+ opt.error.push(value);
+ value = EMPTYOBJECT;
+ }
+
+ if (opt.error.items.length && opt.$repeat > 0) {
+ opt.error.clear();
+ opt.$repeat--;
+ opt.repeated++;
+ setImmediate(opt => opt.$current(opt), opt);
+ } else {
+
+ if (opt.error.items.length) {
+ if (opt.$current.$binderror)
+ value = opt.error.output(false);
+ }
+
+ if (opt.error.items.length && opt.$current.$stop) {
+ error.push(opt.error);
+ name = null;
+ opt.next = null;
+ callback(error, opt.response, opt);
+ } else {
+
+ // Because "controller_json_workflow_multiple()" returns error instead of results
+ // error.push(opt.error);
+
+ if (result && (result === opt.meta.current || result === opt.name))
+ opt.output = value;
+
+ opt.response[opt.name] = value;
+ opt.meta.prev = opt.meta.current;
+ opt.$next();
+ }
+ }
+ };
+
+ name.wait(function(key, next, index) {
+
+ var fn = operations[key];
+ if (!fn) {
+ // What now?
+ // F.error('Operation "{0}" not found'.format(key), 'RUN()');
+ return next();
+ }
+
+ opt.repeated = 0;
+ opt.error = new ErrorBuilder();
+ opt.error.path = 'operation: ' + key;
+ opt.meta.index = index;
+ opt.name = opt.meta.current = key;
+ opt.$repeat = fn.$repeat;
+ opt.$current = fn;
+ opt.$next = next;
+ opt.meta.next = name[index];
+
+ if (CONF.logger)
+ opt.duration = Date.now();
+
+ fn(opt);
+
+ }, () => callback(error.items.length ? error : null, result ? opt.output : opt.response, opt));
+};
+
+function OperationOptions(error, value, options, controller) {
+
+ if (!controller && options instanceof global.Controller) {
+ controller = options;
+ options = EMPTYOBJECT;
+ } else if (options === undefined)
+ options = EMPTYOBJECT;
+
+ this.controller = controller;
+ this.model = this.value = value;
+ this.error = error;
+ this.options = options;
+}
+
+OperationOptions.prototype = {
+
+ get user() {
+ return this.controller ? this.controller.user : null;
+ },
+
+ get session() {
+ return this.controller ? this.controller.session : null;
+ },
+
+ get sessionid() {
+ return this.controller && this.controller ? this.controller.req.sessionid : null;
+ },
+
+ get language() {
+ return (this.controller ? this.controller.language : '') || '';
+ },
+
+ get ip() {
+ return this.controller ? this.controller.ip : null;
+ },
+
+ get id() {
+ return this.controller ? this.controller.id : null;
+ },
+
+ get req() {
+ return this.controller ? this.controller.req : null;
+ },
+
+ get res() {
+ return this.controller ? this.controller.res : null;
+ },
+
+ get params() {
+ return this.controller ? this.controller.params : null;
+ },
+
+ get files() {
+ return this.controller ? this.controller.files : null;
+ },
+
+ get body() {
+ return this.controller ? this.controller.body : null;
+ },
+
+ get query() {
+ return this.controller ? this.controller.query : null;
+ },
+
+ get headers() {
+ return this.controller && this.controller.req ? this.controller.req.headers : null;
+ },
+
+ get ua() {
+ return this.controller && this.controller.req ? this.controller.req.ua : null;
+ },
+
+ get filter() {
+ var ctrl = this.controller;
+ if (ctrl && !ctrl.$filter)
+ ctrl.$filter = ctrl.$filterschema ? CONVERT(ctrl.query, ctrl.$filterschema) : ctrl.query;
+ return ctrl ? ctrl.$filter : EMPTYOBJECT;
+ }
+
+};
+
+const OperationOptionsProto = OperationOptions.prototype;
+
+SchemaOptionsProto.tasks = OperationOptionsProto.tasks = function(taskname, name, callback, options) {
+ return taskname ? TASK(taskname, name, callback, options || this) : new TaskBuilder(this);
+};
+
+OperationOptionsProto.cancel = function() {
+ var self = this;
+ self.callback = null;
+ self.error = null;
+ self.controller = null;
+ self.options = null;
+ self.model = self.value = null;
+ return self;
+};
+
+OperationOptionsProto.noop = function(nocustomresponse) {
+ var self = this;
+ !nocustomresponse && self.controller && self.controller.custom();
+ self.callback(NoOp);
+ return self;
+};
+
+OperationOptionsProto.successful = function(callback) {
+ var self = this;
+ return function(err, a, b, c) {
+ if (err)
+ self.invalid(err);
+ else
+ callback.call(self, a, b, c);
+ };
+};
+
+OperationOptionsProto.redirect = function(url) {
+ this.callback(new F.callback_redirect(url));
+ return this;
+};
+
+OperationOptionsProto.DB = function() {
+ return F.database(this.error);
+};
+
+OperationOptionsProto.done = function(arg) {
+ var self = this;
+ return function(err, response) {
+ if (err) {
+ self.error.push(err);
+ self.callback();
+ } else {
+ if (arg)
+ self.callback(SUCCESS(err == null, arg === true ? response : arg));
+ else
+ self.callback(SUCCESS(err == null));
+ }
+ };
+};
+
+OperationOptionsProto.success = function(a, b) {
+
+ if (a && b === undefined && typeof(a) !== 'boolean') {
+ b = a;
+ a = true;
+ }
+
+ this.callback(SUCCESS(a === undefined ? true : a, b));
+ return this;
+};
+
+OperationOptionsProto.invalid = function(name, error, path, index) {
+
+ var self = this;
+
+ if (arguments.length) {
+ self.error.push(name, error, path, index);
+ self.callback();
+ return self;
+ }
+
+ return function(err) {
+ self.error.push(err);
+ self.callback();
+ };
+};
+
+function AuthOptions(req, res, flags, callback) {
+ this.req = req;
+ this.res = res;
+ this.flags = flags || [];
+ this.processed = false;
+ this.$callback = callback;
+}
+
+AuthOptions.prototype = {
+
+ get language() {
+ return this.req.language || '';
+ },
+
+ get ip() {
+ return this.req.ip;
+ },
+
+ get params() {
+ return this.req.params;
+ },
+
+ get files() {
+ return this.req.files;
+ },
+
+ get body() {
+ return this.req.body;
+ },
+
+ get query() {
+ return this.req.query;
+ },
+
+ get headers() {
+ return this.req.headers;
+ },
+
+ get ua() {
+ return this.req ? this.req.ua : null;
+ }
+};
+
+const AuthOptionsProto = AuthOptions.prototype;
+
+AuthOptionsProto.roles = function() {
+ for (var i = 0; i < arguments.length; i++)
+ this.flags.push('@' + arguments[i]);
+ return this;
+};
+
+SchemaOptionsProto.cookie = OperationOptionsProto.cookie = TaskBuilderProto.cookie = AuthOptionsProto.cookie = function(name, value, expire, options) {
+ var self = this;
+ if (value === undefined)
+ return self.req.cookie(name);
+ if (value === null)
+ expire = '-1 day';
+ self.res.cookie(name, value, expire, options);
+ return self;
+};
+
+AuthOptionsProto.invalid = function(user) {
+ this.next(false, user);
+};
+
+AuthOptionsProto.done = function(response) {
+ var self = this;
+ return function(is, user) {
+ self.next(is, response ? response : user);
+ };
+};
+
+AuthOptionsProto.success = function(user) {
+ this.next(true, user);
+};
+
+AuthOptionsProto.next = AuthOptionsProto.callback = function(is, user) {
+
+ if (this.processed)
+ return;
+
+ // @is "null" for callbacks(err, user)
+ // @is "true"
+ // @is "object" is as user but "user" must be "undefined"
+
+ if (is instanceof Error || is instanceof ErrorBuilder) {
+ // Error handling
+ is = false;
+ } else if (is == null && user) {
+ // A callback error handling
+ is = true;
+ } else if (user == null && is && is !== true) {
+ user = is;
+ is = true;
+ }
+
+ this.processed = true;
+ this.$callback(is, user, this);
+};
+
+AuthOptions.wrap = function(fn) {
+ if (REGEXP_NEWOPERATION.test(fn.toString())) {
+ var fnnew = function(req, res, flags, next) {
+ fn(new AuthOptions(req, res, flags, next));
+ };
+ fnnew.$newversion = true;
+ return fnnew;
+ }
+ return fn;
+};
+
+global.CONVERT = function(value, schema) {
+ var key = schema;
+ if (key.length > 50)
+ key = key.hash();
+ var fn = F.convertors2 && F.convertors2[key];
+ return fn ? fn(value) : convertorcompile(schema, value, key);
+};
+
+function convertorcompile(schema, data, key) {
+ var prop = schema.split(',');
+ var cache = [];
+ for (var i = 0, length = prop.length; i < length; i++) {
+ var arr = prop[i].split(':');
+ var obj = {};
+
+ var type = arr[1].toLowerCase().trim();
+ var size = 0;
+ var isarr = type[0] === '[';
+ if (isarr)
+ type = type.substring(1, type.length - 1);
+
+ var index = type.indexOf('(');
+ if (index !== -1) {
+ size = +type.substring(index + 1, type.length - 1).trim();
+ type = type.substring(0, index);
+ }
+
+ obj.name = arr[0].trim();
+ obj.size = size;
+ obj.type = type;
+ obj.array = isarr;
+
+ switch (type) {
+ case 'string':
+ obj.fn = $convertstring;
+ break;
+ case 'number':
+ obj.fn = $convertnumber;
+ break;
+ case 'number2':
+ obj.fn = $convertnumber2;
+ break;
+ case 'boolean':
+ obj.fn = $convertboolean;
+ break;
+ case 'date':
+ obj.fn = $convertdate;
+ break;
+ case 'uid':
+ obj.fn = $convertuid;
+ break;
+ case 'upper':
+ obj.fn = (val, obj) => $convertstring(val, obj).toUpperCase();
+ break;
+ case 'lower':
+ obj.fn = (val, obj) => $convertstring(val, obj).toLowerCase();
+ break;
+ case 'capitalize':
+ obj.fn = (val, obj) => $convertstring(val, obj).capitalize();
+ break;
+ case 'capitalize2':
+ obj.fn = (val, obj) => $convertstring(val, obj).capitalize(true);
+ break;
+ case 'base64':
+ obj.fn = val => typeof(val) === 'string' ? val.isBase64() ? val : '' : '';
+ break;
+ case 'email':
+ obj.fn = function(val, obj) {
+ var tmp = $convertstring(val, obj);
+ return tmp.isEmail() ? tmp : '';
+ };
+ break;
+ case 'zip':
+ obj.fn = function(val, obj) {
+ var tmp = $convertstring(val, obj);
+ return tmp.isZIP() ? tmp : '';
+ };
+ break;
+ case 'phone':
+ obj.fn = function(val, obj) {
+ var tmp = $convertstring(val, obj);
+ return tmp.isPhone() ? tmp : '';
+ };
+ break;
+ case 'url':
+ obj.fn = function(val, obj) {
+ var tmp = $convertstring(val, obj);
+ return tmp.isURL() ? tmp : '';
+ };
+ break;
+ case 'json':
+ obj.fn = function(val, obj) {
+ var tmp = $convertstring(val, obj);
+ return tmp.isJSON() ? tmp : '';
+ };
+ break;
+ case 'object':
+ return val => val;
+ case 'search':
+ obj.fn = (val, obj) => $convertstring(val, obj).toSearch();
+ break;
+ default:
+ obj.fn = val => val;
+ break;
+ }
+
+ if (isarr) {
+ obj.fn2 = obj.fn;
+ obj.fn = function(val, obj) {
+ if (!(val instanceof Array))
+ val = val == null || val == '' ? [] : [val];
+ var output = [];
+ for (var i = 0, length = val.length; i < length; i++) {
+ var o = obj.fn2(val[i], obj);
+ switch (obj.type) {
+ case 'email':
+ case 'phone':
+ case 'zip':
+ case 'json':
+ case 'url':
+ case 'uid':
+ case 'date':
+ o && output.push(o);
+ break;
+ default:
+ output.push(o);
+ break;
+ }
+ }
+ return output;
+ };
+ }
+
+ cache.push(obj);
+ }
+
+ var fn = function(data) {
+ var output = {};
+ for (var i = 0, length = cache.length; i < length; i++) {
+ var item = cache[i];
+ output[item.name] = item.fn(data[item.name], item);
+ }
+ return output;
+ };
+ if (!F.convertors2)
+ F.convertors2 = {};
+ F.convertors2[key] = fn;
+ return fn(data);
+}
+
+function $convertstring(value, obj) {
+ return value == null ? '' : typeof(value) !== 'string' ? obj.size ? value.toString().max(obj.size) : value.toString() : obj.size ? value.max(obj.size) : value;
+}
+
+function $convertnumber(value) {
+ if (value == null)
+ return 0;
+ if (typeof(value) === 'number')
+ return value;
+ var num = +value.toString().replace(',', '.');
+ return isNaN(num) ? 0 : num;
+}
+
+function $convertnumber2(value) {
+ if (value == null)
+ return null;
+ if (typeof(value) === 'number')
+ return value;
+ var num = +value.toString().replace(',', '.');
+ return isNaN(num) ? null : num;
+}
+
+function $convertboolean(value) {
+ return value == null ? false : value === true || value == '1' || value === 'true' || value === 'on';
+}
+
+function $convertuid(value) {
+ return value == null ? '' : typeof(value) === 'string' ? value.isUID() ? value : '' : '';
+}
+
+function $convertdate(value) {
+
+ if (value == null)
+ return null;
+
+ if (value instanceof Date)
+ return value;
+
+ switch (typeof(value)) {
+ case 'string':
+ case 'number':
+ return value.parseDate();
+ }
+
+ return null;
+}
+
// ======================================================
// EXPORTS
// ======================================================
+exports.SchemaBuilder = SchemaBuilder;
+exports.RESTBuilder = RESTBuilder;
exports.ErrorBuilder = ErrorBuilder;
exports.Pagination = Pagination;
-exports.UrlBuilder = UrlBuilder;
\ No newline at end of file
+exports.Page = Page;
+exports.UrlBuilder = UrlBuilder;
+exports.SchemaOptions = SchemaOptions;
+exports.OperationOptions = OperationOptions;
+exports.RESTBuilderResponse = RESTBuilderResponse;
+exports.AuthOptions = AuthOptions;
+global.RESTBuilder = RESTBuilder;
+global.RESTBuilderResponse = RESTBuilderResponse;
+global.ErrorBuilder = ErrorBuilder;
+global.Pagination = Pagination;
+global.Page = Page;
+global.UrlBuilder = global.URLBuilder = UrlBuilder;
+global.SchemaBuilder = SchemaBuilder;
+global.TaskBuilder = TaskBuilder;
+
+// Uninstall owners
+exports.uninstall = function(owner) {
+
+ if (!owner)
+ return;
+
+ Object.keys(operations).forEach(function(key) {
+ if (operations[key].$owner === owner)
+ delete operations[key];
+ });
+
+ exports.eachschema(function(group, name, schema) {
+ schema.owner === owner && schema.destroy();
+ });
+};
+
+TaskBuilderProto.invalid = function(err, msg) {
+ var self = this;
+ if (!self.$done) {
+ !self.error && (self.error = new ErrorBuilder());
+ self.error.push(err, msg);
+ self.done();
+ }
+ return self;
+};
+
+TaskBuilderProto.push = function(name, fn) {
+ var self = this;
+ self.tasks[name] = fn;
+ return self;
+};
+
+TaskBuilderProto.next = function() {
+ var self = this;
+ if (!self.$done) {
+ self.current && self.controller && CONF.logger && F.ilogger((self.name || 'tasks') + '.' + self.current, self.controller, self.$now);
+ self.prev = self.current;
+ for (var i = 0; i < arguments.length; i++) {
+ self.current = arguments[i];
+ var task = self.tasks[self.current] || (self.taskname ? tasks[self.taskname] && tasks[self.taskname][self.current] : null);
+ if (task == null)
+ continue;
+ else {
+ task.call(self, self);
+ return self;
+ }
+ }
+ self.done();
+ }
+ return self;
+};
+
+TaskBuilderProto.next2 = function(name) {
+ var self = this;
+ return function(err) {
+ if (err)
+ self.invalid(err);
+ else {
+ if (name == null)
+ self.done();
+ else
+ self.next(name);
+ }
+ };
+};
+
+TaskBuilderProto.done = function(data) {
+ var self = this;
+ self.$callback && self.$callback(self.error && self.error.is ? self.error : null, data || self.value);
+ self.$done = true;
+ return self;
+};
+
+TaskBuilderProto.done2 = function(send_value) {
+ var self = this;
+ return function(err, data) {
+ if (err)
+ self.invalid(err);
+ else
+ self.done(send_value ? data : null);
+ };
+};
+
+TaskBuilderProto.success = function(data) {
+ return this.done(SUCCESS(true, data));
+};
+
+TaskBuilderProto.success2 = function(send_value) {
+ var self = this;
+ return function(err, data) {
+ if (err)
+ self.invalid(err);
+ else
+ self.done(SUCCESS(true, send_value ? data : null));
+ };
+};
+
+TaskBuilderProto.callback = function(fn) {
+ var self = this;
+ self.$callback = fn;
+ return self;
+};
+
+TaskBuilderProto.exec = function(name, callback) {
+ var self = this;
+
+ if (callback)
+ self.$callback = callback;
+
+ if (CONF.logger)
+ self.$now = Date.now();
+
+ self.next(name);
+ return self;
+};
\ No newline at end of file
diff --git a/bundles.js b/bundles.js
new file mode 100755
index 000000000..cba3ab529
--- /dev/null
+++ b/bundles.js
@@ -0,0 +1,385 @@
+// Copyright 2012-2020 (c) Peter Širka
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+/**
+ * @module FrameworkBundles
+ * @version 3.4.3
+ */
+
+require('./index');
+
+const Fs = require('fs');
+const Path = require('path');
+const CONSOLE = process.argv.indexOf('restart') === -1;
+const INTERNAL = { '/sitemap': 1, '/versions': 1, '/workflows': 1, '/dependencies': 1, '/config': 1, '/config-release': 1, '/config-debug': 1 };
+const isWindows = require('os').platform().substring(0, 3).toLowerCase() === 'win';
+const REGAPPEND = /\/--[a-z0-9]+/i;
+const REGAPPENDREPLACE = /\/--/g;
+const REGBK = /(-|_)bk\.bundle$/i;
+const META = {};
+
+META.version = 1;
+META.created = new Date();
+META.total = 'v' + F.version_header;
+META.node = F.version_node;
+META.files = [];
+META.skip = false;
+META.directories = [];
+META.ignore = () => true;
+
+exports.make = function(callback) {
+
+ var path = F.path.root();
+ var blacklist = {};
+
+ if (CONSOLE) {
+ console.log('--------------------- BUNDLING ---------------------');
+ console.time('Done');
+ }
+
+ var isignore = false;
+
+ try {
+ META.ignore = makeignore(Fs.readFileSync(Path.join(path, '.bundleignore')).toString('utf8').split('\n'));
+ isignore = true;
+ } catch (e) {}
+
+ if (!isignore) {
+ try {
+ META.ignore = makeignore(Fs.readFileSync(Path.join(path, '.bundlesignore')).toString('utf8').split('\n'));
+ } catch (e) {}
+ }
+
+ blacklist[CONF.directory_temp] = 1;
+ blacklist[CONF.directory_bundles] = 1;
+ blacklist[CONF.directory_src] = 1;
+ blacklist[CONF.directory_logs] = 1;
+ blacklist['/node_modules/'] = 1;
+ blacklist['/debug.pid'] = 1;
+ blacklist['/package-lock.json'] = 1;
+
+ var Files = [];
+ var Dirs = [];
+ var Merge = [];
+ var Length = path.length;
+ var async = [];
+
+ async.push(cleanFiles);
+
+ async.push(function(next) {
+ META.skip && (async.length = 0);
+ next();
+ });
+
+ async.push(function(next) {
+ var target = F.path.root(CONF.directory_src);
+ U.ls(F.path.root(CONF.directory_bundles), function(files) {
+ var dirs = {};
+ files.wait(function(filename, resume) {
+
+ if (!filename.endsWith('.bundle') || REGBK.test(filename))
+ return resume();
+
+ if (CONSOLE)
+ console.log('-----', U.getName(filename));
+
+ var dbpath = CONF.directory_databases;
+ var pathupdate = CONF.directory_updates;
+ var pathstartup = '/startup';
+
+ F.restore(filename, target, resume, function(p, dir) {
+
+ if (dir) {
+ if (!p.startsWith(dbpath) && META.directories.indexOf(p) === -1)
+ META.directories.push(p);
+ } else {
+
+ var dirname = p.substring(0, p.length - U.getName(p).length);
+ if (dirname && dirname !== '/')
+ dirs[dirname] = true;
+
+ // handle files in bundle to merge
+ var mergeme = 0;
+
+ if (REGAPPEND.test(p)) {
+ mergeme = 3;
+ p = p.replace(REGAPPENDREPLACE, '/');
+ }
+
+ var exists = false;
+ try {
+ exists = Fs.statSync(Path.join(target, p)) != null;
+ } catch (e) {}
+
+ if ((dirname === pathupdate || dirname === pathstartup) && !exists) {
+ try {
+ exists = Fs.statSync(Path.join(target, p + '_bk')) != null;
+ } catch (e) {}
+ }
+
+ // A specific file like DB file or startup file or update script
+ if (exists && (p.startsWith(dbpath) || p.startsWith(pathupdate) || p.startsWith(pathstartup)))
+ return false;
+
+ if (INTERNAL[p] || U.getExtension(p) === 'resource' || mergeme) {
+ var hash = p.hash(true).toString(16);
+ Merge.push({ name: p, filename: Path.join(target, p + hash), type: mergeme });
+ META.files.push(p + hash);
+ return p + hash;
+ }
+
+ if (META.files.indexOf(p) === -1)
+ META.files.push(p);
+ }
+
+ return true;
+ });
+ }, function() {
+ dirs = Object.keys(dirs);
+ dirs.length && Dirs.push.apply(Dirs, dirs);
+ next();
+ });
+ });
+ });
+
+ async.push(function(next) {
+ if (Merge.length) {
+ copyFiles(Merge, function() {
+ for (var i = 0; i < Merge.length; i++) {
+ try {
+ Fs.unlinkSync(Merge[i].filename);
+ } catch(e) {}
+ }
+ next();
+ });
+ } else
+ next();
+ });
+
+ async.push(function(next) {
+ U.ls(path, function(files, dirs) {
+
+ for (var i = 0, length = dirs.length; i < length; i++)
+ Dirs.push(normalize(dirs[i].substring(Length)));
+
+ for (var i = 0, length = files.length; i < length; i++) {
+ var file = files[i].substring(Length);
+ var type = 0;
+
+ if (file.startsWith(CONF.directory_databases) || file.startsWith('/flow/') || file.startsWith('/dashboard/'))
+ type = 1;
+ else if (REGAPPEND.test(file)) {
+ file = file.replace(REGAPPENDREPLACE, '/');
+ type = 3;
+ } else if (file.startsWith(CONF.directory_public))
+ type = 2;
+
+ Files.push({ name: file, filename: files[i], type: type });
+ }
+
+ next();
+ }, function(p) {
+ p = normalize(p.substring(Length));
+ return blacklist[p] == null && p.substring(0, 2) !== '/.';
+ });
+ });
+
+ async.push(function(next) {
+ createDirectories(Dirs, function() {
+ copyFiles(Files, next);
+ });
+ });
+
+ async.push(function(next) {
+ Fs.writeFileSync(Path.join(F.path.root(CONF.directory_src), 'bundle.json'), JSON.stringify(META, null, '\t'));
+ next();
+ });
+
+ async.async(function() {
+ CONSOLE && console.timeEnd('Done');
+ callback();
+ });
+
+};
+
+function makeignore(arr) {
+
+ var ext;
+ var code = ['var path=P.substring(0,P.lastIndexOf(\'/\')+1);', 'var ext=U.getExtension(P);', 'var name=U.getName(P).replace(\'.\'+ ext,\'\');'];
+
+ for (var i = 0; i < arr.length; i++) {
+ var item = arr[i];
+ var index = item.lastIndexOf('*.');
+
+ if (index !== -1) {
+ // only extensions on this path
+ ext = item.substring(index + 2);
+ item = item.substring(0, index);
+ code.push('tmp=\'{0}\';'.format(item));
+ code.push('if((!tmp||path===tmp)&&ext===\'{0}\')return;'.format(ext));
+ continue;
+ }
+
+ ext = U.getExtension(item);
+ if (ext) {
+ // only filename
+ index = item.lastIndexOf('/');
+ code.push('tmp=\'{0}\';'.format(item.substring(0, index + 1)));
+ code.push('if(path===tmp&&U.getName(\'{0}\').replace(\'.{1}\', \'\')===name&&ext===\'{1}\')return;'.format(item.substring(index + 1), ext));
+ continue;
+ }
+
+ // all nested path
+ code.push('if(path.startsWith(\'{0}\'))return;'.format(item.replace('*', '')));
+ }
+
+ code.push('return true');
+ return new Function('P', code.join(''));
+}
+
+function normalize(path) {
+ return isWindows ? path.replace(/\\/g, '/') : path;
+}
+
+function cleanFiles(callback) {
+
+ var path = F.path.root(CONF.directory_src);
+ var length = path.length - 1;
+ var blacklist = {};
+
+ blacklist[CONF.directory_public] = 1;
+ blacklist[CONF.directory_private] = 1;
+ blacklist[CONF.directory_databases] = 1;
+
+ var meta;
+
+ try {
+ meta = U.parseJSON(Fs.readFileSync(Path.join(path, 'bundle.json')).toString('utf8'), true) || {};
+
+ if (CONF.bundling === 'shallow') {
+ META.skip = true;
+ callback();
+ return;
+ }
+
+ } catch (e) {
+ meta = {};
+ }
+
+ if (meta.files && meta.files.length) {
+ for (var i = 0, length = meta.files.length; i < length; i++) {
+ var filename = meta.files[i];
+ var dir = filename.substring(0, filename.indexOf('/', 1) + 1);
+ if (!blacklist[dir]) {
+ try {
+ F.consoledebug('Remove', filename);
+ Fs.unlinkSync(Path.join(path, filename));
+ } catch (e) {}
+ }
+ }
+ }
+
+ if (meta.directories && meta.directories.length) {
+ meta.directories.quicksort('length', false);
+ for (var i = 0, length = meta.directories.length; i < length; i++) {
+ try {
+ if (!blacklist[meta.directories[i]])
+ Fs.rmdirSync(Path.join(path, meta.directories[i]));
+ } catch (e) {}
+ }
+ }
+
+ callback();
+}
+
+function createDirectories(dirs, callback) {
+
+ var path = F.path.root(CONF.directory_src);
+
+ try {
+ Fs.mkdirSync(path);
+ } catch(e) {}
+
+ for (var i = 0, length = dirs.length; i < length; i++) {
+ var p = normalize(dirs[i]);
+ if (META.directories.indexOf(p) === -1)
+ META.directories.push(p);
+ try {
+ Fs.mkdirSync(Path.join(path, dirs[i]));
+ } catch (e) {}
+ }
+
+ callback();
+}
+
+function copyFiles(files, callback) {
+ var path = F.path.root(CONF.directory_src);
+ files.wait(function(file, next) {
+
+ if (!META.ignore(file.name))
+ return next();
+
+ var filename = Path.join(path, file.name);
+ var exists = false;
+ var ext = U.getExtension(file.name);
+ var append = file.type === 3;
+
+ try {
+ exists = Fs.statSync(filename) != null;
+ } catch (e) {}
+
+ // DB file
+ if (file.type === 1 && exists) {
+ next();
+ return;
+ }
+
+ var p = normalize(file.name);
+
+ if (file.type !== 1 && META.files.indexOf(p) === -1)
+ META.files.push(p);
+
+ if (exists && (ext === 'resource' || (!ext && file.name.substring(1, 7) === 'config') || INTERNAL[file.name]))
+ append = true;
+
+ if (CONSOLE && exists) {
+ CONF.allow_debug && F.consoledebug(append ? 'EXT:' : 'REW:', p);
+ } else
+ F.consoledebug(append ? 'EXT:' : 'COP:', p);
+
+ if (append) {
+ Fs.appendFile(filename, '\n' + Fs.readFileSync(file.filename).toString('utf8'), next);
+ } else
+ copyFile(file.filename, filename, next);
+
+ if (CONSOLE && exists)
+ CONF.allow_debug && F.consoledebug('REW:', p);
+ else
+ F.consoledebug('COP:', p);
+
+ }, callback);
+}
+
+function copyFile(oldname, newname, callback) {
+ var writer = Fs.createWriteStream(newname);
+ writer.on('finish', callback);
+ Fs.createReadStream(oldname).pipe(writer);
+}
diff --git a/changes.txt b/changes.txt
index ff98388a4..68a16e3a6 100755
--- a/changes.txt
+++ b/changes.txt
@@ -1,9 +1,2324 @@
-IN DEVELOPMENT ======= 1.5.2 (HOTFIX)
+======= 3.4.13
+- fixed wrong counting of week number in the `Date.format()`
+
+======= 3.4.12
+
+- (critical) fixed WebSocket implementation for Safari +15
+- (critical) fixed extracting packages/bundles
+
+======= 3.4.11
+
+- fixed calling `F.snapshotstats()` #785
+- improved RegExp for validating URL addresses by [yetingli](https://github.com/yetingli)
+
+======= 3.4.10
+
+- fixed CSS variables
+
+======= 3.4.9
+
+- added HTML escaping for meta tags
+- added `insecure` flag into the `U.request()` method
+- added `RESTBuilder.insecure()` method
+- fixed security issue when parsing query arguments (reported by )
+- fixed security in `U.get()` and `U.set()` (reported by Agustin Gianni)
+
+======= 3.4.8
+
+- fixed measuring dimension for `.gif` images
+- fixed potential remote code execution in `U.set()` founded by [Snyk](https://snyk.io/vuln)
+
+======= 3.4.7
+
+- fixed: command injection in `Image.pipe()` and `Image.stream()`
+- fixed `DELETE` method for the schemas (now it works like `PATCH` method)
+- fixed: `controller.transfer()`
+
+======= 3.4.6
+
+- added: a support for Total.js v4 UIDs
+
+- updated: file stats
+- updated: calculating of `usage`
+
+- fixed: applying of `default_root` in static files
+- fixed: routing evaluation
+- fixed: parsing of longer WebSocket messages
+- fixed: mail error handling
+- fixed: `versions` with `default_root`
+
+======= 3.4.5
+
+- fixed: a problem with persistent images
+
+======= 3.4.4
+
+- added: schema options `$.successful(function(response) {})`
+- added: `options.reconnectserver {Boolean}` to `WEBSOCKETCLIENT`
+- added: `req.snapshot(callback(err, request_body))`
+- added: a new command `CMD('reload_preferences')`
+- added: a new FILESTORAGE mechanism based on `UID`
+- added: `sql` extension to `U.getContentType()`
+- added: `F.stats.performance.usage` which contains percentual usage of the thread
+
+- updated: `SchemaOptions` method `$.response([index/operation_name])`, e.g. `$.response('workflow.NAME')`
+- updated: snapshot `startscript.js.json` contains tabs instead of spaces
+- updated: `DatabaseBuilder.rule(rule, [param])`, supports string declaration of filter function
+- updated: `URL` validation
+
+- fixed: cleaning of NoSQL embedded databases
+- fixed: `String.parseCSV()`, now supports multiline strings
+- fixed: a bug when closing of websocket
+- fixed: `DatabaseBuilder.search()` method
+- fixed: `Error` in `CLONE()` method
+- fixed: `schema.inherit()` by adding `schema.middleware()` and `schema.verify()`
+- fixed: parsing messages in WebSocket
+- fixed: a problem in some commands pre-render in the view compiler
+- fixed: parsing of query strings
+
+======= 3.4.3
+
+- added: `HASH(value, [type])` for creating hash like in jComponent
+- added: `SchemaOptions.repo` as alias to `SchemaInstance.model.$$repository`
+- added: a new type `CONVERT syntax` to `schema.define()` (more in docs)
+- added: `SchemaEntity.verify(name, function($), [cache])` for async verification of values
+- added: `TEMP` variable as a new global variable (it's cleaned every 7 minutes)
+- added: `CONF.allow_persistent_images: true` which allows to reuse resized images in temp directory
+- added: `req.filecache(callback)` as alias for `F.exists()`
+- added: own QueryParser
+- added: `RESTBuilderInstance.convert('name:String,age:Number')` method
+- added: `RESTBuilder.upgrade(fn(restbuilder))` for upgrading of `RESTBuilder`
+- added: `RESTBuilder` parses Total.js Errors in responses as Error
+- added: `String.prototype.env()` replaces all values in the form `[key]` for `CONF.key`
+- added: WebSocket supports a new type - raw `buffer`
+- added: `Number.fixed(decimals)`
+
+- updated: `websocket.send2(message, comparer, replacer, [params])` by adding `params` argument for comparer function
+- updated: `Websocket.encodedecode` can enable/disable easily encoding of messages
+- updated: bundling skips all bundles with `-bk.bundle` in filename
+- updated: bundle filenames are displayed in console
+- updated: `UPDATE()` method by adding `noarchive` argument
+- updated: `TEST()` method supports `[subdomain]` keyword and `METHOD url` in URL address
+- updated: `MODIFY([filename], fn)` by adding `filename` argument
+- updated: background of schedulers by @fgnm
+- updated: `U.download()` by adding `param` argument
+- updated: `U.request()` by adding `param` argument
+- updated: `schema.cl(name, [value])` method by adding `value` argument for replacing of existing code-list
+- updated: Tangular version to `v4.0.0`
+
+- improved: `filename` in modificators (now filenames contain relative paths)
+- improved: performance of `U.request()` (around +10%)
+- improved: performance of `U.download()` (around +10%)
+- improved: performance of `RESTBuilder`
+- improved: CSS minifier by compressing single hex color from e.g. `#000000` to `#000`
+
+- fixed: localization in `totaljs` executable script
+- fixed: phone validation
+- fixed: `DOWNLOAD()`
+- fixed: `Number.VAT()` by Tomas Novak
+- fixed: debugging mode in Node.js v14
+- fixed: `allow_compile_html` in static files
+- fixed: `ROUTE()` method, there was a problem with spaces `GET /* `
+- fixed: `ACTION()` with json output
+- fixed: controller in `$ACTION()` with used `get` and `query` actions
+- fixed: `PATCH` method in `$ACTION()`
+- fixed: `schema.allow()` in `PATCH` method
+- fixed: image resizing in debug-mode
+
+======= 3.4.1
+
+- added: `SchemaOptions.parent` returns a parent model
+- added: Tangular template engine (experimental)
+- added: `String.makeid()` for creating of unique identifier from string
+- added: a new property called `message.ua` to `FLOWSTREAM()`
+
+- updated: `HttpFile.fs()` by adding `id` argument for updating of existing file
+- updated: default value for `allow_ssc_validation` to `true`
+
+- fixed: `String.parseDate(format)` with defined format
+- fixed: inheriting of controllers between schemas
+- fixed: `MailMessage.attachments()`
+- fixed: calling of `F.snapshotstats` in cache recycle
+- fixed: `controller.success()`
+- fixed: removing of unused files when a bundle is extracting
+- fixed: a processor function in `F.backup()`
+
+- improved: `Date.format()`
+- improved: Total.js translate (supports ErrorBuilder and DBMS)
+
+======= 3.4.0
+
+- added: `date.setTimeZone(timezone)`
+- added: `NOSQL('~absolute_path.nosql')' loads external NoSQL embedded database
+- added: `TABLE('~absolute_path.nosql')' loads external Table
+- added: `(generate)` subtype into the `config` files
+- added: `String.isBase64()`
+- added: new schema type `Base64`
+- added: SchemaEntity supports `schema.addWorkflowExtension(name, fn($, [data]))`
+- added: SchemaEntity supports `schema.addTransformExtension(name, fn($, [data]))`
+- added: SchemaEntity supports `schema.addOperationExtension(name, fn($, [data]))`
+- added: SchemaEntity supports `schema.addHookExtension(name, fn($, [data]))`
+- added: SchemaEntity supports `schema.setSaveExtension(fn($, [data]))`
+- added: SchemaEntity supports `schema.setReadExtension(fn($, [data]))`
+- added: SchemaEntity supports `schema.setQueryExtension(fn($, [data]))`
+- added: SchemaEntity supports `schema.setRemoveExtension(fn($, [data]))`
+- added: SchemaEntity supports `schema.setInsertExtension(fn($, [data]))`
+- added: SchemaEntity supports `schema.setUpdateExtension(fn($, [data]))`
+- added: SchemaEntity supports `schema.setPatchExtension(fn($, [data]))`
+- added: SchemaOptions supports `$.extend([data])` for evaluating of all extensions for the current operation
+- added: `WebSocket.keys` property (it contains all keys with connections)
+- added: `threads` directory for server-less functionality
+- added: a global variable called `THREAD` with a name of current thread
+- added: `require('total.js').http(..., { thread: 'thread_name' })` evaluates only specified thread
+- added: `require('total.js').cluster.http(..., { thread: 'thread_name' })` evaluates only specified thread in cluster
+- added: framework creates a file with app stats in the form `your_init_script_name.js.json`
+- added: a new config key `allow_stats_snapshot`
+- added: view engine `@{import()}` supports auto-merging JS or CSS files: `@{import('default.js + ui.js')}`
+- added: `exports.options` delegate to component in `FLOWSTREAM`
+- added: `DatabaseBuilder.autofill()` from DBMS
+- added: `HttpFile.extension` property
+- added: `HttpFile.size` property alias to `HttpFile.length`
+- added: auto-session cleaner of unused sessions
+- added: `allow_sessions_unused` config key for cleaning of unused sessions
+- added: missing `PATH.schemas`, `PATH.operations` and `PATH.tasks`
+- added: a new method `PATH.updates`
+- added: easy updating of applications via `UPDATE(versions, [callback], [pause_server_message])`
+- added: NOSQL counter `.reset([type], [id], [date], [callback])` method-
+- added: `session.listlive(callback)` returns all live items in session
+- added: `controller.ua` returns parsed User-Agent
+- added: `$.ua` returns parsed User-Agent in Schemas, Operations, TaskBuilder, `MIDDLEWARE()` and `AUTH()`
+- added: support for `.mjs` extensions
+- added: a simple support for DDOS protection `allow_reqlimit : Number` (max. concurent requests by IP just-in-time)
+- added: unit-testing supports colors, added by @dacrhu
+- added: `String.encryptUID()` as alias for `U.encryptUID()`
+- added: `String.decryptUID()` as alias for `U.decryptUID()`
+
+- updated: `WEBSOCKET()` supports `+`, `-` and `🔒` as authorization flags
+- updated: `LOAD()` supports `service` type
+- updated: cluster watches `restart` or `restart_NAME_of_THREAD` files for restarting of existing threads
+- updated: cluster supports `auto` mode
+- updated: cluster supports watcher in `debug` mode
+- updated: `*.filefs()`, `*.filenosql()`, `*.imagefs()`, `*.imagenosql()` by adding `checkmeta` argument
+- updated: `$.done([user_instance])` method in `AUTH()`, added a new argument called `user_instance` (optional)
+- updated: GZIP is enabled only for JSON bodies which have more than 4096 bytes
+- updated: `.env` parser supports parsing of `.env-debug` or `.env-release` files according to the mode
+- updated: list of user-agents in `String.parseUA()`
+
+- fixed: `ON('error404')` when the route doens't exist
+- fixed: `filter` in Schema `workflows`, `transformations` and `operations`
+- fixed: `NOSQL()` joins with absolute paths
+- fixed: `TABLE()` joins with absolute paths
+- fixed: `(random)` subtype in `config` files
+- fixed: `(response)` phrase in `ROUTE()` for multiple `OPERATIONS`
+- fixed: a response in `ROUTE()` with mulitple operations if the result contained some error
+- fixed: a security bug with a path traversal vulnerability
+- fixed: `debug` watcher for `themes`
+- fixed: `generators` in schemas with a new declaration
+- fixed: a problem with handling files in 404 action
+- fixed: `startup` directory in bundles
+- fixed: `schema.inherit()` didn't copy `required` fields.
+- fixed: `SUCCESS()` serialization with `SUCCESS()` argument
+- fixed: a critial bug with `UID()` generator
+- fixed: clearing of DNS cache
+
+- improved: `LOGMAIL()` mail format
+- improved: starting logs in console output (added IPv4 local address)
+- improved: performance with JSON serialization in `controller.success()` and `controller.done()`
+
+======= 3.3.2
+
+- fixed: default time zone (`utc` is default time zone)
+
+======= 3.3.1
+
+- added: `RESTBuilder.callback()` which performs `.exec()` automatically
+- added: `FLOWSTREAM()`
+
+- fixed: `AUDIT()` method
+- fixed: error handling in `controller.invalid()`
+- fixed: `req.authorize()`
+- fixed: CSS auto-vendor-prefixes, fixed `opacity` with `!important`
+- fixed: `CONVERT()` a problem with arrays
+
+======= 3.3.0
+
+- added: `NEWTASK(name, declaration)` for creating preddefined `TaskBuilder`
+- added: `TASK(name, taskname, callback, [controller/SchemaOptions/OperationOptions/ErrorBuilder])` for executing preddefined `TaskBuilder`
+- added: a new config key `directory_tasks` for `TaskBuilder`
+- added: a global alias `MODIFY()` for `F.modify()`
+- added: a global alias `VIEWCOMPILE()` for `F.view_compile()`
+- added: `mail.type = 'html'` can be `html` (default) or `plain`
+- added: `$.headers` into the `SchemaOptions`, `OperationOptions` and `TaskBuilder`
+- added: `String.parseCSV([delimiter])` returns `Object Array`
+- added: `String.parseUA([structured])` a simple user-agent parser
+- added: `req.useragent([structured])` returns parsed User-Agent
+- added: a new config key `default_crypto` it can rewrite Total.js crypto mechanism (default: `undefined`)
+- added: a new config key `default_crypto_iv` it's an initialization vector (default: generated from `secret`) or it can contain a custom `hex` value
+- added: a new config key `allow_workers_silent` can enable/disable silent workers (default: `false`)
+- added: a new config sub-type called `random`, example: `secret_key (random) : 10` and `10` means a length of value
+- added: a new command `clear_dnscache` for clearing DNS cache
+- added: commands `INSTALL('command', 'command_name', function)` for registering commands and `CMD(name, [a], [b], [c], [d])` for executing commands
+- added: `ENCRYPTREQ(req, val, [key], [strict])` to encrypt value according to the request meta data
+- added: `DECRYPTREQ(req, val, [key])` to decrypt value according to the request meta data
+- added: `controller.nocache()`
+- added: `controller.nocontent()`
+- added: `REPO` as a global variable
+- added: `FUNC` as a global variable
+- added: `MAIN` as a global variable
+- added: `DEF` as a global variable for defining of behaviour for some operations (alternative to `F`)
+- added: `PREF.set(name, [value])` (read+write) or `PREF.propname` (only read) for reading/writing a persistent preferences
+- added: `F.onPrefSave = function(obj)` to write preferences
+- added: `F.onPrefLoad = function(next(obj))` to read preferences
+- added: `RESTBuilder.url(url)` which returns a new instance of `RESTBuilder` for chaining
+- added: `restbuilder.keepalive()` enables a keepalive for `RESTBuilder` instance
+- added: `SESSION()` management, more in docs
+- added: `controller.sessionid` with ID of `SESSION()`
+- added: `AUTH()` supports a new auth declaration with `$` as `AuthOptions` like `SchemaOptions` or `OperationOptions`
+- added: `AuthOptions` to prototypes
+- added: `ErrorBuilder.length` property (alias for `instance.items.length)
+- added: Schemas `prepare` supports `req` argument
+- added: `DEF.currencies.eur = function(val) {}` registers a currency formatter
+- added: `DEF.helpers` registers a new view engine helper (`F.helpers` is alias for for this object)
+- added: `DEF.validators` is alias for for `F.validators`
+- added: usage of currency formatter `Number.currency(currency)`
+- added: new schema type `Number2` with default value is `null`, not zero `0`
+- added: `@{json2(model, elementID, key1, key2, key3)}` can serialize data with keys defined into the `
- @{js('default.js')}
- @{favicon('favicon.ico')}
-
-
-
- @{body}
-
-
-
-