New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

citizen

Package Overview
Dependencies
Maintainers
1
Versions
123
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

citizen - npm Package Compare versions

Comparing version 0.2.14 to 0.3.0

11

CHANGELOG.txt

@@ -0,3 +1,14 @@

[0.3.0]
* BREAKING CHANGE: citizen includes are now self-contained and only play in their own sandbox. They no longer receive updated context from the calling controller, and they no longer pass their own content and directives into the request context. This just makes more sense and avoids a lot of pitfalls I was experiencing in my own projects.
* BREAKING CHANGE: Completely rewrote caching, fixing bugs and making additions such as a custom scope option, allowing for easy deletion of groups of cached items. Please see the readme for changes to app.cache(), app.exists(), app.retrieve(), and app.clear(), which now all take options objects instead of a list of arguments.
* BREAKING CHANGE: app.size() now throws an error if the provided argument isn't an object literal
* Fixed several bugs in server.js that broke controller caching, route cache expiration, and cache URL params validation
* Added app.log() helper to make it easier to log application events to the console or files based on application mode (production, debug, etc.)
* Added prettyHTML config setting for Jade templates in production mode (default is true, setting it to false removes all whitespace between tags)
* Fixed the default action in params.route.chain ('handler')
* Rewrote app.dashes(), fixing a few bugs and adding a fallback option that gets returned if the parsed string ends up being empty
[0.2.14]
* The ctznRedirect session variable now has a cookie fallback if sessions aren't enabled
* Fixed a bug in direct cookie assignments

@@ -4,0 +15,0 @@ [0.2.13]

67

lib/citizen.js

@@ -26,10 +26,8 @@ // Initializes the framework

app: '',
fileNotFound: '/404.html'
'404': '/404.html',
'50x': '/50x.html'
},
httpPort: 80,
hostname: undefined,
connectionQueue: undefined,
logs: {
console: true,
file: false
},
sessions: false,

@@ -39,6 +37,18 @@ sessionTimeout: 1200000, // 20 minutes

mimetypes: JSON.parse(fs.readFileSync(path.join(__dirname, '../config/mimetypes.json'))),
prettyHTML: true,
log: {
toConsole: false,
toFile: false,
defaultFile: 'citizen.txt',
contents: {
applicationErrors: true,
applicationStatus: false,
staticErrors: true,
staticStatus: false
}
},
debug: {
output: 'console',
output: 'view',
depth: 2,
enableCache: false,
disableCache: true,
jade: false

@@ -48,11 +58,6 @@ }

