Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

express-state

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

express-state - npm Package Compare versions

Comparing version 0.0.4 to 1.0.0

6

examples/basic/server.js

@@ -7,2 +7,6 @@ var express = require('express'),

// Extend Express app with Express State's functionality. This adds the
// `app.expose()` and `res.expose()` methods.
state.extend(app);
app.engine('hbs', exphbs());

@@ -34,4 +38,4 @@ app.set('view engine', 'hbs');

app.expose('MY APP', 'title', 'app');
app.expose('MY APP', 'title');
app.listen(3000);
Express State Change History
============================
0.4.0 (2013-07-29)
1.0.0 (2013-08-15)
------------------
* __[!]__ Changed how this package extends Express. There's now _only_ one way —
to explicitly pass an Express app instance to the `extend()` method:
```javascript
var express = require('express'),
expstate = require('express-state'),
app = express();
// Extend the Express app with Express State's functionality.
expstate.extend(app);
```
This new `extend()` implementation uses the
[object branding technique](https://gist.github.com/ericf/6133744). ([#6][])
* A `TypeError` is now thrown when trying to serialize native build-in
functions. This matches the behavior of `JSON.stringify()` with circular
references. ([#7][])
* Added documentation in README.md. ([#2][])
[#2]: https://github.com/yahoo/express-state/issues/2
[#6]: https://github.com/yahoo/express-state/issues/6
[#7]: https://github.com/yahoo/express-state/issues/7
0.0.4 (2013-07-29)
------------------
* Added `extend()` function which takes an `express` module instance and adds
the `expose()` method to its application and response prototype, if it does
not already exist.
the `expose()` method to its application and response prototypes, if it does
not already exist. ([#5][])
* Added `augment()` function which takes an Express app instance and adds the
`expose()` method to it application and its response prototype, if it does not
already exist.
`expose()` method to it and its response prototype, if it does not already
exist. ([#5][])
[#5]: https://github.com/yahoo/express-state/issues/5
0.0.3 (2013-06-08)

@@ -20,3 +54,3 @@ ------------------

* Prevented multiple copies of `express-state` from overwriting `expose()` when
it already has been plugged into a copy of `express`.
it already has been plugged into a copy of `express`. ([#4][])

@@ -26,2 +60,5 @@ * Added Screwdriver CI integration.

[#4]: https://github.com/yahoo/express-state/issues/4
0.0.2 (2013-05-03)

@@ -28,0 +65,0 @@ ------------------

46

index.js
'use strict';
var express = require('express'),
Exposed = require('./lib/exposed');
var Exposed = require('./lib/exposed');
exports.local = 'state';
exports.namespace = null;
exports.extend = extendApp;
exports.augment = augment;
exports.extend = extend;
function extendApp(app) {
if (app['@state']) { return app; }
extend(express);
// Brand.
Object.defineProperty(app, '@state', {value: true});
function extend(express) {
var appProto = express.application,
resProto = express.response;
// Protect against multiple express-state module instances augmenting the
// Express `application` and `response` prototypes.
if (typeof appProto.expose === 'function' &&
typeof resProto.expose === 'function') {
return;
}
// Modifies Express' `application` and `response` prototypes by adding the
// Modifies the Express `app` and its `response` prototype by adding the
// `expose()` method.
resProto.expose = appProto.expose = expose;
}
app.expose = expose;
app.response.expose = expose;
function augment(app) {
var resProto = app.response;
// Protect against multiple express-state module instances augmenting the
// Express `app` and its `response` prototypes.
if (typeof app.expose === 'function' &&
typeof resProto.expose === 'function') {
return;
}
// Modifies the Express `app` and its `response` prototype by adding the
// `expose()` method.
resProto.expose = app.expose = expose;
return app;
}

@@ -62,3 +38,3 @@

if(!Exposed.isExposed(exposed)) {
if (!Exposed.isExposed(exposed)) {
// Creates a new `Exposed` instance, and links its prototype to the

@@ -65,0 +41,0 @@ // corresponding app exposed object, if one exists.

@@ -8,6 +8,11 @@ 'use strict';

function Exposed() {
// Defines a "hidden" property which holds an ordered list of exposed
// namespaces. When new namespaces are exposed, existing ones are examined
// and removed if they are to become noops.
Object.defineProperty(this, '__namespaces__', {value: []});
Object.defineProperties(this, {
// Brand.
'@exposed': {value: true},
// Defines a "hidden" property which holds an ordered list of exposed
// namespaces. When new namespaces are exposed, existing ones are
// examined and removed if they are to become noops.
'__namespaces__': {value: []}
});
}

@@ -32,6 +37,7 @@

Exposed.isExposed = function (obj) {
// Quack!
return !!(obj && Array.isArray(obj.__namespaces__));
return !!(obj && obj['@exposed']);
};
// TODO: Should this be a static method so it doesn't reserve the "add"
// namespace on all Exposed instances?
Exposed.prototype.add = function (namespace, value) {

@@ -38,0 +44,0 @@ var nsRegex = new RegExp('^' + namespace + '(?:$|\\..+)'),

@@ -5,3 +5,4 @@ 'use strict';

var PLACE_HOLDER_REGEX = /"@__(FUNCTION|REGEXP)_(\d+)__@"/g;
var IS_NATIVE_CODE_REGEX = /\{\s*\[native code\]\s*\}/g,
PLACE_HOLDER_REGEX = /"@__(FUNCTION|REGEXP)_(\d+)__@"/g;

@@ -38,9 +39,15 @@ function serialize(obj) {

return str.replace(PLACE_HOLDER_REGEX, function (match, type, index) {
switch (type) {
case 'FUNCTION':
return functions[index].toString();
case 'REGEXP':
if (type === 'REGEXP') {
return regexps[index].toString();
}
var fn = functions[index],
serializedFn = fn.toString();
if (IS_NATIVE_CODE_REGEX.test(serializedFn)) {
throw new TypeError('Serializing native function: ' + fn.name);
}
return serializedFn;
});
}
{
"name" : "express-state",
"description": "Share server-side state with the client-side of an Express app via JavaScript.",
"version" : "0.0.4",
"version" : "1.0.0",
"homepage" : "https://github.com/yahoo/express-state",
"keywords": [
"express", "state", "client", "expose", "data", "config", "javascript", "model", "json"
"express", "state", "client", "expose", "data",
"config", "configuration", "javascript", "model", "json"
],

@@ -10,0 +11,0 @@

Express State
=============
Share server-side state with the client-side of an [Express][] app via
JavaScript.
[![Build Status](https://travis-ci.org/yahoo/express-state.png?branch=master)](https://travis-ci.org/yahoo/express-state)
[![Dependency Status](https://gemnasium.com/yahoo/express-state.png)](https://gemnasium.com/yahoo/express-state)
[![npm Version](https://badge.fury.io/js/express-state.png)](https://npmjs.org/package/express-state)
[![Build Status](https://travis-ci.org/yahoo/express-state.png?branch=master)][Build Status]
Share configuration and state data of an [Express][] app with the client-side
via JavaScript.
[Express]: https://github.com/visionmedia/express
[Build Status]: https://travis-ci.org/yahoo/express-state
Goals & Design
--------------
Goals, Overview & Features
--------------------------
Express State is designed to make it easy to share configuration and state data
from the server to the client. It can be used to share any data that needs to be
available to the client-side JavaScript code of the an app; e.g., the current
user, a CSRF token, model data, routes, etc.
Progressively enhanced web apps can be built by rendering an app's initial state
on the server and using Express State as the conduit through which the server
passes data and control over to the client-side JavaScript code.
### Overview
Configuration and state data is exposed to client-side JavaScript via two
methods: `app.expose()` and `res.expose()`, both of which make the data
available on a special `state` "locals" object for views/templates to serialize
and embed into HTML pages.
When Views/templates embed this exposed data into an HTML page it is serialized
as literal JavaScript. The JavaScript serialization format is limited to
expressions which initialize namespaces and the exposed data assigned to those
namespaces, which is a superset of JSON that includes regexps and functions.
### Features
Express State was written because of shortcomings with [express-expose][]. The
following is a list features which highlight differences when compared with
express-expose:
- **Uses an efficient and powerful serialization format:**
Literal JavaScript is used to namespace exposed data which is a superset of
JSON and include regexps and functions. This avoids the cost of allocating and
parsing large JSON strings on the client, and enables things like sharing
routes defined as regexps with a client-side URL router.
- **Smart namespacing:**
A root namespace can be set via an app's `state namespace` setting and it will
be prepended to namespaces passed to `expose()` unless they already contain it
or they start with `"window."`. The "global" on to which the namespaces are
created can also be controlled.
- **Precise data value overrides:**
Sub-values within exposed objects can be easily overridden without clobbering
the entire object. Request scoped values can even override data exposed at the
app's scope.
- **Lazy serialization:**
Exposed data objects are stored by reference, making them "live" and allowing
their values to be updated even after the object has been exposed. Only the
namespaces and data which are still reachable after the series of `expose()`
calls will be serialized. Serialization can happen at anytime, on demand, by
calling the `toString()` method on `state` "locals" objects.
- **Explicit extension of each Express app:** Express State's functionality has
to be explicitly added to an Express app via the exported `extend()` function.
This prevents problems in complex apps where multiple versions of Express
and/or multiple Express apps are used.
[app.locals]: http://expressjs.com/api.html#app.locals
[res.locals]: http://expressjs.com/api.html#res.locals
[express-expose]: https://github.com/visionmedia/express-expose
Installation

@@ -31,7 +93,374 @@ ------------

### Extending an Express App
To use Express State with an Express app, the app must first be extended. Use
the `extend()` method that Express State exports:
```javascript
var express = require('express'),
expstate = require('express-state'),
app = express();
expstate.extend(app);
```
Once extended, the app will have the `app.expose()` method, and response objects
will the `res.expose()` method.
**Note:** It's perfectly fine for the same Express app to be extended more than
once, after the first time the app is extended the subsequent `extend()` calls
will be noops.
### Exposing Data
Data can be exposed at two different scopes: the app's scope, and a
request/response's scope via `app.expose()` and `res.expose()` respectively.
Express State uses Express' built-in "locals" system. When data is exposed at
the app's scope a special `app.locals.state` object is created and used as the
backing store for all `app.expose()` calls. Express also merges `app.locals`
with `res.locals` to create the context object in which views/templates are
rendered. This means that, by default, data exposed at the app's scope will also
be present when rendering views/templates for _all_ requests.
Express State sets up a similar relationship using prototypal inheritence where
`res.locals.state` inherits from `app.locals.state`. This means data exposed at
the request scope will also contain exposed data from the app's scope. If values
for the same namespace are exposed at both scopes, the request/response scope
takes precedence and shadows the value at the app's scope.
#### Exposing App Scoped Data
When data which needs to be exposed to the client-side JavaScript code is _not_
request-specific and should be available to all requests, it should be exposed
at the app's scope using __`app.expose()`__.
The following example exposes a Flickr API key required by Flickr to identify
requests:
```javascript
app.expose({
api_key: '02348notreal2394879137872358bla'
}, 'MY_APP.Flickr');
```
The client-side JavaScript code can now lookup the Flickr API key at
`MY_APP.Flickr.api_key` when it needs to make a request to Flickr's API.
#### Exposing Request Scoped Data
When data which needs to be exposed to the client-side JavaScript _is_
request-specific, it should be exposed at the request/response's scope using
__`res.expose()`__.
The following example shows how to create a middleware function to expose the
current person's Cross Site Request Forgery (CSRF) token — this is a best
practice where the CSRF is used to validate HTTP requests which mutate state:
```javascript
// Add Express' packaged `cookieParser()`, `session()`, and `csrf()` middleware.
app.use(express.cookieParser());
app.use(express.session({secret: 'something secure, not this!'}));
app.use(express.csrf());
// Create a middleware function that will expose the CSRF token for the current
// request only.
app.use(function (req, res, next) {
res.expose(req.session._csrf, 'MY_APP.CSRF_TOKEN');
next();
});
```
The client-side JavaScript code can now add the `X-CSRF-Token` HTTP header with
the value at `MY_APP.CSRF_TOKEN` to all XHRs it makes to the server.
### Setting a Root Namespace
A common practice is to set a root namespace for an app so all of its exposed
data is contained under one global variable in the client-side JavaScript code.
A root namespace can be setup for an app using the `state namesapce` setting:
```javascript
app.set('state namespace', 'MY_APP');
```
Now anytime data is exposed, the root namespace will be prepended unless it
already exists in the `namespace` passed into the `expose()` call or the
passed-in `namespace` starts with `"window."`.
With the above `"MY_APP"` root namespace, the following are all equivalent and
result in `MY_APP.foo === 123` in the client-side JavaScript:
```javascript
// These all have the same result on the client: `MY_APP.foo === 123`
app.expose(123, 'foo');
app.expose(123, 'MY_APP.foo');
app.expose(123, 'window.MY_APP.foo');
```
Setting a root namespace helps keep code DRY and configurable at the app level
while having the `"window."` escape hatch for data which needs to be exposed at
a specific namespace on the client.
### Overriding Exposed Values
Objects that are exposed through either `expose()` method are stored by
reference, and serialization is done lazily. This means the objects are still
"live" after they've been exposed. An object can be exposed early during the
lifecycle of a request, and updated up until the point the response is sent.
The following is a contrived example, but shows how values can overridden at
any time and at any scope:
```javascript
app.expose({root: '/'}, 'url');
app.use(function (req, res, next) {
res.expose(req.path, 'url.path');
res.expose(req.query, 'url.query');
next();
});
```
On the client, the resulting `url` object would look like the following for a
request to the URL `"/foo?bar=baz"`:
```javascript
{ root: '/',
path: '/foo',
query: { bar: 'baz' } }
```
Notice how exposing values at the `url.path` and `url.query` namespaces did
_not_ clobber the original `url` object exposed at the app's scope.
However, previously exposed data can be completely clobbered by simply exposing
a new value at the same namespace. When this happens, Express State is smart
enough to know it can release its references to the previous value objects and
not waste CPU and bytes serializing them.
### Serialization
Express State serializes exposed data to literal, executable JavaScript. The
JavaScript produced during serialization is limited to expressions which
initialize namespaces and the exposed data assigned to those namespaces, which
is a superset of JSON that includes regexps and functions.
JavaScript as the serialization format is more powerful and efficient than JSON.
It avoids the cost of allocating and parsing large JSON strings on the client,
and enables things like sharing routes defined as regexps with a client-side URL
router.
The special `app.locals.state` and `res.locals.state` objects contain a custom
`toString()` method implementation which serializes them to JavaScript that is
human readable and can be embedded inside a `<script>` element in an HTML page.
The following example shows a series of `expose()` calls, and the resulting
output from serialization:
```javascript
app.expose({bar: 'bar'}, 'foo');
app.expose(/baz/, 'foo.baz');
app.expose(function () { return 'bla'; }, 'a.very.big.ns');
// Seralize `app.locals.state` and log the result.
console.log(app.locals.state.toString());
```
The output of the `console.log()` call would be:
```javascript
(function (g) {
// -- Namespaces --
g.foo || (g.foo = {});
g.a || (g.a = {});
g.a.very || (g.a.very = {});
g.a.very.big || (g.a.very.big = {});
// -- Exposed --
g.foo = {"bar":"bar"};
g.foo.baz = /baz/;
g.a.very.big.ns = function () { return 'bla'; };
}(this));
```
**Note:** A `TypeError` will be thrown if a native built-in function is being
serialized, like the `Number` constructor. Native built-ins should be called in
wrapper functions, and the wrapper function can be serialized.
### Embedding Data in HTML with Templates
To pass along the exposed configuration and state data to the client-side
JavaScript code it needs to be embedded in the app's HTML pages inside a
`<script>` element.
In Express, `res.render()` is used to render a view/template and send the
response to the client. When rendering, Express sets up a context which is an
object resulting from merging `app.locals` with `res.locals`. This means the
special __`state`__ object is available to the views/templates.
The following example is a basic [Handlebars][] template that renders the
serialized `state` object:
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test App</title>
</head>
<body>
<h1>Test App</h1>
<script>
{{{state}}}
</script>
</body>
</html>
```
**Note:** In this example triple-mustaches (`{{{ }}}`) are used so that
Handlebars does _not_ HTML-escape the value. Handlebars will automatically call
`toString()` method on the special `state` object, which renders the JavaScript.
[Handlebars]: http://handlebarsjs.com/
Examples
--------
### [Basic Usage][]
A runnable example of the most basic Express app that uses Express State.
[Basic Usage]: https://github.com/yahoo/express-state/tree/master/examples/basic
API
---
### Configuration and Defaults
The following properties are exported from the Express State module. Assigning
values to these properties affects all Express apps extended with this Express
State module instance. To set these values for a specific app, use
[App Settings][].
#### `local = "state"`
A string property name on `app.locals` and `res.locals` where Express State
creates its special objects used to store and serialize exposed data.
By default, Express State will create these objects:
* `app.locals.state`
* `res.locals.state`
#### `namespace = null`
A string root namespace which should be prepended on the namespaces provided to
`app.expose()` and `res.expose()` method calls. By default, no root namespace is
used and namespaces are created directly on the global (`window`) object in the
browser.
See [Setting a Root Namespace][] for more details.
### App Settings
The following settings use the [Express Settings][] feature and only apply to
the app which they are `set()`. These app settings take precedence over Express
State's global configuration settings above.
#### `state local`
A string property name on `app.locals` and `res.locals` where Express State
creates its special objects used to store and serialize exposed data.
By default, no value is set, so Express State's exported `local` configuration
value is used.
The following example sets the locals properties to `app.locals.exposed` and
`res.locals.exposed`:
```javascript
app.set('state local', 'exposed');
```
#### `state namespace`
A string root namespace which should be prepended on the namespaces provided to
`app.expose()` and `res.expose()` method calls. By default, no root namespace is
used and namespaces are created directly on the global (`window`) object in the
browser.
The following example sets the root namespace to `"MY_APP"`:
```javascript
app.set('state namespace', 'MY_APP');
```
See [Setting a Root Namespace][] for more details.
### Static Methods
#### `extend (app)`
A function exported from the Express State module which extends the
functionality of the specified Express `app` by adding the two `expose()`
methods: `app.expose()` and `res.expose()`.
It's perfectly fine for the same Express app to be extended more than once,
after the first time the app is extended the subsequent `extend()` calls will
be noops.
**Parameters:**
* `app`: Express app instance to extend with Express State's functionality.
See [Extending an Express App][] for more details.
### Methods
#### `app.expose (obj, [namespace], [local])`
#### `res.expose (obj, [namespace], [local])`
The two `expose()` methods behave the same, the only difference is at what scope
the data is exposed, either the app's or at the request's scope.
These two methods are used to expose configuration and state to client-side
JavaScript by making the data available on a special `state` "locals" object for
views/templates to serialize and embed into HTML pages.
**Parameters:**
* `obj`: Any serializable JavaScript object which to expose to the client-side.
* `[namespace]`: Optional string namespace where the `obj` should be exposed.
This namespace will be prefixed with any configured root namespace unless it
already contains the root namespace or starts with `"window."`.
* `[local]`: Optional string name of the "locals" property on which to expose
the `obj`. This is used to specify a locals property other than the
configured or default (`"state"`) one.
**Note:** A `TypeError` will be thrown if a native built-in function is being
serialized, like the `Number` constructor. Native built-ins should be called in
wrapper functions, and the wrapper function can be serialized.
See [Exposing Data][] and [Overriding Exposed Values][] for more details.
[App Settings]: #app-settings
[Express Settings]: http://expressjs.com/api.html#app-settings
[Setting a Root Namespace]: #setting-a-root-namespace
[Extending an Express App]: #extending-an-express-app
[Exposing Data]: #exposing-data
[Overriding Exposed Values]: #overriding-exposed-values
License

@@ -43,2 +472,3 @@ -------

[LICENSE file]: https://git.corp.yahoo.com/modown/express-state/blob/master/LICENSE

@@ -28,12 +28,2 @@ /* global describe, it */

describe('.augment', function () {
it('should have a .augment property', function () {
expect(state).to.have.property('augment');
});
it('should respond to .augment()', function () {
expect(state).itself.to.respondTo('augment');
});
});
describe('.extend', function () {

@@ -47,3 +37,12 @@ it('should have a .extend property', function () {

});
it('should always return the Express app being extended', function () {
var app = {response: {}};
// Extended twice to make sure an already extended app is still
// returned.
expect(state.extend(app)).to.equal(app);
expect(state.extend(app)).to.equal(app);
});
});
});

@@ -76,7 +76,7 @@ /* global describe, it, beforeEach, afterEach */

var exposed = Object.create(null, {
'__namespaces__': {value: []}
'@exposed': {value: true}
});
expect(Exposed.isExposed(exposed)).to.equal(true);
expect(Exposed.isExposed({'__namespaces__': []})).to.equal(true);
expect(Exposed.isExposed({'@exposed': true})).to.equal(true);
});

@@ -94,3 +94,3 @@

expect(Exposed.isExposed([])).to.equal(false);
expect(Exposed.isExposed({'__namespaces__': true})).to.equal(false);
expect(Exposed.isExposed({'@exposed': false})).to.equal(false);
});

@@ -97,0 +97,0 @@ });

@@ -29,53 +29,9 @@ /* global describe, it, beforeEach, afterEach */

it('should not override `expose()` if it exists', function () {
var expose = function () {},
express, state;
express = require('express');
express.application.expose = express.response.expose = expose;
state = require('../');
expect(express.application.expose).to.equal(expose);
expect(express.response.expose).to.equal(expose);
});
describe('.extend( express )', function () {
describe('.extend( app )', function () {
it('should add `expose()`', function () {
var express = require('express'),
state = require('../');
mockery.registerMock('express', createExpressMock());
express = require('express');
expect(express.application.expose).to.be.undefined;
expect(express.response.expose).to.be.undefined;
state.extend(express);
expect(express.application).itself.to.respondTo('expose');
expect(express.response).itself.to.respondTo('expose');
});
it('should not override `expose()` if it exists', function () {
var expose = function () {},
express, state;
express = require('express');
state = require('../');
express.application.expose = express.response.expose = expose;
state.extend(express);
expect(express.application.expose).to.equal(expose);
expect(express.response.expose).to.equal(expose);
});
});
describe('.augment( app )', function () {
it('should add `expose()`', function () {
var express = require('express'),
state = require('../'),
app = {response: {}};
state.augment(app);
state.extend(app);

@@ -98,4 +54,10 @@ expect(app).itself.to.respondTo('expose');

state.augment(app);
// Extend, then override.
state.extend(app);
app.expose = expose;
app.response = {expose: expose};
// Try to extend again.
state.extend(app);
expect(app.expose).to.equal(expose);

@@ -102,0 +64,0 @@ expect(app.response.expose).to.equal(expose);

@@ -100,2 +100,9 @@ /* global describe, it, beforeEach */

});
it('should throw a TypeError when serializing native built-ins', function () {
var err;
expect(Number.toString()).to.equal('function Number() { [native code] }');
try { serialize(Number); } catch (e) { err = e; }
expect(err).to.be.an.instanceOf(TypeError);
});
});

@@ -102,0 +109,0 @@

@@ -14,3 +14,3 @@ /* global describe, it, beforeEach, afterEach */

beforeEach(function () {
app = express();
app = state.extend(express());
});

@@ -17,0 +17,0 @@

Sorry, the diff of this file is not supported yet

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