{"id":16594151,"url":"https://github.com/jsdf/flux-coffee","last_synced_at":"2025-10-27T19:04:15.845Z","repository":{"id":18197753,"uuid":"21325075","full_name":"jsdf/flux-coffee","owner":"jsdf","description":"Implements Facebook's Flux pattern in CoffeeScript","archived":false,"fork":false,"pushed_at":"2014-07-01T16:52:34.000Z","size":144,"stargazers_count":16,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-04-09T22:09:03.957Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"CoffeeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jsdf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-06-29T14:39:50.000Z","updated_at":"2022-04-07T08:45:06.000Z","dependencies_parsed_at":"2022-09-25T08:02:47.080Z","dependency_job_id":null,"html_url":"https://github.com/jsdf/flux-coffee","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsdf%2Fflux-coffee","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsdf%2Fflux-coffee/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsdf%2Fflux-coffee/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsdf%2Fflux-coffee/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jsdf","download_url":"https://codeload.github.com/jsdf/flux-coffee/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219857802,"owners_count":16556055,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T23:45:20.440Z","updated_at":"2025-10-27T19:04:10.791Z","avatar_url":"https://github.com/jsdf.png","language":"CoffeeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# flux-coffee\nThis is an implementation of Facebook's Flux pattern for managing and observing datastores in a\nclient side web application.\n\nYou can use it as part of your CommonJS-based projects using a bundler\nsuch as [browserify](https://github.com/substack/node-browserify), however this\nrepo is mainly intended as a demonstration of the pattern, which is fairly easy\nto implement yourself.\n\n## Implementing Flux in CoffeeScript\n\nThe starting point for this code was the Flux TodoMVC example, from which it has\nbeen adapted to take advantage of CoffeeScript's class metaprogramming/DSL\nsugar.\n\n## [dispatcher.coffee](dispatcher.coffee)\nThe Dispatcher in Flux is a singleton, against which the stores register handlers.\n\nI've chosen to use the Bluebird promise library, though any library or polyfill\nfor the standard would work.\n```coffee\n{Promise} = require 'bluebird'\n```\n\nDispatch handlers provided by stores are stored in an object, keyed by the store\nname, as are the promises or promise-resolving values returned by handlers\nduring a particular dispatch cycle.\n```coffee\n_handlers = {}\n_pendingDispatches = {}\n```\nThe dispatcher is implemented as a static class, with only class methods.\n```coffee\nclass Dispatcher\n```\n\nRather than registering a callback, as in the example dispatcher, store\nsingletons are registered against the dispatcher. The stores should have a 'name'\nand a 'receiveDispatch' method, which is used as the dispatch callback.\n```coffee\n  @register: (store) -\u003e\n    _handlers[store.name] = store.receiveDispatch.bind(store)\n```\n\nThe dispatch method accumulates the promises returned by the dispatch handlers\nin objects keyed by store name.\n```coffee\n  @dispatch: (payload) -\u003e\n    _pendingDispatches = {}\n    resolves = {}\n    rejects = {}\n\n    storeNames = Object.keys(_handlers)\n    for storeName in storeNames\n      _pendingDispatches[storeName] = new Promise (resolve, reject) -\u003e\n        resolves[storeName] = resolve\n        rejects[storeName] = reject\n\n    # dispatch to handlers and resolve/reject promises.\n    for storeName in storeNames\n      handleResolve = -\u003e resolves[storeName](payload)\n      handleReject = -\u003e rejects[storeName](new Error(\"Dispatcher handler unsuccessful\"))\n\n      Promise.resolve(_handlers[storeName](payload)).then(handleResolve, handleReject)\n\n    _pendingDispatches = {} # reset\n```\n\n`waitFor` is mostly the same as the example implementation, except it accepts\nan array of store objects as dependencies.\n```coffee\n  @waitFor: (dependencies, handler) -\u003e\n    selectedPromises = dependencies.map (dependency) -\u003e\n      _pendingDispatches[dependency.name] or throw new Error(\"Unknown waitFor dependency #{dependency.name}\")\n    Promise.all(selectedPromises).then(handler)\n```\n\n\n\n\n## [store.coffee](store.coffee)\n\nWe implement a base class for the stores, which can be watched for changes.\n```coffee\nextend = require 'xtend/mutable'\n\nCHANGE_EVENT = 'change'\n\nclass Store\n  extend(@, EventEmitter::)\n\n  @emitChange: -\u003e\n    @emit CHANGE_EVENT\n\n  @addChangeListener: (callback) -\u003e\n    @on CHANGE_EVENT, callback\n\n  @removeChangeListener: (callback) -\u003e\n    @removeListener CHANGE_EVENT, callback\n```\n\nTaking advantage of CoffeeScript's executable class bodies, a class method\n`@action` is provided for convenience when defining actions on Store subclasses.\n```coffee\n  @action: (name, handler) -\u003e\n    @actions ?= {}\n    @actions[name] = handler.bind(@)\n```\nThe `receiveDispatch` method is provided by the store to be bound by the\ndispatcher. It catches errors thrown in the handlers, returning `false` to cause\nthe corresponding dispatch promise for this store to reject. If the handler\nreturns a promise itself (eg. created by `waitFor`) it is passed on, otherwise\n`true` is returned to resolve the corresponding promise in the dispatcher.\n```coffee\n  @receiveDispatch: ({action} = payload) -\u003e\n    actionHandler = @actions[action.actionType]\n    if actionHandler?\n      try\n        result = actionHandler(action)\n      catch err\n        false # reject promise\n      finally\n        result or true # resolve with promise returned by handler (or true)\n    else\n      true # nothing to do for this store, resolve promise\n```\n\nNow we have our Dispatcher singleton and Store base class, we can implement\na concrete store with actions as an example of how these elements would be\ncombined in a real application. For this example we'll implement a store\nrepresenting a queue of notifications to be shown to the user.\n\n## [notification/store.coffee](example/notification/store.coffee)\nThe notification store makes use of the dispatcher, which it gains access to\nvia `require`, which returns a reference to the singleton.\n```coffee\n{after} = require 'method-combinators'\nfindIndex = require 'find-index'\n\nDispatcher = require 'flux-coffee/dispatcher'\nStore = require 'flux-coffee/store'\n```\n\nThe actual notification data is stored inside the module closure but outside of\nthe store singleton class so that it is not directly accessible by the rest of\nthe application, and must instead be accessed via actions and getters.\n```coffee\n_notificationQueue = []\n```\n\nThe concrete notification store subclasses `Store`, and provides a 'name'\nproperty, which is used to register it against the dispatcher.\n```coffee\nclass NotificationStore extends Store\n  @name: 'NotificationStore'\n```\n`after`, from @raganwald's\n[method-combinators](https://github.com/raganwald/method-combinators),\nis used to create a decorator for the actions which trigger a change event.\n\n```coffee\n  # combinator to emit change event after handler\n  @withChange: after =\u003e @emitChange()\n```\nThe `@action` class method is invoked in the executable class body to define\nactions with handlers for each of the actions the store can respond to.\n```coffee\n  @action 'NOTIFICATION_CREATE', @withChange (action) -\u003e\n    _notificationQueue.push action.notification\n\n  @action 'NOTIFICATION_DESTROY', @withChange (action) -\u003e\n    _notificationQueue.splice(findIndex(_notificationQueue, (item) -\u003e item.id is action.id)), 1)\n```\nAdditionally, a getter is provided to make relevant data accessible to the rest\nof the application.\n```coffee\n  @getCurrentNotification: -\u003e _notificationQueue[0]\n```\n\nFinally, the store is registered with the dispatcher, so that it can respond to\ndispatched actions.\n```coffee\nDispatcher.register NotificationStore\n```\nNote that this registration is done in the module which defines the store, so\nthe store's singleton object will come into existence the first time the module\nis required. The same goes for the dispatcher. In this way, Flux applications can be self-assembling, and no particular part of\nthe application needs to 'own' any instances of the objects.\n\nIn fact, multiple parts of the application can  share general-purpose stores,\nsuch as this example notification store, without even knowing that other parts\nof the application are making use of them. It doesn't make any difference which\npart of the application 'created' the store by using it first, and as the\napplication's structure changes over time, parts of the application can add or\nremove dependencies on particular stores without needing to rework any\nsetup/teardown code.\n\n## [notification/actions.coffee](example/notification/actions.coffee)\nIn addition to the store, a public API of semantic actions is provided for the\nrest of the application to use to manipulate the store.\n```coffee\nDispatcher = require 'flux-coffee/dispatcher'\n\nclass NotificationActions\n  @addNotification: (id, text) -\u003e\n    payload =\n      action:\n        actionType: 'NOTIFICATION_CREATE'\n        id: id\n        text: text\n    Dispatcher.dispatch(payload)\n\n  @removeNotification: (id) -\u003e\n    payload =\n      action:\n        actionType: 'NOTIFICATION_DESTROY'\n        id: id\n    Dispatcher.dispatch(payload)\n\nmodule.exports = NotificationActions\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsdf%2Fflux-coffee","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjsdf%2Fflux-coffee","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsdf%2Fflux-coffee/lists"}