config = getConfig(),
finalConfig = helpers.extend(defaultConfig, config.citizen),
finalConfig = helpers.public.extend(defaultConfig, config.citizen),
on = {
application: {
start: function (emitter) {
// TODO: Log handler
// helpers.log({
// modes: 'debug',
// log: 'citizen application start fired'
// });
emitter.emit('ready');

@@ -63,6 +68,6 @@ },

},
error: function (e, params, context, emitter) {
error: function (err, params, context, emitter) {
if ( finalConfig.mode !== 'production' ) {
if ( !e.staticAsset ) {
console.log(e);
if ( !err.staticAsset ) {
helpers.log('application error', err);
console.trace();

@@ -107,7 +112,3 @@ }

global.CTZN = {
cache: {
app: {},
route: {},
controller: {}
},
cache: {},
config: config,

@@ -127,3 +128,5 @@ on: on,

config: config,
controllers: patterns.controllers,
models: patterns.models,
views: patterns.views,
start: server.start,

@@ -138,10 +141,17 @@ session: session,

// export helpers
for ( var property in helpers ) {
if ( helpers.hasOwnProperty(property) ) {
module.exports[property] = helpers[property];
for ( var property in helpers.public ) {
if ( helpers.public.hasOwnProperty(property) ) {
module.exports[property] = helpers.public[property];
}
}
console.log('Configuration: \n' + util.inspect(config));
console.log('citizen is ready to accept requests on port ' + config.citizen.httpPort);
helpers.public.log({
label: 'Configuration',
content: config,
file: 'citizen.txt'
});
helpers.public.log({
content: 'citizen is ready to accept requests on port ' + config.citizen.httpPort,
file: 'citizen.txt'
});

@@ -179,3 +189,3 @@ function getConfig() {

config = JSON.parse(fs.readFileSync(path.join(configDirectory, '/citizen.json')));
console.log('citizen configuration loaded from file: ' + configDirectory + '/citizen.json');
console.log('app configuration loaded from file: ' + configDirectory + '/citizen.json');
} catch ( err ) {

@@ -284,3 +294,4 @@ // No big deal, citizen will start under the default configuration

filename: filePath,
compileDebug: false
compileDebug: false,
pretty: finalConfig.prettyHTML
});

@@ -287,0 +298,0 @@ break;

@@ -6,140 +6,244 @@ // core framework functions that might also be of use in the app

var events = require('events'),
fs = require('fs');
fs = require('fs'),
util = require('util');
module.exports = {
cache: cache,
exists: exists,
retrieve: retrieve,
clear: clear,
copy: copy,
extend: extend,
isNumeric: isNumeric,
listen: listen,
dashes: dashes,
size: size
public: {
cache: cache,
exists: exists,
retrieve: retrieve,
clear: clear,
copy: copy,
extend: extend,
isNumeric: isNumeric,
listen: listen,
dashes: dashes,
size: size,
log: log
},
citizen: {
cacheController: cacheController,
cacheRoute: cacheRoute,
retrieveController: retrieveController
}
};
function cache(options) {
var timer,
key = options.key || options.file || options.controller || options.route || '',
value;
scope = options.scope || 'app',
key = options.key || options.file,
value,
enableCache = CTZN.config.citizen.mode !== 'debug' || ( CTZN.config.citizen.mode === 'debug' && !CTZN.config.citizen.debug.disableCache ) ? true : false;
options.overwrite = options.overwrite || false;
options.lifespan = options.lifespan || 'application';
options.encoding = options.encoding || 'utf-8';
options.synchronous = options.synchronous || false;
options.directives = options.directives || {};
if ( enableCache ) {
options.overwrite = options.overwrite || false;
options.lifespan = options.lifespan || 'application';
options.resetOnAccess = options.resetOnAccess || false;
options.encoding = options.encoding || 'utf-8';
options.synchronous = options.synchronous || false;
if ( key.length === 0 ) {
throw {
thrownBy: 'helpers.cache()',
message: 'You need to specify an absolute file path, a route name, or custom key name when saving objects to the cache.'
};
}
if ( scope !== 'controllers' && scope !== 'routes' ) {
CTZN.cache[scope] = CTZN.cache[scope] || {};
} else {
throw {
thrownBy: 'helpers.cache()',
message: 'The terms "controllers" and "routes" are reserved cache scope names. Please choose a different name for your custom cache scope.'
};
}
if ( ( options.key && !options.file && !options.value ) || ( options.value && !options.key ) ) {
throw {
thrownBy: 'helpers.cache()',
message: 'When using a custom key, you have to specify both a key name and value.'
};
}
if ( !key ) {
throw {
thrownBy: 'helpers.cache()',
message: 'You need to specify a key name or an absolute file path when saving objects to the cache.'
};
}
if ( options.controller && ( !options.context || !options.viewName || !options.view ) ) {
throw {
thrownBy: 'helpers.cache()',
message: 'When caching a controller, you must specify the context, view name, and rendered view contents.'
};
}
if ( ( options.key && !options.file && !options.value ) || ( options.value && !options.key ) ) {
throw {
thrownBy: 'helpers.cache()',
message: 'When using a custom key, you have to specify both a key name and value.'
};
}
if ( options.lifespan !== 'application' && !isNumeric(options.lifespan) ) {
throw {
thrownBy: 'helpers.cache()',
message: 'Cache lifespan needs to be specified in milliseconds.'
};
}
if ( options.lifespan !== 'application' && !isNumeric(options.lifespan) ) {
throw {
thrownBy: 'helpers.cache()',
message: 'Cache lifespan needs to be specified in milliseconds.'
};
}
if ( options.value ) {
if ( !CTZN.cache.app[key] || ( CTZN.cache.app[key] && options.overwrite ) ) {
if ( options.lifespan !== 'application' ) {
timer = setTimeout( function () {
clear(key);
}, options.lifespan);
}
CTZN.cache.app[key] = {
key: key,
// Create a copy of the content object so the cache isn't a pointer to the original
value: copy(options.value),
timer: timer
};
} else {
if ( options.resetOnAccess && options.lifespan === 'application' ) {
throw {
thrownBy: 'helpers.cache()',
message: 'An cache using the specified key [\'' + options.key + '\'] already exists. If your intention is to overwrite the existing cache, you have to pass the overwrite flag explicitly.'
message: 'For the resetOnAccess option to work correctly, you must specify the lifespan option in milliseconds.'
};
}
} else if ( options.file ) {
if ( !CTZN.cache.app[key] || ( CTZN.cache.app[key] && options.overwrite ) ) {
if ( options.lifespan !== 'application' ) {
timer = setTimeout( function () {
clear(key);
}, options.lifespan);
}
if ( options.synchronous ) {
value = fs.readFileSync(options.file, { encoding: options.encoding });
if ( options.parseJSON ) {
value = JSON.parse(value);
// If a value is provided, it's a straight dump into the cache
if ( options.value ) {
if ( !CTZN.cache[scope][key] || ( CTZN.cache[scope][key] && options.overwrite ) ) {
if ( options.lifespan !== 'application' ) {
timer = setTimeout( function () {
clear({ scope: scope, key: key });
log({
label: scope + ' cache item timeout',
content: key,
file: 'citizen.txt'
});
}, options.lifespan);
}
CTZN.cache.app[key] = {
file: options.file,
value: value,
timer: timer
CTZN.cache[scope][key] = {
key: key,
scope: scope,
// Create a copy of the content object so the cache isn't a pointer to the original
value: copy(options.value),
timer: timer,
lifespan: options.lifespan,
resetOnAccess: options.resetOnAccess
};
log({
label: 'cached',
content: {
key: key,
scope: scope,
lifespan: options.lifespan,
resetOnAccess: options.resetOnAccess
},
file: 'citizen.txt'
});
} else {
fs.readFile(options.file, { encoding: options.encoding }, function (err, data) {
if ( err ) {
throw {
thrownBy: 'helpers.cache()',
message: 'There was an error when attempting to read the specified file (' + key + ').'
};
} else {
if ( options.parseJSON ) {
value = JSON.parse(data);
throw {
thrownBy: 'helpers.cache()',
message: 'An cache item using the specified key [\'' + options.key + '\'] already exists. If your intention is to overwrite the existing cache, you have to pass the overwrite flag explicitly.'
};
}
// If a file path is provided, we need to read the file and perhaps parse it
} else if ( options.file ) {
if ( !CTZN.cache[scope][key] || ( CTZN.cache[scope][key] && options.overwrite ) ) {
if ( options.lifespan !== 'application' ) {
timer = setTimeout( function () {
clear({ scope: scope, key: key });
log({
label: 'file cache timeout',
content: key,
file: 'citizen.txt'
});
}, options.lifespan);
}
if ( options.synchronous ) {
value = fs.readFileSync(options.file, { encoding: options.encoding });
if ( options.parseJSON ) {
value = JSON.parse(value);
}
CTZN.cache[scope][key] = {
file: options.file,
key: key,
scope: scope,
value: value,
timer: timer,
lifespan: options.lifespan,
resetOnAccess: options.resetOnAccess
};
log({
label: 'file cached',
content: {
file: options.file,
key: key,
scope: scope,
lifespan: options.lifespan,
resetOnAccess: options.resetOnAccess
},
file: 'citizen.txt'
});
} else {
fs.readFile(options.file, { encoding: options.encoding }, function (err, data) {
if ( err ) {
throw {
thrownBy: 'helpers.cache()',
message: 'There was an error when attempting to read the specified file (' + key + ').'
};
} else {
value = data;
if ( options.parseJSON ) {
value = JSON.parse(data);
} else {
value = data;
}
CTZN.cache[scope][key] = {
file: options.file,
key: key,
scope: scope,
value: value,
timer: timer,
lifespan: options.lifespan,
resetOnAccess: options.resetOnAccess
};
log({
label: 'file cached',
content: {
file: options.file,
key: key,
scope: scope,
value: options.value,
lifespan: options.lifespan,
resetOnAccess: options.resetOnAccess
},
file: 'citizen.txt'
});
}
CTZN.cache.app[key] = {
file: options.file,
value: value,
timer: timer
};
}
});
});
}
} else {
throw {
thrownBy: 'helpers.cache()',
message: 'A cache item containing the specified file [\'' + options.file + '\'] already exists. If your intention is to overwrite the existing cache, you have to pass the overwrite flag explicitly.'
};
}
} else {
throw {
thrownBy: 'helpers.cache()',
message: 'A cache containing the specified file [\'' + options.file + '\'] already exists. If your intention is to overwrite the existing cache, you have to pass the overwrite flag explicitly.'
};
}
} else if ( options.controller ) {
if ( options.route ) {
key = options.controller + '-' + options.viewName + '-' + options.route;
} else {
key = options.controller + '-' + options.viewName;
}
}
}
if ( !CTZN.cache.controller[key] || ( CTZN.cache.controller[key] && options.overwrite ) ) {
function cacheController(options) {
var timer,
enableCache = CTZN.config.citizen.mode !== 'debug' || ( CTZN.config.citizen.mode === 'debug' && !CTZN.config.citizen.debug.disableCache ) ? true : false;
if ( enableCache ) {
CTZN.cache.controllers = CTZN.cache.controllers || {};
CTZN.cache.controllers[options.controller] = CTZN.cache.controllers[options.controller] || {};
CTZN.cache.controllers[options.controller][options.action] = CTZN.cache.controllers[options.controller][options.action] || {};
CTZN.cache.controllers[options.controller][options.action][options.view] = CTZN.cache.controllers[options.controller][options.action][options.view] || {};
if ( !CTZN.cache.controllers[options.controller][options.action][options.view][options.route] || options.overwrite ) {
if ( options.lifespan !== 'application' ) {
timer = setTimeout( function () {
clear(key, 'controller');
clear({
controller: options.controller,
action: options.action,
view: options.view,
route: options.route
});
log({
label: 'controller cache timeout',
content: {
controller: options.controller,
action: options.action,
view: options.view,
route: options.route
},
file: 'citizen.txt'
});
}, options.lifespan);
}
CTZN.cache.controller[key] = {
CTZN.cache.controllers[options.controller][options.action][options.view][options.route] = {
controller: options.controller,
route: options.route || '',
action: options.action,
view: options.view,
route: options.route,
context: options.context,
viewName: options.viewName,
view: options.view,
render: options.render,
timer: timer,

@@ -149,25 +253,67 @@ lifespan: options.lifespan,

};
log({
label: 'controller cached',
content: {
controller: options.controller,
action: options.action,
view: options.view,
route: options.route,
lifespan: options.lifespan,
resetOnAccess: options.resetOnAccess
},
file: 'citizen.txt'
});
} else {
throw {
thrownBy: 'helpers.cache()',
message: 'A cache containing the specified route/controller/view combination already exists. If your intention is to overwrite the existing cache, you have to pass the overwrite flag explicitly.\n route: ' + options.route + '\n controller: ' + options.controller + '\n view: ' + options.viewName
thrownBy: 'helpers.cacheController()',
message: 'A cache item containing the specified controller/action/view/route combination already exists. If your intention is to overwrite the existing cache, you have to pass the overwrite flag explicitly.\n controller: ' + options.controller + '\n action: ' + options.action + '\n view: ' + options.view + '\n route: ' + options.route
};
}
} else if ( options.route ) {
if ( !CTZN.cache.route[key] || ( CTZN.cache.route[key] && options.overwrite ) ) {
if ( options.lifespan !== 'application' ) {
}
}
function cacheRoute(options) {
var timer,
enableCache = CTZN.config.citizen.mode !== 'debug' || ( CTZN.config.citizen.mode === 'debug' && !CTZN.config.citizen.debug.disableCache ) ? true : false;
if ( enableCache ) {
CTZN.cache.routes = CTZN.cache.routes || {};
if ( !CTZN.cache.routes[options.route] || ( CTZN.cache.routes[options.route] && options.overwrite ) ) {
if ( options.lifespan && options.lifespan !== 'application' ) {
timer = setTimeout( function () {
clear(key, 'route');
clear({ route: options.route });
log({
label: 'route cache timeout',
content: options.route,
file: 'citizen.txt'
});
}, options.lifespan);
}
CTZN.cache.route[key] = {
route: key,
CTZN.cache.routes[options.route] = {
route: options.route,
contentType: options.contentType,
view: options.view,
timer: timer
timer: timer,
lifespan: options.lifespan,
resetOnAccess: options.resetOnAccess
};
log({
label: 'route cached',
content: {
route: options.route,
contentType: options.contentType,
lifespan: options.lifespan,
resetOnAccess: options.resetOnAccess
},
file: 'citizen.txt'
});
} else {
throw {
thrownBy: 'helpers.cache()',
thrownBy: 'helpers.cacheRoute()',
message: 'A cache containing the specified route [\'' + options.route + '\'] already exists. If your intention is to overwrite the existing cache, you have to pass the overwrite flag explicitly.'

@@ -177,108 +323,342 @@ };

}
}
function exists(key, namespace) {
namespace = namespace || 'app';
switch ( namespace ) {
case 'app':
if ( CTZN.cache.app[key] ) {
return true;
}
break;
case 'controller':
if ( CTZN.cache.controller[key] ) {
return true;
}
break;
case 'route':
if ( CTZN.cache.route[key] ) {
return true;
}
break;
default:
return false;
function exists(options) {
// If both a scope and key are provided, check the scope for the specified key
if ( options.key && options.scope ) {
if ( CTZN.cache[options.scope] && CTZN.cache[options.scope][options.key] ) {
return true;
}
// If only a key is provided, check the app scope (default) for the specified key
} else if ( options.key ) {
if ( CTZN.cache.app[options.key] ) {
return true;
}
// If only a scope is provided, check if the specified scope has members
} else if ( options.scope ) {
if ( CTZN.cache[options.scope] && size(CTZN.cache[options.scope]) ) {
return true;
}
// If a controller, action, and view are provided, check if the controllers scope
// has that view
} else if ( options.controller && options.action && options.view && options.route ) {
if ( CTZN.cache.controllers && CTZN.cache.controllers[options.controller] && CTZN.cache.controllers[options.controller][options.action] && CTZN.cache.controllers[options.controller][options.action][options.view] && CTZN.cache.controllers[options.controller][options.action][options.view][options.route] ) {
return true;
}
// If a controller and action are provided, check if the controllers scope has them
} else if ( options.controller && options.action ) {
if ( CTZN.cache.controllers[options.controller] && CTZN.cache.controllers[options.controller][options.action] && size(CTZN.cache.controllers[options.controller][options.action]) ) {
return true;
}
// If only a controller is provided, check if the controllers scope has it
} else if ( options.controller ) {
if ( CTZN.cache.controllers[options.controller] && size(CTZN.cache.controllers[options.controller]) ) {
return true;
}
// Throw an error if the required arguments aren't provided
} else {
throw {
thrownBy: 'helpers.exists()',
message: 'Missing arguments. You must provide a cache key, cache scope, or both options.'
};
}
return false;
}
function retrieve(key, namespace) {
namespace = namespace || 'app';
switch ( namespace ) {
case 'app':
if ( CTZN.cache.app[key] ) {
if ( CTZN.cache.app[key].timer && CTZN.cache.app[key].resetOnAccess ) {
clearTimeout(CTZN.cache.app[key].timer);
CTZN.cache.app[key].timer = setTimeout( function () {
clear(key, 'controller');
}, CTZN.cache.app[key].lifespan);
}
return CTZN.cache.app[key].value;
} else {
return false;
function retrieve(options) {
var scope = options.scope || 'app',
resetOnAccess,
matchingKeys = {};
// Return the provided key from the specified scope. This first condition matches
// the following:
// 1. Only a key has been provided
// 2. A key and a custom scope have been provided
if ( options.key ) {
if ( CTZN.cache[scope] && CTZN.cache[scope][options.key] ) {
// Reset the timer (or don't) based on the provided option. If the option isn't
// provided, use the stored resetOnAccess value.
resetOnAccess = options.resetOnAccess || CTZN.cache[scope][options.key].resetOnAccess;
if ( CTZN.cache[scope][options.key].timer && resetOnAccess ) {
clearTimeout(CTZN.cache[scope][options.key].timer);
CTZN.cache[scope][options.key].timer = setTimeout( function () {
clear({ scope: scope, key: options.key });
log({
label: 'cache timeout',
content: options,
file: 'citizen.txt'
});
}, CTZN.cache[scope][options.key].lifespan);
log({
label: 'cache timer reset',
content: options,
file: 'citizen.txt'
});
}
break;
case 'controller':
if ( CTZN.cache.controller[key] ) {
if ( CTZN.cache.controller[key].timer && CTZN.cache.controller[key].resetOnAccess ) {
clearTimeout(CTZN.cache.controller[key].timer);
CTZN.cache.controller[key].timer = setTimeout( function () {
clear(key, 'controller');
}, CTZN.cache.controller[key].lifespan);
}
return CTZN.cache.controller[key];
log({
label: 'cache retrieval',
content: options,
file: 'citizen.txt'
});
if ( scope === 'routes' ) {
return CTZN.cache[scope][options.key];
} else {
return false;
return CTZN.cache[scope][options.key].value;
}
break;
case 'route':
if ( CTZN.cache.route[key] ) {
if ( CTZN.cache.route[key].timer && CTZN.cache.route[key].resetOnAccess ) {
clearTimeout(CTZN.cache.route[key].timer);
CTZN.cache.route[key].timer = setTimeout( function () {
clear(key, 'controller');
}, CTZN.cache.route[key].lifespan);
}
// If only a scope is provided, return the entire scope if it exists and it has
// members, resetting the cache timer on each key if required
} else if ( options.scope ) {
if ( CTZN.cache[options.scope] && size(CTZN.cache[options.scope]) ) {
matchingKeys[options.scope] = {};
for ( var key in CTZN.cache[options.scope] ) {
if ( CTZN.cache[options.scope].hasOwnProperty(key) ) {
// Reset the timer (or don't) based on the provided option. If the option isn't
// provided, use the stored resetOnAccess value.
resetOnAccess = options.resetOnAccess || CTZN.cache[scope][key].resetOnAccess;
matchingKeys[options.scope][key] = retrieve({ scope: options.scope, key: key, resetOnAccess: resetOnAccess });
}
return CTZN.cache.route[key];
} else {
return false;
}
break;
}
if ( size(matchingKeys) ) {
log({
label: 'cache retrieval',
content: options,
file: 'citizen.txt'
});
return matchingKeys;
}
// Throw an error if the required arguments aren't provided
} else {
throw {
thrownBy: 'helpers.retrieve()',
message: 'Missing arguments. You must provide a cache key, cache scope, or both options.'
};
}
return false;
}
function clear(key, namespace) {
namespace = namespace || 'app';
switch ( namespace ) {
case 'app':
if ( CTZN.cache.app[key] ) {
if ( CTZN.cache.app[key].timer ) {
clearTimeout(CTZN.cache.app[key].timer);
}
CTZN.cache.app[key] = undefined;
function retrieveController(options) {
var resetOnAccess;
if ( CTZN.cache.controllers && CTZN.cache.controllers[options.controller] && CTZN.cache.controllers[options.controller][options.action] && CTZN.cache.controllers[options.controller][options.action][options.view] && CTZN.cache.controllers[options.controller][options.action][options.view][options.route] ) {
resetOnAccess = options.resetOnAccess || CTZN.cache.controllers[options.controller][options.action][options.view][options.route].resetOnAccess;
if ( CTZN.cache.controllers[options.controller][options.action][options.view][options.route].timer && resetOnAccess ) {
clearTimeout(CTZN.cache.controllers[options.controller][options.action][options.view][options.route].timer);
CTZN.cache.controllers[options.controller][options.action][options.view][options.route].timer = setTimeout( function () {
clear({
controller: options.controller,
action: options.action,
view: options.view,
route: options.route
});
log({
label: 'cache timeout',
content: {
controller: options.controller,
action: options.action,
view: options.view,
route: options.route,
lifespan: options.lifespan,
resetOnAccess: resetOnAccess
},
file: 'citizen.txt'
});
}, CTZN.cache.controllers[options.controller][options.action][options.view][options.route].lifespan);
log({
label: 'cache timer reset',
content: {
controller: options.controller,
action: options.action,
view: options.view,
route: options.route,
lifespan: options.lifespan,
resetOnAccess: resetOnAccess
},
file: 'citizen.txt'
});
}
log({
label: 'cache retrieval',
content: {
controller: options.controller,
action: options.action,
view: options.view,
route: options.route,
lifespan: options.lifespan,
resetOnAccess: options.resetOnAccess
},
file: 'citizen.txt'
});
return CTZN.cache.controllers[options.controller][options.action][options.view][options.route];
}
}
function clear(options) {
var scope = options.scope || 'app';
// If only a key is provided, remove that key from the app scope
if ( options.key && scope === 'app' ) {
if ( CTZN.cache[scope] && CTZN.cache[scope][options.key] ) {
if ( CTZN.cache[scope][options.key].timer ) {
clearTimeout(CTZN.cache[scope][options.key].timer);
}
break;
case 'controller':
if ( CTZN.cache.controller[key] ) {
if ( CTZN.cache.controller[key].timer ) {
clearTimeout(CTZN.cache.controller[key].timer);
CTZN.cache[scope][options.key] = undefined;
log({
label: scope + ' cache item cleared',
content: options,
file: 'citizen.txt'
});
}
// If a controller name is provided, clear the controller scope based on the
// optionally provided action and view
} else if ( options.controller ) {
if ( CTZN.cache.controllers && CTZN.cache.controllers[options.controller] ) {
if ( options.action && options.view && options.route ) {
if ( CTZN.cache.controllers[options.controller][options.action] && CTZN.cache.controllers[options.controller][options.action][options.view] && CTZN.cache.controllers[options.controller][options.action][options.view][options.route] ) {
if ( CTZN.cache.controllers[options.controller][options.action][options.view][options.route].timer ) {
clearTimeout(CTZN.cache.controllers[options.controller][options.action][options.view][options.route].timer);
}
CTZN.cache.controllers[options.controller][options.action][options.view][options.route] = undefined;
log({
label: options.controller + '/' + options.action + '/' + options.view + '/' + options.route + ' controller/action/view/route cache cleared',
content: options,
file: 'citizen.txt'
});
}
CTZN.cache.controller[key] = undefined;
} else if ( options.action && options.view ) {
if ( CTZN.cache.controllers[options.controller][options.action] && CTZN.cache.controllers[options.controller][options.action][options.view] ) {
for ( var route in CTZN.cache.controllers[options.controller][options.action][options.view] ) {
if ( CTZN.cache.controllers[options.controller][options.action][options.view].hasOwnProperty(route) ) {
if ( CTZN.cache.controllers[options.controller][options.action][options.view][route].timer ) {
clearTimeout(CTZN.cache.controllers[options.controller][options.action][options.view][route].timer);
}
}
}
CTZN.cache.controllers[options.controller][options.action][options.view] = undefined;
log({
label: options.controller + '/' + options.action + '/' + options.view + ' controller/action/view cache cleared',
content: options,
file: 'citizen.txt'
});
}
} else if ( options.action ) {
if ( CTZN.cache.controllers[options.controller][options.action] ) {
for ( var view in CTZN.cache.controllers[options.controller][options.action] ) {
if ( CTZN.cache.controllers[options.controller][options.action].hasOwnProperty(view) ) {
for ( var viewRoute in CTZN.cache.controllers[options.controller][options.action][view] ) {
if ( CTZN.cache.controllers[options.controller][options.action][view].hasOwnProperty(viewRoute) ) {
if ( CTZN.cache.controllers[options.controller][options.action][view][viewRoute].timer ) {
clearTimeout(CTZN.cache.controllers[options.controller][options.action][view][viewRoute].timer);
}
}
}
}
}
CTZN.cache.controllers[options.controller][options.action] = undefined;
log({
label: options.controller + ' controller and ' + options.action + ' action cache cleared',
content: options,
file: 'citizen.txt'
});
}
} else {
for ( var action in CTZN.cache.controllers[options.controller] ) {
if ( CTZN.cache.controllers[options.controller].hasOwnProperty(action) ) {
for ( var actionView in CTZN.cache.controllers[options.controller][action] ) {
if ( CTZN.cache.controllers[options.controller][action].hasOwnProperty(actionView) ) {
for ( var viewRouteB in CTZN.cache.controllers[options.controller][action][actionView] ) {
if ( CTZN.cache.controllers[options.controller][action][actionView].hasOwnProperty(viewRouteB) ) {
if ( CTZN.cache.controllers[options.controller][action][actionView][viewRouteB].timer ) {
clearTimeout(CTZN.cache.controllers[options.controller][action][actionView][viewRouteB].timer);
}
}
}
}
}
}
}
log({
label: options.controller + ' controller cache cleared',
content: options,
file: 'citizen.txt'
});
}
break;
case 'route':
if ( CTZN.cache.route[key] ) {
if ( CTZN.cache.route[key].timer ) {
clearTimeout(CTZN.cache.route[key].timer);
}
// If only a scope is provided, clear the entire scope
} else if ( options.scope ) {
if ( CTZN.cache[options.scope] ) {
if ( options.scope !== 'controllers' ) {
for ( var property in CTZN.cache[options.scope] ) {
if ( CTZN.cache[options.scope].hasOwnProperty(property) ) {
if ( CTZN.cache[options.scope][property].timer ) {
clearTimeout(CTZN.cache[options.scope][property].timer);
}
}
}
CTZN.cache.route[key] = undefined;
} else {
for ( var controller in CTZN.cache.controllers ) {
if ( CTZN.cache.controllers.hasOwnProperty(controller) ) {
clear({ controller: controller });
}
}
}
break;
CTZN.cache[options.scope] = undefined;
log({
label: options.scope + ' scope cache cleared',
content: options,
file: 'citizen.txt'
});
}
// If only a route is provided, clear that route from the route cache
} else if ( options.route ) {
if ( CTZN.cache.routes && CTZN.cache.routes[options.route] ) {
if ( CTZN.cache.routes[options.route].timer ) {
clearTimeout(CTZN.cache.routes[options.route].timer);
}
CTZN.cache.routes[options.route] = undefined;
log({
label: 'route cache item cleared',
content: options,
file: 'citizen.txt'
});
}
// If no options are provided, clear the entire cache
// } else if ( scope === 'app' ) {
// TODO
// }
// Throw an error if the required arguments aren't provided
} else {
throw {
thrownBy: 'helpers.clear()',
message: 'Missing arguments. You must provide a cache key, cache scope, and possibly other options. Please see citizen\'s readme for instructions.'
};
}
}
// The copy() and getValue() functions were inspired by (meaning mostly stolen from)

@@ -304,2 +684,4 @@ // Andrée Hanson:

function extend(original, extension) {

@@ -321,2 +703,4 @@ var mergedObject = copy(original);

function getValue(object) {

@@ -354,2 +738,4 @@ var isArray,

function isNumeric(n) {

@@ -359,2 +745,4 @@ return !isNaN(parseFloat(n)) && isFinite(n);

function listen(functions, callback) {

@@ -431,21 +819,37 @@ var emitter = {},

function dashes(text) {
function dashes(text, fallback) {
var parsedText = text.trim();
parsedText = parsedText.replace(/"/g, '-');
parsedText = parsedText.replace(/\./g, '');
fallback = fallback || '';
parsedText = parsedText.replace(/'/g, '');
parsedText = parsedText.replace(/[^0-9A-Za-z]/g, '-');
if ( parsedText.replace(/-/g, '').length > 0 ) {
while ( parsedText.search(/--/) >= 0 ) {
parsedText = parsedText.replace(/--/g, '-');
}
// Whittle down groups of dashes to a single dash
while ( parsedText.search(/--/) >= 0 ) {
parsedText = parsedText.replace(/--/g, '-');
}
// Remove leading dashes
while ( parsedText.charAt(0) === '-' ) {
parsedText = parsedText.slice(1);
}
// Remove trailing dashes
while ( parsedText.charAt(parsedText.length - 1) === '-' ) {
parsedText = parsedText.slice(0, parsedText.length - 1);
}
// Return the parsed string, or return the fallback if the string is empty
if ( parsedText.length ) {
return parsedText;
} else {
parsedText = 'Untitled';
return fallback;
}
return parsedText;
}
function size(object) {

@@ -461,10 +865,54 @@ var count = 0;

return count;
} else if ( !object ) {
return count;
} else {
throw {
thrownBy: 'helpers.size()',
message: 'The supplied argument is not an object literal.'
message: 'The supplied argument is not an object literal. size() only accepts object literals as arguments.'
};
}
}
function log(options) {
var label = options.label || '',
content = options.content,
toConsole = options.toConsole || CTZN.config.citizen.log.toConsole,
toFile = options.toFile || CTZN.config.citizen.log.toFile,
file = options.file || CTZN.config.citizen.log.defaultFile,
time,
logItem;
if ( CTZN.config.citizen.mode === 'debug' || toConsole || toFile ) {
time = new Date();
logItem = '[' + time.toISOString() + '] ' + label;
if ( label.length && content ) {
logItem += ': ';
}
switch ( typeof content ) {
case 'string':
case 'number':
logItem += content + '\n';
break;
default:
logItem += '\n' + util.inspect(content, { depth: CTZN.config.citizen.debug.depth }) + '\n';
break;
}
if ( toConsole ) {
console.log(logItem);
}
if ( toFile ) {
fs.appendFile(CTZN.config.citizen.directories.logs + '/' + file, logItem + '\n', function (err) {
if ( err ) {
throw {
thrownBy: 'helpers.log()',
message: err
};
}
});
}
}
}

@@ -8,2 +8,4 @@ // router

helpers = helpers.public.extend(helpers.public, helpers.citizen);
module.exports = {

@@ -24,3 +26,3 @@ getRoute: getRoute,

controller: 'index',
chain: [{ controller: 'index', action: 'index', view: 'index'}],
chain: [{ controller: 'index', action: 'handler', view: 'index'}],
renderer: 'index',

@@ -27,0 +29,0 @@ descriptor: '',

@@ -17,2 +17,4 @@ // server

helpers = helpers.public.extend(helpers.public, helpers.citizen);
module.exports = {

@@ -90,3 +92,3 @@ start: start

staticAsset: true,
message: '404 Not Found: ' + params.route.filePath
file: params.route.filePath
};

@@ -97,5 +99,7 @@ } else {

response.end();
if ( CTZN.config.citizen.mode === 'debug' ) {
console.log('200 OK: ' + params.route.filePath);
}
helpers.log({
label: '200 OK',
content: params.route.filePath,
file: 'citizen.txt'
});
}

@@ -109,3 +113,3 @@ });

function setSession(params, context) {
if ( CTZN.config.citizen.sessions && context.session && ( !params.request.headers.origin || ( params.request.headers.origin && params.request.headers.origin.search(params.request.headers.host) ) ) && helpers.size(context.session) > 0 ) {
if ( CTZN.config.citizen.sessions && context.session && ( !params.request.headers.origin || ( params.request.headers.origin && params.request.headers.origin.search(params.request.headers.host) ) ) && helpers.size(context.session) ) {
if ( context.session.expires && context.session.expires === 'now' ) {

@@ -155,10 +159,25 @@ session.end(params.session.id);

case 'production':
if ( err.thrownBy ) {
console.log('Error thrown by ' + err.thrownBy + ': ' + err.message);
if ( !err.staticAsset ) {
console.log(util.inspect(err.domain));
if ( !err.staticAsset ) {
if ( err.thrownBy && err.message ) {
helpers.log({
label: 'Error thrown by ' + err.thrownBy,
content: err.message,
file: 'citizen.txt'
});
} else {
helpers.log({
label: 'Server error',
content: err,
file: 'citizen.txt'
});
}
console.trace();
} else {
console.log(util.inspect(err));
helpers.log({
label: '404 Not Found',
content: err.file,
file: 'citizen.txt'
});
}
if ( !response.headersSent ) {

@@ -176,11 +195,25 @@ response.statusCode = statusCode;

case 'debug':
if ( err.thrownBy ) {
console.log('Error thrown by ' + err.thrownBy + ': ' + err.message);
if ( !err.staticAsset ) {
console.log(util.inspect(err.domain));
if ( !err.staticAsset ) {
if ( err.thrownBy && err.message ) {
helpers.log({
label: 'Error thrown by ' + err.thrownBy,
content: err.message,
file: 'citizen.txt'
});
} else {
helpers.log({
label: 'Server error',
content: err,
file: 'citizen.txt'
});
}
console.trace();
} else {
console.log(util.inspect(err));
console.trace();
helpers.log({
label: '404 Not Found',
content: err.file,
file: 'citizen.txt'
});
}
if ( !response.headersSent ) {

@@ -275,3 +308,3 @@ response.statusCode = statusCode;

// If a previous event in the request context requested a redirect, do it immediately rather than firing the controller.
if ( helpers.size(context.redirect) ) {
if ( context.redirect && helpers.size(context.redirect) ) {
redirect(params, context);

@@ -376,3 +409,5 @@ } else if ( controller && controller[params.route.action] ) {

}, function (output) {
var responseStart = helpers.extend(context, output.responseStart);
var responseStart = helpers.extend(context, output.responseStart),
routeCache = helpers.retrieve({ scope: 'routes', key: params.route.pathname });
if ( CTZN.appOn.response && CTZN.appOn.response.start ) {

@@ -385,7 +420,8 @@ helpers.listen({

responseStart = helpers.extend(responseStart, output.responseStart);
setSession(params, responseStart);
if ( helpers.exists(params.route.pathName, 'route') ) {
if ( routeCache ) {
setCookie(params, responseStart);
params.response.setHeader('Content-Type', CTZN.cache.route[params.route.pathName].contentType);
params.response.write(CTZN.cache.route[params.route.pathName].view);
params.response.setHeader('Content-Type', routeCache.contentType);
params.response.write(routeCache.view);
params.response.end();

@@ -399,6 +435,6 @@ server.emit('responseEnd', params, context);

setSession(params, responseStart);
if ( helpers.exists(params.route.pathName, 'route') ) {
if ( routeCache ) {
setCookie(params, responseStart);
params.response.setHeader('Content-Type', CTZN.cache.route[params.route.pathName].contentType);
params.response.write(CTZN.cache.route[params.route.pathName].view);
params.response.setHeader('Content-Type', routeCache.contentType);
params.response.write(routeCache.view);
params.response.end();

@@ -427,13 +463,10 @@ server.emit('responseEnd', params, context);

responseDomain.run( function () {
var cachedController,
controllerName = context.handoffControllerName || params.route.controller,
var controllerName = context.handoffControllerName || params.route.controller,
view = context.view || params.route.view,
action = context.handoffAction || params.route.action,
cacheKeyController = controllerName + '-' + view + '-' + params.route.pathName,
cacheKeyGlobal = controllerName + '-' + view;
action = context.handoffAction || params.route.action;
helpers.listen({
pattern: function (emitter) {
if ( helpers.exists(cacheKeyController, 'controller') || helpers.exists(cacheKeyGlobal, 'controller') ) {
cachedController = helpers.retrieve(cacheKeyController, 'controller') || helpers.retrieve(cacheKeyGlobal, 'controller');
var cachedController = helpers.retrieveController({ controller: controllerName, action: action, view: view, route: params.route.pathname }) || helpers.retrieveController({ controller: controllerName, action: action, view: view, route: 'global' });
if ( cachedController ) {
emitter.emit('ready', cachedController.context);

@@ -464,15 +497,17 @@ } else {

if ( helpers.size(requestContext.redirect) && typeof requestContext.redirect.refresh === 'undefined' ) {
if ( requestContext.redirect && helpers.size(requestContext.redirect) && typeof requestContext.redirect.refresh === 'undefined' ) {
setCookie(params, requestContext);
redirect(params, requestContext);
cacheController({
controller: controllerName,
route: params.route.pathName,
route: params.route.pathname,
context: requestContext,
format: params.route.format,
viewName: params.route.renderedView,
action: action,
params: params
});
} else {
if ( helpers.size(requestContext.redirect) ) {
if ( requestContext.redirect && helpers.size(requestContext.redirect) ) {
redirect(params, requestContext);

@@ -486,32 +521,38 @@ }

action = include[item].action || 'handler',
cacheKeyController = controllerName + '-' + view + '-' + params.route.pathName,
cacheKeyGlobal = controllerName + '-' + view;
cachedController = helpers.retrieveController({ controller: controllerName, action: action, view: view, route: params.route.pathname }) || helpers.retrieveController({ controller: controllerName, action: action, view: view, route: 'global' });
if ( !CTZN.cache.controller[cacheKeyController] && !CTZN.cache.controller[cacheKeyGlobal] ) {
includeGroup[item] = function (emitter) {
requestContext.includesToRender[item].action = action;
includeGroup[item] = function (emitter) {
if ( cachedController ) {
emitter.emit('ready', cachedController.context);
} else {
CTZN.patterns.controllers[include[item].controller][action](params, requestContext, emitter);
};
}
}
};
});
requestContext.include = undefined;
if ( helpers.size(includeGroup) > 0 ) {
if ( helpers.size(includeGroup) ) {
helpers.listen(includeGroup, function (output) {
includeProperties.forEach( function (item, index, array) {
var requestContextCache = requestContext.cache || false;
requestContext.includesToRender[item].context = output[item];
requestContext.includesToRender[item].view = requestContext.includesToRender[item].view || output[item].view;
// Includes can use all directives except handoff, so we delete that before extending the request context with the include's context
if ( output[item] ) {
output[item].handoff = undefined;
requestContext = helpers.extend(requestContext, output[item]);
}
requestContext.includesToRender[item].context = helpers.copy(requestContext);
requestContext.cache = requestContextCache;
setSession(params, requestContext);
cacheController({
controller: requestContext.includesToRender[item].controller,
action: requestContext.includesToRender[item].action,
view: requestContext.includesToRender[item].view || requestContext.includesToRender[item].controller,
route: params.route.pathname,
context: output[item],
format: params.route.format,
params: params
});
});
cacheController({
controller: controllerName,
route: params.route.pathName,
action: action,
view: params.route.renderedView,
route: params.route.pathname,
context: requestContext,
format: params.route.format,
viewName: params.route.renderedView,
params: params

@@ -529,6 +570,7 @@ });

controller: controllerName,
route: params.route.pathName,
action: action,
view: params.route.renderedView,
route: params.route.pathname,
context: requestContext,
format: params.route.format,
viewName: params.route.renderedView,
params: params

@@ -546,6 +588,7 @@ });

controller: controllerName,
route: params.route.pathName,
action: action,
view: params.route.renderedView,
route: params.route.pathname,
context: requestContext,
format: params.route.format,
viewName: params.route.renderedView,
params: params

@@ -610,3 +653,4 @@ });

function cacheController(options) {
var cacheContext = {},
var cacheExists,
cacheContext = {},
cacheLifespan,

@@ -616,10 +660,30 @@ cacheReset,

if ( options.context.cache ) {
if ( ( options.context.cache.scope === 'controller' && !CTZN.cache.controller[options.controller + '-' + options.viewName + '-' + options.route] ) || ( options.context.cache.scope === 'global' && !CTZN.cache.controller[options.controller + '-' + options.viewName] ) ) {
if ( options.context.cache && !options.context.cache.route ) {
switch ( options.context.cache.scope ) {
case 'route':
cacheExists = helpers.exists({
controller: options.controller,
action: options.action,
view: options.view,
route: options.route
});
break;
case 'global':
cacheExists = helpers.exists({
controller: options.controller,
action: options.action,
view: options.view,
route: 'global'
});
break;
}
if ( !cacheExists ) {
cacheLifespan = options.context.cache.lifespan || 'application';
cacheReset = options.context.cache.resetOnAccess || false;
if ( helpers.size(options.params.url) > 0 && options.context.cache.urlParams ) {
if ( helpers.size(options.params.url) && options.context.cache.urlParams ) {
Object.getOwnPropertyNames(options.params.url).forEach( function ( item, index, array) {
if ( cacheContext.cache.urlParams.indexOf(item) < 0 ) {
if ( options.context.cache.urlParams.indexOf(item) < 0 ) {
throw {

@@ -656,9 +720,10 @@ thrownBy: 'server.cacheController()',

switch ( options.context.cache.scope ) {
case 'controller':
helpers.cache({
case 'route':
helpers.cacheController({
controller: options.controller,
action: options.action,
view: options.view,
route: options.route,
context: cacheContext,
viewName: options.viewName,
view: options.view || renderView(options.controller, options.viewName, options.format, viewContext),
render: options.render || renderView(options.controller, options.view, options.format, viewContext),
lifespan: cacheLifespan,

@@ -669,7 +734,9 @@ resetOnAccess: cacheReset

case 'global':
helpers.cache({
helpers.cacheController({
controller: options.controller,
action: options.action,
view: options.view,
route: 'global',
context: cacheContext,
viewName: options.viewName,
view: options.view || renderView(options.controller, options.viewName, options.format, viewContext),
render: options.render || renderView(options.controller, options.view, options.format, viewContext),
lifespan: cacheLifespan,

@@ -716,4 +783,4 @@ resetOnAccess: cacheReset

requestContext.session = {};
if ( requestContext.cache && ( requestContext.cache.scope === 'controller' || requestContext.cache.scope === 'global' ) ) {
requestContext.cache = undefined;
if ( requestContext.cache && !requestContext.cache.route ) {
requestContext.cache = {};
}

@@ -740,22 +807,30 @@ requestContext.handoffControllerName = thisHandoff.controller;

includeView = requestContext.includesToRender[item].view || requestContext.includesToRender[item].controller,
includeAction = requestContext.includesToRender[item].action,
includeViewContext,
cachedController,
cacheKeyController = includeController + '-' + includeView + '-' + params.route.pathName,
cacheKeyGlobal = includeController + '-' + includeView;
cachedController = helpers.retrieveController({ controller: includeController, action: includeAction, view: includeView, route: params.route.pathname }) || helpers.retrieveController({ controller: includeController, action: includeAction, view: includeView, route: 'global' });
if ( helpers.exists(cacheKeyController, 'controller') || helpers.exists(cacheKeyGlobal, 'controller') ) {
cachedController = helpers.retrieve(cacheKeyController, 'controller') || helpers.retrieve(cacheKeyGlobal, 'controller');
viewContext.include[item] = cachedController.view;
if ( cachedController ) {
viewContext.include[item] = cachedController.render;
helpers.log({
label: 'using cached controller view',
content: item,
file: 'citizen.txt'
});
} else {
includeViewContext = helpers.extend(requestContext.includesToRender[item].context.content, params);
includeViewContext.include = helpers.extend(includeViewContext.include, viewContext.include);
viewContext.include[item] = renderView(requestContext.includesToRender[item].controller, includeView, params.route.format, includeViewContext);
viewContext.include[item] = renderView(includeController, includeView, params.route.format, includeViewContext);
helpers.log({
label: 'rendering controller view',
content: item,
file: 'citizen.txt'
});
cacheController({
controller: includeController,
route: params.route.pathName,
action: includeAction,
view: includeView,
route: params.route.pathname,
context: requestContext.includesToRender[item].context,
render: viewContext.include[item],
format: params.route.format,
viewName: includeView,
view: viewContext.include[item],
params: params

@@ -792,10 +867,22 @@ });

if ( requestContext.cache && requestContext.cache.scope === 'route' ) {
helpers.cache({
route: params.route.pathName,
if ( requestContext.cache && requestContext.cache.route ) {
if ( helpers.size(params.url) && requestContext.cache.urlParams ) {
Object.getOwnPropertyNames(params.url).forEach( function ( item, index, array) {
if ( requestContext.cache.urlParams.indexOf(item) < 0 ) {
throw {
thrownBy: 'server.respond()',
message: 'Invalid cache URL. The URL parameter [' + item + '] isn\'t permitted in a cached URL.'
};
}
});
}
helpers.cacheRoute({
route: params.route.pathname,
contentType: contentType,
view: view
view: view,
lifespan: requestContext.cache.lifespan,
resetOnAccess: requestContext.cache.resetOnAccess
});
}
}

@@ -911,4 +998,10 @@

responseEnd = helpers.extend(responseEnd, output.responseEnd);
if ( CTZN.config.citizen.mode === 'debug' ) {
debugger;
}
});
}
if ( CTZN.config.citizen.mode === 'debug' ) {
debugger;
}
});

@@ -915,0 +1008,0 @@ });

{
"name": "citizen",
"version": "0.2.14",
"description": "An event-driven MVC framework for Node.js web applications.",
"version": "0.3.0",
"description": "An event-driven MVC and caching framework for Node.js web applications.",
"author": {

@@ -6,0 +6,0 @@ "name": "Jay Sylvester",

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc