A ratelimiter that can transmit states of different entities while avoiding transmitting the same state twice, and adhering to a global speed limit.
MIT License
Ratestate is a ratelimiter in the form of a Node.js module that can transmit states of different entities while avoiding transmitting the same state twice, and adhering to a global speed limit.
Let's say you purchased some intelligent lightbulbs and want to set new colors in near-realtime (e.g. based on color detection of camera input), however the central hub receiving the color commands has a rate limiter that only accepts 30 updates per second. Ratestate can help you spread & drip updates amongst the different lightbulbs, without forming queues (by forgetting about superseded colors).
npm install --save ratestate
Here's a little CoffeeScript example
ratestate = new Ratestate
interval: 30
worker : (id, state, cb) ->
# Transmit the state to id
cb null
ratestate.start()
ratestate.setState 1, color: "purple"
ratestate.setState 1, color: "green"
ratestate.setState 1, color: "yellow"
ratestate.setState 1, color: "yellow"
ratestate.setState 1, color: "yellow"
ratestate.setState 1, color: "green"
ratestate.stop()
In this example, entity 1
will reach "green"
and probably won't be set to any other intermediate state (color in this case), as we're setting the state much faster than our configured interval
could keep up with.
Ratestate is similar to Underscore's debounce, but it runs indefintely and assumes you want to update the state of different entities, but for all entities you are globally speed limited. For instance you might want to
.json
files on S3, but your server/network only allows a few updates per second. The part of the program that sets the updates, should fire & forget, and not concern itself with environmental constraints like that.You can call setState
as much as you'd like, and Ratestate will
interval
msworker
if the state has not changedBy default, Ratestate detects if a state has changed by comparing hashes of set state
objects and it won't consider executing the worker
on entity states that have not changed.
If this built-in serializing & hashing is too heavy for your usecase (your states are huge - your interval low), you can supply your own function that will be executed on the state
object to determine its uniqueness. In the following example we'll supply our own hashFunc
to determine if the state is a candidate for passing to the worker
.
megabyte = 1024 * 1024 * 1024
status =
id : "foo-id"
status : "UPLOADING"
bytes_received: 2073741824
client_agent : "Mozilla/5.0 (Windows NT 6.0; rv:34.0) Gecko/20100101 Firefox/34.0"
client_ip : "123.123.123.123"
uploads : [
name: "tesla.jpg"
]
results: [
original:
name: "tesla.jpg"
,
resized:
name: "tesla-100px.jpg"
]
ratestate = new Ratestate
hashFunc: (state) ->
return [
state.status
state.bytes_received - (state.bytes_received % megabyte)
state.uploads.length
state.results.length
].join "-"
ratestate.start()
ratestate.setState "foo-id", status
ratestate.stop()
This would internally be 'hashed' as UPLOADING-653908770816-1-2
, if we detect a change in our system and blindly call setState
for our entity, this only executes the worker
on it if
status
has changed, ORbytes_received
, ORuploads
changed, ORresults
changedAs that covers all the interesting changes for us, it's more efficient than serializing and hashing an entire object.
finalState
is much like setState
(it's called under the hood), but requires a callback, which is called after the worker
successfully finished on it. Additionally, all data of the involved entity are removed from ratestate.
start
, so that intervals are ignored if we don't have a previous state on the entity yet.entityStateCallback
finalState
setState
. Useful for setting the last state of an entity. Otherwise: not recommended as there's no guarantee your callback
will be fired for anything other than the last write.@_desiredStates
bookkeeping after worker executed on it without errorThis project is written in CoffeeScript, and the JavaScript it generates is written to ./lib
. This is only used so that people can use this node module without a CoffeeScript dependency. If you want to work on the source, please do so in ./src
and type: make build
or make test
(also builds first). Please don't edit generated JavaScript in ./lib
!
I'd be happy to accept issues and pull requests. If you plan on working on something big, please first give a shout.
Run tests via make test
.
To single out a test use make test GREP=foobar
Releasing a new version to https://www.npmjs.com/ can be done via make release-patch
(or minor
/ major
, depending on the semantic versioning impact of your changes). This:
package.json
This project received invaluable contributions from:
Like this project? Consider a donation. You'd be surprised how rewarding it is for me see someone spend actual money on these efforts, even if just $1.