{"id":20030925,"url":"https://github.com/mountain-pass/waychaser","last_synced_at":"2026-04-04T07:01:26.389Z","repository":{"id":36994193,"uuid":"307346645","full_name":"mountain-pass/waychaser","owner":"mountain-pass","description":"Client library for HATEOAS level 3 RESTful APIs that provide hypermedia controls using: Link (RFC8288) and Link-Template headers, HAL or Siren","archived":false,"fork":false,"pushed_at":"2026-04-04T06:12:27.000Z","size":11234,"stargazers_count":2,"open_issues_count":20,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-04T06:20:43.498Z","etag":null,"topics":["client","fetch","hal","hal-client","hateoas","hateoas-client","hypermedia","hypermedia-client","isomorphic","javascript","javascript-library","link-header","link-template-header","rest","rest-client","rfc8288","siren","siren-client"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/mountain-pass.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-10-26T11:13:51.000Z","updated_at":"2026-04-04T06:12:31.000Z","dependencies_parsed_at":"2023-02-17T22:00:20.266Z","dependency_job_id":null,"html_url":"https://github.com/mountain-pass/waychaser","commit_stats":{"total_commits":1187,"total_committers":6,"mean_commits":"197.83333333333334","dds":0.6048862679022746,"last_synced_commit":"06620d693b2dc7b129ca29e0e871be62ce3f6b84"},"previous_names":[],"tags_count":250,"template":false,"template_full_name":null,"purl":"pkg:github/mountain-pass/waychaser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mountain-pass%2Fwaychaser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mountain-pass%2Fwaychaser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mountain-pass%2Fwaychaser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mountain-pass%2Fwaychaser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mountain-pass","download_url":"https://codeload.github.com/mountain-pass/waychaser/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mountain-pass%2Fwaychaser/sbom","scorecard":{"id":661519,"data":{"date":"2025-08-11","repo":{"name":"github.com/mountain-pass/waychaser","commit":"06620d693b2dc7b129ca29e0e871be62ce3f6b84"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.6,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/17 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":"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":"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":"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":"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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/build-and-publish.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":"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":"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":"Pinned-Dependencies","score":5,"reason":"dependency not pinned by hash detected -- score normalized to 5","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:111: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:114: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:124: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:179: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:182: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:192: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:77: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:80: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:90: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:96: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:142: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:145: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:155: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:162: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:214: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:217: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:227: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:236: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:728: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:731: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:740: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:784: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:797: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:45: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:48: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-publish.yml:58: update your workflow using https://app.stepsecurity.io/secureworkflow/mountain-pass/waychaser/build-and-publish.yml/main?enable=pin","Info:   0 out of  25 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   7 out of   7 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":"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":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"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":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v5.0.11 not signed: https://api.github.com/repos/mountain-pass/waychaser/releases/71200346","Warn: release artifact v5.0.10 not signed: https://api.github.com/repos/mountain-pass/waychaser/releases/71199432","Warn: release artifact v5.0.9 not signed: https://api.github.com/repos/mountain-pass/waychaser/releases/71172797","Warn: release artifact v5.0.8 not signed: https://api.github.com/repos/mountain-pass/waychaser/releases/70630076","Warn: release artifact v5.0.7 not signed: https://api.github.com/repos/mountain-pass/waychaser/releases/70628787","Warn: release artifact v5.0.11 does not have provenance: https://api.github.com/repos/mountain-pass/waychaser/releases/71200346","Warn: release artifact v5.0.10 does not have provenance: https://api.github.com/repos/mountain-pass/waychaser/releases/71199432","Warn: release artifact v5.0.9 does not have provenance: https://api.github.com/repos/mountain-pass/waychaser/releases/71172797","Warn: release artifact v5.0.8 does not have provenance: https://api.github.com/repos/mountain-pass/waychaser/releases/70630076","Warn: release artifact v5.0.7 does not have provenance: https://api.github.com/repos/mountain-pass/waychaser/releases/70628787"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 13 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":"83 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-c2jc-4fpr-4vhg","Warn: Project is vulnerable to: GHSA-4x6g-3cmx-w76r","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-fwr7-v2mv-hh25","Warn: Project is vulnerable to: GHSA-cph5-m8f7-6c5x","Warn: Project is vulnerable to: GHSA-wf5p-g6vw-rhxx","Warn: Project is vulnerable to: GHSA-jr5f-v2jv-69x6","Warn: Project is vulnerable to: GHSA-6f9p-g466-f8v8","Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-hm92-vgmw-qfmx","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-wm7h-9275-46v2","Warn: Project is vulnerable to: GHSA-4gmj-3p3h-gm8h","Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc","Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx","Warn: Project is vulnerable to: GHSA-x3cc-x39p-42qx","Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc","Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-4q6p-r6v2-jvc5","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-jg8v-48h5-wgxg","Warn: Project is vulnerable to: GHSA-36fh-84j7-cv5h","Warn: Project is vulnerable to: GHSA-76p3-8jx3-jpfq","Warn: Project is vulnerable to: GHSA-3rfm-jhwj-7488","Warn: Project is vulnerable to: GHSA-hhq3-ff78-jv3g","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-6vfc-qv3f-vr6c","Warn: Project is vulnerable to: GHSA-5v2h-r2cx-5xgj","Warn: Project is vulnerable to: GHSA-rrrm-qjm4-v8hf","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-wc69-rhjr-hc9g","Warn: Project is vulnerable to: GHSA-56x4-j7p9-fcf9","Warn: Project is vulnerable to: GHSA-v78c-4p63-2j6c","Warn: Project is vulnerable to: GHSA-44fp-w29j-9vj5","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-px4h-xg32-q955","Warn: Project is vulnerable to: GHSA-rp65-9cf3-cjxr","Warn: Project is vulnerable to: GHSA-q674-xm3x-2926","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-rhx6-c78j-4q9w","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-3965-hpx2-q597","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-44c6-4v22-4mhx","Warn: Project is vulnerable to: GHSA-4x5v-gmq8-25ch","Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg","Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p","Warn: Project is vulnerable to: GHSA-hpqj-7cj6-hfj8","Warn: Project is vulnerable to: GHSA-4vrv-93c7-m92j","Warn: Project is vulnerable to: GHSA-6hwc-9h8r-3vmf","Warn: Project is vulnerable to: GHSA-qqqw-gm93-qf6m","Warn: Project is vulnerable to: GHSA-69f9-h8f9-7vjf","Warn: Project is vulnerable to: GHSA-652h-xwhf-q4h6","Warn: Project is vulnerable to: GHSA-mxhp-79qh-mcx6","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-7p7h-4mm5-852v","Warn: Project is vulnerable to: GHSA-chw2-6c7r-37p7","Warn: Project is vulnerable to: GHSA-g3ch-rx76-35fx","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-776f-qx25-q3cc","Warn: Project is vulnerable to: GHSA-m95q-7qp3-xv42"],"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-21T16:20:59.341Z","repository_id":36994193,"created_at":"2025-08-21T16:20:59.341Z","updated_at":"2025-08-21T16:20:59.341Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31390695,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T04:26:24.776Z","status":"ssl_error","status_checked_at":"2026-04-04T04:23:34.147Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["client","fetch","hal","hal-client","hateoas","hateoas-client","hypermedia","hypermedia-client","isomorphic","javascript","javascript-library","link-header","link-template-header","rest","rest-client","rfc8288","siren","siren-client"],"created_at":"2024-11-13T09:29:27.216Z","updated_at":"2026-04-04T07:01:26.370Z","avatar_url":"https://github.com/mountain-pass.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# waychaser\n\nClient library for HATEOAS level 3 RESTful APIs that provide hypermedia controls using:\n  - Link ([RFC8288](https://tools.ietf.org/html/rfc8288)) and [Link-Template](https://mnot.github.io/I-D/link-template/) headers.\n  - [HAL](https://tools.ietf.org/html/draft-kelly-json-hal-08) \n  - [Siren](https://github.com/kevinswiber/siren)\n\nThis [isomorphic](https://en.wikipedia.org/wiki/Isomorphic_JavaScript) library is compatible with Node.js 12.x, 14.x and 16.x, Chrome, Firefox, Safari and Edge.\n \n| \u003cimg src=\"https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg\" alt=\"Node.js\" width=\"24px\" height=\"24px\" /\u003e\u003cbr/\u003eNode.js | [\u003cimg src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png\" alt=\"Chrome\" width=\"24px\" height=\"24px\" /\u003e\u003cbr/\u003eChrome](https://automate.browserstack.com/dashboard/v2/public-build/M2lUc2Q3VFJicFR2c0N6Y0JvZE5oSXAvYlpUQ1ZPMXgxalpUK2ZtNTdPcz0tLVR3QzU5TXllbEZnemhqK2Z5VEpVQ2c9PQ==--8a61c301655735baed333d4f305980a13ef32c25?browsers=[{%22browser%22:%22chrome%22}]) | [\u003cimg src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png\" alt=\"Firefox\" width=\"24px\" height=\"24px\" /\u003e\u003cbr/\u003eFirefox](https://automate.browserstack.com/dashboard/v2/public-build/M2lUc2Q3VFJicFR2c0N6Y0JvZE5oSXAvYlpUQ1ZPMXgxalpUK2ZtNTdPcz0tLVR3QzU5TXllbEZnemhqK2Z5VEpVQ2c9PQ==--8a61c301655735baed333d4f305980a13ef32c25?browsers=[{%22browser%22:%22firefox%22}]) | [\u003cimg src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png\" alt=\"Safari\" width=\"24px\" height=\"24px\" /\u003e\u003cbr/\u003eSafari](https://automate.browserstack.com/dashboard/v2/public-build/M2lUc2Q3VFJicFR2c0N6Y0JvZE5oSXAvYlpUQ1ZPMXgxalpUK2ZtNTdPcz0tLVR3QzU5TXllbEZnemhqK2Z5VEpVQ2c9PQ==--8a61c301655735baed333d4f305980a13ef32c25?browsers=[{%22browser%22:%22safari%22}]) | [\u003cimg src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png\" alt=\"Edge\" width=\"24px\" height=\"24px\" /\u003e\u003cbr/\u003eEdge](https://automate.browserstack.com/dashboard/v2/public-build/M2lUc2Q3VFJicFR2c0N6Y0JvZE5oSXAvYlpUQ1ZPMXgxalpUK2ZtNTdPcz0tLVR3QzU5TXllbEZnemhqK2Z5VEpVQ2c9PQ==--8a61c301655735baed333d4f305980a13ef32c25?browsers=[{%22browser%22:%22edge%22}]) | [\u003cimg src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari-ios/safari-ios_48x48.png\" alt=\"iOS\" width=\"24px\" height=\"24px\" /\u003e\u003cbr/\u003eiOS](https://automate.browserstack.com/dashboard/v2/public-build/M2lUc2Q3VFJicFR2c0N6Y0JvZE5oSXAvYlpUQ1ZPMXgxalpUK2ZtNTdPcz0tLVR3QzU5TXllbEZnemhqK2Z5VEpVQ2c9PQ==--8a61c301655735baed333d4f305980a13ef32c25?oses=[{%22os%22:%22ios%22}]) | [\u003cimg src=\"https://source.android.com/setup/images/Android_symbol_green_RGB.svg\" alt=\"Android\" width=\"24px\" height=\"24px\" /\u003e\u003cbr/\u003eAndroid](https://automate.browserstack.com/dashboard/v2/public-build/M2lUc2Q3VFJicFR2c0N6Y0JvZE5oSXAvYlpUQ1ZPMXgxalpUK2ZtNTdPcz0tLVR3QzU5TXllbEZnemhqK2Z5VEpVQ2c9PQ==--8a61c301655735baed333d4f305980a13ef32c25?oses=[{%22os%22:%22android%22}]) |\n| -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | \n| 12.x, 14.x, 16.x                                                                                                                       | latest version                                                                                                                                                                                                                                                                                                                                                                                              | latest version                                                                                                                                                                                                                                                                                                                                                                                                   | latest version                                                                                                                                                                                                                                                                                                                                                                                              | latest version                                                                                                                                                                                                                                                                                                                                                                                    | latest version                                                                                                                                                                                                                                                                                                                                                                                    | latest version                                                                                                                                                                                                                                                                                                                                                                    | \n\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fmountain-pass%2Fwaychaser.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fmountain-pass%2Fwaychaser?ref=badge_large)\n\n# ToC\n\n- [waychaser](#waychaser)\n- [ToC](#toc)\n- [Usage](#usage)\n  - [Node.js](#nodejs)\n  - [Browser](#browser)\n  - [Getting the response](#getting-the-response)\n    - [Getting the response body](#getting-the-response-body)\n  - [Requesting linked resources](#requesting-linked-resources)\n    - [Multiple links with the same relationship](#multiple-links-with-the-same-relationship)\n  - [Forms](#forms)\n    - [Query forms](#query-forms)\n    - [Path parameter forms](#path-parameter-forms)\n    - [Request body forms](#request-body-forms)\n    - [DELETE, POST, PUT, PATCH](#delete-post-put-patch)\n- [Examples](#examples)\n  - [HAL](#hal)\n  - [Siren](#siren)\n- [Upgrading from 1.x to 2.x](#upgrading-from-1x-to-2x)\n  - [Removal of Loki](#removal-of-loki)\n    - [Operation count](#operation-count)\n    - [Finding operations](#finding-operations)\n- [Upgrading from 2.x to 3.x](#upgrading-from-2x-to-3x)\n  - [Accept Header](#accept-header)\n    - [Handlers](#handlers)\n  - [Error responses](#error-responses)\n  - [Invoking missing operations](#invoking-missing-operations)\n  - [Handling location headers](#handling-location-headers)\n- [Upgrading from 3.x to 4.x](#upgrading-from-3x-to-4x)\n  - [Problem vs WayChaserResponse](#problem-vs-waychaserresponse)\n\n# Usage\n\n## Node.js\n\n```bash\nnpm install @mountainpass/waychaser\n```\n\n```js\nimport { WayChaser } from '@mountainpass/waychaser'\n\n//...\nconst waychaser = new WayChaser()\ntry {\n  const apiResource = await waychaser.load(apiUrl)\n  // do something with `apiResource`\n} catch (error) {\n  // do something with `error`\n}\n```\n\n## Browser\n\n```html\n\u003cscript\n  type=\"text/javascript\"\n  src=\"https://unpkg.com/@mountainpass/waychaser@5.0.48\"\n\u003e\u003c/script\u003e\n\n...\n\u003cscript type=\"text/javascript\"\u003e\n  var waychaser = new window.waychaser.WayChaser()\n  waychaser\n    .load(apiUrl)\n    .then((apiResource) =\u003e {\n      // do something with `apiResource`\n    })\n    .catch((error) =\u003e {\n      // do something with `error`\n    });\n\u003c/script\u003e\n```\n\n## Getting the response\n\nWayChaser makes it's http requests using `fetch` and the `Fetch.Response` is available via the `response` property.\n\nFor example\n```js\nconst responseUrl = apiResource.response.url\n```\n\n### Getting the response body\n\nWayChaser makes the response body available via the `body()` async method.\n\nFor example\n```js\nconst responseUrl = await apiResource.body()\n```\n## Requesting linked resources\n\nLevel 3 REST APIs are expected to return links to related resources. WayChaser expects to find these links via [RFC 8288](https://tools.ietf.org/html/rfc8288) `link` headers, [`link-template`](https://mnot.github.io/I-D/link-template/) headers, [HAL](https://tools.ietf.org/html/draft-kelly-json-hal-08)  `_link` elements or [Siren](https://github.com/kevinswiber/siren) `link` elements.\n\nWayChaser provides methods to simplify requesting these linked resources.\n\nFor instance, if the `apiResource` we loaded above has a `next`  `link` like any of the following:\n\n**Link header:**\n```\nLink: \u003chttps://api.waychaser.io/example?p=2\u003e; rel=\"next\";\n```\n**HAL**\n```json\n{\n  \"_links\": {\n    \"next\": { \"href\": \"https://api.waychaser.io/example?p=2\" }\n  }\n}\n```\n**Siren**\n```json\n{\n  \"links\": [\n    { \"rel\": [ \"next\" ], \"href\": \"https://api.waychaser.io/example?p=2\" },\n  ]\n}\n```\n\nthen that `next` page can be retrieved using the following code\n\n```js\nconst nextResource = await apiResource.invoke('next')\n```\n\nYou don't need to tell waychaser whether to use Link headers, HAL `_links` or Siren `links`; it will figure it out \nbased on the resource's media-type. If the media-type is `application/hal+json` if will try to parse the links in the \n`_link` property of the body.  If the media-type is `application/vnd.siren+json` if will try to parse the links in the \n`link` property of the body.\n\nRegardless of the resource's media-type, it will always try to parse the links in the `Link` and `Link-Template` \nheaders.\n\n### Multiple links with the same relationship\n\nResources can have multiple links with the same `rel`ationship, such as\n\n**HAL**\n```json\n{\n  \"_links\": {\n    \"item\": [{\n      \"href\": \"/first_item\",\n      \"name\": \"first\"\n    },{\n      \"href\": \"/second_item\",\n      \"name\": \"second\"\n    }]\n  }\n}\n```\n\nIf you know the `name` of the resource, then waychaser can load it using the following code\n\n```js\nconst firstResource = await apiResource.invoke({ rel: 'item', name: 'first' })\n```\n\n## Forms\n\n### Query forms\n\nSupport for query forms is provided via:\n- [RFC6570](https://tools.ietf.org/html/rfc6570) URI Templates in:\n  - [`link-template`](https://mnot.github.io/I-D/link-template/) headers, and\n  - [HAL](https://tools.ietf.org/html/draft-kelly-json-hal-08) `_link`s.\n \nFor instance if our resource has either of the following\n\n**Link-Template header:**\n```\nLink-Template: \u003chttps://api.waychaser.io/search{?q}\u003e; rel=\"search\";\n```\n**HAL**\n```json\n{\n  \"_links\": {\n    \"search\": { \"href\": \"https://api.waychaser.io/search{?q}\" }\n  }\n}\n```\n\nThen waychaser can execute a search for \"waychaser\" with the following code\n\n```js\nconst searchResultsResource = await apiResource.invoke('search', {\n  q: 'waychaser'\n})\n```\n\n### Path parameter forms\n\nSupport for query forms is provided via:\n- [RFC6570](https://tools.ietf.org/html/rfc6570) URI Templates in:\n  - [`link-template`](https://mnot.github.io/I-D/link-template/) headers, and\n  - [HAL](https://tools.ietf.org/html/draft-kelly-json-hal-08) `_link`s.\n \nFor instance if our resource has either of the following\n\n**Link-Template header**\n```\nLink-Template: \u003chttps://api.waychaser.io/users{/username}\u003e; rel=\"item\";\n```\n**HAL**\n```json\n{\n  \"_links\": {\n    \"item\": { \"href\": \"https://api.waychaser.io/users{/username}\" }\n  }\n}\n```\n\nThen waychaser can retrieve the user with the `username` \"waychaser\" with the following code\n\n```js\nconst userResource = await apiResource.invoke('item', {\n  username: 'waychaser'\n})\n```\n\n### Request body forms\n\nSupport for request body forms is provided via:\n- An extended form of [`link-template`](https://mnot.github.io/I-D/link-template/) headers, and\n- Siren `actions`.\n\nTo support request body forms with [`link-template`](https://mnot.github.io/I-D/link-template/) headers, waychaser\nsupports three additional parameters in the `link-template` header:\n- `method` - used to specify the HTTP method to use\n- `params*` - used to specify the fields the form expects\n- `accept*` - used to specify the media-types that can be used to send the body as per,\n[RFC7231](https://tools.ietf.org/html/rfc7231) and defaulting to `application/x-www-form-urlencoded`\n\nIf our resource has either of the following:\n\n**Link-Template header:**\n```\nLink-Template: \u003chttps://api.waychaser.io/users\u003e; \n  rel=\"https://waychaser.io/rels/create-user\"; \n  method=\"POST\";\n  params*=UTF-8'en'%7B%22username%22%3A%7B%7D%7D'\n```\n\nIf your wondering what the `UTF-8'en'%7B%22username%22%3A%7B%7D%7D'` part is, it's just the JSON `{\"username\":{}}`\nencoded as an [Extension Attribute](https://tools.ietf.org/html/rfc8288#section-3.4.2) as per\n ([RFC8288](https://tools.ietf.org/html/rfc8288)) Link Headers. Don't worry, libraries like \n [http-link-header](https://www.npmjs.com/package/http-link-header) can do this encoding for you.\n\n**Siren**\n```json\n{\n  \"actions\": [\n    {\n      \"name\": \"https://waychaser.io/rels/create-user\",\n      \"href\": \"https://api.waychaser.io/users\",\n      \"method\": \"POST\",\n      \"fields\": [\n        { \"name\": \"username\" }\n      ]\n    }\n  ]\n}\n```\nThen waychaser can create a new user with the `username` \"waychaser\" with the following code\n\n```js\nconst createUserResultsResource = await apiResource.invoke('https://waychaser.io/rels/create-user', {\n  username: 'waychaser'\n})\n```\n\n**NOTE:** The URL `https://waychaser.io/rels/create-user` in the above code is **NOT** the end-point the form is \nposted to. That URL is a custom [Extension Relation](https://tools.ietf.org/html/rfc8288#section-2.1.2) that identifies\nthe semantics of the operation. In the example above, the form will be posted to `https://api.waychaser.io/users`\n\n### DELETE, POST, PUT, PATCH\n\nAs mentioned above, waychaser supports `Link` and `Link-Template` headers that include `method` properties, \nto specify the HTTP method the client must use to execute the relationship.\n\nFor instance if our resource has the following link\n\n**Link header:**\n```\nLink: \u003chttps://api.waychaser.io/example/some-resource\u003e; rel=\"https://api.waychaser.io/rel/delete\"; method=\"DELETE\";\n```\n\nThen the following code\n\n```js\nconst deletedResource = await apiResource.invoke('https://waychaser.io/rel/delete')\n```\n\nwill send a HTTP `DELETE` to `https://api.waychaser.io/example/some-resource`.\n\n**NOTE**: The `method` property is not part of the specification for Link\n\n([RFC8288](https://tools.ietf.org/html/rfc8288)) or [Link-Template](https://mnot.github.io/I-D/link-template/) headers.\nThis means that if you use waychaser with a server that provides this headers and it uses the `method` property\nfor something else, then you're going to need a custom handler.\n\n# Examples\n\n## HAL\n\nThe following code demonstrates using waychaser with the REST API for AWS API Gateway to download the 'Error' \nschema from 'test-waychaser' gateway \n\n```js\nimport { waychaser, halHandler, MediaTypes } from '@mountainpass/waychaser'\nimport fetch from 'cross-fetch'\nimport aws4 from 'aws4'\n\n\n// AWS makes us sign each request. This is a fetcher that does that automagically for us.\n/**\n * @param url\n * @param options\n */\nfunction awsFetch (url, options) {\n  const parsedUrl = new URL(url)\n  const signedOptions = aws4.sign(\n    Object.assign(\n      {\n        host: parsedUrl.host,\n        path: `${parsedUrl.pathname}?${parsedUrl.searchParams}`,\n        method: 'GET'\n      },\n      options\n    )\n  )\n  return fetch(url, signedOptions)\n}\n\n// Now we tell waychaser, to only accept HAL and to use our fetcher.\nconst awsWayChaser = waychaser.use(halHandler, MediaTypes.HAL).withFetch(awsFetch)\n\n// now we can load the API\nconst api = await waychaser.load(\n  'https://apigateway.ap-southeast-2.amazonaws.com/restapis'\n)\n\n// then we can find the gateway we're after\nconst gateway = await api.ops\n  .filter('item')\n  .findInRelated({ name: 'test-waychaser' })\n\n// then we can get the models\nconst models = await gateway.invoke(\n  'http://docs.aws.amazon.com/apigateway/latest/developerguide/restapi-restapi-models.html'\n)\n\n// then we can find the schema we're after \nconst model = await models.ops\n  .filter('item')\n  .findInRelated({ name: 'Error' })\n\n// and now we get the schema\nconst schema = JSON.parse((await model.body()).schema)\n```\n\n\n**NOTE:** While the above is a legit, and it works ([here's the test](./src/test/hal-example.feature)), for full\nuse of the AWS API Gateway REST API, you're going to need a custom handler.\n\nThis is because HAL links are supposed retrieved using a HTTP GET, but many of the AWS API Gateway REST API links\nrequire using [POST](https://docs.aws.amazon.com/apigateway/api-reference/link-relation/restapi-create/), \n[PATCH](https://docs.aws.amazon.com/apigateway/api-reference/link-relation/restapi-update/) or \n[DELETE](https://docs.aws.amazon.com/apigateway/api-reference/link-relation/restapi-delete/) HTTP methods. \n\nBut there's nothing in AWS API Gateway links to tell you when to use a different HTTP method. Instead it's \ncommunicated out-of-band in AWS API Gateway documentation. If you write a custom handler, please let me know 👍\n\n\n## Siren\n\nWhile admittedly this is a toy example, the following code demonstrates using waychaser to complete the \n[*Hypermedia in the Wizard's Tower* text-based adventure game](https://github.com/einarwh/hyperwizard).\n\nBut even though it's a game, it shows how waychaser can easily navigate a complex process, including `POST`ing data and\n`DELETE`ing resources.\n\n```js\n  return waychaser\n    .load('http://hyperwizard.azurewebsites.net/hywit/void')\n    .then(current =\u003e\n      current.invoke('start-adventure', {\n        name: 'waychaser',\n        class: 'Burglar',\n        race: 'waychaser',\n        gender: 'Male'\n      })\n    )\n    .then(current =\u003e {\n      if (current.response.status \u003c= 500) return current.invoke('related')\n      else throw new Error('Server Error')\n    })\n    .then(current =\u003e current.invoke('north'))\n    .then(current =\u003e current.invoke('pull-lever'))\n    .then(current =\u003e\n      current.invoke({ rel: 'move', title: 'Cross the bridge.' })\n    )\n    .then(current =\u003e current.invoke('move'))\n    .then(current =\u003e current.invoke('look'))\n    .then(current =\u003e current.invoke('eat-snacks'))\n    .then(current =\u003e current.invoke('related'))\n    .then(current =\u003e current.invoke('north'))\n    .then(current =\u003e current.invoke('pull-lever'))\n    .then(current =\u003e current.invoke('look'))\n    .then(current =\u003e current.invoke('eat-snacks'))\n    .then(current =\u003e current.invoke('enter'))\n    .then(current =\u003e current.invoke('answer-skull', { master: 'Edsger' }))\n    .then(current =\u003e current.invoke('east'))\n    .then(current =\u003e current.invoke('smash-mirror-1') || current)\n    .then(current =\u003e current.invoke('related') || current)\n    .then(current =\u003e current.invoke('smash-mirror-2') || current)\n    .then(current =\u003e current.invoke('related') || current)\n    .then(current =\u003e current.invoke('smash-mirror-3') || current)\n    .then(current =\u003e current.invoke('related') || current)\n    .then(current =\u003e current.invoke('smash-mirror-4') || current)\n    .then(current =\u003e current.invoke('related') || current)\n    .then(current =\u003e current.invoke('smash-mirror-5') || current)\n    .then(current =\u003e current.invoke('related') || current)\n    .then(current =\u003e current.invoke('smash-mirror-6') || current)\n    .then(current =\u003e current.invoke('related') || current)\n    .then(current =\u003e current.invoke('smash-mirror-7') || current)\n    .then(current =\u003e current.invoke('related') || current)\n    .then(current =\u003e current.invoke('look'))\n    .then(current =\u003e current.invoke('enter-mirror'))\n    .then(current =\u003e current.invoke('north'))\n    .then(current =\u003e current.invoke('down'))\n    .then(current =\u003e current.invoke('take-book-3'))\n```\n\n# Upgrading from 1.x to 2.x\n\n## Removal of Loki\n\nLoki is no longer use for storing operations and has been replaced with an subclass of `Array`. We originally \nintroduced Loki it's querying capability, but it turned out to be far to large a dependency.\n\n### Operation count\n\nPreviously you could get the number of operations on a resource by calling\n\n```js\napiResource.count()\n```\n\nFor 2.x, replace this with \n\n```js\napiResource.length\n```\n\n### Finding operations\n\nTo find an operation, instead of using\n\n```js\napiResource.operations.findOne(relationship)\n// or\napiResource.operations.findOne({ rel: relationship })\n// or\napiResource.ops.findOne(relationship)\n// or\napiResource.ops.findOne({ rel: relationship })\n```\n\nuse \n\n```js\napiResource.operations.find(relationship)\n// or\napiResource.operations.find({ rel: relationship })\n// or\napiResource.operations.find(operation =\u003e {\n  return operation.rel === relationship\n})\n// or\napiResource.ops.find(relationship)\n// or\napiResource.ops.find({ rel: relationship })\n// or\napiResource.ops.find(operation =\u003e {\n  return operation.rel === relationship\n})\n```\n\nAdditionally when invoking an operation, you can use an array finder function as well. e.g. the following are all\nequivalent\n\n```js\nawait apiResource.invoke(relationship)\nawait apiResource.invoke({ rel: relationship })\nawait apiResource.invoke(operation =\u003e {\n  return operation.rel === relationship\n})\nawait apiResource.operations.invoke(relationship)\nawait apiResource.operations.invoke({ rel: relationship })\nawait apiResource.operations.invoke(operation =\u003e {\n  return operation.rel === relationship\n})\nawait apiResource.ops.invoke(relationship)\nawait apiResource.ops.invoke({ rel: relationship })\nawait apiResource.ops.invoke(operation =\u003e {\n  return operation.rel === relationship\n})\nawait apiResource.operations.find(relationship).invoke()\nawait apiResource.operations.find({ rel: relationship }).invoke()\nawait apiResource.operations.find(operation =\u003e {\n  return operation.rel === relationship\n}).invoke()\nawait apiResource.ops.find(relationship).invoke()\nawait apiResource.ops.find({ rel: relationship }).invoke()\nawait apiResource.ops.find(operation =\u003e {\n  return operation.rel === relationship\n}).invoke()\n```\n\n**NOTE**: When `findOne` could not find an operation, `null` was returned, whereas when `find` cannot find an operation\nit returns `undefined`\n\n# Upgrading from 2.x to 3.x\n\n## Accept Header\n\nwaychaser now automatically provides an `accept` header in requests.\n\nThe accept header can be overridden for individual requests, by including an alternate `header.accept` value in the\n `options` parameter when calling the `invoke` method.\n\n### Handlers\n\nThe `use` method now expects both a `handler` and the `mediaType` it can handle. WayChaser uses the provided \nmediaTypes to automatically generate the `accept` request header. \n\n**NOTE:** Currently waychaser does use the corresponding `content-type` header to filter the responses passed to\nhandlers. **THIS MAY CHANGE IN THE FUTURE.** Handlers should only process responses that match the mediaType provided\nwhen they are registered using the `use` method.\n\n## Error responses\n\nIn 2.x waychaser would throw an `Error` if `response.ok` was false. This is no longer the case as some APIs provide\nhypermedia responses for 4xx and 5xx responses.\n\nCode like the following\n\n```js\ntry {\n  return apiResource.invoke(relationship)\n} catch(error) {\n  if( error.response ) {\n    // handle error response...\n  }\n  else {\n    // handle fetch error\n  }\n}\n```\n\nshould be replaced with\n\n```js\ntry {\n  const resource = await apiResource.invoke(relationship)\n  if( resource.response.ok ) {\n    return resource\n  }\n  else {\n    // handle error response...\n  }\n} catch(error) {\n  // handle fetch error\n}\n```\n\nor if there is no special processing needed for error responses\n\n```js\ntry {\n  return apiResource.invoke(relationship)\n} catch(error) {\n  // handle fetch error\n}\n```\n\n## Invoking missing operations\n\nIn 2.x invoking an operation that didn't exist would throw an error, leading to code like\n\n```js\nconst found = apiResource.ops.find(relationship)\nif( found ) {\n  return found.invoke()\n}\nelse {\n  // handle op missing\n}\n```\n\nIn 3.x invoking an operation that doesn't exist returns `undefined`, allowing for simpler code, as follows\n\n```js\nconst resource = await apiResource.invoke(relationship)\nif( resource === undefined ) {\n  // handle operation missing \n}\n```\n\nor\n\n\u003c!-- eslint-skip --\u003e\n```js\nreturn apiResource.invoke(relationship) || //... return a default\n```\n\n**NOTE:** When we say it returns `undefined` we actually mean `undefined`, **NOT** a promise the resolves\nto `undefined`. This is what makes the `...invoke(rel) || default` code possible.\n\n## Handling location headers\n\nWayChaser 3.x now includes a `location` header hander, which will create an operation with the `related` relationship.\nThis allows support for APIs that, when creating a resource (ie using POST), provide a `location` to the created \nresource in the response, or APIs that, when updating a resource (ie using PUT or PATCH),  provide a `location` to the\nupdated resource in the response.\n\n# Upgrading from 3.x to 4.x\n\nPreviously WayChaser provided a default instance via the `waychaser` export. This is no longer the case and you will\nneed to create your own instance using `new WayChaser()`\n\n\n## Problem vs WayChaserResponse\n\nProblems can be client side or server side\n\nClient side\n - fetch throws exception - No Response \n - can't parse response - Has Response\n - can parse response, but the type predicate fails - Has Response\n\nServer side\n - server returns problem document - Has Response\n\nResponse may include links that tell the client how to resolve,\nso we want it to be a WayChaserResponse\n\nOptions:\n1. invoke returns WayChaserResponse with problem or content\n   - client uses content !== undefined \u0026\u0026 problem === undefined to check if the were not problems\n   - unclear if we got a problem or not\n2. invoke returns a clean WayChaserResponse with content or a WayChaserProblem with a problem document\n  - client would need to use instanceOf to differentiate\n  - clean WayChaserResponse has a response and content (which could be expectedly undefined)\n  - WayChaserProblem has a problem document and may or may not have a response\n3. invoke returns a clean WayChaserResponse or a ProblemDocument with optional waychaser response as extention\n  - client would need to use instanceOf to differentiate\n  - if server returns PD, then do we wrap the PD? Feels ugly\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmountain-pass%2Fwaychaser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmountain-pass%2Fwaychaser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmountain-pass%2Fwaychaser/lists"}