From cddcdbeab50160ae15c65d4af6063044cac1e0a0 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Fri, 30 Aug 2024 16:34:09 -0400 Subject: [PATCH 1/6] Added context documentation --- www/assets/images/overriding-context.svg | 13 ++ www/docs/context.mdx | 158 +++++++++++++++++++++++ www/docs/structure.json | 3 +- 3 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 www/assets/images/overriding-context.svg create mode 100644 www/docs/context.mdx diff --git a/www/assets/images/overriding-context.svg b/www/assets/images/overriding-context.svg new file mode 100644 index 000000000..da279718a --- /dev/null +++ b/www/assets/images/overriding-context.svg @@ -0,0 +1,13 @@ + + + + + + + + yield* MyContext.set('A')yield* MyContext.set('B');yield* MyContext;BByield* MyContext;yield* MyContextBAyield* MyContext.set('C');yield* MyContext;CCA \ No newline at end of file diff --git a/www/docs/context.mdx b/www/docs/context.mdx new file mode 100644 index 000000000..de368fb2d --- /dev/null +++ b/www/docs/context.mdx @@ -0,0 +1,158 @@ +Usually, you can pass information from a parent operation to a child operation +via function argument or lexical scope. But passing function arguments can become +verbose and inconvenient when the you have to pass them through many operations in +the middle, or if many operations need the same information. Likewise, passing information +via lexical scope is only possible if you define the child operation in the body of the +parent operation. `Context` lets the parent operation make some information available to any +operation in the tree below it-no matter how deep-without passing it explicitely through function +arguments or lexical scope. + +> 💁 If you're familiar with React Context, you already know most of +> what you need to know about Effection Context. The biggest difference +> is the API but general concepts are same. + +# What is argument drilling? + +Passing argument to operations is a convenient way to make data from parent operation available to the child operation. + +But passing arguments can become inconvenient when the child operation is nested deeply in the tree of operations, or +the same arguments need to be passed to many operations. This situation is called "argument drilling". + +Wouldn't it be great if you could access information in a deeply nested operation from a parent operation without +modifying operations in the middle? That's exaclty what Effection Context does. + +# Context: an alternative to passing arguments + +Context makes a value available to any child process within a tree of processes. + +``` typescript +import { createContext, main } from 'effection'; + +// 1. create the context +const GithubTokenContext = createContext("token"); + +await main(function* () { + // 2. set the context value + yield* TokenContext.set("gha-1234567890"); + + yield* fetchGithubData(); +}) + +function* fetchGithubData() { + yield* fetchRepositories(); +} + +function* fetchRepositories() { + // 3. use the context value in a child operation + const token = yield* GithubTokenContext; + + console.log(token); + // -> gha-1234567890 +} +``` + +# Context: overriding nested context + +Context is attached to the scope of the parent operation. This allows you to override the context value of an operation +in the tree without affecting the value higher up the tree. + +
+ ![Parent sets value to A, Child overrides value to B and all children below get B](/assets/images/overriding-context.svg) +
+ +# Using Context + +To use context in your operations, you need to do the following 3 steps: +1. **Create** a context. +2. **Set** the context value. +3. **Yield** the context value. + +## Step 1: Create a context. + +Effection provides a function for creating a context appropriatelly called `createContext`. This function will return +a reference that you use to identify the context value in the scope. + +``` javascript +import { createContext } from 'effection' + +const MyValueContext = createContext("my-value"); +``` + +## Step 2: Set the context value. + +``` javascript +await main(function* () { + yield* MyValueContext.set("Hello World!"); +}); +``` + +## Step 3: Yield the context value. + +```javascript + +await main(function* () { + yield* MyValueContext.set("Hello World!"); + + yield* logMyValue(); +}); + +function* logMyValue() { + const value = yield* MyValueContext; + + console.log(value); +} +``` + +# Use cases for context + +* **Config**: most apps written with Effection require values that come from the environment variables. Use can use context to store values from configuration to easily retrieve it in any operation. +* **Client APIs**: many APIs require creating a client instance. You can create the client instance once and make it available to all operations using context. + +# Naming conventions + +The following is absolutely optional, but if you don't know how to organize your context related code, try the following: + +## `Context` to the end of the context reference + +This makes it easy to identify context references. For example, a config context might be called `ConfigContext`. + +``` javascript +import { createContext } from 'effection'; + +const ConfigContext = createContext('config'); +``` + +## Provide an `init` function for initializing context + +You'll typically call this once at the beginning of your program. + +```javascript +import { main } from 'effection'; + +function* initConfigContext() { + yield* ConfigContext.set({ + mySecret: process.env.MY_SECRET + }) +} + +await main(function* () { + yield* initConfigContext(); +}); +``` + +## Provide a `use` helper for reading the context + +Provide a helper for retrieving the value from the context with `use` prefix. + +``` javascript +function* useConfig() { + return yield* ConfigContext; +} + +function* someOperation() { + const config = yield* useConfig(); +} +``` + +[scope]: /docs/guides/scope +[React Context]: https://react.dev/learn/passing-data-deeply-with-context \ No newline at end of file diff --git a/www/docs/structure.json b/www/docs/structure.json index 9eb7a54d1..09b1640a2 100644 --- a/www/docs/structure.json +++ b/www/docs/structure.json @@ -13,7 +13,8 @@ ["spawn.mdx", "Spawn"], ["collections.mdx", "Streams and Subscriptions"], ["events.mdx", "Events"], - ["errors.mdx", "Error Handling"] + ["errors.mdx", "Error Handling"], + ["context.mdx", "Context"] ], "Advanced": [ ["scope.mdx", "Scope"], From aa73f398fc3288c61ee9201abfc04070f79b0c69 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Fri, 30 Aug 2024 18:44:20 -0400 Subject: [PATCH 2/6] Fix the styling of the TOC --- www/docs/context.mdx | 30 +++---- www/routes/docs-route.tsx | 181 ++++++++++++++++++++++++++++---------- 2 files changed, 150 insertions(+), 61 deletions(-) diff --git a/www/docs/context.mdx b/www/docs/context.mdx index de368fb2d..33835798f 100644 --- a/www/docs/context.mdx +++ b/www/docs/context.mdx @@ -11,7 +11,7 @@ arguments or lexical scope. > what you need to know about Effection Context. The biggest difference > is the API but general concepts are same. -# What is argument drilling? +## What is argument drilling? Passing argument to operations is a convenient way to make data from parent operation available to the child operation. @@ -21,7 +21,7 @@ the same arguments need to be passed to many operations. This situation is calle Wouldn't it be great if you could access information in a deeply nested operation from a parent operation without modifying operations in the middle? That's exaclty what Effection Context does. -# Context: an alternative to passing arguments +## Context: an alternative to passing arguments Context makes a value available to any child process within a tree of processes. @@ -51,7 +51,7 @@ function* fetchRepositories() { } ``` -# Context: overriding nested context +## Context: overriding nested context Context is attached to the scope of the parent operation. This allows you to override the context value of an operation in the tree without affecting the value higher up the tree. @@ -60,14 +60,14 @@ in the tree without affecting the value higher up the tree. ![Parent sets value to A, Child overrides value to B and all children below get B](/assets/images/overriding-context.svg) -# Using Context +## Using Context To use context in your operations, you need to do the following 3 steps: 1. **Create** a context. 2. **Set** the context value. 3. **Yield** the context value. -## Step 1: Create a context. +### Step 1: Create a context. Effection provides a function for creating a context appropriatelly called `createContext`. This function will return a reference that you use to identify the context value in the scope. @@ -78,7 +78,7 @@ import { createContext } from 'effection' const MyValueContext = createContext("my-value"); ``` -## Step 2: Set the context value. +### Step 2: Set the context value. ``` javascript await main(function* () { @@ -86,7 +86,7 @@ await main(function* () { }); ``` -## Step 3: Yield the context value. +### Step 3: Yield the context value. ```javascript @@ -103,18 +103,18 @@ function* logMyValue() { } ``` -# Use cases for context +## Use cases for context * **Config**: most apps written with Effection require values that come from the environment variables. Use can use context to store values from configuration to easily retrieve it in any operation. * **Client APIs**: many APIs require creating a client instance. You can create the client instance once and make it available to all operations using context. -# Naming conventions +## Naming conventions The following is absolutely optional, but if you don't know how to organize your context related code, try the following: -## `Context` to the end of the context reference +### For the reference -This makes it easy to identify context references. For example, a config context might be called `ConfigContext`. +Add `Context` to the end of the context reference. This makes it easy to identify context references. For example, a config context might be called `ConfigContext`. ``` javascript import { createContext } from 'effection'; @@ -122,9 +122,9 @@ import { createContext } from 'effection'; const ConfigContext = createContext('config'); ``` -## Provide an `init` function for initializing context +### For the initializer -You'll typically call this once at the beginning of your program. +Provide an `init` function for initializing context. You'll typically call this once at the beginning of your program. ```javascript import { main } from 'effection'; @@ -140,9 +140,9 @@ await main(function* () { }); ``` -## Provide a `use` helper for reading the context +### For the use helper -Provide a helper for retrieving the value from the context with `use` prefix. +Provide a `use` helper for reading the context. Provide a helper for retrieving the value from the context with `use` prefix. ``` javascript function* useConfig() { diff --git a/www/routes/docs-route.tsx b/www/routes/docs-route.tsx index b912c005a..2e57ad306 100644 --- a/www/routes/docs-route.tsx +++ b/www/routes/docs-route.tsx @@ -17,6 +17,8 @@ import { IconGithub } from "../components/icons/github.tsx"; import { IconDiscord } from "../components/icons/discord.tsx"; import { ProjectSelect } from "../components/project-select.tsx"; import { Navburger } from "../components/navburger.tsx"; +import { type HtmlElementNode } from "npm:@jsdevtools/rehype-toc@3.0.2"; +import { nodeTypes } from "npm:@mdx-js/mdx@2.3.0"; export function docsRoute(docs: Docs): JSXHandler { return function* () { @@ -33,39 +35,42 @@ export function docsRoute(docs: Docs): JSXHandler { let AppHtml = yield* useAppHtml({ title: `${doc.title} | Effection` }); return ( - Guides, - API, - - - - - - , - - - - - - , - , - <> -

- -

- - - ]}> + + , + ]} + >