Skip to content

Commit

Permalink
Do not expose types (#184)
Browse files Browse the repository at this point in the history
* Version Packages (#177)

RELEASING: Releasing 1 package(s)

Releases:
  @farfetched/core@0.3.5

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Deep-dive: data flow (#167)

* WIP

* WIP

* WIP

* WIP

* Finalize

* Add a link to new article to cache deep dive

* Fix

* Fix

* Fix

* Upgrade vitest

* POC

* Apply to all packages

* Finalize typepack script

* docs(changeset): Hide private types from published package

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Nov 24, 2022
1 parent 5e0edf3 commit 68b76f5
Show file tree
Hide file tree
Showing 18 changed files with 346 additions and 54 deletions.
10 changes: 10 additions & 0 deletions .changeset/tiny-mails-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@farfetched/core': patch
'@farfetched/misc': patch
'@farfetched/react': patch
'@farfetched/runtypes': patch
'@farfetched/solid': patch
'@farfetched/zod': patch
---

Hide private types from published package
4 changes: 4 additions & 0 deletions apps/website/docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ export default withMermaid(
collapsible: true,
items: [
{ text: 'Unique store identifiers', link: '/recipes/sids' },
{
text: 'Data flow in Remote Operation',
link: '/recipes/data_flow',
},
{ text: 'Automated cache', link: '/recipes/cache' },
],
},
Expand Down
6 changes: 5 additions & 1 deletion apps/website/docs/recipes/cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ Farfetched provides a way to [`cache`](/api/operators/cache) the result of the [

Internal implementation of this guarantee is a pretty simple. Farfetched is based on [Do not trust remote data](/statements/never_trust) principle, so it uses [_Contract_](/api/primitives/contract) and [_Validator_](/api/primitives/validator) to validate the data from the remote source.

Same [_Contract_](/api/primitives/contract) and [_Validator_](/api/primitives/validator) are used to validate the cached data. If the cached data is valid, it is saved to the [_Query_](/api/primitives/query). Otherwise, the cached data is ignored, and the [_Query_](/api/primitives/query) is updated with the new data from the remote source.
::: tip
Read more detailed description of data-flow in [Data flow in Remote Operation](/recipes/data_flow) article.
:::

`cache` operator pushes the data to the [_Query_](/api/primitives/query) right after the **response parsing** stage of the data-flow. It means, same [_Contract_](/api/primitives/contract) and [_Validator_](/api/primitives/validator) are used to validate the cached data as any other data from the regular remote source. If the cached data is valid, it is saved to the [_Query_](/api/primitives/query). Otherwise, the cached data is ignored, and the [_Query_](/api/primitives/query) is updated with the new data from the remote source.

::: info
User-land code can't access the cached data directly. It is only available through the [_Query_](/api/primitives/query) object. So, invalid cached data is not exposed to the user.
Expand Down
146 changes: 146 additions & 0 deletions apps/website/docs/recipes/data_flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Data flow in Remote Operation

Farfetched is designed to deal with data on the remote source (e.g. backend server), so there is an abstraction to represent an operation on this data — _Remote Operation_. For now there are two types of Remote Operations: [_Query_](/api/primitives/query) and [_Mutation_](/api/primitives/mutation).

Because Farfetched [considers data on the remote source as untrusted](/statements/never_trust), it is required to pass any response through a couple stages of validation and transformation before it is used in the application.

:::details Flow of for any _Remote Operation_

```mermaid
sequenceDiagram
participant A as User-land
participant C as Remote Operation
participant S as Remote Source
A->>C: start
activate C
C->>S: request
activate S
S->>C: response
deactivate S
C-->>A: finished.failed
C->>C: parse response
C-->>A: finished.failed
C->>C: apply contract
C-->>A: finished.failed
C->>C: apply validator
C-->>A: finished.failed
C->>C: apply data mapper
C->>A: finished.success
deactivate C
```

:::

## Basic and specific factories

There are two types of factories for _Remote Operations_: **basic** and **specific**. **Basic** factories are used to create _Remote Operations_ with a more control of data-flow in user-land, while **specific** factories are used to create _Remote Operations_ with a more control of data-flow in the library. **Specific** factories are built on top of **basic** ones and are providing better DX for more specific use-cases.

E.g. quite often API is basically HTTP-endpoint, which responses to you with some JSON — Farfetched provides you `createJsonQuery` for this case. This factory hides complexity under the declarative API and handles a lot of edge-cases for you

### Basic factories

- [`createQuery`](/api/factories/create_query)
- [`createMutation`](/api/factories/create_mutation)

### Specific factories

::: tip
Data-flow control is a boring and complex task, so it is recommended to use **specific** factories in many cases to delegate this task to the library.
:::

- [`createJsonQuery`](/api/factories/create_json_query)
- [`createJsonMutation`](/api/factories/create_json_mutation)

## Data-flow in specific factories

Since only **specific** factories are allows Farfetched to have a full control of data-flow, in the following articles we will take a closer look to them. **Basic** factories work in the same way, but they require more attention from the user.

### Request-response cycle

The first step is to send a request to the remote source and wait for a response. Because of Farfetched handles this stage internally, user-land code have to describe only the desired result of this stage and the library will perform the request-response cycle internally in the most optimal way.

Failed response stops the data-flow and returns control to the user-land code through `.finished.failed` [_Event_](https://effector.dev/docs/api/effector/event). Successful response continues the data-flow and passes control to the next step — response parsing.

### Response parsing

Specific factories of Farfetched performs this stage internally, based on a use-case they were created for. In case of parsing error, the data-flow stops and returns control to the user-land code through `.finished.failed` [_Event_](https://effector.dev/docs/api/effector/event). Otherwise, the data-flow continues and passes control to the next step — contract application.

::: details JSON example

[`createJsonQuery`](/api/factories/create_json_query) and [`createJsonMutation`](/api/factories/create_json_mutation) use `JSON.parse` to parse the response and throw an error if the response is not a valid JSON. Because these factories handle this stage internally, they can optimize the parsing process in the most optimal way.

For example, if some when in the future `JSON.parse` will be considered as a bottleneck, the library can replace it with a more optimal implementation without breaking the API. Your application would not be affected by this change, because it does not know anything about the implementation details of the library.

:::

### Contract application

**Specific factories** require explicit [_Contract_](/api/primitives/contract) because they [consider the response as `unknown` by default](/statements/never_trust). So, the user-land code have to describe the contract of the response or explicitly use `unkownContarct` to preserve the `unknown` type.

If parsed data does not satisfy the [_Contract_](/api/primitives/contract), the data-flow stops and returns control to the user-land code through `.finished.failed` [_Event_](https://effector.dev/docs/api/effector/event) with an error-message that is returned from the [_Contract_](/api/primitives/contract). Otherwise, the data-flow continues and passes control to the next step — validation.

### Validation

This is optional stage. It is performed by [_Validator_](/api/primitives/validator) and is used to check if the response is valid. If the response is not valid, the data-flow stops and returns control to the user-land code through `.finished.failed` [_Event_](https://effector.dev/docs/api/effector/event) with an error-message that is returned from the [_Validator_](/api/primitives/validator). Otherwise, the data-flow continues and passes control to the next step — data mapping.

Since [_Validator_](/api/primitives/validator) is a [_Sourced_](/api/primitives/sourced), it's possible to add some extra data from the application to the validation process. For example, it could be a user's session token:

```ts
const $session = createStore<{ userId: string } | null>(null);

const userQuery = createJsonQuery({
//...
response: {
validate: {
source: $session,
fn: (result, _params, sessionToken) => result.userId !== session.userId,
},
},
});
```

### Data mapping

This is optional stage. So, you can define a mapper to transform the response to the desired format.

::: warning
Data mappers have to be pure function, so they are not allowed to throw an error. If the mapper throws an error, the data-flow stops immediately without any error handling.
:::

Since mapper is a [_Sourced_](/api/primitives/sourced), it's possible to add some extra data from the application to the mapping process. For example, it could be a current language:

```ts
const $language = createStore<string>('EN');

const userQuery = createJsonQuery({
//...
response: {
mapData: {
source: $language,
fn: (result, _params, language) => ({
...result,
name: result.name.translations[language],
}),
},
},
});
```

## Data-flow in basic factories

**Basic factories** are used to create _Remote Operations_ with a more control of data-flow in user-land. In this case, the user-land code have to describe **request-response cycle** and **response parsing** stages. Other stages could be handled by the library, but it is not required for **basic factories**.

## Summary

In this article, we have learned how data-flow works under the hood in _Remote operations_. We have also learned about two types of factories and how they differ from each other. Key points:

- **Basic factories** are used to create _Remote Operations_ with a more control of data-flow in user-land, which allows you to have a very granular control over _Remote Operation_ behavior.
- **Specific factories** are used to create _Remote Operations_ with a more control of data-flow being in the library, which creates a better DX for you.
- Prefer **specific factories** in many cases to delegate data-flow control to the library.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
"nx": "14.7.5",
"prettier": "^2.6.2",
"react-test-renderer": "18.2.0",
"rollup": "^3.4.0",
"rollup-plugin-dts": "^5.0.0",
"semver-parser": "^4.0.1",
"size-limit": "^7.0.8",
"solid-testing-library": "^0.3.0",
Expand All @@ -63,7 +65,7 @@
"vite-tsconfig-paths": "^3.5.1",
"vitepress": "1.0.0-alpha.28",
"vitepress-plugin-mermaid": "^2.0.8",
"vitest": "^0.23.4",
"vitest": "^0.25.3",
"vue": "^3.2.37"
},
"dependencies": {
Expand Down
6 changes: 6 additions & 0 deletions packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @farfetched/core

## 0.3.5

### Patch Changes

- 7cc84da: Fix incorrect cache saving in `cache` on _Query_ with `mapData`

## 0.3.4

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@farfetched/core",
"version": "0.3.4",
"version": "0.3.5",
"type": "commonjs",
"dependencies": {
"@farfetched/misc": "0.1.2"
Expand Down
14 changes: 13 additions & 1 deletion packages/core/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
"sourceRoot": "packages/core/src",
"projectType": "library",
"targets": {
"pack": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"command": "node tools/scripts/typepack.mjs --package core"
},
"dependsOn": [
{
"projects": "self",
"target": "build"
}
]
},
"build": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
Expand All @@ -24,7 +36,7 @@
"dependsOn": [
{
"projects": "self",
"target": "build"
"target": "pack"
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion packages/misc/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { createDefer, type Defer } from './lib/defer';
export { createDefer, type Defer } from './src/defer';
14 changes: 13 additions & 1 deletion packages/misc/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
"sourceRoot": "packages/misc/src",
"projectType": "library",
"targets": {
"pack": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"command": "node tools/scripts/typepack.mjs --package misc"
},
"dependsOn": [
{
"projects": "self",
"target": "build"
}
]
},
"build": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
Expand All @@ -24,7 +36,7 @@
"dependsOn": [
{
"projects": "self",
"target": "build"
"target": "pack"
}
]
},
Expand Down
File renamed without changes.
File renamed without changes.
14 changes: 13 additions & 1 deletion packages/react/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
"projectType": "library",
"tags": [],
"targets": {
"pack": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"command": "node tools/scripts/typepack.mjs --package react"
},
"dependsOn": [
{
"projects": "self",
"target": "build"
}
]
},
"build": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
Expand All @@ -25,7 +37,7 @@
"dependsOn": [
{
"projects": "self",
"target": "build"
"target": "pack"
}
]
},
Expand Down
14 changes: 13 additions & 1 deletion packages/runtypes/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
"sourceRoot": "packages/runtypes/src",
"projectType": "library",
"targets": {
"pack": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"command": "node tools/scripts/typepack.mjs --package runtypes"
},
"dependsOn": [
{
"projects": "self",
"target": "build"
}
]
},
"build": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
Expand All @@ -24,7 +36,7 @@
"dependsOn": [
{
"projects": "self",
"target": "build"
"target": "pack"
}
]
},
Expand Down
14 changes: 13 additions & 1 deletion packages/solid/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
"projectType": "library",
"tags": [],
"targets": {
"pack": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"command": "node tools/scripts/typepack.mjs --package solid"
},
"dependsOn": [
{
"projects": "self",
"target": "build"
}
]
},
"build": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
Expand All @@ -25,7 +37,7 @@
"dependsOn": [
{
"projects": "self",
"target": "build"
"target": "pack"
}
]
},
Expand Down
14 changes: 13 additions & 1 deletion packages/zod/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
"sourceRoot": "packages/zod/src",
"projectType": "library",
"targets": {
"pack": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"command": "node tools/scripts/typepack.mjs --package zod"
},
"dependsOn": [
{
"projects": "self",
"target": "build"
}
]
},
"build": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
Expand All @@ -24,7 +36,7 @@
"dependsOn": [
{
"projects": "self",
"target": "build"
"target": "pack"
}
]
},
Expand Down
Loading

0 comments on commit 68b76f5

Please sign in to comment.