{"id":16696639,"url":"https://github.com/atry/tail-call-proxy","last_synced_at":"2025-08-02T10:11:58.596Z","repository":{"id":65231821,"uuid":"587930557","full_name":"Atry/tail-call-proxy","owner":"Atry","description":"Delayed initialized objects that support tail-call optimization","archived":false,"fork":false,"pushed_at":"2023-01-29T05:56:55.000Z","size":1636,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-24T02:46:13.765Z","etag":null,"topics":[],"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/Atry.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2023-01-11T23:16:13.000Z","updated_at":"2023-02-03T04:20:31.000Z","dependencies_parsed_at":"2023-02-15T00:15:59.871Z","dependency_job_id":null,"html_url":"https://github.com/Atry/tail-call-proxy","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":"ryansonshine/typescript-npm-package-template","purl":"pkg:github/Atry/tail-call-proxy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Atry%2Ftail-call-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Atry%2Ftail-call-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Atry%2Ftail-call-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Atry%2Ftail-call-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Atry","download_url":"https://codeload.github.com/Atry/tail-call-proxy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Atry%2Ftail-call-proxy/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268370180,"owners_count":24239766,"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-08-02T02:00:12.353Z","response_time":74,"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":[],"created_at":"2024-10-12T17:44:25.849Z","updated_at":"2025-08-02T10:11:58.544Z","avatar_url":"https://github.com/Atry.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n- [tail-call-proxy](#tail-call-proxy)\n  - [Functions](#functions)\n    - [lazy](#lazy)\n    - [parasitic](#parasitic)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n\n\u003ca name=\"readmemd\"\u003e\u003c/a\u003e\n\n# tail-call-proxy\n\nDelayed initialized objects that support tail-call optimization.\n\n[![npm package][npm-img]][npm-url]\n[![Build Status][build-img]][build-url]\n[![Downloads][downloads-img]][downloads-url]\n[![Issues][issues-img]][issues-url]\n[![Code Coverage][codecov-img]][codecov-url]\n[![Commitizen Friendly][commitizen-img]][commitizen-url]\n[![Semantic Release][semantic-release-img]][semantic-release-url]\n\n[build-img]:https://github.com/Atry/tail-call-proxy/actions/workflows/release.yml/badge.svg\n[build-url]:https://github.com/Atry/tail-call-proxy/actions/workflows/release.yml\n[downloads-img]:https://img.shields.io/npm/dt/tail-call-proxy\n[downloads-url]:https://www.npmtrends.com/tail-call-proxy\n[npm-img]:https://img.shields.io/npm/v/tail-call-proxy\n[npm-url]:https://www.npmjs.com/package/tail-call-proxy\n[issues-img]:https://img.shields.io/github/issues/Atry/tail-call-proxy\n[issues-url]:https://github.com/Atry/tail-call-proxy/issues\n[codecov-img]:https://codecov.io/gh/Atry/tail-call-proxy/branch/main/graph/badge.svg\n[codecov-url]:https://codecov.io/gh/Atry/tail-call-proxy\n[semantic-release-img]:https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg\n[semantic-release-url]:https://github.com/semantic-release/semantic-release\n[commitizen-img]:https://img.shields.io/badge/commitizen-friendly-brightgreen.svg\n[commitizen-url]:http://commitizen.github.io/cz-cli/\n\n## Functions\n\n### lazy\n\n▸ **lazy**\u003c`T`\\\u003e(`tailCall`): `T`\n\nReturns an proxy object backed by `tailCall`, which will be lazily created\nat the first time its properties or methods are used.\n\n`lazy` can eliminate tail calls, preventing stack overflow errors in tail\nrecursive functions or mutual recursive functions.\n\n**`Example`**\n\nThe initializer passed to `lazy` should not be called until the first time\n`lazyObject.hello` is accessed. When `lazyObject.hello` is accessed more than\nonce, the second access would not trigger the initializer again.\n\n```typescript doctest\nimport { lazy } from 'tail-call-proxy';\n\nlet counter = 0;\nconst lazyObject = lazy(() =\u003e {\n  counter++;\n  return { hello: 'world' };\n});\nexpect(counter).toBe(0);\n\nexpect(lazyObject.hello).toBe('world');\nexpect(counter).toBe(1);\n\nexpect(lazyObject.hello).toBe('world');\nexpect(counter).toBe(1);\n```\n\n**`Example`**\n\nNote that errors thrown in the initializer will be delayed as well.\n\n```typescript doctest\nimport { lazy } from 'tail-call-proxy';\n\nlet counter = 0;\nconst lazyError: Record\u003cstring, unknown\u003e = lazy(() =\u003e {\n  counter++;\n  throw new Error();\n});\n\n// No error is thrown, given that the underlying object have not been created\n// yet.\nexpect(counter).toBe(0);\n\nexpect(() =\u003e lazyError.toString()).toThrow();\nexpect(counter).toBe(1);\n\nexpect(() =\u003e lazyError.toLocaleString()).toThrow();\nexpect(counter).toBe(1);\n```\n\n**`Example`**\n\nThe following mutual recursive functions would result in stack overflow:\n\n```typescript doctest\nfunction isEven(n: number): Boolean {\n  if (n === 0) {\n    return new Boolean(true);\n  }\n  return isOdd(n - 1);\n}\n\nfunction isOdd(n: number): Boolean {\n  if (n === 0) {\n    return new Boolean(false);\n  }\n  return isEven(n - 1);\n}\n\nexpect(() =\u003e isOdd(1000000)).toThrow();\n```\n\nHowever, if you replace `return xxx` with `return lazy(() =\u003e xxx)`, it will\nuse a constant size of stack memory and avoid the stack overflow.\n\n```typescript doctest\nimport { lazy } from 'tail-call-proxy';\nfunction isEven(n: number): Boolean {\n  if (n === 0) {\n    return new Boolean(true);\n  }\n  return lazy(() =\u003e isOdd(n - 1));\n}\n\nfunction isOdd(n: number): Boolean {\n  if (n === 0) {\n    return new Boolean(false);\n  }\n  return lazy(() =\u003e isEven(n - 1));\n}\n\nexpect(isOdd(1000000).valueOf()).toBe(false);\n```\n\n#### Type parameters\n\n| Name | Type |\n| :------ | :------ |\n| `T` | extends `object` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :------ | :------ | :------ |\n| `tailCall` | () =\u003e `T` | the function to create the underlying object |\n\n#### Returns\n\n`T`\n\n#### Defined in\n\n[index.ts:253](https://github.com/Atry/tail-call-proxy/blob/1d1f6c9/src/index.ts#L253)\n\n___\n\n### parasitic\n\n▸ **parasitic**\u003c`T`\\\u003e(`tailCall`): `T`\n\nPerforms a tail call as soon as possible.\n\n`parasitic` returns either exactly the object returned by `tailCall`, or a\nproxy object backed by the object returned by `tailCall`, if there are any\npreviously started pending tail calls. In the latter case, the underlying\nobject will be created after all the previous tail calls are finished.\n\n**`Example`**\n\nUnlike [lazy](#lazy), `parasitic` performs the initialization as soon as\npossible:\n\n```typescript doctest\nimport { parasitic } from 'tail-call-proxy';\n\nlet counter = 0;\nconst parasiticObject = parasitic(() =\u003e {\n  counter++;\n  return { hello: 'world' };\n});\nexpect(counter).toBe(1);\n\nexpect(parasiticObject.hello).toBe('world');\nexpect(counter).toBe(1);\n\nexpect(parasiticObject.hello).toBe('world');\nexpect(counter).toBe(1);\n```\n\n**`Example`**\n\n`parasitic` is useful when you need tail call optimization while you don't\nneed the lazy evaluation. It can be used together with [lazy](#lazy)\nalternately.\n\n```typescript doctest\nimport { lazy, parasitic } from 'tail-call-proxy';\n\nlet isEvenCounter = 0;\nconst trueObject = new Boolean(true);\nfunction isEven(n: number): Boolean {\n  isEvenCounter++;\n  if (n === 0) {\n    return trueObject;\n  }\n  return lazy(() =\u003e isOdd(n - 1));\n};\n\nlet isOddCounter = 0;\nconst falseObject = new Boolean(false);\nfunction isOdd(n: number): Boolean {\n  isOddCounter++;\n  if (n === 0) {\n    return falseObject;\n  }\n  return parasitic(() =\u003e isEven(n - 1));\n};\n\ntry {\n  // `isEven` is called, but `lazy(() =\u003e isOdd(n - 1))` does not trigger\n  // `isOdd` immediately.\n  const is1000000Even = isEven(1000000);\n  expect(isOddCounter).toBe(0);\n  expect(isEvenCounter).toBe(1);\n\n  // `valueOf` triggers the rest of the recursion.\n  expect(is1000000Even.valueOf()).toBe(true);\n  expect(isOddCounter).toBe(500000);\n  expect(isEvenCounter).toBe(500001);\n\n  // `is1000000Even` is a lazy proxy backed by `trueObject`, not the exactly\n  // same object of `trueObject`.\n  expect(is1000000Even).not.toStrictEqual(trueObject);\n  expect(is1000000Even).toEqual(trueObject);\n} finally {\n  isEvenCounter = 0;\n  isOddCounter = 0;\n}\n\n// `isOdd` is called, in which `parasitic(() =\u003e isEven(n - 1))` triggers the\n// rest of the recursion immediately.\nconst is1000000Odd = isOdd(1000000);\nexpect(isOddCounter).toBe(500001);\nexpect(isEvenCounter).toBe(500000);\nexpect(is1000000Odd.valueOf()).toBe(false);\nexpect(isOddCounter).toBe(500001);\nexpect(isEvenCounter).toBe(500000);\n\n// `is1000000Odd` is exactly the same object of `falseObject`, not a lazy\n// proxy.\nexpect(is1000000Odd).toStrictEqual(falseObject);\n```\n\n#### Type parameters\n\n| Name | Type |\n| :------ | :------ |\n| `T` | extends `object` |\n\n#### Parameters\n\n| Name | Type | Description |\n| :------ | :------ | :------ |\n| `tailCall` | () =\u003e `T` | the function to create the underlying object |\n\n#### Returns\n\n`T`\n\n#### Defined in\n\n[index.ts:353](https://github.com/Atry/tail-call-proxy/blob/1d1f6c9/src/index.ts#L353)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatry%2Ftail-call-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fatry%2Ftail-call-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatry%2Ftail-call-proxy/lists"}