Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"name": "@effection/effection",
"exports": "./mod.ts",
"license": "ISC",
"publish": { "include": ["lib", "mod.ts", "experimental.ts", "README.md"] },
"lock": false,
Expand Down
3 changes: 1 addition & 2 deletions lib/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import { trap } from "./task.ts";
*
* If any of the operations become errored, then `all` will also become errored.
*
* ### Example
*
* @example
* ``` javascript
* import { all, expect, main } from 'effection';
*
Expand Down
51 changes: 51 additions & 0 deletions lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,33 @@ import type { Api, Context, Operation, Scope } from "./types.ts";
import { createApiInternal } from "./api-internal.ts";
import type { ScopeInternal } from "./scope-internal.ts";

/**
* Create a new {@link Api}. This is the constructor behind
* middleware decoration used through core such as with {@link Scope#around}.
* One may implement an API around any operation or value and then decorate it per-scope.
*
* @example
* ```ts
* import { createApi, type Operation } from "effection/experimental";
*
* interface DatabaseApi {
* query(sql: string): Operation<{ id: number; title: string }[]>;
* }
*
* // pass in the generic type or allow it to infer
* let Database = createApi<DatabaseApi>("database", {
* *query(sql) {
* console.log("running", sql);
* return [];
* },
* });
* ```
*
* @param name - the API identifier used in error and debug messages
* @param core - the core function/value implementations for this API
* @returns an {@link Api} that can be invoked and decorated per-scope
* @since 4.1
*/
export function createApi<A extends {}>(name: string, core: A): Api<A> {
return createApiInternal(name, core);
}
Expand All @@ -23,6 +50,30 @@ interface Apis {
Main: Api<MainApi>;
}

/**
* Built-in internal APIs used by Effection's runtime and host integration.
* Advanced integrations can decorate these APIs in a scope.
*
* @example
* ```ts
* import { run, useScope } from "effection";
* import { api } from "effection/experimental";
*
* await run(function* () {
* let scope = yield* useScope();
*
* // Observe every scope creation in this scope subtree
* scope.around(api.Scope, {
* create(args, next) {
* console.log("creating scope");
* return next(...args);
* },
* });
* });
* ```
*
* @since 4.1
*/
export const api: Apis = {
Scope: createApi<ScopeApi>("Scope", {
create() {
Expand Down
29 changes: 29 additions & 0 deletions lib/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ import { call } from "./call.ts";
*
* This allows you to consume any `AsyncIterator` as a {@link Subscription}.
*
* @example
* ```ts
* import { subscribe } from "effection";
*
* let response = await fetch("https://example.com/data.bin");
* let iterator = response.body?.[Symbol.asyncIterator]();
*
* if (!iterator) throw new Error("response has no body");
*
* let subscription = subscribe(iterator);
* let first = yield* subscription.next();
* if (!first.done) {
* console.log(first.value); // Uint8Array chunk
* }
* ```
*
* @param iter - the iterator to convert
* @returns a subscription that will produce each item of `iter`
* @since 3.0
Expand All @@ -22,6 +38,19 @@ export function subscribe<T, R>(iter: AsyncIterator<T, R>): Subscription<T, R> {
*
* This allows you to consume any `AsyncIterable` as a {@link Stream}.
*
* @example
* ```ts
* import { each, stream, until } from "effection";
*
* let response = yield* until(fetch("https://example.com/data.bin"));
* if (!response.body) throw new Error("response has no body");
*
* for (let chunk of yield* each(stream(response.body))) {
* console.log(chunk.byteLength);
* yield* each.next();
* }
* ```
*
* @param iterable - the async iterable to convert
* @returns a stream that will produce each item of `iterable`
* @since 3.0
Expand Down
5 changes: 3 additions & 2 deletions lib/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { Operation } from "./types.ts";
* APIs that accept `Callable` values allow end developers to pass simple
* functions without necessarily needing to know anything about Operations.
*
* @example
* ```javascript
* function hello(to: Callable<string>): Operation<string> {
* return function*() {
Expand All @@ -21,7 +22,7 @@ import type { Operation } from "./types.ts";
*
* await run(() => hello(() => "world!")); // => "hello world!"
* await run(() => hello(async () => "world!")); // => "hello world!"
* await run(() => hello(function*() { return "world!" })); "hello world!";
* await run(() => hello(function*() { return "world!" })); // => "hello world!"
* ```
* @since 3.0
*/
Expand Down Expand Up @@ -61,7 +62,7 @@ export interface Callable<
*
* The function will be invoked anew every time that the `call()` operation is evaluated.
*
* @param callable - the operation, promise, async function, generator funnction,
* @param callable - the operation, promise, async function, generator function,
* or plain function to call as part of this operation
*
* @returns an {@link Operation} that evaluates to the result of executing the function to completion
Expand Down
9 changes: 9 additions & 0 deletions lib/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ import { lift } from "./lift.ts";
* via the same {@link Stream}, and messages sent to the channel are
* received by all consumers. The channel is not buffered, so if there
* are no consumers, the message is dropped.
*
* @example
* ```ts
* let channel = createChannel<string>();
* let subscription = yield* channel;
*
* yield* channel.send("hello");
* console.log(yield* subscription.next()); // { done: false, value: "hello" }
* ```
* @since 3.0
*/
export interface Channel<T, TClose> extends Stream<T, TClose> {
Expand Down
16 changes: 16 additions & 0 deletions lib/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ import { Do } from "./do.ts";
/**
* Create a new {@link Context}
*
* @example
* ```ts
* import { createContext, main } from "effection";
*
* const YourContext = createContext<string>("context-id");
*
* await main(function* () {
* yield* YourContext.with("abc-123", function* () {
* console.log(yield* YourContext.expect()); // "abc-123"
* });
* });
* ```
*
* @example
*
*
* @param name - the unique name to give this context.
* @returns the new context
* @since 3.0
Expand Down
2 changes: 2 additions & 0 deletions lib/each.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { withResolvers } from "./with-resolvers.ts";
* Given any stream, you can access its values sequentially using the `each()`
* operation just as you would use `for await of` loop with an async iterable:
*
* @example
* ```javascript
* function* logvalues(stream) {
* for (let value of yield* each(stream)) {
Expand All @@ -18,6 +19,7 @@ import { withResolvers } from "./with-resolvers.ts";
* }
* }
* ```
*
* You must always invoke `each.next` at the end of each iteration of the loop,
* including if the interation ends with a `continue` statement.
*
Expand Down
21 changes: 21 additions & 0 deletions lib/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export type EventList<T> = T extends {
* Create an {@link Operation} that yields the next event to be emitted by an
* [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget).
*
* @example
* ```ts
* // wait for connection establishment before proceeding
* yield* once(socket, "open");
* ```
*
* @param target - the event target to be watched
* @param name - the name of the event to watch. E.g. "click"
* @returns an Operation that yields the next emitted event
Expand All @@ -48,6 +54,21 @@ export function once<
* See the guide on [Streams and Subscriptions](https://frontside.com/effection/docs/collections)
* for details on how to use streams.
*
* @example
* ```ts
* import { each, on, once, spawn } from "effection";
*
* // handle socket errors in a concurrent child task
* yield* spawn(function* () {
* throw yield* once(socket, "error");
* });
*
* for (let event of yield* each(on(socket, "message"))) {
* console.log(event.data);
* yield* each.next();
* }
* ```
*
* @param target - the event target whose events will be streamed
* @param name - the name of the event to stream. E.g. "click"
* @returns a stream that will see one item for each event
Expand Down
2 changes: 2 additions & 0 deletions lib/interval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Stream } from "./types.ts";
/**
* Consume an interval as an infinite stream.
*
* @example
* ```ts
* let startTime = Date.now();
*
Expand All @@ -14,6 +15,7 @@ import type { Stream } from "./types.ts";
* yield* each.next();
* }
* ```
*
* @param milliseconds - how long to delay between each item in the stream
* @since 3.6
*/
Expand Down
3 changes: 2 additions & 1 deletion lib/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import { api } from "./api.ts";
* Halt process execution immediately and initiate shutdown. If a message is
* provided, it will be logged to the console after shutdown:
*
* @example
* ```js
* if (invalidArgs()) {
* yield* exit(5, "invalid arguments")
* }
* ```
* @param status - the exit code to use for the process exit
* @param message - message to print to the console before exiting.
* @param returns an operation that exits the program
* @returns an operation that exits the program
* @since 3.0
*/
export function* exit(status: number, message?: string): Operation<void> {
Expand Down
11 changes: 11 additions & 0 deletions lib/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ import { action } from "./action.ts";
* or a {@link Channel} as the mechanism, but `Queue` allows you to manage
* a single subscription directly.
*
* @example
* ```ts
* let queue = createQueue<number, void>();
* queue.add(1);
*
* let first = yield* queue.next();
* if (!first.done) {
* console.log(first.value); // 1
* }
* ```
*
* @typeParam T the type of the items in the queue
* @typeParam TClose the type of the value that the queue is closed with
* @since 3.0
Expand Down
19 changes: 14 additions & 5 deletions lib/race.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,21 @@ import { Err, Ok, type Result } from "./result.ts";
* @example
*
* ```typescript
* import { main, race, fetch } from 'effection';
* import { race, sleep } from "effection";
*
* await main(function*() {
* let fastest = yield* race([fetch('http://google.com'), fetch('http://bing.com')]);
* // ...
* });
* let fastest = yield* race([
* call(function* () {
* yield* sleep(100);
* // this is halted as it loses the race
* return "slow";
* }),
* call(function* () {
* yield* sleep(10);
* return "fast";
* }),
* ]);
*
* console.log(fastest); // "fast"
* ```
*
* @param operations a list of operations to race against each other
Expand Down
29 changes: 25 additions & 4 deletions lib/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,33 @@ export function resource<T>(
}

/**
* Provide `value` to the calling operation as a resource.
* @returns an operation that suspends the resource operation until the caller is completed.
*
* @example
* ```ts
* type ConxPool = { ... } // example resource which manages a connection pool
*
* function useValue() {
* return resource(function* (provide: Provide<ConxPool>) {
* const pool: ConxPool = setupPool();
* yield* provide(pool);
* });
* }
*
* console.log(yield* useValue()); // pool
*
* // alternatively, the preferred pattern is
* function useValue() {
* return resource<ConxPool>(function* (provide) {
* const pool: ConxPool = setupPool();
* yield* provide(pool);
* });
* }
Comment on lines +89 to +104
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example doesn't provide much. A resource is about acquisition and release and so any example should demonstrate those two things. A connection pool maybe?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying not to complicate it too much, but I see the point. The main intent was for folks that land on this API page trying to understand what the value of provide is.
https://frontside.com/effection/api/v4/Provide/

Is it valid to have an expectation that they arrive there having already looked at the value of using a resource? Maybe just a simple comment or something saying as such?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they will most likely arrive by hovering on a parameter resource() so could definitely be like "what is this?"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated it. What do you think of this?

* ```
*
* @since 3.0
*/
export interface Provide<T> {
/**
* Provide `value` to the calling operation as a resource.
* @returns an operation that suspends the resource operation until the caller is completed.
*/
(value: T): Operation<void>;
}
14 changes: 14 additions & 0 deletions lib/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,20 @@ export function createScope(
/**
* Get the scope of the currently running {@link Operation}.
*
* @example
* ```ts
* import { sleep, useScope } from "effection";
*
* let scope = yield* useScope();
* let task = yield* scope.spawn(function* () {
* yield* sleep(100);
* return "done";
* });
*
* // task is attached to the current scope and cannot outlive it
* console.log(yield* task);
* ```
*
* @returns an operation yielding the current scope
* @since 3.0
*/
Expand Down
Loading
Loading