synclane 0.6.1
framework-agnostic RPC API with a smart auto-generated TypeScript client
License: MIT
Requires Python: >=3.7

# Welcome to synclane

`synclane` is a framework-agnostic RPC API with a smart auto-generated
TypeScript client.

[![License](https://img.shields.io/github/license/westandskif/synclane.svg)](https://github.com/westandskif/synclane/blob/master/LICENSE.txt)
[![codecov](https://codecov.io/gh/westandskif/synclane/graph/badge.svg?token=JL9C46RNGU)](https://codecov.io/gh/westandskif/synclane)
[![Tests status](https://github.com/westandskif/synclane/workflows/tests/badge.svg)](https://github.com/westandskif/synclane/actions/workflows/pytest.yml)
[![Docs status](https://readthedocs.org/projects/synclane/badge/?version=latest)](https://synclane.readthedocs.io/en/latest/?badge=latest)
[![PyPI](https://badge.fury.io/py/synclane.svg)](https://pypi.org/project/synclane/)
[![Downloads](https://static.pepy.tech/badge/synclane)](https://pepy.tech/project/synclane)
[![Python versions](https://img.shields.io/pypi/pyversions/synclane.svg)](https://pypi.org/project/synclane/)

## Idea


The below must be enough to define an API:

```python
class UserParams(pydantic.BaseModel):
    uid: str

class GetUsers(AbstractProcedure):
    def call(self, in_: UserParams, context) -> List[UserDetails]:
        ...
```

and use an automatically generated frontend TypeScript client:

```typescript
import { callGetUsers } from "./src/out";

expect(callGetUsers(userParams).$promise).resolves.toEqual(listOfUserDetails);
```

## Benefits

#### Automated typescript client generation

Of course, it's possible to annotate your API, export an OpenAPI schema and
generate a typescript client from it. However it will lack the below nice bits.

#### Browser Dates done right

Javascript doesn't have a separate `date` type, so it uses `Date` for both
python's `date` and `datetime`.

Hence when you pass `2000-01-01` to a browser in New York, the browser will
read it as UTC datetime and then convert it to the local timezone, so it will
give you Dec 31, 1991 7:00PM, which is fine if you wanted to work with a
particular moment in time, but what if you wanted to display someone's date of
birth? That's why lacking date type is a problem.

`synclane` will see that you wanted to pass python's `date` to the browser and
will automatically prepare it in the browser, so that Jan 1st is preserved in
the case above.

#### Browser friendly types only

`synclane` raises an exception if you use types, which browser won't be able to
understand.

#### No need to define URLs

Once you name a procedure, e.g. `AddUser`, you just get `callAddUser` function
in the typescript client. You don't need to define any other identifier like
API endpoint url.

#### Enums

If your procedure in/out types include enums, they will become available in the
typescript client.

## Installation

```bash
pip install synclane
```

[pydantic](https://github.com/pydantic/pydantic) is the only dependency.

## Usage

1. define procedures
1. define RPC instance, its error handling method, register procedures and dump
   TypeScript client code
1. connect RPC to an API
1. on TypeScript side: import `rpcConfig` and initialize:
     - `rpcConfig.url`: url where RPC is listening
     - `rpcConfig.initFetch` (optional): function, which accepts and can mutate
       [fetch options](https://developer.mozilla.org/en-US/docs/Web/API/fetch)
       as needed

## Example

#### Step 1: Define procedures

```python
--8<-- "tests/int_tst/main.py:def_procedures"
```

#### Step 2: Define RPC, dump TS

```python
--8<-- "tests/int_tst/main.py:def_rpc"
```

#### Step 3.a: Connect to Django

/// tab | async rpc
```python
--8<-- "tests/int_tst/main.py:django_async"
```
///

/// tab | sync rpc
```python
--8<-- "tests/int_tst/main.py:django_sync"
```
///

#### Step 3.b: Connect to FastAPI

/// tab | async rpc
```python
--8<-- "tests/int_tst/main.py:fastapi_async"
```
///

/// tab | async rpc
```python
--8<-- "tests/int_tst/main.py:fastapi_sync"
```
///

#### Step 4: Use autogenerated TS client
```typescript
--8<-- "tests/int_tst/tests/client.test.ts:imports"
--8<-- "tests/int_tst/tests/client.test.ts:rpc_config"
--8<-- "tests/int_tst/tests/client.test.ts:get_user"
```