{"id":34100290,"url":"https://github.com/westandskif/synclane","last_synced_at":"2025-12-14T16:50:22.956Z","repository":{"id":224414321,"uuid":"757123120","full_name":"westandskif/synclane","owner":"westandskif","description":"synclane is a framework-agnostic RPC API with a smart auto-generated TypeScript client.","archived":false,"fork":false,"pushed_at":"2025-05-11T20:33:18.000Z","size":150,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-28T02:11:24.252Z","etag":null,"topics":["python","rpc","typescript","validation"],"latest_commit_sha":null,"homepage":"http://synclane.readthedocs.io","language":"Python","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/westandskif.png","metadata":{"files":{"readme":"docs/README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-02-13T21:10:44.000Z","updated_at":"2025-08-12T14:07:40.000Z","dependencies_parsed_at":"2024-11-20T16:21:02.477Z","dependency_job_id":"b039ce4d-371e-4be3-9432-92f23fa252d0","html_url":"https://github.com/westandskif/synclane","commit_stats":null,"previous_names":["westandskif/synclane"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/westandskif/synclane","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westandskif%2Fsynclane","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westandskif%2Fsynclane/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westandskif%2Fsynclane/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westandskif%2Fsynclane/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/westandskif","download_url":"https://codeload.github.com/westandskif/synclane/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/westandskif%2Fsynclane/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27731719,"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-12-14T02:00:11.348Z","response_time":56,"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":["python","rpc","typescript","validation"],"created_at":"2025-12-14T16:50:22.451Z","updated_at":"2025-12-14T16:50:22.946Z","avatar_url":"https://github.com/westandskif.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Welcome to synclane\n\n`synclane` is a framework-agnostic RPC API with a smart auto-generated\nTypeScript client.\n\n[![License](https://img.shields.io/github/license/westandskif/synclane.svg)](https://github.com/westandskif/synclane/blob/master/LICENSE.txt)\n[![codecov](https://codecov.io/gh/westandskif/synclane/graph/badge.svg?token=JL9C46RNGU)](https://codecov.io/gh/westandskif/synclane)\n[![Tests status](https://github.com/westandskif/synclane/workflows/tests/badge.svg)](https://github.com/westandskif/synclane/actions/workflows/pytest.yml)\n[![Docs status](https://readthedocs.org/projects/synclane/badge/?version=latest)](https://synclane.readthedocs.io/en/latest/?badge=latest)\n[![PyPI](https://badge.fury.io/py/synclane.svg)](https://pypi.org/project/synclane/)\n[![Downloads](https://static.pepy.tech/badge/synclane)](https://pepy.tech/project/synclane)\n[![Python versions](https://img.shields.io/pypi/pyversions/synclane.svg)](https://pypi.org/project/synclane/)\n\n## Idea\n\n\nThe below must be enough to define an API:\n\n```python\nclass UserParams(pydantic.BaseModel):\n    uid: str\n\nclass GetUsers(AbstractProcedure):\n    def call(self, in_: UserParams, context) -\u003e List[UserDetails]:\n        ...\n```\n\nand use an automatically generated frontend TypeScript client:\n\n```typescript\nimport { callGetUsers } from \"./src/out\";\n\nexpect(callGetUsers(userParams).$promise).resolves.toEqual(listOfUserDetails);\n```\n\n## Benefits\n\n#### Automated typescript client generation\n\nOf course, it's possible to annotate your API, export an OpenAPI schema and\ngenerate a typescript client from it. However it will lack the below nice bits.\n\n#### Browser Dates done right\n\nJavascript doesn't have a separate `date` type, so it uses `Date` for both\npython's `date` and `datetime`.\n\nHence when you pass `2000-01-01` to a browser in New York, the browser will\nread it as UTC datetime and then convert it to the local timezone, so it will\ngive you Dec 31, 1999 7:00PM, which is fine if you wanted to work with a\nparticular moment in time, but what if you wanted to display someone's date of\nbirth? That's why lacking date type is a problem.\n\n`synclane` will see that you wanted to pass python's `date` to the browser and\nwill automatically prepare it in the browser, so that Jan 1st is preserved in\nthe case above.\n\n#### Browser friendly types only\n\n`synclane` raises an exception if you use types, which browser won't be able to\nunderstand.\n\n#### No need to define URLs\n\nOnce you name a procedure, e.g. `AddUser`, you just get `callAddUser` function\nin the typescript client. You don't need to define any other identifier like\nAPI endpoint url.\n\n#### Enums\n\nIf your procedure in/out types include enums, they will become available in the\ntypescript client.\n\n## Installation\n\n```bash\npip install synclane\n```\n\n[pydantic](https://github.com/pydantic/pydantic) is the only dependency.\n\n## Usage\n\n1. define procedures\n1. define RPC instance, its error handling method, register procedures and dump\n   TypeScript client code\n1. connect RPC to an API\n1. on TypeScript side: import `rpcConfig` and initialize:\n     - `rpcConfig.url`: url where RPC is listening\n     - `rpcConfig.initFetch` (optional): function, which accepts and can mutate\n       [fetch options](https://developer.mozilla.org/en-US/docs/Web/API/fetch)\n       as needed\n\n## Example\n\n#### Step 1: Define procedures\n\n```python\n--8\u003c-- \"tests/int_tst/main.py:def_procedures\"\n```\n\n#### Step 2: Define RPC, dump TS\n\n```python\n--8\u003c-- \"tests/int_tst/main.py:def_rpc\"\n```\n\n#### Step 3.a: Connect to Django\n\n/// tab | async rpc\n```python\n--8\u003c-- \"tests/int_tst/main.py:django_async\"\n```\n///\n\n/// tab | sync rpc\n```python\n--8\u003c-- \"tests/int_tst/main.py:django_sync\"\n```\n///\n\n#### Step 3.b: Connect to FastAPI\n\n/// tab | async rpc\n```python\n--8\u003c-- \"tests/int_tst/main.py:fastapi_async\"\n```\n///\n\n/// tab | async rpc\n```python\n--8\u003c-- \"tests/int_tst/main.py:fastapi_sync\"\n```\n///\n\n#### Step 4: Use autogenerated TS client\n```typescript\n--8\u003c-- \"tests/int_tst/tests/client.test.ts:imports\"\n--8\u003c-- \"tests/int_tst/tests/client.test.ts:rpc_config\"\n--8\u003c-- \"tests/int_tst/tests/client.test.ts:get_user\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwestandskif%2Fsynclane","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwestandskif%2Fsynclane","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwestandskif%2Fsynclane/lists"}