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
17 changes: 14 additions & 3 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 Expand Up @@ -37,9 +48,9 @@ export interface Queue<T, TClose> extends Subscription<T, TClose> {
*
* await main(function*() {
* let queue = createQueue<number>();
* queue.send(1);
* queue.send(2);
* queue.send(3);
* queue.add(1);
* queue.add(2);
* queue.add(3);
*
* let next = yield* queue.subscription.next();
* while (!next.done) {
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
40 changes: 36 additions & 4 deletions lib/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,44 @@ export function resource<T>(
}

/**
* Provide `value` to the calling operation as a {@link resource}. You likely will not need
* to use this interface directly; instead, it is passed as an argument to `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
*
* // preferred pattern in implementing a resource
* function useValue(): Operation<ConxPool> {
* return resource(function* (provide) {
* const pool: ConxPool = setupPool();
* yield* provide(pool);
* });
* }
*
* // alternatively if you can't type the return value
* // then prefer typing the resource argument
* function useValue() {
* return resource<ConxPool>(function* (provide) {
* const pool: ConxPool = setupPool();
* yield* provide(pool);
* });
* }
*
* // if neither pattern above is possible, then use `Provide<T>` directly
* function useValue() {
* return resource(function* (provide: Provide<ConxPool>) {
* const pool: ConxPool = setupPool();
* yield* provide(pool);
* });
* }
*
* console.log(yield* useValue()); // pool
* ```
*
* @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>;
}
Loading
Loading