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 80c35ff5f..0b4524423 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,6 @@
*.log
.idea
test/tmp/*
-node_modules
\ No newline at end of file
+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 11d4be540..f823ea1f3 100644
--- a/.npmignore
+++ b/.npmignore
@@ -7,7 +7,10 @@ helpers/
test/
*.markdown
*.md
+.editorconfig
+.gitingore
.git*
+*.yml
*.DS_Store
*.sublime-project
*.sublime-workspace
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/totaljs b/bin/totaljs
index cd8150b5c..8ce0e9ab4 100755
--- a/bin/totaljs
+++ b/bin/totaljs
@@ -6,12 +6,31 @@ var path = require('path');
var os = require('os');
var Utils = require('total.js/utils');
var Internal = require('total.js/internal');
-var $type = 1;
+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('without arguments : creates empty-project');
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');
@@ -21,7 +40,8 @@ function display_help() {
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('-v or -version : Total.js version');
+ 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('');
@@ -286,25 +306,20 @@ function clean_resource(content) {
}
function parse_csv(content) {
+
var output = {};
var max = 0;
-
- content.split('\n').forEach(function(line) {
- line = line.trim();
- if (!line)
- return;
- var arr = line.split(';');
- if (arr.length <= 1)
- return;
-
- var key = arr[0].trim().replace(/(^\")|(\"$)/g, '');
- var value = arr[1].trim().replace(/(^\")|(\"$)/g, '');
- if (!key || !value)
- return;
-
- max = Math.max(key.length, max);
- output[key] = value;
- });
+ 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;
@@ -320,7 +335,7 @@ function main() {
console.log('');
console.log('|==================================================|');
- console.log('| Total.js - Web framework for Node.js |');
+ console.log('| Total.js - www.totaljs.com |');
console.log('| Version: v' + require('total.js').version_header.padRight(39) + '|');
console.log('|==================================================|');
console.log('');
@@ -337,11 +352,11 @@ function main() {
var port = cmd.parseInt();
if (port) {
- F.config['directory-temp'] = '~' + path.join(os.tmpdir(), 'totaljs' + dir.hash());
- F.config['directory-public'] = '~' + dir;
- F.config['allow-compile-html'] = false;
- F.config['allow-compile-script'] = false;
- F.config['allow-compile-style'] = false;
+ 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');
@@ -393,32 +408,32 @@ function main() {
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['content-type']));
+ 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 (cmd === '-v' || cmd === '-version')
+ if (!$type && (cmd === '-v' || cmd === '-version'))
return;
- if (cmd === '-t' || cmd === '-translate') {
+ if (!$type && (cmd === '-t' || cmd === '-translate')) {
$type = 4;
continue;
}
- if (cmd === '-merge') {
+ if (!$type && cmd === '-merge') {
merge(process.argv[i + 1] || '', process.argv[i + 2] || '');
return;
}
- if (cmd === '-translate-csv' || cmd === '-translatecsv' || cmd === '-c') {
+ if (!$type && (cmd === '-translate-csv' || cmd === '-translatecsv' || cmd === '-c')) {
$type = 6;
continue;
}
- if (cmd === '-csv') {
+ 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')));
@@ -428,45 +443,57 @@ function main() {
console.log('');
console.log('output : ' + tt);
console.log('');
- return;
+ continue;
}
- if (cmd === '-i' || cmd === '-install') {
+ if (!$type && (cmd === '-i' || cmd === '-install')) {
- var libs = ['jc', 'jc.min', 'jcta', 'jcta.min', 'jctajr', 'jctajr.min', 'ta', 'jr', 'jr.jc'];
+ 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');
+ 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)
+ 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/jComponent/master/jc.js';
+ 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 'jc.min':
- url = 'https://rawgit.com/totaljs/jComponent/master/jc.min.js';
+ case 'spa':
+ case 'spa.min':
+ url = 'https://rawgit.com/totaljs/components/master/0dependencies/spa.min.js';
break;
- case 'jcta.min':
- url = 'https://rawgit.com/totaljs/jComponent/master/jcta.min.js';
+ case 'spa@14':
+ case 'spa.min@14':
+ url = 'https://rawgit.com/totaljs/components/master/0dependencies/spa.min@14.js';
break;
- case 'jctajr.min':
- url = 'https://rawgit.com/totaljs/jComponent/master/jctajr.min.js';
+ case 'spa@15':
+ case 'spa.min@15':
+ url = 'https://rawgit.com/totaljs/components/master/0dependencies/spa.min@15.js';
break;
- case 'ta':
+ case 'ta':
url = 'https://rawgit.com/totaljs/Tangular/master/Tangular.js';
break;
- case 'jr':
+ case 'jr':
url = 'https://rawgit.com/totaljs/jRouting/master/jrouting.js';
break;
- case 'jr.jc':
+ case 'jr.jc':
url = 'https://rawgit.com/totaljs/jRouting/master/jrouting.jcomponent.js';
break;
}
@@ -481,10 +508,10 @@ function main() {
if (cmd === '-minify' || cmd === '-compress' || cmd === '-compile') {
$type = 5;
- continue;
+ break;
}
- if (cmd === '-clean') {
+ 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')));
@@ -497,7 +524,77 @@ function main() {
return;
}
- if (cmd === '-diff') {
+ 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;
}
@@ -507,26 +604,28 @@ function main() {
continue;
}
- if (cmd === '-m' || cmd === '-minimal' || cmd === '-minimum' || cmd === 'minimum') {
+ if (!$type && (cmd === '-m' || cmd === '-minimal' || cmd === '-minimum' || cmd === 'minimum')) {
$type = 2;
continue;
}
- if (cmd === '-n' || cmd === '-normal' || cmd === 'normal') {
+ if (!$type && (cmd === '-n' || cmd === '-normal' || cmd === 'normal')) {
$type = 1;
continue;
}
- if (cmd === '-h' || cmd === '-help' || cmd === '--help' || cmd === 'help') {
+ if (!$type && (cmd === '-h' || cmd === '-help' || cmd === '--help' || cmd === 'help')) {
display_help();
return;
}
dir = arg;
isDirectory = true;
- break;
}
+ if (!$type)
+ $type = 1;
+
if (dir === '.')
dir = process.cwd();
@@ -582,6 +681,8 @@ function main() {
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];
@@ -600,22 +701,61 @@ function main() {
continue;
}
- var key = 'T' + command.command.hash();
- var file = filename.substring(dir.length + 1);
+ 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
+ } 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);
@@ -669,7 +809,7 @@ function main() {
texts[key] = command.command;
if (!resource[key]) {
- output.push(key + ';"' + command.command.replace(/\"/g, '""') + '";');
+ output.push(key + ';"' + command.command.replace(/"/g, '""') + '";');
resource[key] = true;
count++;
}
@@ -701,23 +841,131 @@ function main() {
}
}
- console.log('Downloading "empty-project" from www.totaljs.com');
+ git(dir, 'emptyproject');
+}
+
+function git(dir, type) {
+
+ var done = function() {
+ console.log('Installed: {0}'.format(type));
+ console.log();
+ };
- var url = 'https://cdn.totaljs.com/empty-project.package';
+ U.ls(dir, function(fol, fil) {
+
+ if (fol.length || fil.length) {
+ console.log('Directory "{0}"" is not empty.'.format(dir));
+ console.log();
+ return;
+ }
- Utils.download(url, ['get'], function(err, response) {
- var filename = path.join(dir, 'total.package');
- var stream = fs.createWriteStream(filename);
- response.pipe(stream);
- stream.on('finish', function() {
- console.log('Unpacking file.');
- exec('totalpackage unpack total.package', function() {
- fs.unlink(filename, NOOP);
- console.log('Done.');
- console.log('');
+ 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);
+ });
+ });
});
});
});
}
-main();
+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 64a7a052c..a55e7e928 100755
--- a/bin/tpm
+++ b/bin/tpm
@@ -2,7 +2,7 @@
'use strict';
-const VERSION = 'v3.0.0';
+const VERSION = 'v4.0.0';
const PADDING = 120;
const Fs = require('fs');
@@ -528,6 +528,7 @@ function display_help() {
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');
@@ -536,6 +537,15 @@ function display_help() {
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/');
@@ -804,10 +814,60 @@ function create() {
log('Success :', path);
log('');
- }, function(path) {
- // return path.lastIndexOf('.package') === -1;
- return true;
- });
+ }, () => 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) {
@@ -865,6 +925,11 @@ function main() {
continue;
}
+ if (cmd === 'bundle') {
+ $type = 7;
+ continue;
+ }
+
if (cmd === 'repository') {
$type = 3;
continue;
@@ -938,6 +1003,10 @@ function main() {
create();
break;
+ case 7:
+ createbundle();
+ break;
+
case 3:
repository_add();
break;
diff --git a/builders.js b/builders.js
index 4c89b6de6..ef1c17963 100755
--- a/builders.js
+++ b/builders.js
@@ -1,4 +1,4 @@
-// Copyright 2012-2018 (c) Peter Širka
+// 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
@@ -21,37 +21,139 @@
/**
* @module FrameworkBuilders
- * @version 2.9.3
+ * @version 3.4.4
*/
'use strict';
const REQUIRED = 'The field "@" is invalid.';
const DEFAULT_SCHEMA = 'default';
-const SKIP = { $$schema: true, $$result: true, $$callback: true, $$async: true, $$index: true, $$repository: true, $$can: true, $$controller: true };
+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-Z0-9$]+\)|^function anonymous\(\$/;
+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 transforms = { pagination: {}, error: {}, transformbuilder: {}, restbuilder: {} };
+var tasks = {};
+var transforms = { pagination: {}, error: {}, restbuilder: {} };
+var restbuilderupgrades = [];
function SchemaBuilder(name) {
this.name = name;
this.collection = {};
}
-function SchemaOptions(error, model, options, callback, controller) {
+const SchemaBuilderProto = SchemaBuilder.prototype;
+
+function SchemaOptions(error, model, options, callback, controller, name, schema) {
this.error = error;
this.value = this.model = model;
- this.options = options;
+ this.options = options || EMPTYOBJECT;
this.callback = this.next = callback;
- this.controller = controller;
+ this.controller = (controller instanceof SchemaOptions || controller instanceof OperationOptions) ? controller.controller : controller;
+ this.name = name;
+ this.schema = schema;
+}
+
+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 = {
+
+ 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 model() {
+ return this.value;
+ },
+
+ set model(val) {
+ this.value = val;
+ },
+
+ 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 TaskBuilderProto = TaskBuilder.prototype;
+
SchemaOptions.prototype = {
get user() {
@@ -62,6 +164,26 @@ SchemaOptions.prototype = {
return this.controller ? this.controller.session : null;
},
+ get keys() {
+ return this.model.$$keys;
+ },
+
+ get parent() {
+ return this.model.$$parent;
+ },
+
+ get repo() {
+ if (this.controller)
+ return this.controller.repository;
+ if (!this.model.$$repository)
+ this.model.$$repository = {};
+ return this.model.$$repository;
+ },
+
+ get sessionid() {
+ return this.controller && this.controller ? this.controller.req.sessionid : null;
+ },
+
get language() {
return (this.controller ? this.controller.language : '') || '';
},
@@ -74,6 +196,14 @@ SchemaOptions.prototype = {
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;
},
@@ -88,14 +218,138 @@ SchemaOptions.prototype = {
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;
+ }
+};
+
+var SchemaOptionsProto = SchemaOptions.prototype;
+
+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;
+};
+
+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;
}
};
-SchemaOptions.prototype.DB = function() {
+SchemaOptionsProto.redirect = function(url) {
+ this.callback(new F.callback_redirect(url));
+ return this;
+};
+
+SchemaOptionsProto.clean = function() {
+ return this.model.$clean();
+};
+
+SchemaOptionsProto.$async = function(callback, index) {
+ return this.model.$async(callback, index);
+};
+
+SchemaOptionsProto.$workflow = function(name, helper, callback, async) {
+ return this.model.$workflow(name, helper, callback, async);
+};
+
+SchemaOptionsProto.$transform = function(name, helper, callback, async) {
+ return this.model.$transform(name, helper, callback, async);
+};
+
+SchemaOptionsProto.$operation = function(name, helper, callback, async) {
+ return this.model.$operation(name, helper, callback, async);
+};
+
+SchemaOptionsProto.$hook = function(name, helper, callback, async) {
+ return this.model.$hook(name, helper, callback, async);
+};
+
+SchemaOptionsProto.$save = function(helper, callback, async) {
+ return this.model.$save(helper, callback, async);
+};
+
+SchemaOptionsProto.$insert = function(helper, callback, async) {
+ return this.model.$insert(helper, callback, async);
+};
+
+SchemaOptionsProto.$update = function(helper, callback, async) {
+ return this.model.$update(helper, callback, async);
+};
+
+SchemaOptionsProto.$patch = function(helper, callback, async) {
+ return this.model.$patch(helper, callback, async);
+};
+
+SchemaOptionsProto.$query = function(helper, callback, async) {
+ return this.model.$query(helper, callback, async);
+};
+
+SchemaOptionsProto.$delete = SchemaOptionsProto.$remove = function(helper, callback, async) {
+ return this.model.$remove(helper, callback, async);
+};
+
+SchemaOptionsProto.$get = SchemaOptionsProto.$read = function(helper, callback, async) {
+ return this.model.$get(helper, callback, async);
+};
+
+SchemaOptionsProto.push = function(type, name, helper, first) {
+ return this.model.$push(type, name, helper, first);
+};
+
+SchemaOptionsProto.next = function(type, name, helper) {
+ return this.model.$next(type, name, helper);
+};
+
+SchemaOptionsProto.output = function() {
+ return this.model.$output();
+};
+
+SchemaOptionsProto.stop = function() {
+ return this.model.$stop();
+};
+
+SchemaOptionsProto.response = function(index) {
+ return this.model.$response(index);
+};
+
+SchemaOptionsProto.DB = function() {
return F.database(this.error);
};
-SchemaOptions.prototype.success = function(a, b) {
+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);
+ };
+};
+
+SchemaOptionsProto.success = function(a, b) {
if (a && b === undefined && typeof(a) !== 'boolean') {
b = a;
@@ -106,39 +360,53 @@ SchemaOptions.prototype.success = function(a, b) {
return this;
};
-SchemaOptions.prototype.done = function(arg) {
+SchemaOptionsProto.done = function(arg) {
var self = this;
return function(err, response) {
+ if (err) {
- if (err && !(err instanceof ErrorBuilder)) {
- self.error.push(err);
- self.callback();
- }
+ if (self.error !== err)
+ self.error.push(err);
- if (arg)
- self.callback(SUCCESS(err == null, response));
+ self.callback();
+ } else if (arg)
+ self.callback(SUCCESS(err == null, arg === true ? response : arg));
else
self.callback(SUCCESS(err == null));
};
};
-SchemaOptions.prototype.invalid = function(name, error, path, index) {
- this.error.push(name, error, path, index);
- this.callback();
- return this;
+SchemaOptionsProto.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();
+ };
};
-SchemaOptions.prototype.repository = function(name, value) {
+SchemaOptionsProto.repository = function(name, value) {
return this.model && this.model.$repository ? this.model.$repository(name, value) : value;
};
+SchemaOptionsProto.noop = function() {
+ this.callback(NoOp);
+ return this;
+};
+
/**
*
* Get a schema
* @param {String} name
* @return {Object}
*/
-SchemaBuilder.prototype.get = function(name) {
+SchemaBuilderProto.get = function(name) {
return this.collection[name];
};
@@ -147,7 +415,7 @@ SchemaBuilder.prototype.get = function(name) {
* @alias
* @return {SchemaBuilderEntity}
*/
-SchemaBuilder.prototype.create = function(name) {
+SchemaBuilderProto.create = function(name) {
this.collection[name] = new SchemaBuilderEntity(this, name);
return this.collection[name];
};
@@ -157,19 +425,20 @@ SchemaBuilder.prototype.create = function(name) {
* @param {String} name Schema name, optional.
* @return {SchemaBuilder}
*/
-SchemaBuilder.prototype.remove = function(name) {
+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 {
- delete schemas[this.name];
+ exports.remove(this.name);
this.collection = null;
}
};
-SchemaBuilder.prototype.destroy = function(name) {
+SchemaBuilderProto.destroy = function(name) {
return this.remove(name);
};
@@ -182,7 +451,9 @@ function SchemaBuilderEntity(parent, name) {
this.meta = {};
this.properties = [];
this.inherits = [];
+ this.verifications = null;
this.resourcePrefix;
+ this.extensions = {};
this.resourceName;
this.transforms;
this.workflows;
@@ -195,6 +466,8 @@ function SchemaBuilderEntity(parent, name) {
this.$onDefault; // Array of functions for inherits
this.onValidate = F.onValidate;
this.onSave;
+ this.onInsert;
+ this.onUpdate;
this.onGet;
this.onRemove;
this.onQuery;
@@ -208,22 +481,37 @@ function SchemaBuilderEntity(parent, name) {
this.CurrentSchemaInstance.prototype.$$schema = this;
}
-SchemaBuilderEntity.prototype.allow = function() {
+const SchemaBuilderEntityProto = SchemaBuilderEntity.prototype;
+
+SchemaBuilderEntityProto.allow = function() {
var self = this;
if (!self.fields_allow)
self.fields_allow = [];
- for (var i = 0, length = arguments.length; i < length; i++) {
- if (arguments[i] instanceof Array)
- arguments[i].forEach(item => self.fields_allow.push(item));
+ var arr = arguments;
+
+ if (arr.length === 1)
+ arr = arr[0].split(',').trim();
+
+ 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(arguments[i]);
+ self.fields_allow.push(arr[i]);
}
return self;
};
-SchemaBuilderEntity.prototype.required = function(name, fn) {
+SchemaBuilderEntityProto.before = function(name, fn) {
+ var self = this;
+ if (!self.preparation)
+ self.preparation = {};
+ self.preparation[name] = fn;
+ return self;
+};
+
+SchemaBuilderEntityProto.required = function(name, fn) {
var self = this;
@@ -259,6 +547,59 @@ SchemaBuilderEntity.prototype.required = function(name, fn) {
return self;
};
+SchemaBuilderEntityProto.clear = function() {
+ var self = this;
+
+ self.schema = {};
+ self.properties = [];
+ self.fields = [];
+ self.verifications = null;
+
+ if (self.preparation)
+ self.preparation = null;
+
+ if (self.dependencies)
+ self.dependencies = null;
+
+ if (self.fields_allow)
+ self.fields_allow = null;
+
+ return self;
+};
+
+SchemaBuilderEntityProto.middleware = function(fn) {
+ var self = this;
+ if (!self.middlewares)
+ self.middlewares = [];
+ self.middlewares.push(fn);
+ return self;
+};
+
+function runmiddleware(opt, schema, callback, index, processor) {
+
+ if (!index)
+ index = 0;
+
+ 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);
+}
+
/**
* Define type in schema
* @param {String|String[]} name
@@ -267,7 +608,7 @@ SchemaBuilderEntity.prototype.required = function(name, fn) {
* @param {Number|String} [custom] Custom tag for search.
* @return {SchemaBuilder}
*/
-SchemaBuilderEntity.prototype.define = function(name, type, required, custom) {
+SchemaBuilderEntityProto.define = function(name, type, required, custom) {
if (name instanceof Array) {
for (var i = 0, length = name.length; i < length; i++)
@@ -282,11 +623,20 @@ SchemaBuilderEntity.prototype.define = function(name, type, required, custom) {
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;
- this.schema[name] = this.$parse(name, type, required, custom);
-
+ var a = this.schema[name] = this.$parse(name, type, required, custom);
switch (this.schema[name].type) {
case 7:
if (this.dependencies)
@@ -298,16 +648,36 @@ SchemaBuilderEntity.prototype.define = function(name, type, required, custom) {
this.fields = Object.keys(this.schema);
- if (required) {
- if (this.properties == null)
- this.properties = [];
+ 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 this;
+ return function(val) {
+ a.def = val;
+ return this;
+ };
+};
+
+SchemaBuilderEntityProto.verify = function(name, fn, cache) {
+ var self = this;
+
+ if (!self.verifications)
+ self.verifications = [];
+
+ var cachekey;
+
+ if (cache)
+ cachekey = self.name + '_verify_' + name + '_';
+
+ self.verifications.push({ name: name, fn: fn, cache: cache, cachekey: cachekey });
+ return self;
};
-SchemaBuilderEntity.prototype.inherit = function(group, name) {
+SchemaBuilderEntityProto.inherit = function(group, name) {
if (!name) {
name = group;
@@ -338,10 +708,30 @@ SchemaBuilderEntity.prototype.inherit = function(group, name) {
copy_inherit(self, 'operations', schema.operations);
copy_inherit(self, 'constants', schema.constants);
+ if (schema.middlewares) {
+ self.middlewares = [];
+ for (var i = 0; i < schema.middlewares.length; i++)
+ self.middlewares.push(schema.middlewares[i]);
+ }
+
+ if (schema.verifications) {
+ self.verifications = [];
+ for (var i = 0; i < schema.verifications.length; i++)
+ self.verifications.push(schema.verifications[i]);
+ }
+
schema.properties.forEach(function(item) {
- self.properties.indexOf(item) === -1 && self.properties.push(item);
+ if (self.properties.indexOf(item) === -1)
+ self.properties.push(item);
});
+ if (schema.preparation) {
+ self.preparation = {};
+ Object.keys(schema.preparation).forEach(function(key) {
+ self.preparation[key] = schema.preparation[key];
+ });
+ }
+
if (schema.onPrepare) {
if (!self.$onPrepare)
self.$onPrepare = [];
@@ -360,6 +750,12 @@ SchemaBuilderEntity.prototype.inherit = function(group, name) {
if (!self.onSave && schema.onSave)
self.onSave = schema.onSave;
+ if (!self.onInsert && schema.onInsert)
+ self.onInsert = schema.onInsert;
+
+ if (!self.onUpdate && schema.onUpdate)
+ self.onUpdate = schema.onUpdate;
+
if (!self.onGet && schema.onGet)
self.onGet = schema.onGet;
@@ -398,7 +794,7 @@ function copy_inherit(schema, field, value) {
* Set primary key
* @param {String} name
*/
-SchemaBuilderEntity.prototype.setPrimary = function(name) {
+SchemaBuilderEntityProto.setPrimary = function(name) {
this.primary = name;
return this;
};
@@ -410,7 +806,7 @@ SchemaBuilderEntity.prototype.setPrimary = function(name) {
* @param {Boolean} reverse Reverse results.
* @return {Array|Object} Returns Array (with property names) if the model is undefined otherwise returns Object Name/Value.
*/
-SchemaBuilderEntity.prototype.filter = function(custom, model, reverse) {
+SchemaBuilderEntityProto.filter = function(custom, model, reverse) {
if (typeof(model) === 'boolean') {
var tmp = reverse;
@@ -479,7 +875,7 @@ function parseLength(lower, result) {
return result;
}
-SchemaBuilderEntity.prototype.$parse = function(name, value, required, custom) {
+SchemaBuilderEntityProto.$parse = function(name, value, required, custom) {
var type = typeof(value);
var result = {};
@@ -493,14 +889,19 @@ SchemaBuilderEntity.prototype.$parse = function(name, value, required, custom) {
result.isArray = false;
result.custom = custom || '';
- // 0 = undefined
- // 1 = integer
- // 2 = float
- // 3 = string
- // 4 = boolean
- // 5 = date
- // 6 = object
- // 7 = custom object
+ // 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;
@@ -512,6 +913,14 @@ SchemaBuilderEntity.prototype.$parse = function(name, value, required, custom) {
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;
@@ -542,6 +951,15 @@ SchemaBuilderEntity.prototype.$parse = function(name, value, required, custom) {
return result;
}
+ if (value instanceof SchemaBuilderEntity)
+ result.type = 7;
+ else {
+ result.type = 10;
+ if (!this.asyncfields)
+ this.asyncfields = [];
+ this.asyncfields.push(name);
+ }
+
return result;
}
@@ -572,11 +990,23 @@ SchemaBuilderEntity.prototype.$parse = function(name, value, required, custom) {
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';
@@ -589,6 +1019,13 @@ SchemaBuilderEntity.prototype.$parse = function(name, value, required, custom) {
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;
@@ -642,6 +1079,11 @@ SchemaBuilderEntity.prototype.$parse = function(name, value, required, custom) {
return result;
}
+ if (lower === 'number2') {
+ result.type = 11;
+ return result;
+ }
+
if (['int', 'integer', 'byte'].indexOf(lower) !== -1) {
result.type = 1;
return result;
@@ -666,7 +1108,7 @@ SchemaBuilderEntity.prototype.$parse = function(name, value, required, custom) {
return result;
};
-SchemaBuilderEntity.prototype.getDependencies = function() {
+SchemaBuilderEntityProto.getDependencies = function() {
var dependencies = [];
for (var name in this.schema) {
@@ -692,7 +1134,7 @@ SchemaBuilderEntity.prototype.getDependencies = function() {
* @param {Function(propertyName, value, path, entityName, model)} fn A validation function.
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.setValidate = function(properties, fn) {
+SchemaBuilderEntityProto.setValidate = function(properties, fn) {
if (fn === undefined && properties instanceof Array) {
this.properties = properties;
@@ -708,12 +1150,12 @@ SchemaBuilderEntity.prototype.setValidate = function(properties, fn) {
return this;
};
-SchemaBuilderEntity.prototype.setPrefix = function(prefix) {
+SchemaBuilderEntityProto.setPrefix = function(prefix) {
this.resourcePrefix = prefix;
return this;
};
-SchemaBuilderEntity.prototype.setResource = function(name) {
+SchemaBuilderEntityProto.setResource = function(name) {
this.resourceName = name;
return this;
};
@@ -723,7 +1165,7 @@ SchemaBuilderEntity.prototype.setResource = function(name) {
* @param {Function(propertyName, isntPreparing, entityName)} fn
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.setDefault = function(fn) {
+SchemaBuilderEntityProto.setDefault = function(fn) {
this.onDefault = fn;
return this;
};
@@ -733,7 +1175,7 @@ SchemaBuilderEntity.prototype.setDefault = function(fn) {
* @param {Function(name, value)} fn Must return a new value.
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.setPrepare = function(fn) {
+SchemaBuilderEntityProto.setPrepare = function(fn) {
this.onPrepare = fn;
return this;
};
@@ -743,46 +1185,183 @@ SchemaBuilderEntity.prototype.setPrepare = function(fn) {
* @param {Function(error, model, helper, next(value), controller)} fn
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.setSave = function(fn, description) {
+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;
+ this.meta.save = description || null;
+ this.meta.savefilter = filter;
+ !fn.$newversion && OBSOLETE('Schema("{0}").setSave()'.format(this.name), MSG_OBSOLETE_NEW);
return this;
};
-
-/**
- * Set error handler
- * @param {Function(error)} fn
- * @return {SchemaBuilderEntity}
- */
-SchemaBuilderEntity.prototype.setError = function(fn) {
- this.onError = fn;
+SchemaBuilderEntityProto.setSaveExtension = function(fn) {
+ var key = 'save';
+ if (this.extensions[key])
+ this.extensions[key].push(fn);
+ else
+ this.extensions[key] = [fn];
return this;
};
/**
- * Set getter handler
+ * Set insert handler
* @param {Function(error, model, helper, next(value), controller)} fn
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.setGet = SchemaBuilderEntity.prototype.setRead = function(fn, description) {
+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.onGet = fn;
- this.meta.get = description;
+ 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 query handler
- * @param {Function(error, helper, next(value), controller)} fn
+ * 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}
*/
-SchemaBuilderEntity.prototype.setQuery = function(fn, description) {
+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;
+ 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;
};
@@ -792,10 +1371,27 @@ SchemaBuilderEntity.prototype.setQuery = function(fn, description) {
* @param {String} description Optional.
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.setRemove = function(fn, description) {
+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;
+ 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;
};
@@ -806,14 +1402,16 @@ SchemaBuilderEntity.prototype.setRemove = function(fn, description) {
* @param {String} description Optional.
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.constant = function(name, value, description) {
+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;
+ this.meta['constant#' + name] = description || null;
return this;
};
@@ -824,17 +1422,33 @@ SchemaBuilderEntity.prototype.constant = function(name, value, description) {
* @param {String} description Optional.
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.addTransform = function(name, fn, description) {
+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;
+ 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;
};
@@ -845,17 +1459,33 @@ SchemaBuilderEntity.prototype.addTransform = function(name, fn, description) {
* @param {String} description Optional.
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.addOperation = function(name, fn, description) {
+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;
+ 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;
};
@@ -866,29 +1496,61 @@ SchemaBuilderEntity.prototype.addOperation = function(name, fn, description) {
* @param {String} description Optional.
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.addWorkflow = function(name, fn, description) {
+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;
+ 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;
};
-SchemaBuilderEntity.prototype.addHook = function(name, fn, description) {
+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;
+ 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;
};
@@ -897,40 +1559,42 @@ SchemaBuilderEntity.prototype.addHook = function(name, fn, description) {
* @param {String} name
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.find = function(name) {
+SchemaBuilderEntityProto.find = function(name) {
return this.parent.get(name);
};
/**
* Destroys current entity
*/
-SchemaBuilderEntity.prototype.destroy = function() {
+SchemaBuilderEntityProto.destroy = function() {
delete this.parent.collection[this.name];
- this.properties = undefined;
- this.schema = undefined;
- this.onDefault = undefined;
- this.$onDefault = undefined;
- this.onValidate = undefined;
- this.onSave = undefined;
- this.onRead = undefined;
- this.onGet = undefined;
- this.onRemove = undefined;
- this.onQuery = undefined;
- this.workflows = undefined;
- this.operations = undefined;
- this.transforms = undefined;
- this.meta = undefined;
- this.newversion = undefined;
- this.properties = undefined;
- this.hooks = undefined;
- this.constants = undefined;
- this.onPrepare = undefined;
- this.$onPrepare = undefined;
- this.onError = undefined;
- this.gcache = undefined;
- this.dependencies = undefined;
- this.fields = undefined;
- this.fields_allow = undefined;
+ 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;
};
/**
@@ -942,7 +1606,41 @@ SchemaBuilderEntity.prototype.destroy = function() {
* @param {Boolean} skip Skips preparing and validation, optional
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.save = function(model, options, callback, controller, skip) {
+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;
@@ -963,7 +1661,25 @@ SchemaBuilderEntity.prototype.save = function(model, options, callback, controll
callback = function(){};
var self = this;
- var $type = 'save';
+ 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) {
@@ -972,21 +1688,37 @@ SchemaBuilderEntity.prototype.save = function(model, options, callback, controll
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.onSave)) {
- if (self.onSave.$newversion)
- self.onSave(new SchemaOptions(builder, model, options, function(res) {
- self.$process(arguments, model, $type, undefined, builder, res, callback);
- }, controller));
- else
- self.onSave(builder, model, options, function(res) {
- self.$process(arguments, model, $type, undefined, builder, res, callback);
+ 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;
}
@@ -996,13 +1728,20 @@ SchemaBuilderEntity.prototype.save = function(model, options, callback, controll
var onError = function(err) {
if (!err || callback.success)
return;
+
callback.success = true;
- builder.push(err);
+
+ 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;
@@ -1012,21 +1751,27 @@ SchemaBuilderEntity.prototype.save = function(model, options, callback, controll
res = arguments[1];
}
- var has = builder.hasError();
+ 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.onSave.$newversion)
- async.call(self, self.onSave)(onError, new SchemaOptions(builder, model, options, onCallback, controller));
- else
- async.call(self, self.onSave)(onError, builder, model, options, onCallback, controller, skip !== true);
- });
+ 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*';
}
@@ -1037,7 +1782,7 @@ function isGenerator(obj, name, fn) {
* @param {Function(err, result)} callback
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.get = SchemaBuilderEntity.prototype.read = function(options, callback, controller) {
+SchemaBuilderEntityProto.get = SchemaBuilderEntityProto.read = function(options, callback, controller) {
if (typeof(options) === 'function') {
callback = options;
@@ -1049,21 +1794,40 @@ SchemaBuilderEntity.prototype.get = SchemaBuilderEntity.prototype.read = functio
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)
- self.onGet(new SchemaOptions(builder, output, options, function(res) {
- self.$process(arguments, output, $type, undefined, builder, res, callback);
- }, controller));
- else
+ 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) {
- self.$process(arguments, output, $type, undefined, builder, res, callback);
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+ self.$process(arguments, output, $type, undefined, builder, res, callback, controller);
}, controller);
return self;
}
@@ -1074,12 +1838,18 @@ SchemaBuilderEntity.prototype.get = SchemaBuilderEntity.prototype.read = functio
if (!err || callback.success)
return;
callback.success = true;
- builder.push(err);
+
+ 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;
@@ -1090,14 +1860,18 @@ SchemaBuilderEntity.prototype.get = SchemaBuilderEntity.prototype.read = functio
}
callback.success = true;
- var has = builder.hasError();
+ var has = builder.is;
has && self.onError && self.onError(builder, output, $type);
callback(has ? builder : null, res === undefined ? output : res);
};
- if (self.onGet.$newversion)
- async.call(self, self.onGet)(onError, new SchemaOptions(builder, output, options, onCallback, controller));
- else
+ 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;
@@ -1109,7 +1883,7 @@ SchemaBuilderEntity.prototype.get = SchemaBuilderEntity.prototype.read = functio
* @param {Function(err, result)} callback
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.remove = function(options, callback, controller) {
+SchemaBuilderEntityProto.remove = function(options, callback, controller) {
if (typeof(options) === 'function') {
callback = options;
@@ -1119,18 +1893,41 @@ SchemaBuilderEntity.prototype.remove = function(options, callback, controller) {
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)
- self.onRemove(new SchemaOptions(builder, undefined, options, function(res) {
- self.$process(arguments, undefined, $type, undefined, builder, res, callback);
- }, controller));
- else
+ 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) {
- self.$process(arguments, undefined, $type, undefined, builder, res, callback);
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+ self.$process(arguments, undefined, $type, undefined, builder, res, callback, controller);
}, controller);
return self;
}
@@ -1141,13 +1938,18 @@ SchemaBuilderEntity.prototype.remove = function(options, callback, controller) {
if (!err || callback.success)
return;
callback.success = true;
- builder.push(err);
+
+ 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;
@@ -1157,15 +1959,19 @@ SchemaBuilderEntity.prototype.remove = function(options, callback, controller) {
res = arguments[1];
}
- var has = builder.hasError();
+ 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)
- async.call(self, self.onRemove)(onError, new SchemaOptions(builder, undefined, options, onCallback, controller));
- else
+ 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;
@@ -1177,28 +1983,48 @@ SchemaBuilderEntity.prototype.remove = function(options, callback, controller) {
* @param {Function(err, result)} callback
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.query = function(options, callback, controller) {
+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)
- self.onQuery(new SchemaOptions(builder, undefined, options, function(res) {
- self.$process(arguments, undefined, $type, undefined, builder, res, callback);
- }, controller));
- else
+ 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) {
- self.$process(arguments, undefined, $type, undefined, builder, res, callback);
+ CONF.logger && F.ilogger(self.getLoggerName($type), controller, $now);
+ self.$process(arguments, undefined, $type, undefined, builder, res, callback, controller);
}, controller);
return self;
}
@@ -1209,13 +2035,18 @@ SchemaBuilderEntity.prototype.query = function(options, callback, controller) {
if (!err || callback.success)
return;
callback.success = true;
- builder.push(err);
+
+ 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;
@@ -1225,15 +2056,19 @@ SchemaBuilderEntity.prototype.query = function(options, callback, controller) {
res = arguments[1];
}
- var has = builder.hasError();
+ var has = builder.is;
has && self.onError && self.onError(builder, EMPTYOBJECT, $type);
callback.success = true;
- callback(builder.hasError() ? builder : null, res);
+ callback(builder.is ? builder : null, res);
};
- if (self.onQuery.$newversion)
- async.call(self, self.onQuery)(onError, new SchemaOptions(builder, undefined, options, onCallback, controller));
- else
+ 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;
@@ -1247,7 +2082,7 @@ SchemaBuilderEntity.prototype.query = function(options, callback, controller) {
* @param {ErrorBuilder} builder ErrorBuilder, INTERNAL.
* @return {ErrorBuilder}
*/
-SchemaBuilderEntity.prototype.validate = function(model, resourcePrefix, resourceName, builder, filter, path, index) {
+SchemaBuilderEntityProto.validate = function(model, resourcePrefix, resourceName, builder, filter, path, index) {
var self = this;
@@ -1277,38 +2112,7 @@ SchemaBuilderEntity.prototype.validate = function(model, resourcePrefix, resourc
else
path = '';
- framework_utils.validate_builder.call(self, model, builder, self.name, self.parent.collection, self.name, index, filter, path);
-
- /*
- if (!self.dependencies)
- return builder;
-
- for (var i = 0, length = self.dependencies.length; i < length; i++) {
- var key = self.dependencies[i];
- var schema = self.schema[key];
- var s = self.parent.collection[schema.raw];
-
- if (!s) {
- F.error(new Error('Schema "{0}" not found (validation).'.format(schema.raw)));
- continue;
- }
-
-
- if (schema.isArray) {
- var arr = model[key];
- for (var j = 0, jl = arr.length; j < jl; j++) {
- if (model[key][j] != null || schema.required) {
- if (!schema.can || schema.can(model))
- s.validate(model[key][j], resourcePrefix, resourceName, builder, filter, path + key + '[' + j + ']', j);
- }
- }
- } else if (model[key] != null || schema.required) {
- if (!schema.can || schema.can(model))
- s.validate(model[key], resourcePrefix, resourceName, builder, filter, path + key, -1);
- }
- }
- */
-
+ framework_utils.validate_builder.call(self, model, builder, self, '', index, filter, path);
return builder;
};
@@ -1317,11 +2121,11 @@ SchemaBuilderEntity.prototype.validate = function(model, resourcePrefix, resourc
* @alias SchemaBuilderEntity.default()
* @return {Object}
*/
-SchemaBuilderEntity.prototype.create = function() {
+SchemaBuilderEntityProto.create = function() {
return this.default();
};
-SchemaBuilderEntity.prototype.Create = function() {
+SchemaBuilderEntityProto.Create = function() {
return this.default();
};
@@ -1330,11 +2134,11 @@ SchemaBuilderEntity.prototype.Create = function() {
* @param {Object} obj
* @return {Object}
*/
-SchemaBuilderEntity.prototype.$make = function(obj) {
- return obj; // TODO remove
+SchemaBuilderEntityProto.$make = function(obj) {
+ return obj;
};
-SchemaBuilderEntity.prototype.$prepare = function(obj, callback) {
+SchemaBuilderEntityProto.$prepare = function(obj, callback) {
if (obj && typeof(obj.$save) === 'function')
callback(null, obj);
else
@@ -1346,7 +2150,7 @@ SchemaBuilderEntity.prototype.$prepare = function(obj, callback) {
* Create a default object according the schema
* @return {SchemaInstance}
*/
-SchemaBuilderEntity.prototype.default = function() {
+SchemaBuilderEntityProto.default = function() {
var obj = this.schema;
if (obj === null)
@@ -1367,11 +2171,18 @@ SchemaBuilderEntity.prototype.default = function() {
}
}
+ 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
@@ -1379,9 +2190,13 @@ SchemaBuilderEntity.prototype.default = function() {
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 ? [] : '';
+ item[property] = type.isArray ? [] : type.subtype === 'email' ? '@' : '';
break;
// boolean
case 4:
@@ -1389,7 +2204,7 @@ SchemaBuilderEntity.prototype.default = function() {
break;
// date
case 5:
- item[property] = type.isArray ? [] : F.datetime;
+ item[property] = type.isArray ? [] : NOW;
break;
// schema
case 7:
@@ -1397,7 +2212,7 @@ SchemaBuilderEntity.prototype.default = function() {
if (type.isArray) {
item[property] = [];
} else {
- var tmp = this.find(type.raw);
+ var tmp = this.parent.collection[type.raw] || GETSCHEMA(type.raw);
if (tmp) {
item[property] = tmp.default();
} else {
@@ -1406,6 +2221,7 @@ SchemaBuilderEntity.prototype.default = function() {
}
}
break;
+
// enum + keyvalue
case 8:
case 9:
@@ -1417,69 +2233,211 @@ SchemaBuilderEntity.prototype.default = function() {
return item;
};
-/**
- * Create schema instance
- * @param {function|object} model
- * @param [filter]
- * @param [callback]
- * @returns {SchemaInstance}
- */
-SchemaBuilderEntity.prototype.make = function(model, filter, callback, argument, novalidate) {
-
- if (typeof(model) === 'function') {
- model.call(this, this);
- return this;
- }
+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();
+ };
+}
- if (typeof(filter) === 'function') {
- var tmp = callback;
- callback = filter;
- filter = tmp;
- }
+SchemaOptionsVerify.prototype = {
- var output = this.prepare(model);
+ get user() {
+ return this.controller ? this.controller.user : null;
+ },
- if (novalidate) {
- callback && callback(null, output, argument);
- return output;
- }
+ get session() {
+ return this.controller ? this.controller.session : null;
+ },
- var builder = this.validate(output, undefined, undefined, undefined, filter);
- if (builder.hasError()) {
- this.onError && this.onError(builder, model, 'make');
- callback && callback(builder, null, argument);
- return output;
- }
+ get sessionid() {
+ return this.controller && this.controller ? this.controller.req.sessionid : null;
+ },
- callback && callback(null, output, argument);
- return output;
-};
+ get language() {
+ return (this.controller ? this.controller.language : '') || '';
+ },
-SchemaBuilderEntity.prototype.load = SchemaBuilderEntity.prototype.make; // Because JSDoc doesn't work with double asserting
+ 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;
}
-SchemaBuilderEntity.prototype.$onprepare = function(name, value, index, model) {
+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);
+ 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);
+ 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;
};
-SchemaBuilderEntity.prototype.$ondefault = function(property, create, entity) {
+SchemaBuilderEntityProto.$ondefault = function(property, create, entity) {
var val;
@@ -1504,7 +2462,7 @@ SchemaBuilderEntity.prototype.$ondefault = function(property, create, entity) {
* @param {String|Array} [dependencies] INTERNAL.
* @return {SchemaInstance}
*/
-SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
+SchemaBuilderEntityProto.prepare = function(model, dependencies, req, verifications) {
var self = this;
var obj = self.schema;
@@ -1519,22 +2477,36 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
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;
- if (val === undefined && defaults)
- val = self.$ondefault(property, false, self.name);
+ 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 type = obj[property];
var typeval = typeof(val);
if (typeval === 'function')
@@ -1548,11 +2520,11 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
break;
// number: integer
case 1:
- item[property] = self.$onprepare(property, framework_utils.parseInt(val), undefined, model);
+ 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), undefined, model);
+ item[property] = self.$onprepare(property, framework_utils.parseFloat(val, def ? type.def() : type.def), undefined, model, req);
break;
// string
@@ -1585,6 +2557,7 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
tmp = '';
break;
case 'zip':
+ tmp = tmp.replace(REGEXP_CLEAN_EMAIL, '');
if (tmp && !type.required && !tmp.isZIP())
tmp = '';
break;
@@ -1596,6 +2569,9 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
case 'capitalize':
tmp = tmp.capitalize();
break;
+ case 'capitalize2':
+ tmp = tmp.capitalize(true);
+ break;
case 'lowercase':
tmp = tmp.toLowerCase();
break;
@@ -1606,15 +2582,24 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
if (tmp && !type.required && !tmp.isJSON())
tmp = '';
break;
+ case 'base64':
+ if (tmp && !type.required && !tmp.isBase64())
+ tmp = '';
+ break;
}
- item[property] = self.$onprepare(property, tmp, undefined, model);
+ 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;
- item[property] = self.$onprepare(property, tmp === 'true' || tmp === '1' || tmp === 'on', undefined, model);
+ 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
@@ -1631,38 +2616,50 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
tmp = val;
if (framework_utils.isDate(tmp))
- tmp = self.$onprepare(property, tmp, undefined, model);
- else
- tmp = (defaults ? isUndefined(self.$ondefault(property, false, self.name), null) : null);
+ 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);
+ // 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);
+ // 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);
+ // 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 = (defaults ? isUndefined(self.$ondefault(property, false, self.name), null) : null);
- // val = defaults(property, false, self.name);
+ 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;
@@ -1677,20 +2674,45 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
}
}
- entity = self.parent.get(type.raw);
+ entity = GETSCHEMA(type.raw);
if (entity) {
- item[property] = entity.prepare(val, undefined);
- dependencies && dependencies.push({ name: type.raw, value: self.$onprepare(property, item[property], undefined, model) });
+
+ 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] = (defaults ? isUndefined(self.$ondefault(property, false, self.name), []) : []);
+ item[property] = (type.def === undefined ? defaults ? isUndefined(self.$ondefault(property, false, self.name), EMPTYARRAY) : [] : (def ? type.def() : type.def));
continue;
}
@@ -1703,15 +2725,15 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
switch (type.type) {
case 0:
- tmp = self.$onprepare(property, tmp, j, model);
+ tmp = self.$onprepare(property, tmp, j, model, req);
break;
case 1:
- tmp = self.$onprepare(property, framework_utils.parseInt(tmp), j, model);
+ tmp = self.$onprepare(property, framework_utils.parseInt(tmp), j, model, req);
break;
case 2:
- tmp = self.$onprepare(property, framework_utils.parseFloat(tmp), j, model);
+ tmp = self.$onprepare(property, framework_utils.parseFloat(tmp), j, model, req);
break;
case 3:
@@ -1742,6 +2764,9 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
case 'capitalize':
tmp = tmp.capitalize();
break;
+ case 'capitalize2':
+ tmp = tmp.capitalize(true);
+ break;
case 'lowercase':
tmp = tmp.toLowerCase();
break;
@@ -1752,15 +2777,19 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
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);
+ tmp = self.$onprepare(property, tmp, j, model, req);
break;
case 4:
if (tmp)
tmp = tmp.toString().toLowerCase();
- tmp = self.$onprepare(property, tmp === 'true' || tmp === '1' || tmp === 'on', j, model);
+ tmp = self.$onprepare(property, BOOL[tmp], j, model, req);
break;
case 5:
@@ -1772,33 +2801,50 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
tmp = new Date(tmp);
if (framework_utils.isDate(tmp))
- tmp = self.$onprepare(property, tmp, j, model);
+ tmp = self.$onprepare(property, tmp, j, model, req);
else
tmp = undefined;
break;
case 6:
- tmp = self.$onprepare(property, tmp, j, model);
+ tmp = self.$onprepare(property, tmp, j, model, req);
break;
case 7:
- entity = self.parent.get(type.raw);
+
+ entity = self.parent.collection[type.raw] || GETSCHEMA(type.raw);
+
if (entity) {
- tmp = entity.prepare(tmp, dependencies);
- if (dependencies)
- dependencies.push({ name: type.raw, value: self.$onprepare(property, tmp, j, model) });
+ 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
- tmp = null;
+ 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 });
- tmp = self.$onprepare(property, tmp, j, model);
break;
- }
- if (tmp === undefined)
- continue;
+ 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;
+ }
- item[property].push(tmp);
+ if (tmp !== undefined)
+ item[property].push(tmp);
}
}
@@ -1806,14 +2852,28 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
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)
+ 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
@@ -1824,13 +2884,14 @@ SchemaBuilderEntity.prototype.prepare = function(model, dependencies) {
* @param {Object} controller Optional
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.transform = function(name, model, options, callback, skip, controller) {
+SchemaBuilderEntityProto.transform = function(name, model, options, callback, skip, controller) {
return this.$execute('transform', name, model, options, callback, skip, controller);
};
-SchemaBuilderEntity.prototype.transform2 = function(name, options, callback, controller) {
+SchemaBuilderEntityProto.transform2 = function(name, options, callback, controller) {
if (typeof(options) === 'function') {
+ controller = callback;
callback = options;
options = undefined;
}
@@ -1839,7 +2900,7 @@ SchemaBuilderEntity.prototype.transform2 = function(name, options, callback, con
return this.transform(name, this.create(), options, callback, true, controller);
};
-SchemaBuilderEntity.prototype.$process = function(arg, model, type, name, builder, response, callback) {
+SchemaBuilderEntityProto.$process = function(arg, model, type, name, builder, response, callback, controller) {
var self = this;
@@ -1849,17 +2910,24 @@ SchemaBuilderEntity.prototype.$process = function(arg, model, type, name, builde
response = arg[1];
}
- var has = builder.hasError();
+ var has = builder.is;
has && self.onError && self.onError(builder, model, type, name);
- callback(has ? builder : null, response === undefined ? model : response, model);
+
+ 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;
};
-SchemaBuilderEntity.prototype.$process_hook = function(model, type, name, builder, result, callback) {
+SchemaBuilderEntityProto.$process_hook = function(model, type, name, builder, result, callback) {
var self = this;
- var has = builder.hasError();
+ var has = builder.is;
has && self.onError && self.onError(builder, model, type, name);
- callback(has ? builder : null, model);
+ callback(has ? builder : null, model, result);
return self;
};
@@ -1873,13 +2941,14 @@ SchemaBuilderEntity.prototype.$process_hook = function(model, type, name, builde
* @param {Object} controller Optional
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.workflow = function(name, model, options, callback, skip, controller) {
+SchemaBuilderEntityProto.workflow = function(name, model, options, callback, skip, controller) {
return this.$execute('workflow', name, model, options, callback, skip, controller);
};
-SchemaBuilderEntity.prototype.workflow2 = function(name, options, callback, controller) {
+SchemaBuilderEntityProto.workflow2 = function(name, options, callback, controller) {
if (typeof(options) === 'function') {
+ controller = callback;
callback = options;
options = undefined;
}
@@ -1897,7 +2966,7 @@ SchemaBuilderEntity.prototype.workflow2 = function(name, options, callback, cont
* @param {Boolean} skip Skips preparing and validation, optional.
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.hook = function(name, model, options, callback, skip, controller) {
+SchemaBuilderEntityProto.hook = function(name, model, options, callback, skip, controller) {
var self = this;
@@ -1923,36 +2992,50 @@ SchemaBuilderEntity.prototype.hook = function(name, model, options, callback, sk
var hook = self.hooks ? self.hooks[name] : undefined;
if (!hook || !hook.length) {
- callback(null, model);
+ 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) {
- var builder = new ErrorBuilder();
+ 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) {
- if (item.fn.$newversion)
- item.fn.call(self, new SchemaOptions(builder, model, options, function(result) {
+ var opt = new SchemaOptions(builder, model, options, function(result) {
output.push(result == undefined ? model : result);
next();
- }, controller));
- else
+ }, 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);
@@ -1968,58 +3051,74 @@ SchemaBuilderEntity.prototype.hook = function(name, model, options, callback, sk
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)
+ 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));
- else
+ }, 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)
+ 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;
- builder.push(err);
- next();
- }, new SchemaOptions(builder, model, options, function(res) {
- output.push(res == undefined ? model : res);
+ if (builder !== err)
+ builder.push(err);
next();
- }, controller));
- else
+ }, opt);
+
+ } else {
async.call(self, item.fn)(function(err) {
if (!err)
return;
- builder.push(err);
+ 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;
};
-SchemaBuilderEntity.prototype.hook2 = function(name, options, callback, controller) {
+SchemaBuilderEntityProto.hook2 = function(name, options, callback, controller) {
if (typeof(options) === 'function') {
+ controller = callback;
callback = options;
options = undefined;
}
@@ -2030,7 +3129,7 @@ SchemaBuilderEntity.prototype.hook2 = function(name, options, callback, controll
return this.hook(name, this.create(), options, callback, true, controller);
};
-SchemaBuilderEntity.prototype.$execute = function(type, name, model, options, callback, skip, controller) {
+SchemaBuilderEntityProto.$execute = function(type, name, model, options, callback, skip, controller) {
var self = this;
if (typeof(name) !== 'string') {
@@ -2054,26 +3153,50 @@ SchemaBuilderEntity.prototype.$execute = function(type, name, model, options, ca
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;
- if (skip === true) {
+ 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)
- item.call(self, new SchemaOptions(builder, model, options, function(res) {
- self.$process(arguments, model, type, name, builder, res, callback);
- }, controller));
- else
+ 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) {
- self.$process(arguments, model, type, name, builder, res, callback);
+ 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;
}
@@ -2085,19 +3208,32 @@ SchemaBuilderEntity.prototype.$execute = function(type, name, model, options, ca
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);
- if (!isGenerator(self, type + '.' + name, item)) {
- if (item.$newversion)
- item.call(self, new SchemaOptions(builder, model, options, function(res) {
- self.$process(arguments, model, type, name, builder, res, callback);
- }, controller));
- else
+ 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) {
- self.$process(arguments, model, type, name, builder, res, callback);
+ CONF.logger && F.ilogger(self.getLoggerName(type, name), controller, $now);
+ self.$process(arguments, model, type, name, builder, res, callback, controller);
}, controller);
return;
}
@@ -2108,13 +3244,16 @@ SchemaBuilderEntity.prototype.$execute = function(type, name, model, options, ca
if (!err || callback.success)
return;
callback.success = true;
- builder.push(err);
+ 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;
@@ -2124,21 +3263,29 @@ SchemaBuilderEntity.prototype.$execute = function(type, name, model, options, ca
res = arguments[1];
}
- var has = builder.hasError();
+ 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)
- async.call(self, item)(onError, new SchemaOptions(builder, model, options, onCallback, controller));
- else
+ 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
@@ -2149,7 +3296,7 @@ SchemaBuilderEntity.prototype.$execute = function(type, name, model, options, ca
* @param {Object} controller Optional
* @return {SchemaBuilderEntity}
*/
-SchemaBuilderEntity.prototype.operation = function(name, model, options, callback, skip, controller) {
+SchemaBuilderEntityProto.operation = function(name, model, options, callback, skip, controller) {
var self = this;
@@ -2191,21 +3338,32 @@ SchemaBuilderEntity.prototype.operation = function(name, model, options, callbac
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 (!isGenerator(self, 'operation.' + name, operation)) {
+ 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) {
- self.$process(arguments, model, $type, name, builder, res, callback);
- }, controller));
+ 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) {
- self.$process(arguments, model, $type, name, builder, res, callback);
+ 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;
}
@@ -2216,13 +3374,16 @@ SchemaBuilderEntity.prototype.operation = function(name, model, options, callbac
if (!err || callback.success)
return;
callback.success = true;
- builder.push(err);
+ 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;
@@ -2232,23 +3393,24 @@ SchemaBuilderEntity.prototype.operation = function(name, model, options, callbac
res = arguments[1];
}
- var has = builder.hasError();
+ 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));
+ 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;
};
-SchemaBuilderEntity.prototype.operation2 = function(name, options, callback, controller) {
+SchemaBuilderEntityProto.operation2 = function(name, options, callback, controller) {
if (typeof(options) === 'function') {
+ controller = callback;
callback = options;
options = undefined;
}
@@ -2263,7 +3425,7 @@ SchemaBuilderEntity.prototype.operation2 = function(name, options, callback, con
* @param {Boolean} isCopied Internal argument.
* @return {Object}
*/
-SchemaBuilderEntity.prototype.clean = function(m) {
+SchemaBuilderEntityProto.clean = function(m) {
return clone(m);
};
@@ -2296,7 +3458,10 @@ function clone(obj) {
o[i] = obj[i];
continue;
}
- o[i] = clone(obj[i]);
+ if (obj[i] instanceof SchemaInstance)
+ o[i] = obj[i].$clean();
+ else
+ o[i] = clone(obj[i]);
}
return o;
@@ -2311,11 +3476,16 @@ function clone(obj) {
var val = obj[m];
- if (val instanceof SchemaInstance) {
+ 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')
@@ -2323,7 +3493,12 @@ function clone(obj) {
continue;
}
- o[m] = clone(obj[m]);
+ // 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;
@@ -2333,10 +3508,91 @@ function clone(obj) {
* Returns prototype of instances
* @returns {Object}
*/
-SchemaBuilderEntity.prototype.instancePrototype = function() {
+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
@@ -2352,28 +3608,84 @@ SchemaInstance.prototype.$$schema = null;
SchemaInstance.prototype.$async = function(callback, index) {
var self = this;
!callback && (callback = function(){});
- self.$$async = [];
- self.$$result = [];
- self.$$index = index;
- self.$$callback = callback;
- self.$$can = true;
- setImmediate(async_continue, self);
+
+ 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_continue(self) {
- self.$$can = false;
- async_queue(self.$$async, function() {
- self.$$callback(null, self.$$index !== undefined ? self.$$result[self.$$index] : self.$$result);
- self.$$callback = null;
- });
+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();
}
-SchemaInstance.prototype.$repository = function(name, value) {
+Object.defineProperty(SchemaInstance.prototype, '$parent', {
+ get: function() {
+ return this.$$parent;
+ },
+ set: function(value) {
+ this.$$parent = value;
+ }
+});
- if (this.$$repository === undefined) {
- if (value === undefined)
- return undefined;
+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 = {};
}
@@ -2386,85 +3698,104 @@ SchemaInstance.prototype.$repository = function(name, value) {
};
SchemaInstance.prototype.$index = function(index) {
- if (typeof(index) === 'string')
- this.$$index = (this.$$index || 0).add(index);
- this.$$index = 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) {
- this.$$callback = callback;
+ var a = this.$$async;
+ if (a)
+ a.callback = callback;
return this;
};
-SchemaInstance.prototype.$response = SchemaInstance.prototype.$output = function() {
- this.$$index = this.$$result.length;
+SchemaInstance.prototype.$output = function() {
+ var a = this.$$async;
+ if (a)
+ a.index = true;
return this;
};
-SchemaInstance.prototype.$push = function(type, name, helper, first) {
+SchemaInstance.prototype.$stop = function() {
+ this.async.length = 0;
+ return this;
+};
- var self = this;
- var fn;
+const PUSHTYPE1 = { save: 1, insert: 1, update: 1, patch: 1 };
+const PUSHTYPE2 = { query: 1, get: 1, read: 1, remove: 1 };
- if (type === 'save') {
+SchemaInstance.prototype.$push = function(type, name, helper, first, async, callback) {
- helper = name;
- name = undefined;
+ var self = this;
+ var fn;
- fn = function(next) {
+ if (PUSHTYPE1[type]) {
+ fn = function(next, indexer) {
self.$$schema[type](self, helper, function(err, result) {
- self.$$result && self.$$result.push(err ? null : copy(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;
- self.$$async = null;
- self.$$callback(err, self.$$result);
- self.$$callback = null;
+ a.callback(err, a.response);
}, self.$$controller);
};
- } else if (type === 'query' || type === 'get' || type === 'read' || type === 'remove') {
-
- helper = name;
- name = undefined;
-
- fn = function(next) {
+ } else if (PUSHTYPE2[type]) {
+ fn = function(next, indexer) {
self.$$schema[type](helper, function(err, result) {
- self.$$result && self.$$result.push(err ? null : copy(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;
- self.$$async = null;
- self.$$callback(err, self.$$result);
- self.$$callback = null;
+ a.callback(err, a.response);
}, self.$$controller);
};
-
} else {
- fn = function(next) {
+ fn = function(next, indexer) {
self.$$schema[type](name, self, helper, function(err, result) {
- self.$$result && self.$$result.push(err ? null : copy(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;
- self.$$async = null;
- self.$$callback(err, self.$$result);
- self.$$callback = null;
+ a.callback(err, a.response);
}, self.$$controller);
};
}
- if (first)
- self.$$async.unshift(fn);
- else
- self.$$async.push(fn);
+ 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) {
- return this.$push(type, name, helper, true);
+SchemaInstance.prototype.$next = function(type, name, helper, async) {
+ return this.$push(type, name, helper, true, async);
};
SchemaInstance.prototype.$exec = function(name, helper, callback) {
@@ -2491,37 +3822,140 @@ SchemaInstance.prototype.$controller = function(controller) {
return this;
};
-SchemaInstance.prototype.$save = function(helper, callback) {
- if (this.$$can && this.$$async)
- this.$push('save', helper);
- else
+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.$query = function(helper, callback) {
- if (this.$$can && this.$$async)
- this.$push('query', helper);
- else
+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) {
+SchemaInstance.prototype.$read = SchemaInstance.prototype.$get = function(helper, callback, async) {
- if (this.$$can && this.$$async)
- this.$push('get', helper);
- else
+ 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) {
+SchemaInstance.prototype.$delete = SchemaInstance.prototype.$remove = function(helper, callback, async) {
- if (this.$$can && this.$$async)
- this.$push('remove', helper);
- else
+ 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;
@@ -2535,41 +3969,88 @@ SchemaInstance.prototype.$destroy = function() {
return this.$$schema.destroy();
};
-SchemaInstance.prototype.$transform = function(name, helper, callback) {
+SchemaInstance.prototype.$transform = function(name, helper, callback, async) {
- if (this.$$can && this.$$async)
- this.$push('transform', name, helper);
- else
+ 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) {
+SchemaInstance.prototype.$workflow = function(name, helper, callback, async) {
- if (this.$$can && this.$$async)
- this.$push('workflow', name, helper);
- else
+ 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) {
+SchemaInstance.prototype.$hook = function(name, helper, callback, async) {
- if (this.$$can && this.$$async)
- this.$push('hook', name, helper);
- else
+ 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) {
+SchemaInstance.prototype.$operation = function(name, helper, callback, async) {
- if (this.$$can && this.$$async)
- this.$push('operation', name, helper);
- else
+ 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;
@@ -2609,20 +4090,23 @@ SchemaInstance.prototype.$constant = function(name) {
function ErrorBuilder(onResource) {
this.items = [];
- this.transformName = transforms['error_default'];
+ this.transformName = transforms.error_default;
this.onResource = onResource;
- this.resourceName = F.config['default-errorbuilder-resource-name'];
- this.resourcePrefix = F.config['default-errorbuilder-resource-prefix'] || '';
+ 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 = F.config['default-errorbuilder-status'] || 200;
+ 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();
}
@@ -2664,24 +4148,30 @@ global.EACHSCHEMA = exports.eachschema = function(group, fn) {
}
};
-exports.getschema = function(group, name, fn, timeout) {
+global.$$$ = global.GETSCHEMA = exports.getschema = function(group, name, fn, timeout) {
if (!name || typeof(name) === 'function') {
+ timeout = fn;
fn = name;
- name = group;
- group = DEFAULT_SCHEMA;
+ } 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(function() {
- var g = schemas[group];
- return g && g.get(name) ? true : false;
- }, err => fn(err, schemas[group].get(name)), timeout || 20000);
- return;
- }
+ if (fn)
+ framework_utils.wait(() => !!schemasall[group], err => fn(err, schemasall[group]), timeout || 20000);
+ else
+ return schemasall[group];
+};
- var g = schemas[group];
- return g ? g.get(name) : undefined;
+exports.findschema = function(groupname) {
+ return schemasall[groupname.toLowerCase()];
};
exports.newschema = function(group, name) {
@@ -2693,7 +4183,11 @@ exports.newschema = function(group, name) {
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;
};
@@ -2704,10 +4198,23 @@ exports.newschema = function(group, name) {
*/
exports.remove = function(group, name) {
if (name) {
+
var g = schemas[group || DEFAULT_SCHEMA];
g && g.remove(name);
- } else
+ 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) {
@@ -2824,7 +4331,7 @@ exports.prepare = function(name, model) {
};
function isUndefined(value, def) {
- return value === undefined ? def : value;
+ return value === undefined ? (def === EMPTYARRAY ? [] : def) : value;
}
// ======================================================
@@ -2843,6 +4350,14 @@ ErrorBuilder.prototype = {
var self = this;
!self.isPrepared && self.prepare();
return self._transform();
+ },
+
+ get is() {
+ return this.items.length > 0;
+ },
+
+ get length() {
+ return this.items.length;
}
};
@@ -2891,7 +4406,7 @@ ErrorBuilder.prototype._resource = function() {
ErrorBuilder.prototype._resource_handler = function(name) {
var self = this;
- return typeof(F) !== 'undefined' ? F.resource(self.resourceName || 'default', name) : '';
+ return global.F ? F.resource(self.resourceName || 'default', name) : '';
};
ErrorBuilder.prototype.exception = function(message) {
@@ -2911,6 +4426,8 @@ 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.
@@ -2924,7 +4441,7 @@ ErrorBuilder.prototype.push = function(name, error, path, index, prefix) {
this.isPrepared = false;
if (name instanceof ErrorBuilder) {
- if (name.hasError()) {
+ 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;
@@ -2962,9 +4479,25 @@ ErrorBuilder.prototype.push = function(name, error, path, index, prefix) {
path = undefined;
}
- if (!error)
+ 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;
@@ -2976,6 +4509,16 @@ ErrorBuilder.prototype.push = function(name, error, path, index, prefix) {
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.
@@ -3636,72 +5179,6 @@ UrlBuilder.prototype.toOne = function(keys, delimiter) {
return builder.join(delimiter || '&');
};
-function TransformBuilder() {}
-
-TransformBuilder.transform = function(name, obj) {
-
- OBSOLETE('TransformBuilder', 'Builders.TransformBuilder will be removed in next versions.');
-
- var index = 2;
-
- if (obj === undefined) {
- obj = name;
- name = transforms['transformbuilder_default'];
- index = 1;
- }
-
- var current = transforms['transformbuilder'][name];
- if (!current) {
- F.error('Transformation "' + name + '" not found.', 'TransformBuilder.transform()');
- return obj;
- }
-
- var sum = arguments.length - index;
- if (sum <= 0)
- return current.call(obj, obj);
-
- var arr = new Array(sum + 1);
- var indexer = 1;
- arr[0] = obj;
- for (var i = index; i < arguments.length; i++)
- arr[indexer++] = arguments[i];
- return current.apply(obj, arr);
-};
-
-/**
- * STATIC: Create a transformation
- * @param {String} name
- * @param {Function} fn
- * @param {Boolean} isDefault Default transformation for all TransformBuilders.
- */
-TransformBuilder.addTransform = function(name, fn, isDefault) {
- transforms['transformbuilder'][name] = fn;
- isDefault && TransformBuilder.setDefaultTransform(name);
-};
-
-TransformBuilder.setDefaultTransform = function(name) {
- if (name)
- transforms['transformbuilder_default'] = name;
- else
- delete transforms['transformbuilder_default'];
-};
-
-function async_queue(arr, callback) {
- var item = arr.shift();
- if (item)
- item(() => async_queue(arr, callback));
- else
- callback();
-}
-
-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();
-}
-
function RESTBuilder(url) {
this.$url = url;
@@ -3721,14 +5198,69 @@ function RESTBuilder(url) {
// 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(instance);
+ 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
@@ -3747,19 +5279,38 @@ RESTBuilder.setDefaultTransform = function(name) {
delete transforms['restbuilder_default'];
};
-RESTBuilder.prototype.setTransform = function(name) {
+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;
};
-RESTBuilder.prototype.url = function(url) {
+RESTP.url = function(url) {
if (url === undefined)
return this.$url;
this.$url = url;
return this;
};
-RESTBuilder.prototype.file = function(name, filename, buffer) {
+RESTP.file = function(name, filename, buffer) {
var obj = { name: name, filename: filename, buffer: buffer };
if (this.$files)
this.$files.push(obj);
@@ -3768,7 +5319,7 @@ RESTBuilder.prototype.file = function(name, filename, buffer) {
return this;
};
-RESTBuilder.prototype.maketransform = function(obj, data) {
+RESTP.maketransform = function(obj, data) {
if (this.$transform) {
var fn = transforms['restbuilder'][this.$transform];
return fn ? fn(obj, data) : obj;
@@ -3776,67 +5327,73 @@ RESTBuilder.prototype.maketransform = function(obj, data) {
return obj;
};
-RESTBuilder.prototype.timeout = function(number) {
+RESTP.timeout = function(number) {
this.$timeout = number;
return this;
};
-RESTBuilder.prototype.maxlength = function(number) {
+RESTP.maxlength = function(number) {
this.$length = number;
this.$flags = null;
return this;
};
-RESTBuilder.prototype.auth = function(user, password) {
- this.$headers['authorization'] = 'Basic ' + framework_utils.createBuffer(user + ':' + password).toString('base64');
+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;
};
-RESTBuilder.prototype.schema = function(group, name) {
+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;
};
-RESTBuilder.prototype.noDnsCache = function() {
+RESTP.noDnsCache = function() {
this.$nodnscache = true;
this.$flags = null;
return this;
};
-RESTBuilder.prototype.noCache = function() {
+RESTP.noCache = function() {
this.$nocache = true;
return this;
};
-RESTBuilder.prototype.make = function(fn) {
+RESTP.make = function(fn) {
fn.call(this, this);
return this;
};
-RESTBuilder.prototype.xhr = function() {
+RESTP.xhr = function() {
this.$headers['X-Requested-With'] = 'XMLHttpRequest';
return this;
};
-RESTBuilder.prototype.method = function(method) {
- this.$method = method.toLowerCase();
+RESTP.method = function(method, data) {
+ this.$method = method.charCodeAt(0) < 97 ? method.toLowerCase() : method;
this.$flags = null;
+ data && this.raw(data);
return this;
};
-RESTBuilder.prototype.referer = RESTBuilder.prototype.referrer = function(value) {
+RESTP.referer = RESTP.referrer = function(value) {
this.$headers['Referer'] = value;
return this;
};
-RESTBuilder.prototype.origin = function(value) {
+RESTP.origin = function(value) {
this.$headers['Origin'] = value;
return this;
};
-RESTBuilder.prototype.robot = function() {
+RESTP.robot = function() {
if (this.$headers['User-Agent'])
this.$headers['User-Agent'] += ' Bot';
else
@@ -3844,7 +5401,7 @@ RESTBuilder.prototype.robot = function() {
return this;
};
-RESTBuilder.prototype.mobile = function() {
+RESTP.mobile = function() {
if (this.$headers['User-Agent'])
this.$headers['User-Agent'] += ' iPhone';
else
@@ -3852,7 +5409,7 @@ RESTBuilder.prototype.mobile = function() {
return this;
};
-RESTBuilder.prototype.put = function(data) {
+RESTP.put = RESTP.PUT = function(data) {
if (this.$method !== 'put') {
this.$flags = null;
this.$method = 'put';
@@ -3862,7 +5419,7 @@ RESTBuilder.prototype.put = function(data) {
return this;
};
-RESTBuilder.prototype.delete = function(data) {
+RESTP.delete = RESTP.DELETE = function(data) {
if (this.$method !== 'delete') {
this.$flags = null;
this.$method = 'delete';
@@ -3872,7 +5429,7 @@ RESTBuilder.prototype.delete = function(data) {
return this;
};
-RESTBuilder.prototype.get = function(data) {
+RESTP.get = RESTP.GET = function(data) {
if (this.$method !== 'get') {
this.$flags = null;
this.$method = 'get';
@@ -3881,7 +5438,7 @@ RESTBuilder.prototype.get = function(data) {
return this;
};
-RESTBuilder.prototype.post = function(data) {
+RESTP.post = RESTP.POST = function(data) {
if (this.$method !== 'post') {
this.$flags = null;
this.$method = 'post';
@@ -3891,7 +5448,25 @@ RESTBuilder.prototype.post = function(data) {
return this;
};
-RESTBuilder.prototype.json = function(data) {
+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;
@@ -3905,7 +5480,7 @@ RESTBuilder.prototype.json = function(data) {
return this;
};
-RESTBuilder.prototype.urlencoded = function(data) {
+RESTP.urlencoded = function(data) {
if (this.$type !== 2)
this.$flags = null;
@@ -3918,15 +5493,25 @@ RESTBuilder.prototype.urlencoded = function(data) {
return this;
};
-RESTBuilder.prototype.accept = function(ext) {
- var type = framework_utils.getContentType(ext);
- if (this.$headers['Accept'] !== type)
+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.$headers['Accept'] = type;
+
+ this.$flags = null;
+ this.$headers.Accept = type;
+
return this;
};
-RESTBuilder.prototype.xml = function(data) {
+RESTP.xml = function(data, replace) {
if (this.$type !== 3)
this.$flags = null;
@@ -3935,71 +5520,95 @@ RESTBuilder.prototype.xml = function(data) {
this.$method = 'post';
this.$type = 3;
+
+ if (replace)
+ this.$replace = true;
+
data && this.raw(data);
return this;
};
-RESTBuilder.prototype.redirect = function(value) {
+RESTP.redirect = function(value) {
this.$redirect = value;
return this;
};
-RESTBuilder.prototype.raw = function(value) {
+RESTP.raw = function(value) {
this.$data = value && value.$clean ? value.$clean() : value;
return this;
};
-RESTBuilder.prototype.cook = function(value) {
+RESTP.plain = function() {
+ this.$plain = true;
+ return this;
+};
+
+RESTP.cook = function(value) {
this.$flags = null;
this.$persistentcookies = value !== false;
return this;
};
-RESTBuilder.prototype.cookies = function(obj) {
+RESTP.cookies = function(obj) {
this.$cookies = obj;
return this;
};
-RESTBuilder.prototype.cookie = function(name, value) {
+RESTP.cookie = function(name, value) {
!this.$cookies && (this.$cookies = {});
this.$cookies[name] = value;
return this;
};
-RESTBuilder.prototype.header = function(name, value) {
+RESTP.header = function(name, value) {
this.$headers[name] = value;
return this;
};
-RESTBuilder.prototype.cache = function(expire) {
+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;
};
-RESTBuilder.prototype.set = function(name, value) {
+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;
- return this;
+ } else {
+ var arr = Object.keys(name);
+ for (var i = 0, length = arr.length; i < length; i++)
+ this.$data[arr[i]] = name[arr[i]];
}
-
- var arr = Object.keys(name);
- for (var i = 0, length = arr.length; i < length; i++)
- this.$data[arr[i]] = name[arr[i]];
-
return this;
};
-RESTBuilder.prototype.rem = function(name) {
+RESTP.rem = function(name) {
if (this.$data && this.$data[name])
this.$data[name] = undefined;
return this;
};
-RESTBuilder.prototype.stream = function(callback) {
+RESTP.stream = function(callback) {
var self = this;
var flags = self.$flags ? self.$flags : [self.$method];
@@ -4020,7 +5629,21 @@ RESTBuilder.prototype.stream = function(callback) {
return U.download(self.$url, flags, self.$data, callback, self.$cookies, self.$headers, undefined, self.$timeout);
};
-RESTBuilder.prototype.exec = function(callback) {
+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;
@@ -4030,6 +5653,13 @@ RESTBuilder.prototype.exec = function(callback) {
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;
@@ -4039,6 +5669,9 @@ RESTBuilder.prototype.exec = function(callback) {
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');
@@ -4061,62 +5694,116 @@ RESTBuilder.prototype.exec = function(callback) {
var data = F.cache.read2(key);
if (data) {
var evt = new framework_utils.EventEmitter2();
- process.nextTick(exec_removelisteners, evt);
+ setImmediate(exec_removelisteners, evt);
callback(null, self.maketransform(this.$schema ? this.$schema.make(data.value) : data.value, data), data);
return evt;
}
}
- return U.request(self.$url, flags, self.$data, function(err, response, status, headers, hostname) {
+ self.$callback_key = key;
+ return U.request(self.$url, flags, self.$data, exec_callback, self.$cookies, self.$headers, undefined, self.$timeout, self.$files, self);
+};
- var type = err ? '' : headers['content-type'] || '';
- var output = new RESTBuilderResponse();
+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':
- output.value = response.parseXML();
+ case 'application/xml':
+ output.value = response ? response.parseXML(self.$replace ? true : false) : {};
break;
case 'application/x-www-form-urlencoded':
- output.value = F.onParseQuery(response);
+ output.value = response ? F.onParseQuery(response) : {};
break;
case 'application/json':
- output.value = response.parseJSON(true);
+ case 'text/json':
+ output.value = response ? response.parseJSON(true) : null;
break;
default:
- output.value = response.isJSON() ? response.parseJSON(true) : null;
+ output.value = response && response.isJSON() ? response.parseJSON(true) : null;
break;
}
+ }
- if (output.value == null)
- output.value = EMPTYOBJECT;
+ if (output.value == null)
+ output.value = EMPTYOBJECT;
- output.response = response;
- output.status = status;
- output.headers = headers;
- output.hostname = hostname;
- output.cache = false;
- output.datetime = F.datetime;
+ output.response = response;
+ output.status = status;
+ output.headers = headers;
+ output.hostname = hostname;
+ output.cache = false;
+ output.datetime = NOW;
- if (self.$schema) {
+ var val;
- if (err)
- return callback(err, EMPTYOBJECT, output);
+ if (self.$schema) {
- self.$schema.make(self.maketransform(output.value, output), function(err, model) {
- !err && key && F.cache.add(key, output, self.$cache_expire);
- callback(err, err ? EMPTYOBJECT : model, output);
- output.cache = true;
- });
+ 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;
+ }
+ }
+ }
- 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;
+ }
}
- !err && key && F.cache.add(key, output, self.$cache_expire);
- callback(err, self.maketransform(output.value, output), output);
- output.cache = true;
+ if (self.$convert && val && val !== EMPTYOBJECT)
+ val = CONVERT(val, self.$convert);
- }, self.$cookies, self.$headers, undefined, self.$timeout, self.$files);
-};
+ callback(err, val, output);
+ output.cache = true;
+ }
+}
function exec_removelisteners(evt) {
evt.removeAllListeners();
@@ -4158,60 +5845,155 @@ function $decodeURIComponent(value) {
}
}
-global.NEWOPERATION = function(name, fn) {
+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];
- return this;
+ } 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);
}
-
- operations[name] = fn;
- operations[name].$owner = F.$owner();
- operations[name].$newversion = REGEXP_NEWOPERATION.test(fn.toString());
- return this;
};
-global.OPERATION = function(name, value, callback, param) {
+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);
- if (callback && callback !== NOOP) {
- self.callback = function(value) {
- if (arguments.length > 1) {
- if (value instanceof Error || (value instanceof ErrorBuilder && value.hasError())) {
- self.error.push(value);
- value = EMPTYOBJECT;
- } else
- value = arguments[1];
- } else if (value instanceof Error || (value instanceof ErrorBuilder && value.hasError())) {
+ 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;
+ }
- callback(self.error.hasError() ? self.error : null, value, self.options);
- return self;
- };
- } else
- self.callback = NOOP;
+ 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;
}
- callback(error.hasError() ? error : null, value, param);
+ if (value !== NoOp)
+ callback(error.is ? error : null, value, param);
}
});
} else {
@@ -4220,33 +6002,259 @@ global.OPERATION = function(name, value, callback, param) {
}
};
-function OperationOptions(error, value, options) {
+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.DB = function() {
+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);
};
-OperationOptions.prototype.done = function(arg) {
+OperationOptionsProto.done = function(arg) {
var self = this;
return function(err, response) {
-
- if (err && !(err instanceof ErrorBuilder)) {
+ 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));
}
-
- if (arg)
- self.callback(SUCCESS(err == null, response));
- else
- self.callback(SUCCESS(err == null));
};
};
-OperationOptions.prototype.success = function(a, b) {
+OperationOptionsProto.success = function(a, b) {
if (a && b === undefined && typeof(a) !== 'boolean') {
b = a;
@@ -4257,12 +6265,330 @@ OperationOptions.prototype.success = function(a, b) {
return this;
};
-OperationOptions.prototype.invalid = function(name, error, path, index) {
- this.error.push(name, error, path, index);
- this.callback();
+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
// ======================================================
@@ -4273,18 +6599,18 @@ exports.ErrorBuilder = ErrorBuilder;
exports.Pagination = Pagination;
exports.Page = Page;
exports.UrlBuilder = UrlBuilder;
-exports.TransformBuilder = TransformBuilder;
exports.SchemaOptions = SchemaOptions;
exports.OperationOptions = OperationOptions;
exports.RESTBuilderResponse = RESTBuilderResponse;
+exports.AuthOptions = AuthOptions;
global.RESTBuilder = RESTBuilder;
global.RESTBuilderResponse = RESTBuilderResponse;
global.ErrorBuilder = ErrorBuilder;
-global.TransformBuilder = TransformBuilder;
global.Pagination = Pagination;
global.Page = Page;
global.UrlBuilder = global.URLBuilder = UrlBuilder;
global.SchemaBuilder = SchemaBuilder;
+global.TaskBuilder = TaskBuilder;
// Uninstall owners
exports.uninstall = function(owner) {
@@ -4302,11 +6628,102 @@ exports.uninstall = function(owner) {
});
};
-exports.restart = function() {
- schemas = {};
- operations = {};
- Object.keys(transforms).forEach(function(key) {
- if (key.indexOf('_') === -1)
- transforms[key] = {};
- });
+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 2a4b3d73e..68a16e3a6 100755
--- a/changes.txt
+++ b/changes.txt
@@ -1,3 +1,712 @@
+======= 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 `
+
+