{"id":18064800,"url":"https://github.com/franciscop/swear","last_synced_at":"2026-06-18T20:41:54.826Z","repository":{"id":57145374,"uuid":"145999903","full_name":"franciscop/swear","owner":"franciscop","description":"🙏 Flexible promise handling with Javascript","archived":false,"fork":false,"pushed_at":"2026-05-02T11:56:21.000Z","size":146,"stargazers_count":58,"open_issues_count":2,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2026-05-15T14:13:04.979Z","etag":null,"topics":["async","chainable","javascript","js","promise","promises"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/franciscop.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"custom":"https://www.paypal.me/franciscopresencia/19"}},"created_at":"2018-08-24T14:12:13.000Z","updated_at":"2026-05-11T21:29:53.000Z","dependencies_parsed_at":"2023-01-29T02:46:01.871Z","dependency_job_id":null,"html_url":"https://github.com/franciscop/swear","commit_stats":null,"previous_names":["franciscop/magic-promises"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/franciscop/swear","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fswear","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fswear/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fswear/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fswear/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/franciscop","download_url":"https://codeload.github.com/franciscop/swear/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fswear/sbom","scorecard":{"id":409136,"data":{"date":"2025-08-11","repo":{"name":"github.com/franciscop/swear","commit":"bb6d8ff22437180abe4b37a935106125fedf4956"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","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":"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":"Code-Review","score":0,"reason":"Found 0/30 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":"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":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/franciscop/swear/tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/franciscop/swear/tests.yml/master?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/tests.yml:21","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Info: no jobLevel write permissions found"],"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":"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":"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":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: 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":"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"}}]},"last_synced_at":"2025-08-18T22:10:28.652Z","repository_id":57145374,"created_at":"2025-08-18T22:10:28.652Z","updated_at":"2025-08-18T22:10:28.652Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34507160,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"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":["async","chainable","javascript","js","promise","promises"],"created_at":"2024-10-31T06:07:47.064Z","updated_at":"2026-06-18T20:41:54.819Z","avatar_url":"https://github.com/franciscop.png","language":"TypeScript","funding_links":["https://www.paypal.me/franciscopresencia/19"],"categories":[],"sub_categories":[],"readme":"# 🙏 Swear [![npm install swear](https://img.shields.io/badge/npm%20install-swear-blue.svg)](https://www.npmjs.com/package/swear)  [![test badge](https://github.com/franciscop/swear/workflows/tests/badge.svg)](https://github.com/franciscop/swear/blob/master/.github/workflows/tests.yml) [![gzip size](https://img.badgesize.io/franciscop/swear/master/index.min.js.svg?compression=gzip)](https://github.com/franciscop/swear/blob/master/index.min.js)\n\nFlexible promise handling with Javascript:\n\n```js\nimport swear from \"swear\";\n\nconst name = await swear(fetch(\"/some.json\")).json().user.name;\nconsole.log(name);  // Francisco\n\nconst error = await swear(readFile(\"./error.log\", \"utf-8\")).split(\"\\n\").pop();\nconsole.log(error);  // *latest error log message*\n```\n\nFeatures:\n\n- Extends **native Promises**; you can treat them as promises with `await`, `.then()` and `.catch()`.\n- Automatic **Promise.all()** for arrays.\n- **Chainable** interface that allows for a scripting syntax like jQuery.\n- Extends the **API of the Promise value** so it's intuitive to follow.\n- Can transparently wrap an async function to make it use swear().\n- Full **TypeScript** support with recursive generic types.\n\nSee how `swear()` compares to native promises when you have some async operations:\n\n```js\n// Using this library\nconst value = await swear(data).map(op1).filter(op2).map(op3);\n\n// NATIVE; the pure-javascript way of dealing with the same is a lot longer\nconst value = await Promise.all(data.map(op1)).then(files =\u003e files.filter(op2)).then(files =\u003e Promise.all(files.map(op3)));\n\n// NATIVE; even when we try to make it more readable it is still longer:\nlet value = await Promise.all(data.map(op1));\nvalue = value.filter(op2);\nvalue = await Promise.all(value.map(op3));\n```\n\nNote that in the example above, `op2` has to be sync since the native `.filter()` cannot deal with an async one, but with `swear()` you can do `.filter(async item =\u003e {...})` as well! Keep reading 😄\n\n\n\n## API\n\nThe coolest bit is that _you already know the API_ since it uses the native Javascript one! You can call the methods, properties, etc. of the value that you pass to swear() as you would do normally:\n\n```js\nimport swear from \"swear\";\n\n// No need for swear in this example, but isn't it cool?\nconst value = await swear(getPi()).toFixed(1).split(\".\").map(n =\u003e n * 2).join(\".\");\nconsole.log(value); // 6.2 (string)\n\nconst name = await swear(fetch(\"/some.json\")).json().user.name;\nconsole.log(name); // Francisco\n\n// Native code (extra verbose for clarity)\nconst res = await fetch(\"/some.json\");\nconst data = await res.json();\nconst user = data.user;\nconst name = user.name;\nconsole.log(name);\n```\n\n\n\n### Number\n\nThe [**Number documentation**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) explains the native API that is available. For instance, let's see with `.toFixed()`:\n\n```js\nimport swear from \"swear\";\n\nasync function getPi() { /* ... */ }\n\nconst pi = await swear(getPi()).toFixed(2);\nconsole.log(pi);  // 3.14\n```\n\nYou can apply other string operations as well, for instance you might want to extract some decimals:\n\n```js\nconst decimals = await swear(getPi()).toFixed(3).split(\".\").pop();\nconsole.log(decimals);\n```\n\n\n\n### String\n\nThe [**String documentation**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) explains the native API that is available. For instance, let's see with `.split()`:\n\n```js\nimport swear from \"swear\";\n\nasync function getCsv(url) { /* ... */ }\n\nconst first = await swear(getCsv(\"/some.csv\")).split(\"\\n\").shift().split(\",\");\nconsole.log(first);  // [a,1,6,z]\n```\n\n\n\n### Function\n\nIf you pass a function, swear will return a function that, when called, will return a swear instance. It transparently passes the arguments (resolving them if needed!) and resolves with the returned value:\n\n```js\nimport swear from \"swear\";\n\nconst getSomeInfo = swear(async (file) =\u003e {\n  const res = await fetch(file);\n  return res.json();\n});\n\nconst names = await getSomeInfo(\"users.json\").map(user =\u003e user.name).join(\",\");\n```\n\nThis is great if you want to write a library with swear; it will behave the same as the async version when treated like such; but has a lot of new useful functionality from the underlying values.\n\n\n\n### Array\n\nWe are **extending** native arrays by adding **async** and **RegExp** methods to them:\n\n```js\nimport swear from \"swear\";\n\n// It allows the .filter() callback to return a promise\nconst evens = await swear([0, 1, 2]).filter(async n =\u003e n * 2 \u003c 2);\nconsole.log(evens); // [0, 1]\n\n// It also accepts a Regular Expression for an array of strings\nconst found = await swear([\"a\", \"b\", \"C\"]).find(/c/i);\nconsole.log(found); // \"C\"\n```\n\n\u003e Note: don't worry, we are not touching the prototype. These extensions are *only available* until you call `await`, `.then()` or `.catch()`.\n\nFor sync methods they behave the same way as the native counterparts. For `async` methods you need to be aware whether each of those callbacks is called in parallel (concurrent) or in series:\n\n- `.every()`: **series**, _\"executes the provided callback function once for each element present in the array until it finds one where callback returns a falsy value\"_ - [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every#Description).\n- `.filter()`: parallel\n- `.find()`: **series**\n- `.findIndex()`: **series**\n- `.forEach()`: parallel\n- `.map()`: parallel\n- `.reduce()`: **series**\n- `.reduceRight()`: **series**\n- `.some()`: **series**, _\"executes the callback function once for each element present in the array until it finds one where callback returns a truthy value\"_ - [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some#Description).\n- `.sort()`: **none**. This method is not modified and it does not accept an async callback.\n\nThe ones called in series is because later iterations might depend on previous ones.\n\n\n\n## TypeScript\n\nSwear is written in TypeScript and ships with full type definitions. The return type of `swear()` is `Swear\u003cT\u003e`, which extends `Promise\u003cT\u003e` and recursively maps all properties and methods of `T` into swear-wrapped equivalents, so the chain stays typed end to end:\n\n```ts\nimport swear, { type Swear } from \"swear\";\n\n// Property chains are fully typed\nconst name: string = await swear({ user: { name: \"Alice\" } }).user.name;\n\n// Method chains carry their return types\nconst parts: string[] = await swear(\"hello world\").split(\" \");\n\n// Async array methods accept sync or async callbacks\nconst evens: number[] = await swear([1, 2, 3, 4]).filter(async n =\u003e n % 2 === 0);\n```\n\nThe `Swear\u003cT\u003e` type is exported if you need to annotate your own functions:\n\n```ts\nimport swear, { type Swear } from \"swear\";\n\nfunction getUsers(): Swear\u003c{ id: number; name: string }[]\u003e {\n  return swear(fetch(\"/api/users\").then(r =\u003e r.json()));\n}\n\nconst names = await getUsers().map(u =\u003e u.name);\n```\n\nYou can extend swear with custom methods using the `SwearOptions` type:\n\n```ts\nimport swear, { type SwearOptions } from \"swear\";\n\nconst opts: SwearOptions\u003c{ double: (v: string) =\u003e string }\u003e = {\n  double: (v) =\u003e v + v,\n};\n\nconst result = await (swear(\"hi\", opts) as any).double();\nconsole.log(result); // \"hihi\"\n```\n\n\n\n## Examples\n\nThis library is specially useful if we want to do things like fetching urls, mapping their arrays, working with strings, etc. For instance, let's read all the files in the current directory:\n\n```js\nimport swear from \"swear\";\nimport { readdir, readFile } from \"node:fs/promises\";\n\n// You can apply `.map()` straight to the output of swear()\nconst files = await swear(readdir(import.meta.dirname)).map(file =\u003e readFile(file, \"utf-8\"));\n\n// NATIVE; this is how you'd have to do with vanilla JS\nconst names = await readdir(import.meta.dirname);\nconst contents = await Promise.all(names.map(file =\u003e readFile(file, \"utf-8\")));\n```\n\nRetrieve a bunch of websites with valid responses:\n\n```js\nimport swear from \"swear\";\n\nconst urls = [\"francisco.io\", \"serverjs.io\", \"umbrellajs.com\"];\nconst websites = await swear(urls)\n  .map(url =\u003e fetch(url))       // Fetch the URLs in parallel like Promise.all()\n  .map(res =\u003e res.text())       // Retrieve the actual bodies\n  .filter(Boolean);             // Only those bodies with content\n\n// NATIVE; How to do this with traditional Promises + arrays\nconst responses = await Promise.all(urls.map(url =\u003e fetch(url)));\nconst websites = (await Promise.all(responses.map(res =\u003e res.text()))).filter(Boolean);\n```\n\nWorks with any value that a promise can resolve to:\n\n```js\nimport swear from \"swear\";\n\n// Get and parse a CSV file. Promise =\u003e text =\u003e array =\u003e number\nconst sum = await swear(fetch(\"example.com/data.csv\").then(r =\u003e r.text()))\n  .split(\"\\n\")\n  .filter(Boolean)\n  .map(line =\u003e line.split(\"\\t\").shift())\n  .map(num =\u003e parseFloat(num))\n  .reduce((total, num) =\u003e total + num, 0);\n```\n\n\n\n## Acknowledgements\n\nLibraries based on this:\n\n- [`atocha`](https://npmjs.com/package/atocha): run terminal commands from Node.js.\n- [`create-static-web`](https://npmjs.com/package/create-static-web): another static site generator.\n- [`fch`](https://www.npmjs.com/package/fch): an improved version of fetch().\n- [`files`](https://npmjs.com/package/files): Node.js filesystem API easily usable with Promises and arrays.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffranciscop%2Fswear","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffranciscop%2Fswear","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffranciscop%2Fswear/lists"}