diff --git a/apps/website/docs/.vitepress/config.js b/apps/website/docs/.vitepress/config.js index 4eb138c8..03c12168 100644 --- a/apps/website/docs/.vitepress/config.js +++ b/apps/website/docs/.vitepress/config.js @@ -326,7 +326,6 @@ export default withMermaid( text: 'Deep dive', collapsed: false, items: [ - { text: 'Unique store identifiers', link: '/recipes/sids' }, { text: 'Data flow in Remote Operation', link: '/recipes/data_flow', diff --git a/apps/website/docs/recipes/cache.md b/apps/website/docs/recipes/cache.md index ead582ab..25b07979 100644 --- a/apps/website/docs/recipes/cache.md +++ b/apps/website/docs/recipes/cache.md @@ -30,7 +30,7 @@ To achieve this, Every [_Query_](/api/primitives/query) exposes `.__.lowLevelAPI ## Cache key generation -[`cache`](/api/operators/cache) does not require any manual key generation to work, it uses the [SID](/recipes/sids) of the [_Query_](/api/primitives/query) and all external [_Stores_](https://effector.dev/en/api/effector/store/) that affect [_Query_](/api/primitives/query) to create a unique identifier for every cache entry. It means, key generation is fully automatic, and you don't need to worry about it. +[`cache`](/api/operators/cache) does not require any manual key generation to work, it uses the [SID](https://effector.dev/en/explanation/sids/) of the [_Query_](/api/primitives/query) and all external [_Stores_](https://effector.dev/en/api/effector/store/) that affect [_Query_](/api/primitives/query) to create a unique identifier for every cache entry. It means, key generation is fully automatic, and you don't need to worry about it. ### Sources extraction @@ -86,7 +86,7 @@ Static nature of [Effector](/statements/effector) allows us to perform this tran ### SID -Every [_Query_](/api/primitives/query) has a unique identifier — [SID](/recipes/sids). Effector provides a couple of plugins for automatic SIDs generation. +Every [_Query_](/api/primitives/query) has a unique identifier — [SID](https://effector.dev/en/explanation/sids/). Effector provides a couple of plugins for automatic SIDs generation. diff --git a/apps/website/docs/recipes/form_data.md b/apps/website/docs/recipes/form_data.md index 4c562de1..abc465d9 100644 --- a/apps/website/docs/recipes/form_data.md +++ b/apps/website/docs/recipes/form_data.md @@ -159,7 +159,7 @@ const uploadPhotoMutation = createUploadFileMutation(); ### SSR, `cache` and DevTools support ::: tip Deep dive -If you want to learn more about the reasons behind this requirement, please read [this article](/recipes/sids). +If you want to learn more about the reasons behind this requirement, please read [this article](https://effector.dev/en/explanation/sids/). ::: If you use Farfetched in SSR, want to use [DevTools](/tutorial/devtools) or [`cache`](/api/operators/cache), you need to provide a unique name for each [_Mutation_](/api/primitives/mutation). It can be done by passing the `name` option to the [`createMutation`](/api/factories/create_mutation) factory: diff --git a/apps/website/docs/recipes/graphql_query.md b/apps/website/docs/recipes/graphql_query.md index ffed955f..ee4c5724 100644 --- a/apps/website/docs/recipes/graphql_query.md +++ b/apps/website/docs/recipes/graphql_query.md @@ -29,7 +29,7 @@ function createGraphQLQuery(config) { ``` :::tip -Do not forget to add path of the factory to a factories list in the [code transformations configuration](/recipes/sids). +Do not forget to add path of the factory to a factories list in the [code transformations configuration](https://effector.dev/en/explanation/sids/). ::: Now, we have to write code with mapping from config of `createGraphQLQuery` to config of `createJsonQuery`. diff --git a/apps/website/docs/recipes/server_cache.md b/apps/website/docs/recipes/server_cache.md index 5f1074e4..dc62e79a 100644 --- a/apps/website/docs/recipes/server_cache.md +++ b/apps/website/docs/recipes/server_cache.md @@ -10,7 +10,7 @@ Let's talk about [caching](/tutorial/caching) and [SSR](/recipes/ssr) in the sin ## Pre-requisites -Do not forget that [`cache`](/api/operators/cache) operator requires setting up [SIDs](/recipes/sids) in your application. It can be done by using code transformation tools. +Do not forget that [`cache`](/api/operators/cache) operator requires setting up [SIDs](https://effector.dev/en/explanation/sids/) in your application. It can be done by using code transformation tools. diff --git a/apps/website/docs/recipes/sids.md b/apps/website/docs/recipes/sids.md index 2ac0f022..1cf9ff00 100644 --- a/apps/website/docs/recipes/sids.md +++ b/apps/website/docs/recipes/sids.md @@ -1,228 +1,3 @@ # SIDs -Farfetched [uses Effector](/statements/effector) that is based on idea of atomic store. It means that any application does not have some centralized state controller or other entry point to collect all states in one place. - -So, there is the question — how to distinguish units between different environments? For example, if we ran an application on the server and serialize its state to JSON, how do we know which part of the JSON should be filled in a particular store on the client? - -Let's discuss how this problem solved by other state managers. - -## Other state managers - -### Single store - -In the state manager with single store (e.g. Redux), this problem does not exist at all. It is a single store, which can be serialized and deserialized without any additional information. - -:::info -Actually, single store forces you to create unique names of each part of it implicitly. In any object you won't be able to create duplicate keys, so the path to store slice is a unique identifier of this slice. -::: - -```ts -// server.ts -import { createStore } from 'single-store-state-manager'; - -function handlerRequest() { - const store = createStore({ initialValue: null }); - - return { - // It is possible to just serialize the whole store - state: JSON.stringify(store.getState()), - }; -} - -// client.ts -import { createStore } from 'single-store-state-manager'; - -// Let's assume that server put the state into the HTML -const serverState = readServerStateFromWindow(); - -const store = createStore({ - // Just parse the whole state and use it as client state - initialValue: JSON.parse(serverState), -}); -``` - -It's great that you do not need any additional tools for serialization and deserialization, but single store has a few problems: - -- It does not support tree-shaking and code-splitting, you have to load the whole store anyway -- Because its architecture, it requires some additional tools for fixing performance (like `reselect`) -- It does not support any kind of micro-frontends and stuff which is getting bigger recently - -### Multi stores - -Unfortunately, state managers that built around idea of multi stores do not solve this problem good. Some tools offer single store like solutions (MobX), some does not try to solve this issue at all (Recoil, Zustand). - -:::info -E.g., the common pattern to solve serialization problem in MobX is [Root Store Pattern](https://dev.to/ivandotv/mobx-root-store-pattern-with-react-hooks-318d) which is destroying the whole idea of multi stores. -::: - -So, we are considering SSR as a first class citizen of modern web applications, and we are going to support code-splitting or micro-frontends. It is the reason why Farfetched uses [Effector under the hood](/statements/effector). - -## Unique identifiers for every store - -Because of multi-store architecture, Effector requires a unique identifier for every store. It is a string that is used to distinguish stores between different environments. In Effector's world this kind of strings are called `sid`. - -:::tip TL;DR - -`sid` is a unique identifier of a store. It is used to distinguish stores between different environments. - -::: - -Let's add it to some stores: - -```ts -const $name = createStore(null, { sid: 'name' }); -const $age = createStore(null, { sid: 'age' }); -``` - -Now, we can serialize and deserialize stores: - -```ts -// server.ts -async function handlerRequest() { - // create isolated instance of application - const scope = fork(); - - // fill some data to stores - await allSettled($name, { scope, params: 'Igor' }); - await allSettled($age, { scope, params: 25 }); - - const state = JSON.serialize(serialize(scope)); - // -> { "name": "Igor", "age": 25 } - - return { state }; -} -``` - -After this code, we have a serialized state of our application. It is a plain object with stores' values. We can put it back to the stores on the client: - -```ts -// Let's assume that server put the state into the HTML -const serverState = readServerStateFromWindow(); - -const scope = fork({ - // Just parse the whole state and use it as client state - values: JSON.parse(serverState), -}); -``` - -Of course, it's a lot of boring jobs to write `sid` for every store. Effector provides a way to do it automatically with code transformation plugins. - -## Plugins for code transformations - - - -## How plugins work under the hood - -Both plugins use the same approach to code transformation. Basically they do two things: - -1. add `sid`-s and meta-information to raw Effector's units creation, like `createStore` or `createUnit` -2. wrap custom factories with `withFactory` helper that allow to make `sid`-s of inner units unique as well - -Let's take a look at the first point. For the following source code: - -```ts -const $name = createStore(null); -``` - -Plugin will apply these transformations: - -```ts -const $name = createStore(null, { sid: 'j3l44' }); -``` - -:::tip -Plugins create `sid`-s as a hash of the file path and the line number where the unit was created. It allows making `sid`-s unique and stable. -::: - -So, the second point is about custom factories. For example, we have a custom factory for creating stores: - -```ts -function nameStore() { - const $name = createStore(null); - - return { $name }; -} - -const personOne = nameStore(); -const personTwo = nameStore(); -``` - -First, plugin will add `sid`-s to inner units: - -```ts -function nameStore() { - const $name = createStore(null, { sid: 'ffds2' }); - - return { $name }; -} - -const personOne = nameStore(); -const personTwo = nameStore(); -``` - -But it's not enough. Because we can create two instances of `nameStore`, and they will have the same `sid`-s. So, plugin will wrap `nameStore` with `withFactory` helper: - -```ts -function nameStore() { - const $name = createStore(null, { sid: 'ffds2' }); - - return { $name }; -} - -const personOne = withFactory({ - sid: 'gre24f', - fn: () => nameStore(), -}); -const personTwo = withFactory({ - sid: 'lpefgd', - fn: () => nameStore(), -}); -``` - -Now, `sid`-s of inner units are unique, and we can serialize and deserialize them. - -```ts -personOne.$name.sid; // gre24f|ffds2 -personTwo.$name.sid; // lpefgd|ffds2 -``` - -### How `withFactory` works - -`withFactory` is a helper that allows to create unique `sid`-s for inner units. It is a function that accepts an object with `sid` and `fn` properties. `sid` is a unique identifier of the factory, and `fn` is a function that creates units. - -Internal implementation of `withFactory` is pretty simple, it puts received `sid` to the global scope before `fn` call, and removes it after. Any Effector's creator function tries to read this global value while creating and append its value to the `sid` of the unit. - -```ts -let globalSid = null; - -function withFactory({ sid, fn }) { - globalSid = sid; - - const result = fn(); - - globalSid = null; - - return result; -} - -function createStore(initialValue, { sid }) { - if (globalSid) { - sid = `${globalSid}|${sid}`; - } - - // ... -} -``` - -Because of single thread nature of JavaScript, it is safe to use global variables for this purpose. - -:::info -Of course, the real implementation is a bit more complicated, but the idea is the same. -::: - -## Summary - -1. Any multi-store state manager requires unique identifiers for every store to distinguish them between different environments. -2. In Effector's world this kind of strings are called `sid`. -3. Plugins for code transformations add `sid`-s and meta-information to raw Effector's units creation, like `createStore` or `createEvent`. -4. Plugins for code transformations wrap custom factories with `withFactory` helper that allow to make `sid`-s of inner units unique as well. +Article is moved to [official documentation of Effector](https://effector.dev/en/explanation/sids/). diff --git a/apps/website/docs/recipes/ssr.md b/apps/website/docs/recipes/ssr.md index f61ef01d..77a6449c 100644 --- a/apps/website/docs/recipes/ssr.md +++ b/apps/website/docs/recipes/ssr.md @@ -5,7 +5,7 @@ One of the most important and difficult to implement part of SSR is data-fetchin Farfetched is based on [Effector](https://effector.dev), that have an [excellent support of SSR](https://dev.to/effector/the-best-part-of-effector-4c27), so you can handle data-fetching easily if you follow some simple rules: - [**do not** start fetching in render-cycle](/statements/render_as_you_fetch.md) -- **do** use [Fork API in Effector](https://effector.dev/en/api/effector/fork/) and make sure that your application has correct [SIDs](/recipes/sids) +- **do** use [Fork API in Effector](https://effector.dev/en/api/effector/fork/) and make sure that your application has correct [SIDs](https://effector.dev/en/explanation/sids/) - **do** use [Effector](https://effector.dev) operators to express your control flow That is it, just start your application on server and wait for all computation finished: diff --git a/apps/website/docs/recipes/testing.md b/apps/website/docs/recipes/testing.md index fd9d200b..94172d08 100644 --- a/apps/website/docs/recipes/testing.md +++ b/apps/website/docs/recipes/testing.md @@ -1,7 +1,7 @@ # Unit tests ::: info -For better testing experience, we recommend reading article about [SIDs](/recipes/sids) +For better testing experience, we recommend reading article about [SIDs](https://effector.dev/en/explanation/sids/) ::: Farfetched offers a couple of features to simplify writing unit tests. diff --git a/apps/website/docs/recipes/vite.md b/apps/website/docs/recipes/vite.md index bcb9e9fe..6f5423f8 100644 --- a/apps/website/docs/recipes/vite.md +++ b/apps/website/docs/recipes/vite.md @@ -1,7 +1,7 @@ # Vite :::info -Farfetched does not require any special configuration for Vite for basic usage. However, if you want to use advanced features like [`cache`](/api/operators/cache) or [server-side rendering](/recipes/ssr), you need to configure Vite. Detailed explanation of the reasons is available in [deep-dive article](/recipes/sids). +Farfetched does not require any special configuration for Vite for basic usage. However, if you want to use advanced features like [`cache`](/api/operators/cache) or [server-side rendering](/recipes/ssr), you need to configure Vite. Detailed explanation of the reasons is available in [deep-dive article](https://effector.dev/en/explanation/sids/). ::: [Vite](https://vitejs.dev/) uses ESBuild under the hood, which does not allow to use its internal AST in the plugins. To apply custom transformations to the code one must use either [Babel](https://github.com/owlsdepartment/vite-plugin-babel) or [SWC](https://github.com/egoist/unplugin-swc), which are allowing custom AST-transformations. diff --git a/apps/website/docs/tutorial/install.md b/apps/website/docs/tutorial/install.md index 45e679f0..28c0ffc9 100644 --- a/apps/website/docs/tutorial/install.md +++ b/apps/website/docs/tutorial/install.md @@ -45,4 +45,4 @@ For some advanced usage, like [`cache`](/api/operators/cache) or [server-side re ### Deep dive -If you are interested in how code transformations works under the hood and why they are required for some use cases, you can dive into [advanced article about SIDs](/recipes/sids). +If you are interested in how code transformations works under the hood and why they are required for some use cases, you can dive into [advanced article about SIDs](https://effector.dev/en/explanation/sids/). diff --git a/packages/core/src/cache/key/key.ts b/packages/core/src/cache/key/key.ts index d868d2a6..c4246284 100644 --- a/packages/core/src/cache/key/key.ts +++ b/packages/core/src/cache/key/key.ts @@ -50,7 +50,7 @@ export function queryUniqId(query: Query) { } throw new Error( - 'Query does not have sid or uniq name, which is required for caching, read more https://ff.effector.dev/recipes/sids.html' + 'Query does not have sid or uniq name, which is required for caching, read more https://effector.dev/en/explanation/sids/' ); }