Permalink
Browse files

Version 0.0.1

  • Loading branch information...
0 parents commit ee882afe346a432b16232b72baef7e55028adef3 @Gozala committed Nov 4, 2012
Showing with 346 additions and 0 deletions.
  1. +5 −0 .travis.yml
  2. +5 −0 History.md
  3. +18 −0 License.md
  4. +14 −0 Readme.md
  5. +144 −0 event.js
  6. +4 −0 index.js
  7. +41 −0 package.json
  8. +6 −0 send.js
  9. +109 −0 test/index.js
5 .travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+ - 0.4
+ - 0.5
+ - 0.6
5 History.md
@@ -0,0 +1,5 @@
+# Changes
+
+## 0.0.1 / 2012-11-03
+
+ - Initial release
18 License.md
@@ -0,0 +1,18 @@
+Copyright 2012 Irakli Gozalishvili. All rights reserved.
+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.
14 Readme.md
@@ -0,0 +1,14 @@
+# event
+
+[![Build Status](https://secure.travis-ci.org/Gozala/event.png)](http://travis-ci.org/Gozala/event)
+
+Functional reactive style events. FRP comes down to one simple idea:
+some values change over time, these time-varying values are usually called
+`signals`. Signals can be expressed in terms `event`-s - a time-varying stream
+of values that can occur at arbitrary times. These library provides an APIs
+for making such event stream and for emitting new values in an imperative
+style.
+
+## Install
+
+ npm install event
144 event.js
@@ -0,0 +1,144 @@
+"use strict";
+
+var send = require("./send")
+var watch = require("watchables/watch")
+var unwatch = require("watchables/unwatch")
+
+// `Event` is data type representing a stream of values that can be dispatched
+// manually in an imperative style by calling `send(event, value)`
+function Event() {}
+
+// `Event` type has internal property of for aggregating `watchers`. This
+// property has a unique name and is intentionally made non-enumerable (in
+// a future it will be a private names
+// http://wiki.ecmascript.org/doku.php?id=harmony:private_name_objects) so
+// that it's behavior can not be tempered.
+var observers = "watchers@" + module.id
+Object.defineProperty(Event.prototype, observers, {
+ value: void(0), enumerable: false, configurable: false, writable: true
+})
+
+// ## Watchable
+//
+// Type implements subset of `Watchable` abstraction to allow registration
+// and un-registration of watchers that would wish to be notified on once
+// new value are dispatched. Type intentionally does not implements `watchers`
+// method so that inconsistent event dispatch could not be emulated.
+
+// ### watch
+//
+// `Event` type implements `watch` as a primary mechanism for subscribing to a
+// new dispatched values of the given instance. `watcher` must be a function.
+watch.define(Event, function watchEvent(event, watcher) {
+ var watchers = event[observers]
+ // Event type optimizes for a case with a single `watcher` case as it's a
+ // most common case.
+ switch (typeof(watchers)) {
+ // If there is no watchers yet `watcher` is stored directly without
+ // creation of an array.
+ case "undefined":
+ event[observers] = watcher
+ return void(0)
+ // If type is a `function` then `event` already has a `watcher`, in such
+ // case array of pre-existing and a new watcher is created, unless
+ // pre-existing watcher is this one (in such case do nothing to avoid
+ // double notifications of a same watcher).
+ case "function":
+ if (watchers !== watcher) event[observers] = [watchers, watcher]
+ return void(0)
+ // Otherwise it's an array and a `watcher` is pushed into it was already
+ // in it.
+ default:
+ if (watchers.indexOf(watcher) < 0) watchers.push(watcher)
+ return void(0)
+ }
+})
+
+// ### unwatch
+//
+// `Event` type implements `unwatch` function that can be used to unsubscribe
+// a `watcher` from the new values for the given `event`.
+unwatch.define(Event, function unwatchEvent(event, watcher) {
+ var watchers = event[observers]
+ // Optimize for a case when it's an only `watcher`.
+ if (watchers === watcher) {
+ event[observers] = void(0)
+ return void(0)
+ }
+
+
+ switch (typeof(watchers)) {
+ // If `event` has no `watchers` ignore.
+ case "undefined": return void(0)
+ // If `event` has an only watcher different from given one (it's different
+ // since other case was handled in `if` clause already) ignore.
+ case "function": return void(0)
+ // Otherwise `event` has multiple `watchers`, if given `watcher` is one
+ // of them remove it from the `watchers` array.
+ default:
+ var index = watchers.indexOf(watcher)
+ if (index >= 0) watchers.splice(index, 1)
+ // If only single watcher is left set it as internal `watchers` property
+ // to optimize a dispatch by avoiding slicing arrays and enumerations.
+ if (watchers.length === 1) event[observers] = watchers[0]
+ return void(0)
+ }
+})
+
+// ## send
+//
+// `Event` type implements `send` as a primary mechanism for dispatching new
+// values of the given `event`. All of the `watchers` of the `event` will
+// be invoked in FIFO order. Any new `watchers` added in side effect to this
+// call will not be invoked until next `send`. Note at this point `send` will
+// return `false` if no watchers have being invoked and will return `true`
+// otherwise, although this implementation detail is not guaranteed and may
+// change in a future.
+send.define(Event, function sendEvent(event, value) {
+ var watchers = event[observers]
+ switch (typeof(watchers)) {
+ // If there are no watchers return `false`
+ case "undefined":
+ return false
+ // If event has only `watcher` invoke it and return `true`
+ case "function":
+ watchers(value)
+ return true
+ // Otherwise slice array of watchers (this will guarantee that `unwatch`
+ // and `watch` calls in side effect to the dispatch will not break FIFO
+ // dispatch order) and invoke each one with a value. Return `true` as
+ // result.
+ default:
+ watchers = watchers.slice()
+ var index = 0
+ var count = watchers.length
+ while (index < count) {
+ watchers[index](value)
+ index = index + 1
+ }
+ return true
+ }
+})
+
+function event() {
+ /**
+ Function creates new `Event` that can be `watched` for a new values `send`-ed
+ on it. Also `send` function can be used on returned instance to send new
+ values.
+
+ ## Example
+
+ var e = event()
+
+ send(e, 0)
+
+ watch(e, consolel.log.bind(console, "=>"))
+
+ send(e, 1) // => 1
+ send(e, 2) // => 2
+ **/
+ return new Event()
+}
+event.type = Event
+
+module.exports = event
4 index.js
@@ -0,0 +1,4 @@
+"use strict";
+
+exports.send = require("./send")
+exports.event = require("./event")
41 package.json
@@ -0,0 +1,41 @@
+{
+ "name": "event",
+ "id": "event",
+ "version": "0.0.1",
+ "description": "Functional reactive style events",
+ "keywords": [
+ "event"
+ ],
+ "author": "Irakli Gozalishvili <rfobic@gmail.com> (http://jeditoolkit.com)",
+ "homepage": "https://github.com/Gozala/event",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/Gozala/event.git",
+ "web": "https://github.com/Gozala/event"
+ },
+ "bugs": {
+ "url": "http://github.com/Gozala/event/issues/"
+ },
+ "devDependencies": {
+ "test": "~0.x.0",
+ "phantomify": "~0.x.0",
+ "repl-utils": "~1.0.0"
+ },
+ "main": "./index.js",
+ "scripts": {
+ "repl": "node node_modules/repl-utils",
+ "test": "npm run test-node && npm run test-browser",
+ "test-browser": "node ./node_modules/phantomify/bin/cmd.js ./test/index.js",
+ "test-node": "node ./test/index.js"
+ },
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "https://github.com/Gozala/event/License.md"
+ }
+ ],
+ "dependencies": {
+ "method": "~0.1.1",
+ "watchables": "0.0.3"
+ }
+}
6 send.js
@@ -0,0 +1,6 @@
+"use strict";
+
+var method = require("method")
+var send = method()
+
+module.exports = send
109 test/index.js
@@ -0,0 +1,109 @@
+"use strict";
+
+var send = require("../send")
+var event = require("../event")
+var watch = require("watchables/watch")
+var unwatch = require("watchables/unwatch")
+
+exports["test watch event"] = function(assert) {
+ var e = event()
+
+ var actual = []
+
+ watch(e, function(data) { actual.push(data) })
+
+ send(e, "hello", "a", "b")
+ send(e, "world")
+
+ assert.deepEqual(actual, ["hello", "world"], "two values send to an event")
+}
+
+exports["test send on unwatched event"] = function(assert) {
+ var actual = []
+ var e = event()
+
+ send(e, "hello", "a", "b")
+
+ watch(e, function(data) { actual.push(data) })
+
+ send(e, "world")
+
+ assert.deepEqual(actual, ["world"], "got only value dispatched after watch")
+}
+
+exports["test FIFO dispatch on event"] = function(assert) {
+ var actual = []
+ var e = event()
+
+ function watcherSideEffect1(value) {
+ actual.push(value + "#@")
+ unwatch(e, watcher2)
+ unwatch(e, watcherSideEffect1)
+ watch(e, watcher3)
+ }
+ function watcher1(value) { actual.push(value + "#1") }
+ function watcher2(value) { actual.push(value + "#2") }
+ function watcher3(value) { actual.push(value + "#3") }
+
+ watch(e, watcher1)
+ send(e, "a")
+ watch(e, watcherSideEffect1)
+ watch(e, watcher2)
+ send(e, "b")
+ send(e, "c")
+ send(e, "d")
+
+ assert.deepEqual(actual, [
+ "a#1", "b#1", "b#@", "b#2", "c#1", "c#3", "d#1", "d#3"
+ ], "events are dispatched in FIFO order")
+}
+
+exports["test start watch several times"] = function(assert) {
+ var e = event()
+ var actual = []
+ function watcher1(value) { actual.push(value + "#1") }
+ function watcher2(value) { actual.push(value + "#2") }
+
+ watch(e, watcher1)
+
+ send(e, "a")
+
+ watch(e, watcher1)
+
+ send(e, "b")
+
+ watch(e, watcher2)
+ watch(e, watcher1)
+ unwatch(e, function() {})
+
+ send(e, "c")
+
+ unwatch(e, watcher1)
+
+ send(e, "d")
+
+ watch(e, watcher2)
+
+ send(e, "e")
+
+ watch(e, watcher1)
+
+ send(e, "f")
+
+ watch(e, watcher1)
+
+ send(e, "g")
+
+ unwatch(e, watcher2)
+ unwatch(e, watcher2)
+ unwatch(e, watcher1)
+ unwatch(e, watcher1)
+
+ send(e, "h")
+
+ assert.deepEqual(actual, [
+ "a#1", "b#1", "c#1", "c#2", "d#2", "e#2", "f#2", "f#1", "g#2", "g#1"
+ ], "subsequent watch & unwatches are ignored")
+}
+
+require("test").run(exports)

0 comments on commit ee882af

Please sign in to comment.