{"id":20471348,"url":"https://github.com/statewalker/statewalker-utils","last_synced_at":"2026-06-05T17:32:06.444Z","repository":{"id":57863630,"uuid":"528057702","full_name":"statewalker/statewalker-utils","owner":"statewalker","description":null,"archived":false,"fork":false,"pushed_at":"2022-11-17T21:40:23.000Z","size":32,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-20T07:51:16.674Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/statewalker.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-08-23T15:41:45.000Z","updated_at":"2022-08-23T15:43:23.000Z","dependencies_parsed_at":"2023-01-22T17:15:19.715Z","dependency_job_id":null,"html_url":"https://github.com/statewalker/statewalker-utils","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statewalker%2Fstatewalker-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statewalker%2Fstatewalker-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statewalker%2Fstatewalker-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statewalker%2Fstatewalker-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/statewalker","download_url":"https://codeload.github.com/statewalker/statewalker-utils/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242038952,"owners_count":20061921,"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-11-15T14:15:51.990Z","updated_at":"2025-03-05T13:42:19.216Z","avatar_url":"https://github.com/statewalker.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @statewalker/utils: Common Utility Methods and Classes\n\nThis package contains the following classes and methods used in other packages:\n* `newEventEmitter` - basic event emitter with three methods: \"on\", \"off\", \"emit\"\n* `newMutex` - mutual exclusion function; used to avoid infinite recursive calls\n* `newRegestry` - registers methods to call at once; very useful when it is required to cleanup resources\n* `iterate` - async iterator properly managing backpressure \n* `newUpdatesTracker` - tracking list changes and executing `enter`/`exit`/`update` operations on each item\n\n## newEventEmitter()\n\n```javascript\n  const emitter = newEventEmitter();\n\n  // Register a handler\n  const handler = (ev) =\u003e console.log(handler);\n  const unreg = emitter.on(\"hello\", handler);\n\n  // This method will print \"Hello World!\" on console\n  emitter.emit(\"hello\", \"Hello World!\");\n  \n  // Removes the listener registration:\n  unreg();\n  // OR: emitter.off(\"hello\", handler);\n\n  // Print nothing - there is no more listeners \n  emitter.emit(\"hello\", \"Hello World!\");\n\n```\n\n## newMutex()\n\nThis method is used to return a mutex function allowing to avoid infinite loops.\n\n```javascript\n  // Each store give access to underlying data.\n  // When the\n  function newStore(value) {\n    const events = newEventEmitter();\n    return {\n      get: () =\u003e value,\n      set: (v) =\u003e {\n        value = v;\n        events.emit(\"update\", value);\n      },\n      subscribe: (listener) =\u003e (events.on(\"update\", listener), listener(value))\n    };\n  }\n  // This method synchronizes values managed by stores\n  function sync(...stores) {\n    const [register, cleanup] = newRegistry();\n    const mutex = newMutex();\n    const notify = (value) =\u003e\n      mutex(() =\u003e stores.forEach((store) =\u003e store.set(value)));\n    mutex(() =\u003e stores.forEach((store) =\u003e register(store.subscribe(notify))));\n    return cleanup;\n  }\n\n  const storeOne = newStore();\n  const storeTwo = newStore();\n  const storeThree = newStore();\n\n  // List of all stores\n  const stores = [storeOne, storeTwo, storeThree];\n  // Synchronize stores\n  sync(...stores);\n\n  storeThree.set(\"ABC\");\n  // At this stage all stores return the \"ABC\"\n\n\n  storeOne.set(\"CDE\");\n  // All store values are \"CDE\";\n\n```\n\n## newRegistry()\n\nThis method allows to register multiple callbacks and call all of them later.\n\n\n```javascript\n  const [register, cleanup, unregister] = newRegistry();\n  register(() =\u003e console.log('One'));\n  const unreg = register(() =\u003e console.log('Two'));\n  register(() =\u003e console.log('Three'));\n\n// This method removes the specified actions to call\n  unreg();\n  \n// At this stage console will print the following messages:\n// - \"One\"\n// - \"Three\"\n  cleanup();\n\n```\n\n## iterate(init)\n\nThis method allows to define an async iterator using the following method provided in the activation callback:\n- next - returns the next value\n- complete - notifies about a successful completion of the iteration process\n- error - finalizes iterations with an error\n\n```javascript\n// In this example the provider and consumer are synchronized between them:\n// the provider sends a new value only after the previous one was sucecssfully consumed.\n// This iterator allows also to terminate the iteration by both sides - by consumer\n// as well as by provider.\nconst maxDelay = 1000;\nconst N = 100;\n\nconst it = iterate(({ next, complete, error }) =\u003e { \n  let stop = false;\n  (async () =\u003e {\n    // Async process providing new values\n    for (let i = 0; !stop \u0026\u0026 i \u003c N; i++) {\n       await new Promise(y =\u003e setTimeout(y, maxDelay * Math.random()));\n      // Awaits when the provided value is consumed\n      await next(`Hello - ${i}`)\n    }\n    await complete();\n  })();\n  // Finalizes iterations\n  return () =\u003e stop = true;\n})\n\n\n// Async consumption of provided messages.\nfor await (let message of it) {\n  console.log(message);\n  await new Promise(y =\u003e setTimeout(y, maxDelay * Math.random()));\n}\n```\n\n\n## newUpdatesTracker()\n\nThis method returns a function allowing to tracks data modifications.\n\nCallback methods used to notify about new elements (`onEnter`), about removed data values (`onExit`)\nand about updated elements (`onUpdate`):\n```javascript\n\n  // This method is called to notify about new elements in the data array.\n  // @params\n  // * value - the data value\n  // * key - the key of the data element; if the `getKey` function is not defined then it is the data object itself\n  // * index - index (position) of this data element in the list\n  // @return: an newly created object associated with the given data value\n  const onEnter = (value, key, index) =\u003e { ... return { value } }\n\n  // Callback method to call when a data was removed from the data list.\n  // @params\n  // * object - the object corresponding to the data\n  // * value - the data value\n  // * key - the key of the data element; if the `getKey` function is not defined then it is the data object itself\n  // * index - index (position) of this data element in the previous list, before removal\n  // @return: undefined; this method should return nothing \n  const onExit = (object, value, key, index) =\u003e { ... }\n\n  // Callback method to call when a data was updated.\n  // @params\n  // * object - previously returned object corresponding to the data with the same key\n  // * value - the data value in the list\n  // * prevValue - previous data value corresponding to the same key\n  // * key - the key of the data element; if the `getKey` function is not defined then it is the data object itself\n  // * index - index (position) of this data element in the list\n  // * prevIndex - previous index (position) of this data element in the list\n  // @return: an object associated with the updated data value; if this method returns nothing \n  // then the previous object is used\n  const onUpdate = (\n    object,\n    value, prevValue,\n    key,\n    index, prevIndex\n  ) =\u003e { ... return { value } }\n\n```\n\nTo get the unique identity of each data values the `getKey` method used:\n```javascript\n\n  // This method is used to detect the unique identifier of the object. If this method is not defined\n  // the the object itself is used as the key.\n  // @params\n  // * value - the data value in the data list\n  // * index - index (position) of the value in the list\n  const getKey = (value, index) =\u003e { return value; }\n```\n\nUpdate tracker initialization:\n```javascript\n\n  // Creates a new tracker object\n  let tracker = newUpdatesTracker({ onEnter, onExit, onUpdate, getKey });\n\n  // Alternatively, the same configuration can be done like that:\n  tracker = newUpdatesTracker()\n    .key(getKey)\n    .update(onUpdate)\n    .exit(onExit)\n    .enter(onEnter);\n\n```\n\nExample of usage of these methods:\n```javascript\n\n  const container = document.body;\n\n  // Creates a new tracker object\n  let tracker = newUpdatesTracker()\n    .key((d) =\u003e d.id)\n    .enter((d, key, idx) =\u003e {\n      const div = document.createElement(\"div\");\n      container.appendChild(div);\n      div.innerText = d.content;\n      return div;\n    })\n    .update((div, d, oldD, key, idx, prevIdx) =\u003e {\n      div.innerText = d.content;\n      container.appendChild(div); // Move the element to the new position\n      return div;\n    })\n    .exit((div, d, key, idx) =\u003e {\n      div.parentElement.removeChild(div);\n    });\n  \n  // Set the initial values:\n  tracker([\n    { id : 1, content : \"Hello\" },\n    { id : 2, content : \"Wonderful\" },\n    { id : 3, content : \"World\" },\n  ]);\n\n\n  // Update data:\n  tracker([\n    { id : 1, content : \"Hello\" },\n    { id : 2, content : \"Beautiful\" }, // Only this value is updated\n    { id : 3, content : \"World\" },\n  ]);\n\n  // The second data update:\n  // - id=0: insert a new line (\"New information\")\n  // - id=1: no changes (\"Hello\")\n  // - id=2: diseapears (\"Beautiful\")\n  // - id=5: insert (\"John\")\n  // - id=3: updated (\"World\" =\u003e \"Smith\")\n  tracker([\n    { id : 0, content : \"New information\" },\n    { id : 1, content : \"Hello\" },\n    { id : 5, content : \"John\" },\n    { id : 3, content : \"Smith\" },\n  ]);\n\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstatewalker%2Fstatewalker-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstatewalker%2Fstatewalker-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstatewalker%2Fstatewalker-utils/lists"}