{"id":19858129,"url":"https://github.com/brianneisler/stutter","last_synced_at":"2025-10-27T22:43:47.128Z","repository":{"id":7966740,"uuid":"56994834","full_name":"brianneisler/stutter","owner":"brianneisler","description":"Functional programming framework for Javascript/ES6 inspired by Clojure","archived":false,"fork":false,"pushed_at":"2023-01-07T04:18:56.000Z","size":2790,"stargazers_count":11,"open_issues_count":11,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-27T15:24:46.740Z","etag":null,"topics":["functional-programming","javascript"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brianneisler.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-04-24T21:18:27.000Z","updated_at":"2021-07-13T21:35:42.000Z","dependencies_parsed_at":"2023-01-13T14:35:01.877Z","dependency_job_id":null,"html_url":"https://github.com/brianneisler/stutter","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/brianneisler/stutter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianneisler%2Fstutter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianneisler%2Fstutter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianneisler%2Fstutter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianneisler%2Fstutter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brianneisler","download_url":"https://codeload.github.com/brianneisler/stutter/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianneisler%2Fstutter/sbom","scorecard":{"id":253360,"data":{"date":"2025-08-11","repo":{"name":"github.com/brianneisler/stutter","commit":"757f01c7e44eeeb6951bffdd671ef02f9eb376f0"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.6,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/22 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: npmCommand not pinned by hash: scripts/setup.sh:6","Info:   0 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 8 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"35 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp","Warn: Project is vulnerable to: GHSA-jf85-cpcp-j695","Warn: Project is vulnerable to: GHSA-fvqr-27wr-82fm","Warn: Project is vulnerable to: GHSA-4xc9-xhrj-v574","Warn: Project is vulnerable to: GHSA-x5rq-j2xg-h7qm","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-6vfc-qv3f-vr6c","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-r683-j2x4-v87g","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-jv35-xqg7-f92r","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-17T08:59:59.575Z","repository_id":7966740,"created_at":"2025-08-17T08:59:59.575Z","updated_at":"2025-08-17T08:59:59.575Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281355366,"owners_count":26486896,"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","status":"online","status_checked_at":"2025-10-27T02:00:05.855Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["functional-programming","javascript"],"created_at":"2024-11-12T14:21:33.726Z","updated_at":"2025-10-27T22:43:47.113Z","avatar_url":"https://github.com/brianneisler.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](docs/assets/stutter.png)\n\n[Website](https://stutter.io) • [Guide](docs/GUIDE.md) • [Full API documentation](docs/API.md)\n\n**PLEASE NOTE: THIS PROJECT IS STILL UNDER DEVELOPMENT**\n\n## Why?\n\nWe're big fans of both lodash and ramda. But after years of use there are\nparticular sets of problems that we wanted solutions to.\n- Autocurrying is a pitfall without helpful guiderails\n- Sometimes you want data first, sometimes you want function first. There's use\n  cases for both!\n- Why is support for Promises not built in? Do I really need yet another library\n  (`async`)?\n- How come I can't use these functions with generators?\n- Why do I need separate functions for sync and async?\n- When using curried functions, it's impossible to tell where in my code an\n  error really came from.\n- Why don't I get an meaningful error when I accidentally feed the wrong type to\n  a function?\n\n\n## Features\n\n### Supports sync, async AND generator functions\n\nWe believe you shouldn't have to use a different `map` function when you're\nmapping using an async function, generator function or a standard function. The\nmethod should be able to naturally upgrade to match the requirements based upon\nthe iteratee function that YOU give it. That's why our functions will support\nany of these options and return you the appropriate matching type. This makes\nstutter compatible for use with such libraries as [`redux-saga`](https://redux-saga.js.org/)\n\n**Standard function will run synchronously**\n```js\nimport { map } from 'stutter'\n\nmap([1, 2, 3], (value) =\u003e value + 1)\n//=\u003e [2, 3, 4]\n```\n\n**Async functions will upgrade and return a Promise**\n```js\nconst result = await map([1, 2, 3], async (value) =\u003e value + 1)\n//=\u003e [2, 3, 4]\n```\n\n**Generator functions will upgrade and return a Generator**\n```js\nconst result = yield* map([1, 2, 3], function* (value) {\n  return value + 1\n})\n//=\u003e [2, 3, 4]\n```\n\n\n### Data first AND function first\n\nStutter supports both data first AND function first (lodash style and ramda\nstyle). We're able to do this because stutter supports [multi-functions](#multi-functions).\nMulti-functions in stutter are matched based upon parameter types which means it\ncan tell when you've put the function first and when you've put the data first.\n\n**function first**\n```js\nimport { reduce } from 'stutter'\n\nreduce(\n  (accum, value) =\u003e accum + value,\n  0,\n  [1, 2, 3]\n)\n// =\u003e 6\n```\n\n**data first**\n```js\nimport { reduce } from 'stutter'\n\nreduce(\n  [1, 2, 3],\n  0,\n  (accum, value) =\u003e accum + value\n)\n// =\u003e 6\n```\n\n\n### Multi-functions\n\nStutter supports multi-functions with [type delcarations](#type-delcarations). This enables you to\ncreate a single function that will execute the correct implementation based on\nwhich arguments are given to it.\n\n```js\nimport { fn } from 'stutter'\n\nconst add = fn(\n  [String, Number],\n  (str, num) =\u003e parseInt(str) + num,\n\n  [Number, String],\n  (num, str) =\u003e num + parseInt(str),\n\n  [Number, Number],\n  (num1, num2) =\u003e num1 + num2\n)\n\nadd('9', 1)\n// =\u003e 10\n\nadd(9, '1')\n// =\u003e 10\n\nadd(9, 1)\n// =\u003e 10\n```\n\n### Type declarations\n\nStutter's methods are capable of accepting type defintitions so that it knows\nwhat kind of data a function is meant to handle. \nThis alerts you to problems when the wrong type of data is encountered or when a matching\n  function signature cannot be found.\n\n```js\nconst add = fn(\n  [Number, Number],\n  (num1, num2) =\u003e num1 + num2\n)\n\nadd(1, 2)\n// =\u003e 3\n\nadd('foo', 2)\n// Throws \"TypeError\"\n```\n\n\n### Auto curried with type hinting\n\nAll functions are automatically curried. However, we do this in conjunction with\ntype checking to help sort out issues where a curried function has received the\nwrong type.\nAutomatic currying also works for all forms of a multi-function, making it\npossible to curry mixed function signatures.\n\n```js\nimport Immutable from 'immutable'\nimport { fn } from 'stutter'\n\nconst concat = fn(\n  [Array, Array],\n  (arr1, arr2) =\u003e arr1.concat(arr2),\n\n  [Array, Immutable.List],\n  (arr, list) =\u003e arr.concat(list.toJs())\n)\n\nconst mistake = concat(null)\n//=\u003e Throws \"TypeError\"\n\nconst concatFoo = concat(['foo'])\n\nconcatFoo(['bar'])\n//=\u003e ['foo', 'bar']\n\nconcatFoo(Immutable.List(['bar']))\n//=\u003e ['foo', 'bar']\n\nconcatFoo(null)\n//=\u003e Throws \"TypeError\"\n```\n\n\n### All operations are Immutable\n\nAll operations perfomed in stutter are immutable. This makes your code much\neasier to reason about and prevents nasty bugs caused by mutability.\n\n```js\nimport { assoc } from 'stutter'\n\nconst obj1 = { a: 1, b: 2 }\nconst obj2 = assoc('c', 3, obj1)\n\nobj1\n//=\u003e { a: 1, b: 2 }\nobj2\n//=\u003e { a: 1, b: 2, c: 3 }\n\nconst map1 = new Map([['a', 1], ['b', 2]])\nconst map2 = assoc('c', 3, map1)\n\nmap1\n//=\u003e Map { a: 1, b: 2 }\nmap2\n//=\u003e Map { a: 1, b: 2, c: 3 }\n```\n\n\n### Supports Immutable.js and ES6 data types\n\nStutter is built on top of [Immutable.JS](https://facebook.github.io/immutable-js/) data\nstructures but supports both Immutable.JS data types and standard JS values\n(including ES6 Map, WeakMap, Set and WeakSet).\nThis makes it easy to handle mixed nested data types making it easier to process values of mixed Immutable.JS/mutable data objects\n\n```js\nimport { get } from 'stutter'\nimport Immutable from 'immutable'\n\n// Immutable.JS example\nconst map = Immutable.Map({ a: 1, b: 2 })\n\nget('b', map)  \n//=\u003e 2\n\n// Standard JS Object example\nconst obj = { a: 1, b: 2 }\n\nget('b', obj)    \n//=\u003e 2\n\n// Mixed Standard and Immutable values\nconst mixed = { \n  a: Immutable.Map({ \n    c: 3, \n    d: 4 \n  }), \n  b: 2 \n}\nget('a.c', mixed)\n//=\u003e 3\n```\n\n### Custom data types\n- supports custom data types for [Immutable.JS](https://facebook.github.io/immutable-js/)\n\n\n### Contagion support\n\nTypes are contagious across deep operations. When a deep operation is performed\non a value that generates a new value, that new value will be of the same or similar\n\"type\". For instance, if you do a deep `assocIn` to an `ImmutableMap` the newly\ngenerated nested object will be an ImmutableMap\n\n```js\nimport { assocIn } from 'stutter'\nimport Immutable from 'immutable'\n\nassocIn(Immutable.Map({}), ['foo', 'bar'], 'baz')\n//=\u003e ImmutableMap { foo : ImmutableMap { bar: 'baz' }}\n\nassocIn(Map(), ['foo', 'bar'], 'baz')\n//=\u003e Map { foo : Map { bar: 'baz' }}\n\nassocIn({}, ['foo', 'bar'], 'baz')\n//=\u003e Object { foo : Object { bar: 'baz' }}\n```\n\n\n### Protocols\n\nHaving a library with built in support for common data types like native JS\ntypes is great. However, there are times you want to extend the type of data\nthat a function can support without having to write your own and then replace all\nof the uses of that function. Using Protocols you can easily teach `stutter` how\nto deal with new types of data.\n\n**Teach stutter about a custom type and how to integrate with its `add` method**\n```js\nimport { add, deftype, fn } from 'stutter'\n\nclass Custom {\n  constructor(value) {\n    this.value = value\n  }\n  add(num) {\n    return new Custom(this.value + num)\n  }\n  toNumber() {\n    return this.value\n  }\n}\n\ndeftype('Custom', 'A custom addable type', {\n  class: Custom,\n  protocols: [\n    add.protocol,\n    {\n      add: fn(\n        [Self, Number, () =\u003e Self], \n        (self, num) =\u003e self.add(num),\n\n        [Number, Self, () =\u003e Number], \n        (num, self) =\u003e num + self.toNumber()\n      )\n    }\n  ]\n})\n```\n\n\n### Built in threading (piping) macros\n\n**Basic threading macro**\n```js\nimport { _, invert, set } from 'stutter'\n\nconst obj = { a: 1, b: 2 }\n\n_(obj, \n  set('c', 3),\n  invert) \n//=\u003e Object { 1: \"a\", 2: \"b\", 3: \"c\" }\n```\n\n**Reusable pipelines**\n```js\nimport { _, invert, set } from 'stutter'\n\nconst obj = { a: 1, b: 2 }\n\nconst pipeline = _\n  .set('c', 3)\n  .invert()\n\npipeline(obj)\n//=\u003e Object { 1: \"a\", 2: \"b\", 3: \"c\" }\n```\n\n**Pre-filled pipeline**\n```js\nimport { _, invert, set } from 'stutter'\n\nconst obj = { a: 1, b: 2 }\n\nconst prefilled = _(obj)\n\nprefilled(\n  set('c', 3), \n  invert) \n//=\u003e Object { 1: \"a\", 2: \"b\", 3: \"c\" }\n```\n\n\n## Project Status\n\n[![npm version](https://badge.fury.io/js/stutter.svg)](https://badge.fury.io/js/stutter)\u003cbr /\u003e\n[![Build Status](https://travis-ci.org/brianneisler/stutter.svg)](https://travis-ci.org/brianneisler/stutter)\u003cbr /\u003e\n[![NPM](https://nodei.co/npm/stutter.png?downloads=true\u0026downloadRank=true\u0026stars=true)](https://nodei.co/npm/stutter/)\n\n\n## Documentation\n\n[Guide](docs/GUIDE.md) - A guide to getting started and using stutter\n[Full API documentation](docs/API.md) - Learn about each method\n\n\n## Install\n\n```sh\nnpm install --save stutter\n```\n\n\n## Usage\n\nSimply import the functions you want to use into your project.\n```js\nimport { map, set } from 'stutter'\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianneisler%2Fstutter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrianneisler%2Fstutter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianneisler%2Fstutter/lists"}