Socket
Socket
Sign inDemoInstall

@effection/core

Package Overview
Dependencies
Maintainers
1
Versions
83
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@effection/core - npm Package Compare versions

Comparing version 2.0.0-side-effects.1628189696867 to 2.0.0-v2-writable-unification.1633595877341

README.md

56

CHANGELOG.md
# @effection/core
## \[2.0.0-beta.16]
- Fix a bug when using blockParent where the children are not getting halt on an explicit halt.
- [1cd9803](https://github.com/thefrontside/effection/commit/1cd98033d2641989114f9589c7d887954fa66781) Fix halting children for blockParent tasks on 2021-09-30
## \[2.0.0-beta.15]
- - [0248d79](https://github.com/thefrontside/effection/commit/0248d79a33dcfc4200b0832aba975c9cad08981e) Add package readmes on 2021-09-28
- Remove operation resolutions entirely, use Future instead
- [5f67d61](https://github.com/thefrontside/effection/commit/5f67d610324af158eba67be5600d413fc1f2ace1) Add changeset on 2021-09-29
## \[2.0.0-beta.14]
- Adjust the propagation of errors for resources to make it possible to catch errors from `init`
- [75a7248](https://github.com/thefrontside/effection/commit/75a7248ae13d1126bbcaf9b6223f348168e987d0) Catch errors thrown during resource init on 2021-09-21
- Enable support for resources in higher order operations `all`, `race` and `withTimeout`.
- [bbe6cdc](https://github.com/thefrontside/effection/commit/bbe6cdc44184a7669278d0d01ad23a2a79a69e52) Enable resource support for higher order operations on 2021-09-09
## \[2.0.0-beta.13]
- Add shortcuts to create resolved/rejected/halted futures via Future.resolve(123), etc...
- [9599dde](https://github.com/thefrontside/effection/commit/9599dde14e9bc3ba4ac7ea473e8624164727be0c) Add shortcuts for resolves/rejected/halted future on 2021-09-08
## \[2.0.0-beta.12]
- Add @effection/fetch as a dependency and reexport it
- [5ab5d06](https://github.com/thefrontside/effection/commit/5ab5d0691af75f3583de97402b5aac12325e2918) Reexport @effection/fetch from effection package on 2021-08-26
- Share internal run loop among task, task future and task controller. Prevents race conditions which cause internal errors.
- [222d511](https://github.com/thefrontside/effection/commit/222d5116c388c5b597cc3ec5e0fb64b4d22b273a) Share event loop among controller, task and future on 2021-09-01
- Introduce task scope as an alternative to resources for being able to access the outer scope of an operation
- [3ed11bd](https://github.com/thefrontside/effection/commit/3ed11bd4f5d980cd130ea894a63acb57450c5aac) Make resource task accessible through init task on 2021-08-27
- Add `toString()` method to task for nicely formatted rendering of task structure
- [9a63928](https://github.com/thefrontside/effection/commit/9a6392836704ad527d6da5195f5736462d69bef8) Add toString output for tasks on 2021-08-31
## \[2.0.0-beta.11]
- Change named import from a package.json file to default
- [65a856a](https://github.com/thefrontside/effection/commit/65a856a8d498205c27de00432fd43bc11bbb0e37) Change named import from a package.json file to default. ([#490](https://github.com/thefrontside/effection/pull/490)) on 2021-08-25
## \[2.0.0-beta.10]
- Use Object.create to wrap error objects rather than copying properties
- [a56ae2a](https://github.com/thefrontside/effection/commit/a56ae2af8a6247697b8b6253bd35b6d9e569613d) Use Object.create to create error object with trace on 2021-08-16
## \[2.0.0-beta.9]
- add `Task#spawn` operation to spawn new task with a specific scope
- [a71d65b](https://github.com/thefrontside/effection/commit/a71d65b77df5c337a78b7934edd181080eacf5bf) Add changefile on 2021-07-27
## \[2.0.0-beta.8]
- remove eslint-plugin-treeshaking
- [2779056](https://github.com/thefrontside/effection/commit/27790562cfc672d1c047b0860433b8986f7a7f5e) remove eslint-plugin-treeshaking ([#472](https://github.com/thefrontside/effection/pull/472)) on 2021-08-06
- Add sideEffects field to package.json
- [383141d](https://github.com/thefrontside/effection/commit/383141dc556c6a781d98087f3b68085d5eb31173) Add sideEffects field to package.json ([#470](https://github.com/thefrontside/effection/pull/470)) on 2021-08-05
## \[2.0.0-beta.7]

@@ -4,0 +60,0 @@

6

dist-cjs/controller/controller.d.ts
import type { Task } from '../task';
import type { RunLoop } from '../run-loop';
import type { Operation } from '../operation';

@@ -7,2 +8,3 @@ import { Future } from '../future';

operation: Operation<TOut>;
resourceTask?: Task;
start(): void;

@@ -13,6 +15,6 @@ halt(): void;

export declare type Options = {
resourceScope?: Task;
runLoop: RunLoop;
onYieldingToChange?: (task: Task | undefined) => void;
};
export declare function createController<T>(task: Task<T>, operation: Operation<T>, options?: Options): Controller<T>;
export declare function createController<T>(task: Task<T>, operation: Operation<T>, options: Options): Controller<T>;
//# sourceMappingURL=controller.d.ts.map

@@ -9,6 +9,6 @@ "use strict";

const iterator_controller_1 = require("./iterator-controller");
const resolution_controller_1 = require("./resolution-controller");
const future_controller_1 = require("./future-controller");
const resource_controller_1 = require("./resource-controller");
function createController(task, operation, options = {}) {
const future_1 = require("../future");
function createController(task, operation, options) {
if (typeof (operation) === 'function') {

@@ -26,5 +26,2 @@ return function_controller_1.createFunctionController(task, operation, () => createController(task, operation(task), options));

}
else if (predicates_1.isResolution(operation)) {
return resolution_controller_1.createResolutionController(task, operation);
}
else if (predicates_1.isPromise(operation)) {

@@ -36,5 +33,7 @@ return promise_controller_1.createPromiseController(task, operation);

}
throw new Error(`unkown type of operation: ${operation}`);
else {
return future_controller_1.createFutureController(task, future_1.Future.reject(new Error(`unkown type of operation: ${operation}`)));
}
}
exports.createController = createController;
//# sourceMappingURL=controller.js.map
import { Task } from '../task';
import { Controller } from './controller';
import { Future } from '../future';
import { Operation, OperationFunction } from '../operation';
interface FunctionController<TOut> {
readonly type: string;
readonly operation: Operation<TOut>;
future: Future<TOut>;
start: () => void;
halt: () => void;
}
export declare function createFunctionController<TOut>(task: Task<TOut>, fn: OperationFunction<TOut>, createController: () => Controller<TOut>): FunctionController<TOut>;
export {};
import { OperationFunction } from '../operation';
export declare function createFunctionController<TOut>(task: Task<TOut>, fn: OperationFunction<TOut>, createController: () => Controller<TOut>): Controller<TOut>;
//# sourceMappingURL=function-controller.d.ts.map

@@ -42,2 +42,5 @@ "use strict";

},
get resourceTask() {
return delegate === null || delegate === void 0 ? void 0 : delegate.resourceTask;
},
future,

@@ -44,0 +47,0 @@ start,

@@ -8,4 +8,4 @@ import { Controller, Options } from './controller';

}
export declare function createIteratorController<TOut>(task: Task<TOut>, iterator: OperationIterator<TOut> & Claimable, options?: Options): Controller<TOut>;
export declare function createIteratorController<TOut>(task: Task<TOut>, iterator: OperationIterator<TOut> & Claimable, options: Options): Controller<TOut>;
export {};
//# sourceMappingURL=iterator-controller.d.ts.map

@@ -6,9 +6,7 @@ "use strict";

const future_1 = require("../future");
const run_loop_1 = require("../run-loop");
const claimed = Symbol.for('effection/v2/iterator-controller/claimed');
function createIteratorController(task, iterator, options = {}) {
function createIteratorController(task, iterator, options) {
let didHalt = false;
let yieldingTo;
let { produce, future } = future_1.createFuture();
let runLoop = run_loop_1.createRunLoop();
function start() {

@@ -26,3 +24,3 @@ if (iterator[claimed]) {

function resume(iter) {
runLoop.run(() => {
options.runLoop.run(() => {
let next;

@@ -45,3 +43,3 @@ try {

else {
yieldingTo = task_1.createTask(next.value, { resourceScope: options.resourceScope || task, ignoreError: true });
yieldingTo = task_1.createTask(next.value, { scope: task.options.yieldScope || task, ignoreError: true });
yieldingTo.consume(trap);

@@ -48,0 +46,0 @@ yieldingTo.start();

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createResourceController = void 0;
const iterator_controller_1 = require("./iterator-controller");
const future_1 = require("../future");
function createResourceController(task, resource) {
let delegate;
let { resourceScope } = task.options;
let resourceTask;
let initTask;
let { scope } = task.options;
let { produce, future } = future_1.createFuture();
function start() {
if (!resourceScope) {
var _a;
if (!scope) {
throw new Error('cannot spawn resource in task which has no resource scope');
}
let init;
try {
init = resource.init(resourceScope, task);
}
catch (error) {
produce({ state: 'errored', error });
return;
}
delegate = iterator_controller_1.createIteratorController(task, init, { resourceScope });
delegate.future.consume((value) => {
let name = resource.name || ((_a = resource.labels) === null || _a === void 0 ? void 0 : _a.name) || 'resource';
let labels = resource.labels || {};
resourceTask = scope.run(undefined, { type: 'resource', labels: { ...labels, name } });
initTask = resourceTask.run((task) => resource.init(resourceTask, task), {
yieldScope: resourceTask,
labels: { name: 'init' },
ignoreError: true,
});
initTask.consume((value) => {
if (value.state !== 'completed') {
resourceTask.halt();
}
produce(value);
});
delegate.start();
}
function halt() {
delegate.halt();
resourceTask === null || resourceTask === void 0 ? void 0 : resourceTask.halt();
}
return { start, halt, future, type: 'resource', operation: resource };
return {
type: 'resource constructor',
start,
halt,
future,
get resourceTask() {
return resourceTask;
},
operation: resource
};
}
exports.createResourceController = createResourceController;
//# sourceMappingURL=resource-controller.js.map

@@ -0,2 +1,5 @@

/**
* @hidden
*/
export declare function deprecated<TThis, TArgs extends unknown[], TReturn>(message: string, fn: (this: TThis, ...args: TArgs) => TReturn): (this: TThis, ...args: TArgs) => TReturn;
//# sourceMappingURL=deprecated.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.deprecated = void 0;
/**
* @hidden
*/
function deprecated(message, fn) {

@@ -5,0 +8,0 @@ return function (...args) {

import { Task } from './task';
/**
* The `Effection` constant provides access to some global properties in
* Effection. Most importantly, `Effection` has a {@link root} task, which all
* tasks spawned by {@link run} or `main` are spawned under. You normally do
* not need to work with this task directly, but it is useful for debugging and
* testing.
*/
export declare const Effection: {
/**
* All tasks spawned by {@link run} or `main` are spawned as children of this
* task. You normally do not need to work with this task directly, but it is
* useful for debugging and testing.
*/
root: Task<unknown>;
/**
* Completely halt all running Effection tasks and create a new root task.
*/
reset(): Promise<void>;
/**
* Completely halt all running Effection tasks.
*/
halt(): Promise<void>;
};
//# sourceMappingURL=effection.d.ts.map
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });

@@ -8,4 +11,5 @@ exports.Effection = void 0;

// @ts-ignore
const package_json_1 = require("../package.json");
const MAJOR_VERSION = package_json_1.version.split('.')[0];
const package_json_1 = __importDefault(require("../package.json"));
const { version } = package_json_1.default;
const MAJOR_VERSION = version.split('.')[0];
const GLOBAL_PROPERTY = `__effectionV${MAJOR_VERSION}`;

@@ -25,9 +29,27 @@ function createRootTask() {

}
/**
* The `Effection` constant provides access to some global properties in
* Effection. Most importantly, `Effection` has a {@link root} task, which all
* tasks spawned by {@link run} or `main` are spawned under. You normally do
* not need to work with this task directly, but it is useful for debugging and
* testing.
*/
exports.Effection = {
/**
* All tasks spawned by {@link run} or `main` are spawned as children of this
* task. You normally do not need to work with this task directly, but it is
* useful for debugging and testing.
*/
get root() {
return getGlobalConfig().root;
},
/**
* Set the root task to a new task.
*/
set root(value) {
getGlobalConfig().root = value;
},
/**
* Completely halt all running Effection tasks and create a new root task.
*/
async reset() {

@@ -37,2 +59,5 @@ await exports.Effection.root.halt();

},
/**
* Completely halt all running Effection tasks.
*/
async halt() {

@@ -39,0 +64,0 @@ await exports.Effection.root.halt();

import type { Task, TaskInfo } from './task';
/**
* @hidden
*/
export interface HasEffectionTrace {

@@ -3,0 +6,0 @@ effectionTrace: TaskInfo[];

@@ -11,10 +11,10 @@ "use strict";

};
let properties = Object.getOwnPropertyDescriptors(error);
properties.effectionTrace = {
value: [...(error.effectionTrace || []), info],
enumerable: true,
};
return Object.create(Object.getPrototypeOf(error), properties);
return Object.create(error, {
effectionTrace: {
value: [...(error.effectionTrace || []), info],
enumerable: true,
}
});
}
exports.addTrace = addTrace;
//# sourceMappingURL=error.js.map

@@ -0,1 +1,2 @@

import { RunLoop } from './run-loop';
export declare type State = 'pending' | 'errored' | 'completed' | 'halted';

@@ -14,5 +15,29 @@ export declare type Value<T> = {

}
/**
* `FutureLike` is a slimmed down interface, similar to `PromiseLink` which
* does not make equally strong requirements on the implementor. In Effection,
* {@link Task} implements `FutureLike`, but not {@linkcode Future}.
*
* See [the Futures guide](https://frontside.com/effection/docs/guides/futures)
* for a more detailed description of futures and how they work.
*/
export interface FutureLike<T> {
consume(consumer: Consumer<T>): void;
}
/**
* A Future represents a value which may or may not be available yet. It is
* similar to a JavaScript Promise, except that it can resolve synchronously.
*
* See [the Futures guide](https://frontside.com/effection/docs/guides/futures)
* for a more detailed description of futures and how they work.
*
* A Future may resolve to *three* different states. A Future can either become
* `completed` with a value, it can become `errored` with an Error or it can
* become `halted`, meaning it was prematurely cancelled.
*
* A Future can be created via the {@link createFuture} function, or via the
* The `resolve`, `reject` and `halt` functions on {@link Future}.
*
* See also the slimmed down {@link FutureLike} interface.
*/
export interface Future<T> extends Promise<T>, FutureLike<T> {

@@ -24,6 +49,66 @@ state: State;

produce(value: Value<T>): void;
/** @deprecated Use produce(value) instead */
resolve(value: Value<T>): void;
resolve(value: T): void;
reject(error: Error): void;
halt(): void;
}
/**
* Create a new Future. This returns an object which contains the future itself
* as well as a function to produce a value for the future, and also shortcuts
* to resolve/reject/halt the future.
*
* ### Example
*
* ```typescript
* import { createFuture } from 'effection';
*
* let { future, produce } = createFuture<number>();
*
* // later...
* produce({ state: 'completed', value: 100 });
*
* future.consume((value) => console.log(value)) // => { state: 'completed', value: 100 }
* ```
*
* ### Example using shortcut
*
* ```typescript
* import { createFuture } from 'effection';
*
* let { future, resolve } = createFuture<number>();
*
* // later...
* resolve(100);
*
* future.consume((value) => console.log(value)) // => { state: 'completed', value: 100 }
* ```
*/
export declare function createFuture<T>(): NewFuture<T>;
export declare function createFutureOnRunLoop<T>(runLoop: RunLoop): NewFuture<T>;
/**
* Namespace for `Future` functions to create resolved futures.
*
* See [Future](./interfaces/Future.html).
*/
export declare const Future: {
/**
* Create a future which has resolved successfully to a `completed` state.
*
* @param value The value to resolve to
* @typeParam T The type that the future resolves to
*/
resolve<T>(value: T): Future<T>;
/**
* Create a future which has been rejected and is in an `errored` state.
*
* @param error The error that the future rejects with
* @typeParam T The type that the future resolves to
*/
reject<T_1 = unknown>(error: Error): Future<T_1>;
/**
* Create a future which has been halted and is in a `halted` state.
*
* @typeParam T The type that the future resolves to
*/
halt<T_2 = unknown>(): Future<T_2>;
};
//# sourceMappingURL=future.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createFuture = void 0;
exports.Future = exports.createFutureOnRunLoop = exports.createFuture = void 0;
const halt_error_1 = require("./halt-error");
const run_loop_1 = require("./run-loop");
/**
* Create a new Future. This returns an object which contains the future itself
* as well as a function to produce a value for the future, and also shortcuts
* to resolve/reject/halt the future.
*
* ### Example
*
* ```typescript
* import { createFuture } from 'effection';
*
* let { future, produce } = createFuture<number>();
*
* // later...
* produce({ state: 'completed', value: 100 });
*
* future.consume((value) => console.log(value)) // => { state: 'completed', value: 100 }
* ```
*
* ### Example using shortcut
*
* ```typescript
* import { createFuture } from 'effection';
*
* let { future, resolve } = createFuture<number>();
*
* // later...
* resolve(100);
*
* future.consume((value) => console.log(value)) // => { state: 'completed', value: 100 }
* ```
*/
function createFuture() {
let runLoop = run_loop_1.createRunLoop();
return createFutureOnRunLoop(run_loop_1.createRunLoop('future'));
}
exports.createFuture = createFuture;
function createFutureOnRunLoop(runLoop) {
let consumers = [];

@@ -33,6 +67,2 @@ let result;

}
function resolve(value) {
console.warn(`DEPRECATED: resolve() is deprecated and will be changed or removed prior to the release of effection 2.0\nuse produce() instead`);
produce(value);
}
let promise;

@@ -64,6 +94,47 @@ function getPromise() {

},
resolve
resolve: (value) => produce({ state: 'completed', value }),
reject: (error) => produce({ state: 'errored', error }),
halt: () => produce({ state: 'halted' }),
};
}
exports.createFuture = createFuture;
exports.createFutureOnRunLoop = createFutureOnRunLoop;
/**
* Namespace for `Future` functions to create resolved futures.
*
* See [Future](./interfaces/Future.html).
*/
exports.Future = {
/**
* Create a future which has resolved successfully to a `completed` state.
*
* @param value The value to resolve to
* @typeParam T The type that the future resolves to
*/
resolve(value) {
let { future, resolve } = createFuture();
resolve(value);
return future;
},
/**
* Create a future which has been rejected and is in an `errored` state.
*
* @param error The error that the future rejects with
* @typeParam T The type that the future resolves to
*/
reject(error) {
let { future, reject } = createFuture();
reject(error);
return future;
},
/**
* Create a future which has been halted and is in a `halted` state.
*
* @typeParam T The type that the future resolves to
*/
halt() {
let { future, halt } = createFuture();
halt();
return future;
},
};
//# sourceMappingURL=future.js.map

@@ -13,3 +13,3 @@ "use strict";

exports.HaltError = HaltError;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
function isHaltError(value) {

@@ -16,0 +16,0 @@ return !!(value && value.__isEffectionHaltError);

@@ -8,3 +8,3 @@ import { Operation } from './operation';

export { deprecated } from './deprecated';
export { Labels, withLabels } from './labels';
export { Labels, withLabels, setLabels } from './labels';
export { HasEffectionTrace } from './error';

@@ -20,3 +20,27 @@ export { createFuture, Future, FutureLike } from './future';

export { label } from './operations/label';
/**
* Run takes an operation and runs it in a task. It returns the task it created.
*
* Run is an entry point into Effection, and is especially useful when
* embedding Effection code into existing code. If you are writing your whole
* program using Effection, you should prefer using `main`.
*
* ### Example
*
* ```
* import { run, fetch } from 'effection';
*
* async function fetchExample() {
* await run(function*() {
* let response = yield fetch('http://www.example.com');
* yield response.text();
* });
* });
* ```
*
* @param operation the operation to run
* @param options the options to configure the task with
* @returns the new task
*/
export declare function run<TOut>(operation?: Operation<TOut>, options?: TaskOptions): Task<TOut>;
//# sourceMappingURL=index.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.run = exports.label = exports.all = exports.race = exports.spawn = exports.withTimeout = exports.timeout = exports.ensure = exports.sleep = exports.createFuture = exports.withLabels = exports.deprecated = exports.Effection = exports.createTask = void 0;
exports.run = exports.label = exports.all = exports.race = exports.spawn = exports.withTimeout = exports.timeout = exports.ensure = exports.sleep = exports.Future = exports.createFuture = exports.setLabels = exports.withLabels = exports.deprecated = exports.Effection = exports.createTask = void 0;
const effection_1 = require("./effection");

@@ -13,4 +13,6 @@ var task_1 = require("./task");

Object.defineProperty(exports, "withLabels", { enumerable: true, get: function () { return labels_1.withLabels; } });
Object.defineProperty(exports, "setLabels", { enumerable: true, get: function () { return labels_1.setLabels; } });
var future_1 = require("./future");
Object.defineProperty(exports, "createFuture", { enumerable: true, get: function () { return future_1.createFuture; } });
Object.defineProperty(exports, "Future", { enumerable: true, get: function () { return future_1.Future; } });
var sleep_1 = require("./operations/sleep");

@@ -32,2 +34,26 @@ Object.defineProperty(exports, "sleep", { enumerable: true, get: function () { return sleep_1.sleep; } });

Object.defineProperty(exports, "label", { enumerable: true, get: function () { return label_1.label; } });
/**
* Run takes an operation and runs it in a task. It returns the task it created.
*
* Run is an entry point into Effection, and is especially useful when
* embedding Effection code into existing code. If you are writing your whole
* program using Effection, you should prefer using `main`.
*
* ### Example
*
* ```
* import { run, fetch } from 'effection';
*
* async function fetchExample() {
* await run(function*() {
* let response = yield fetch('http://www.example.com');
* yield response.text();
* });
* });
* ```
*
* @param operation the operation to run
* @param options the options to configure the task with
* @returns the new task
*/
function run(operation, options) {

@@ -34,0 +60,0 @@ return effection_1.Effection.root.run(operation, options);

import { Operation } from './operation';
/**
* A map of labels. Each label is a key/value pair, where the key must be a
* string and the value may be a string, number or boolean.
*/
export declare type Labels = Record<string, string | number | boolean>;
/**
* Apply the given labels to an operation. When the operation is run as a task,
* using {@link run} or {@link spawn}, the labels get applied to the task as
* well.
*
* If the task operation already has labels, the existing labels are not
* removed. See {@link setLabels} if you want to replace the existing labels
* entirely.
*/
export declare function withLabels<T>(operation: Operation<T>, labels: Labels): Operation<T>;
/**
* Like {@link withLabels}, but replaces the existing labels entirely.
*/
export declare function setLabels<T>(operation: Operation<T>, labels: Labels): Operation<T>;
//# sourceMappingURL=labels.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.withLabels = void 0;
exports.setLabels = exports.withLabels = void 0;
/**
* Apply the given labels to an operation. When the operation is run as a task,
* using {@link run} or {@link spawn}, the labels get applied to the task as
* well.
*
* If the task operation already has labels, the existing labels are not
* removed. See {@link setLabels} if you want to replace the existing labels
* entirely.
*/
function withLabels(operation, labels) {

@@ -11,2 +20,12 @@ if (operation) {

exports.withLabels = withLabels;
/**
* Like {@link withLabels}, but replaces the existing labels entirely.
*/
function setLabels(operation, labels) {
if (operation) {
operation.labels = labels;
}
return operation;
}
exports.setLabels = setLabels;
//# sourceMappingURL=labels.js.map

@@ -12,9 +12,21 @@ import type { Task } from './task';

}
export interface OperationResolution<TOut> extends Labelled {
perform(resolve: (value: TOut) => void, reject: (err: Error) => void): void | (() => void);
}
export interface OperationFuture<TOut> extends FutureLike<TOut>, Labelled {
}
/**
* An `Resource` in Effection represents a long running computation which also
* provides some means of interaction while it is running.
*
* See [the Resource guide](https://frontside.com/effection/docs/guides/resources) for more information.
*
*/
export interface Resource<TOut> extends Labelled {
init(scope: Task, local: Task): OperationIterator<TOut>;
/**
* A resource's `init` method is called when the resource is initialized. It
* runs inside the resource's resource task, and is able to spawn tasks and
* other resources directly under the resource task.
*
* @param resourceTask a handle to the resource task that the resource is running
* @param initTask a handle to the task of the `init` itself
*/
init(resourceTask: Task, initTask: Task): OperationIterator<TOut>;
}

@@ -24,3 +36,10 @@ export interface OperationFunction<TOut> extends Labelled {

}
export declare type Operation<TOut> = OperationPromise<TOut> | OperationIterator<TOut> | OperationResolution<TOut> | OperationFuture<TOut> | OperationFunction<TOut> | Resource<TOut> | undefined;
/**
* An `Operation` in Effection describes an abstract computation, that is a
* computation which is not currently running.
*
* See [the Task and Operations guide](https://frontside.com/effection/docs/guides/tasks) for more information.
*
*/
export declare type Operation<TOut> = OperationPromise<TOut> | OperationIterator<TOut> | OperationFuture<TOut> | OperationFunction<TOut> | Resource<TOut> | undefined;
//# sourceMappingURL=operation.d.ts.map

@@ -5,4 +5,26 @@ import type { Operation } from '../operation';

};
/**
* Block and wait for all of the given operations to complete. Returns an array of
* values that the given operations evaluated to. This has the same purpose as
* [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all).
*
* If any of the operations become errored, then `all` will also become errored.
*
* ### Example
*
* ```typescript
* import { main, all, fetch } from 'effection';
*
* main(function*() {
* let [google, bing] = yield all([fetch('http://google.com').text(), fetch('http://bing.com').text()]);
* // ...
* });
* ```
*
* @typeParam T the type of the array of options, this can a heterogenous array
* @param operations a list of operations to wait for
* @returns the list of values that the operations evaluate to, in the order they were given
*/
export declare function all<T extends Operation<any>[]>(operations: T): Operation<All<T>>;
export {};
//# sourceMappingURL=all.d.ts.map

@@ -5,14 +5,45 @@ "use strict";

const labels_1 = require("../labels");
const spawn_1 = require("./spawn");
/**
* Block and wait for all of the given operations to complete. Returns an array of
* values that the given operations evaluated to. This has the same purpose as
* [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all).
*
* If any of the operations become errored, then `all` will also become errored.
*
* ### Example
*
* ```typescript
* import { main, all, fetch } from 'effection';
*
* main(function*() {
* let [google, bing] = yield all([fetch('http://google.com').text(), fetch('http://bing.com').text()]);
* // ...
* });
* ```
*
* @typeParam T the type of the array of options, this can a heterogenous array
* @param operations a list of operations to wait for
* @returns the list of values that the operations evaluate to, in the order they were given
*/
function all(operations) {
return labels_1.withLabels(function* (scope) {
return labels_1.withLabels(function* (task) {
let tasks = [];
let results = [];
for (let operation of operations) {
if (scope.state === 'running') {
tasks.push(scope.run(operation));
try {
yield function* () {
for (let operation of operations) {
tasks.push(yield spawn_1.spawn(operation, { scope: task.options.scope }));
}
for (let task of tasks) {
results.push(yield task);
}
};
}
catch (err) {
for (let task of tasks) {
task.halt();
}
throw (err);
}
for (let task of tasks) {
results.push(yield task);
}
return results;

@@ -19,0 +50,0 @@ }, { name: 'all', count: operations.length });

@@ -1,3 +0,46 @@

import { Operation, Resource } from '../operation';
export declare function ensure<T>(fn: () => Operation<T> | void): Resource<undefined>;
import { Operation } from '../operation';
/**
* Run the given function or operation when the current task shuts down. This
* is equivalent to running the function or operation in a finally block, but
* it can help you avoid rightward drift.
*
* ### Example using function
*
* Using a function:
*
* ```typescript
* import { main, ensure } from 'effection';
* import { createServer } from 'http';
*
* main(function*() {
* let server = createServer(...);
* yield ensure(() => { server.close() });
* });
* ```
*
* Note that you should wrap the function body in braces, so the function
* returns `undefined`.
*
* ### Example using operation
*
* ```typescript
* import { main, ensure, once } from 'effection';
* import { createServer } from 'some-library';
*
* main(function*() {
* let server = createServer(...);
* yield ensure(function*() {
* server.close();
* yield once(server, 'closed');
* });
* });
* ```
*
* Here we're using some library where server shutdown is asynchronous, we can
* block and wait for the shutdown to complete inside of ensure by using an
* operation.
*
* @param fn a function which returns an operation or void
*/
export declare function ensure<T>(fn: () => Operation<T> | void): Operation<undefined>;
//# sourceMappingURL=ensure.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ensure = void 0;
const spawn_1 = require("./spawn");
const future_1 = require("../future");
/**
* Run the given function or operation when the current task shuts down. This
* is equivalent to running the function or operation in a finally block, but
* it can help you avoid rightward drift.
*
* ### Example using function
*
* Using a function:
*
* ```typescript
* import { main, ensure } from 'effection';
* import { createServer } from 'http';
*
* main(function*() {
* let server = createServer(...);
* yield ensure(() => { server.close() });
* });
* ```
*
* Note that you should wrap the function body in braces, so the function
* returns `undefined`.
*
* ### Example using operation
*
* ```typescript
* import { main, ensure, once } from 'effection';
* import { createServer } from 'some-library';
*
* main(function*() {
* let server = createServer(...);
* yield ensure(function*() {
* server.close();
* yield once(server, 'closed');
* });
* });
* ```
*
* Here we're using some library where server shutdown is asynchronous, we can
* block and wait for the shutdown to complete inside of ensure by using an
* operation.
*
* @param fn a function which returns an operation or void
*/
function ensure(fn) {
return {
name: 'ensure',
*init() {
yield spawn_1.spawn(function* () {
try {
yield;
return function ensure(task) {
let { scope } = task.options;
if (!scope) {
throw new Error('cannot run `ensure` on a task without scope');
}
scope.run(function* ensureHandler() {
try {
yield;
}
finally {
let result = fn();
if (result) {
yield result;
}
finally {
let result = fn();
if (result) {
yield result;
}
}
}, { labels: { name: 'ensureHandler' } });
return undefined;
}
}
});
return future_1.Future.resolve(undefined);
};

@@ -23,0 +67,0 @@ }

@@ -1,4 +0,41 @@

import { Resource } from '../operation';
import { Operation } from '../operation';
import { Labels } from '../labels';
export declare function label(labels: Labels): Resource<void>;
/**
* Apply the given labels to the current task. This can be used to dynamically
* set labels, for example as the state of the operation changes, or as more
* information becomes available.
*
* If possible you should prefer to set labels statically using {@link withLabels},
* or by passing them as options to {@link run} or {@link spawn}.
*
* Existing labels are not removed, but may be overwritten if the key is the
* same.
*
* ### Example
*
* ```typescript
* import { main, label } from 'effection';
*
* function createSocket(url) {
* // use `withLabels` for information which is known statically
* return withLabels(function*() {
* let socket = new WebSocket(url);
* yield once(socket, 'open');
*
* // state has changed dynamically, use `label` to update labels
* yield label({ state: 'open'});
*
* let initMessage = yield once(socket, 'message');
*
* // we received a handshake message with some new information
* yield label({ state: 'initialized', connectionId: initMessage.data.connectionId });
*
* // ...
* }, { labels: { name: 'createSocket', state: 'pending' }});
* });
* ```
*
* @param labels the labels to apply
*/
export declare function label(labels: Labels): Operation<void>;
//# sourceMappingURL=label.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.label = void 0;
const future_1 = require("../future");
const labels_1 = require("../labels");
/**
* Apply the given labels to the current task. This can be used to dynamically
* set labels, for example as the state of the operation changes, or as more
* information becomes available.
*
* If possible you should prefer to set labels statically using {@link withLabels},
* or by passing them as options to {@link run} or {@link spawn}.
*
* Existing labels are not removed, but may be overwritten if the key is the
* same.
*
* ### Example
*
* ```typescript
* import { main, label } from 'effection';
*
* function createSocket(url) {
* // use `withLabels` for information which is known statically
* return withLabels(function*() {
* let socket = new WebSocket(url);
* yield once(socket, 'open');
*
* // state has changed dynamically, use `label` to update labels
* yield label({ state: 'open'});
*
* let initMessage = yield once(socket, 'message');
*
* // we received a handshake message with some new information
* yield label({ state: 'initialized', connectionId: initMessage.data.connectionId });
*
* // ...
* }, { labels: { name: 'createSocket', state: 'pending' }});
* });
* ```
*
* @param labels the labels to apply
*/
function label(labels) {
return {
name: 'label',
*init(scope) {
scope.setLabels(labels);
return labels_1.withLabels((task) => {
let { scope } = task.options;
if (!scope) {
throw new Error('cannot run `label` on a task without scope');
}
};
scope.setLabels(labels);
return future_1.Future.resolve(undefined);
}, { name: 'label' });
}
exports.label = label;
//# sourceMappingURL=label.js.map
import type { Operation } from '../operation';
/**
* Race the given operations against each other and return the value of
* whichever operation returns first. This has the same purpose as
* [Promise.race](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race).
*
* If an operation become errored first, then `race` will fail with this error.
* After the first operation wins the race, all other operations will become
* halted and therefore cannot throw any further errors.
*
* ### Example
*
* ```typescript
* import { main, race, fetch } from 'effection';
*
* main(function*() {
* let fastest = yield race([fetch('http://google.com').text(), fetch('http://bing.com').text()]);
* // ...
* });
* ```
*
* @typeParam T the type of the operations that race against each other
* @param operations a list of operations to race against each other
* @returns the value of the fastest operation
*/
export declare function race<T>(operations: Operation<T>[]): Operation<T>;
//# sourceMappingURL=race.d.ts.map

@@ -5,21 +5,48 @@ "use strict";

const labels_1 = require("../labels");
const future_1 = require("../future");
/**
* Race the given operations against each other and return the value of
* whichever operation returns first. This has the same purpose as
* [Promise.race](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race).
*
* If an operation become errored first, then `race` will fail with this error.
* After the first operation wins the race, all other operations will become
* halted and therefore cannot throw any further errors.
*
* ### Example
*
* ```typescript
* import { main, race, fetch } from 'effection';
*
* main(function*() {
* let fastest = yield race([fetch('http://google.com').text(), fetch('http://bing.com').text()]);
* // ...
* });
* ```
*
* @typeParam T the type of the operations that race against each other
* @param operations a list of operations to race against each other
* @returns the value of the fastest operation
*/
function race(operations) {
return labels_1.withLabels((scope) => ({
perform: (resolve, reject) => {
for (let operation of operations) {
if (scope.state === 'running') {
scope.run(function* () {
try {
resolve(yield operation);
}
catch (e) {
reject(e);
}
});
}
return labels_1.withLabels(function* (task) {
let { resolve, future } = future_1.createFuture();
let tasks = [];
for (let operation of operations) {
if (task.state === 'running') {
let child = task.run(operation, { ignoreError: true, scope: task.options.scope });
child.consume(() => resolve(child));
tasks.push(child);
}
}
}), { name: 'race', count: operations.length });
let resultChild = yield future;
for (let child of tasks) {
if (child !== resultChild) {
child.halt();
}
}
return yield resultChild;
}, { name: 'race', count: operations.length });
}
exports.race = race;
//# sourceMappingURL=race.js.map
import { Operation } from '../operation';
/**
* Sleep for the given amount of milliseconds. If no duration is given, then
* sleep indefinitely.
*
* ### Example
*
* ```typescript
* import { main, sleep } from 'effection';
*
* main(function*() {
* yield sleep(2000);
* console.log("Hello lazy world!");
* });
* ```
*
* @param duration the number of milliseconds to sleep
*/
export declare function sleep(duration?: number): Operation<void>;
//# sourceMappingURL=sleep.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.sleep = void 0;
const future_1 = require("../future");
const labels_1 = require("../labels");
/**
* Sleep for the given amount of milliseconds. If no duration is given, then
* sleep indefinitely.
*
* ### Example
*
* ```typescript
* import { main, sleep } from 'effection';
*
* main(function*() {
* yield sleep(2000);
* console.log("Hello lazy world!");
* });
* ```
*
* @param duration the number of milliseconds to sleep
*/
function sleep(duration) {
return {
labels: { name: 'sleep', duration: (duration != null) ? duration : 'forever' },
perform(resolve) {
if (duration != null) {
let timeoutId = setTimeout(resolve, duration);
return () => clearTimeout(timeoutId);
}
return labels_1.withLabels((task) => {
let { future, resolve } = future_1.createFuture();
if (duration != null) {
let timeoutId = setTimeout(resolve, duration);
task.consume(() => clearTimeout(timeoutId));
}
};
return future;
}, { name: 'sleep', duration: (duration != null) ? duration : 'forever' });
}
exports.sleep = sleep;
//# sourceMappingURL=sleep.js.map

@@ -1,8 +0,38 @@

import { Operation, Resource } from '../operation';
import { Operation, OperationFunction } from '../operation';
import type { Task, TaskOptions } from '../task';
interface Spawn<T> extends Resource<Task<T>> {
within(scope: Task): Resource<Task<T>>;
interface Spawn<T> extends OperationFunction<Task<T>> {
within(scope: Task): Operation<Task<T>>;
}
/**
* An operation which spawns the given operation as a child of the current task.
*
* You should prefer using the spawn operation over calling `task.run` from
* within Effection code. The reason being that a synchronous failure in the
* spawned task will not be caught until the next yield point when using `run`,
* which can be confusing. Additionally, using `spawn` is usually more
* ergonomic.
*
* ### Example
*
* ```typescript
* import { main, sleep, spawn } from 'effection';
*
* main(function*() {
* yield spawn(function*() {
* yield sleep(1000);
* console.log("hello");
* });
* yield spawn(function*() {
* yield sleep(2000);
* console.log("world");
* });
* yield;
* });
* ```
*
* @param operation the operation to run as a child of the current task
* @typeParam T the type that the spawned task evaluates to
*/
export declare function spawn<T>(operation?: Operation<T>, options?: TaskOptions): Spawn<T>;
export {};
//# sourceMappingURL=spawn.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.spawn = void 0;
const future_1 = require("../future");
/**
* An operation which spawns the given operation as a child of the current task.
*
* You should prefer using the spawn operation over calling `task.run` from
* within Effection code. The reason being that a synchronous failure in the
* spawned task will not be caught until the next yield point when using `run`,
* which can be confusing. Additionally, using `spawn` is usually more
* ergonomic.
*
* ### Example
*
* ```typescript
* import { main, sleep, spawn } from 'effection';
*
* main(function*() {
* yield spawn(function*() {
* yield sleep(1000);
* console.log("hello");
* });
* yield spawn(function*() {
* yield sleep(2000);
* console.log("world");
* });
* yield;
* });
* ```
*
* @param operation the operation to run as a child of the current task
* @typeParam T the type that the spawned task evaluates to
*/
function spawn(operation, options) {
function* init(scope) {
return scope.run(operation, options);
function spawn(task) {
let { scope } = task.options;
if (!scope) {
throw new Error('cannot run `spawn` on a task without scope');
}
let result = scope.run(operation, options);
return future_1.Future.resolve(result);
}
function within(scope) {
return {
init: () => init(scope)
};
return scope.spawn(operation, options);
}
return { init, within, name: 'spawn' };
return Object.assign(spawn, { within });
}
exports.spawn = spawn;
//# sourceMappingURL=spawn.js.map
import { Operation } from '../operation';
/**
* Throw an error after the given amount of milliseconds. See also {@link withTimeout}.
*
* ### Example
*
* ```typescript
* import { main, spawn, timeout } from 'effection';
*
* main(function*() {
* yield spawn(timeout(2000));
* yield fetch('http://google.com');
* });
* ```
*
* @param duration the timeout duration in milliseconds
*/
export declare function timeout(duration: number): Operation<never>;
//# sourceMappingURL=timeout.d.ts.map

@@ -12,2 +12,18 @@ "use strict";

}
/**
* Throw an error after the given amount of milliseconds. See also {@link withTimeout}.
*
* ### Example
*
* ```typescript
* import { main, spawn, timeout } from 'effection';
*
* main(function*() {
* yield spawn(timeout(2000));
* yield fetch('http://google.com');
* });
* ```
*
* @param duration the timeout duration in milliseconds
*/
function timeout(duration) {

@@ -14,0 +30,0 @@ return labels_1.withLabels(function* () {

import { Operation } from '../operation';
/**
* Runs the given operation and throws an error if it takes more than the given
* number of milliseconds. See also {@link timeout}.
*
* ### Example
*
* ```typescript
* import { main, spawn, withTimeout } from 'effection';
*
* main(function*() {
* yield withTimeout(2000, fetch('http://google.com'));
* });
* ```
*
* @param duration the timeout duration in milliseconds
*/
export declare function withTimeout<T>(duration: number, operation: Operation<T>): Operation<T>;
//# sourceMappingURL=with-timeout.d.ts.map

@@ -5,11 +5,24 @@ "use strict";

const timeout_1 = require("./timeout");
const spawn_1 = require("./spawn");
const race_1 = require("./race");
const labels_1 = require("../labels");
/**
* Runs the given operation and throws an error if it takes more than the given
* number of milliseconds. See also {@link timeout}.
*
* ### Example
*
* ```typescript
* import { main, spawn, withTimeout } from 'effection';
*
* main(function*() {
* yield withTimeout(2000, fetch('http://google.com'));
* });
* ```
*
* @param duration the timeout duration in milliseconds
*/
function withTimeout(duration, operation) {
return labels_1.withLabels(function* () {
yield spawn_1.spawn(timeout_1.timeout(duration));
return yield operation;
}, { name: 'withTimeout', duration });
return labels_1.setLabels(race_1.race([timeout_1.timeout(duration), operation]), { name: 'withTimeout', duration });
}
exports.withTimeout = withTimeout;
//# sourceMappingURL=with-timeout.js.map

@@ -1,8 +0,7 @@

import type { OperationResolution, Resource } from "./operation";
import type { Resource } from "./operation";
import type { FutureLike } from "./future";
export declare function isPromise(value: any): value is PromiseLike<unknown>;
export declare function isGenerator(value: any): value is Iterator<unknown>;
export declare function isResolution<T>(value: any): value is OperationResolution<T>;
export declare function isFuture<T>(value: any): value is FutureLike<T>;
export declare function isResource<TOut>(value: any): value is Resource<TOut>;
//# sourceMappingURL=predicates.d.ts.map

@@ -5,3 +5,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.isResource = exports.isFuture = exports.isResolution = exports.isGenerator = exports.isPromise = void 0;
exports.isResource = exports.isFuture = exports.isGenerator = exports.isPromise = void 0;
function isPromise(value) {

@@ -15,6 +15,2 @@ return value && typeof (value.then) === 'function';

exports.isGenerator = isGenerator;
function isResolution(value) {
return value && typeof (value.perform) === 'function';
}
exports.isResolution = isResolution;
function isFuture(value) {

@@ -21,0 +17,0 @@ return value && typeof (value.consume) === 'function';

@@ -5,3 +5,3 @@ export declare type Runnable = () => void;

}
export declare function createRunLoop(): RunLoop;
export declare function createRunLoop(name?: string): RunLoop;
//# sourceMappingURL=run-loop.d.ts.map

@@ -8,3 +8,3 @@ "use strict";

// of synchronous mutex
function createRunLoop() {
function createRunLoop(name) {
let didEnter = false;

@@ -24,3 +24,3 @@ let runnables = [];

catch (e) {
console.error("Caught error in run loop:");
console.error(`Caught error in run loop \`${name}\`:`);
console.error(e);

@@ -27,0 +27,0 @@ }

/// <reference types="node" />
import { EventEmitter } from 'events';
/**
* The state of a Task can be one of the following:
*
* - `pending`: the task has not been started yet
* - `running`: the task is currently running
* - `halting`: the task has been halted, but halting is not finished yet
* - `halted`: the task is fully halted
* - `erroring`: the task or one of its children has failed and is in the process of shutting down
* - `errored`: the task has fully failed and evaluates to an Error
* - `completing`: the task is completing and shutting down its children
* - `completed`: the task is fully complete and evaluates to a value
*/
export declare type State = 'pending' | 'running' | 'halting' | 'halted' | 'erroring' | 'errored' | 'completing' | 'completed';
/**
* @hidden
*/
export declare type StateTransition = {

@@ -13,2 +28,3 @@ from: State;

private transition;
get isFinalized(): boolean;
start(): void;

@@ -15,0 +31,0 @@ completing(): void;

@@ -20,2 +20,5 @@ "use strict";

}
get isFinalized() {
return this.current === 'errored' || this.current === 'completed' || this.current === 'halted';
}
start() {

@@ -22,0 +25,0 @@ this.transition('start', {

@@ -7,2 +7,5 @@ /// <reference types="node" />

import { Future, FutureLike } from './future';
/**
* @hidden
*/
export interface TaskInfo {

@@ -14,2 +17,5 @@ id: number;

}
/**
* @hidden
*/
export interface TaskTree extends TaskInfo {

@@ -19,30 +25,277 @@ yieldingTo?: TaskTree;

}
/**
* Task options can be used to configure the behaviour of a task. Options can
* be passed to {@link run}, `main`, {@link spawn} and when creating a
* task manually through {@link createTask}.
*/
export interface TaskOptions {
readonly resourceScope?: Task;
/**
* Normally a task's type is inferred from its operation, but it can also be
* explicitly set via this option.
*/
readonly type?: string;
/**
* The scope of a task describes the scope in which this task creates resource
* tasks.
*
* Usually this is the parent task, but in some instances it can be useful to
* change the scope of a task. This is for example often the case with
* "higher-order operations", that is operations which take another operation
* as an argument, such as the {@link all} operation.
*
* This is a very advanced feature and great care should be taken when using
* it as it can lead to possibly breaking some of the guarantees that
* Effection makes.
*/
readonly scope?: Task;
/**
* @internal
*
* The yieldScope of a task describes the scope in which this task's yield
* points create resources. This is normally the task itself. It is currently
* only changed for resource's `init` tasks.
*
* This is an internal API and you should not set this option yourself.
*/
readonly yieldScope?: Task;
/**
* Normally when a task finishes its operation, it halts all of its children.
* When setting this option to `true` on a child, it will cause the parent
* task to wait for the child to complete after it finishes its operation.
* Essentially it allows a child to block the completion of its parent.
*/
readonly blockParent?: boolean;
/**
* When a child task becomes errored it normally causes its parent task to
* become errored as well. When setting this option to `true` on a parent
* task, an error in its children will be ignored. This is useful for some
* tasks which want to have finer grained control over their children, such
* as supervisors or root tasks.
*/
readonly ignoreChildErrors?: boolean;
/**
* When a child task becomes errored it normally causes its parent task to
* become errored as well. When setting this option to `true` on a child
* task, then an error in the child will not cause the parent to become
* errored. This is useful for some tasks which want finer grained control
* over error conditions.
*/
readonly ignoreError?: boolean;
/**
* Set the given labels on the task.
*/
readonly labels?: Labels;
}
/**
* A `Task` in Effection has many responsibilities. Fundamentally it represents
* a computation which at some point will result in either a value or an error,
* or become halted. It can also have children, which are also tasks.
*
* See [the Task and Operations guide](https://frontside.com/effection/docs/guides/tasks) for more information.
*
*/
export interface Task<TOut = unknown> extends Promise<TOut>, FutureLike<TOut> {
/**
* Each `Task` has a unique id
*/
readonly id: number;
/**
* The type of a `Task` is usually determined by the operation that the task
* is running, but it can also be adjusted via the `type` option.
*/
readonly type: string;
/**
* Returns the {@link State} that the task is currently in.
*/
readonly state: State;
/**
* Returns the options that the task was created with
*/
readonly options: TaskOptions;
/**
* Returns a map of {@link Labels} which provide additional information about
* the task, such as its name, or any other metadata that the task wants to
* provide.
*/
readonly labels: Labels;
/**
* Returns a list of the task's children
*/
readonly children: Task[];
/**
* Returns a {@link Future} for this task. The task itself can act as a
* Future, so usually you do not need to access this property explicitly.
*/
readonly future: Future<TOut>;
/**
* Returns the task that this task is currently yielding to. When using a
* generator with a task, a task ends up processing one operation at a time,
* each such operation runs in its own task, and can be accessed via this
* property.
*
* ### Example
*
* ``` typescript
* import { run, sleep } from 'effection'
*
* let task = run(function*() {
* yield sleep(2000);
* });
*
* console.log(task.yieldingTo); // => logs the sleep task
* ```
*/
readonly yieldingTo: Task | undefined;
/**
* Tasks which construct a resource create a special task in the background called
* a resource task, which groups everything running inside the resource. This property
* provides access to such a task.
*
* ### Example
*
* ``` typescript
* import { main, spawn, fetch } from 'effection'
*
* main(function*() {
* let fetchTask = yield spawn(fetch('http://www.example.com'));
* console.log(fetchTask.resourceTask); // => logs the resource task of the fetch
* });
* ```
*/
readonly resourceTask: Task | undefined;
/**
* When using a task as a `Promise`, it would usually be rejected when the task
* is halted. Using `catchHalt()` we can treat a `halt` as the promise
* resolving to `undefined`, rather than being rejected.
*
* ### Example
*
* ``` typescript
* import { run, sleep } from 'effection'
*
* let task = run(function*() {
* yield sleep(2000);
* return "done!";
* });
*
* task.halt();
*
* task.then((value) => console.log(value)); // => log "undefined"
*/
catchHalt(): Promise<TOut | undefined>;
/**
* Sets the given {@link Labels} on the task. This only adds new labels or
* overwrites labels with the same key, but does not remove other labels.
*
* See also the {@link label} operation for setting labels dynamically.
*
* ### Example
*
* ``` typescript
* import { run } from 'effection'
*
* let task = run();
* task.setLabels({ name: 'myTask', florb: 123 });
* ```
*/
setLabels(labels: Labels): void;
run<R>(operation?: Operation<R>, options?: TaskOptions): Task<R>;
/** @deprecated Use run() instead */
spawn<R>(operation?: Operation<R>, options?: TaskOptions): Task<R>;
/**
* Run the given operation as a child of this task.
*
* This is similar to {@link spawn}, but it is *not* an operation and
* therefore should *not* be used with `yield`. If you are inside Effection
* code, you should generally prefer {@link spawn}. `run` is useful when you
* are creating children from outside of Effection.
*
* ### Example
*
* ``` typescript
* import { run } from 'effection'
*
* let task = run();
* task.run(function*() { ... }) // run a child of this task
* ```
*
* @typeParam TOutChild the type that the child resolves to
*/
run<TOutChild>(operation?: Operation<TOutChild>, options?: TaskOptions): Task<TOutChild>;
/**
* An operation to run the given operation as child of this task.
*
* This is similar to {@link run}, but it is an operation and therefore must
* be used with `yield` or `run`. If you are inside Effection code, you
* should generally prefer `spawn`. {@link run} is useful when you are
* creating children from outside of Effection.
*
* ### Example
*
* ``` typescript
* import { main, spawn } from 'effection'
*
* main(function*(mainTask) {
* yield function*() {
* yield mainTask.spawn(function*() { ... }); // => run as child of `main`.
* }
* });
* ```
*
* @param operation the operation that the child runs
* @typeParam TOutChild the type that the child resolves to
*/
spawn<TOutChild>(operation?: Operation<TOutChild>, options?: TaskOptions): Operation<Task<TOutChild>>;
/**
* Cause this task to halt. This will halt the task itself, as well as any task
* it is currently {@link yieldingTo} and its {@link children}.
*/
halt(): Promise<void>;
/**
* Starts running the task if it has not yet started, does nothing otherwise.
* You will only need to call this if you created your task manually through
* {@link createTask}.
*
* ### Example
*
* ``` typescript
* import { createTask } from 'effection'
*
* let task = createTask(someOperation);
* console.log(task.state) // => 'pending';
*
* task.start();
* console.log(task.state) // => 'running';
* ```
*/
start(): void;
/**
* Serializes information about the task into an object. This will include
* the task's {@link id}, {@link state}, {@link type}, {@link labels} as well
* as the task it is {@link yieldingTo} and its {@link children}.
*/
toJSON(): TaskTree;
/**
* Returns a readable description of the task and its children. This is
* useful for debugging. The output can be improved by applying {@link labels}.
* For more advanced debugging you can also use the
* [inspector](https://frontside.com/effection/docs/guides/inspector).
*/
toString(): string;
/**
* @hidden
*/
on: EventEmitter['on'];
/**
* @hidden
*/
off: EventEmitter['off'];
}
/**
* Low level interface to create a task which does not have a parent. Normally all
* tasks are spawned as children of {@link Effection}.root, but on rare occasions it is necessary
* to create a task outside the normal task hierarchy.
*
* @param operation the operation that the task runs
* @param options the options that the task is configured with
* @returns the new task
*/
export declare function createTask<TOut = unknown>(operation: Operation<TOut>, options?: TaskOptions): Task<TOut>;
//# sourceMappingURL=task.d.ts.map

@@ -13,2 +13,11 @@ "use strict";

let COUNTER = 0;
/**
* Low level interface to create a task which does not have a parent. Normally all
* tasks are spawned as children of {@link Effection}.root, but on rare occasions it is necessary
* to create a task outside the normal task hierarchy.
*
* @param operation the operation that the task runs
* @param options the options that the task is configured with
* @returns the new task
*/
function createTask(operation, options = {}) {

@@ -20,5 +29,5 @@ let id = ++COUNTER;

let stateMachine = new state_machine_1.StateMachine(emitter);
let { produce, future } = future_1.createFuture();
let result;
let runLoop = run_loop_1.createRunLoop();
let runLoop = run_loop_1.createRunLoop(`task ${id}`);
let { produce, future } = future_1.createFutureOnRunLoop(runLoop);
let controller;

@@ -41,5 +50,6 @@ let labels = { ...operation === null || operation === void 0 ? void 0 : operation.labels, ...options.labels };

get state() { return stateMachine.current; },
get type() { return controller.type; },
get type() { return options.type || controller.type; },
get children() { return Array.from(children); },
get yieldingTo() { return yieldingTo; },
get resourceTask() { return controller.resourceTask; },
catchHalt() {

@@ -56,3 +66,3 @@ return future.catch(halt_error_1.swallowHalt);

}
let child = createTask(operation, { resourceScope: task, ...options });
let child = createTask(operation, { scope: task, ...options });
link(child);

@@ -63,17 +73,24 @@ child.start();

spawn(operation, options = {}) {
console.warn(`DEPRECATED: task.spawn() is deprecated and will be changed or removed prior to the release of effection 2.0\nuse task.run() instead`);
return task.run(operation, options);
return {
name: 'spawn',
*init() {
return task.run(operation, options);
}
};
},
start() {
if (stateMachine.current === 'pending') {
stateMachine.start();
controller.start();
}
runLoop.run(() => {
if (stateMachine.current === 'pending') {
stateMachine.start();
controller.start();
}
});
},
async halt() {
if (stateMachine.current === 'running' || stateMachine.current === 'completing') {
controller.halt();
if (stateMachine.current === 'running') {
stateMachine.halting();
result = { state: 'halted' };
shutdown(true);
}
shutdown(true);
await future.catch(() => {

@@ -87,3 +104,3 @@ // TODO: should this catch all errors, or only halt errors?

id: id,
type: controller.type,
type: task.type,
labels: labels,

@@ -95,2 +112,10 @@ state: stateMachine.current,

},
toString() {
let formattedLabels = Object.entries(labels).filter(([key]) => key !== 'name' && key !== 'expand').map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(' ');
return [
[labels.name || 'task', formattedLabels, `[${task.type} ${id}]`].filter(Boolean).join(' '),
yieldingTo && yieldingTo.toString().split('\n').map(l => '┃ ' + l).join('\n').replace(/^┃ /, `┣ yield `),
...Array.from(children).map((c) => c.toString().split('\n').map(l => '┃ ' + l).join('\n').replace(/^┃/, '┣'))
].filter(Boolean).join('\n');
},
on: (...args) => emitter.on(...args),

@@ -105,2 +130,3 @@ off: (...args) => emitter.off(...args),

controller = controller_1.createController(task, operation, {
runLoop,
onYieldingToChange(value) {

@@ -112,2 +138,4 @@ yieldingTo = value;

controller.future.consume((value) => {
if (stateMachine.isFinalized)
return;
if (value.state === 'completed') {

@@ -125,2 +153,7 @@ stateMachine.completing();

}
else if (value.state === 'halted' && stateMachine.current !== 'erroring') {
stateMachine.halting();
result = { state: 'halted' };
shutdown(true);
}
finalize();

@@ -131,12 +164,17 @@ });

child.consume((value) => {
if (value.state === 'errored' && !child.options.ignoreError && !options.ignoreChildErrors) {
stateMachine.erroring();
result = { state: 'errored', error: error_1.addTrace(value.error, task) };
shutdown(true);
}
if (children.has(child)) {
children.delete(child);
emitter.emit('unlink', child);
}
finalize();
runLoop.run(() => {
if (stateMachine.isFinalized)
return;
if (value.state === 'errored' && !child.options.ignoreError && !options.ignoreChildErrors) {
stateMachine.erroring();
result = { state: 'errored', error: error_1.addTrace(value.error, task) };
controller.halt();
shutdown(true);
}
if (children.has(child)) {
children.delete(child);
emitter.emit('unlink', child);
}
finalize();
});
});

@@ -148,3 +186,2 @@ children.add(child);

function shutdown(force) {
controller.halt();
controller.future.consume(() => {

@@ -167,12 +204,10 @@ let nextChild;

function finalize() {
runLoop.run(() => {
if (Array.from(children).length !== 0)
return;
if (controller.future.state === 'pending')
return;
if (future.state !== 'pending')
return;
stateMachine.finish();
produce(result);
});
if (Array.from(children).length !== 0)
return;
if (controller.future.state === 'pending')
return;
if (future.state !== 'pending')
return;
stateMachine.finish();
produce(result);
}

@@ -179,0 +214,0 @@ return task;

import type { Task } from '../task';
import type { RunLoop } from '../run-loop';
import type { Operation } from '../operation';

@@ -7,2 +8,3 @@ import { Future } from '../future';

operation: Operation<TOut>;
resourceTask?: Task;
start(): void;

@@ -13,6 +15,6 @@ halt(): void;

export declare type Options = {
resourceScope?: Task;
runLoop: RunLoop;
onYieldingToChange?: (task: Task | undefined) => void;
};
export declare function createController<T>(task: Task<T>, operation: Operation<T>, options?: Options): Controller<T>;
export declare function createController<T>(task: Task<T>, operation: Operation<T>, options: Options): Controller<T>;
//# sourceMappingURL=controller.d.ts.map

@@ -1,2 +0,2 @@

import { isResource, isResolution, isFuture, isPromise, isGenerator } from '../predicates';
import { isResource, isFuture, isPromise, isGenerator } from '../predicates';
import { createFunctionController } from './function-controller';

@@ -6,6 +6,6 @@ import { createSuspendController } from './suspend-controller';

import { createIteratorController } from './iterator-controller';
import { createResolutionController } from './resolution-controller';
import { createFutureController } from './future-controller';
import { createResourceController } from './resource-controller';
export function createController(task, operation, options = {}) {
import { Future } from '../future';
export function createController(task, operation, options) {
if (typeof (operation) === 'function') {

@@ -23,5 +23,2 @@ return createFunctionController(task, operation, () => createController(task, operation(task), options));

}
else if (isResolution(operation)) {
return createResolutionController(task, operation);
}
else if (isPromise(operation)) {

@@ -33,4 +30,6 @@ return createPromiseController(task, operation);

}
throw new Error(`unkown type of operation: ${operation}`);
else {
return createFutureController(task, Future.reject(new Error(`unkown type of operation: ${operation}`)));
}
}
//# sourceMappingURL=controller.js.map
import { Task } from '../task';
import { Controller } from './controller';
import { Future } from '../future';
import { Operation, OperationFunction } from '../operation';
interface FunctionController<TOut> {
readonly type: string;
readonly operation: Operation<TOut>;
future: Future<TOut>;
start: () => void;
halt: () => void;
}
export declare function createFunctionController<TOut>(task: Task<TOut>, fn: OperationFunction<TOut>, createController: () => Controller<TOut>): FunctionController<TOut>;
export {};
import { OperationFunction } from '../operation';
export declare function createFunctionController<TOut>(task: Task<TOut>, fn: OperationFunction<TOut>, createController: () => Controller<TOut>): Controller<TOut>;
//# sourceMappingURL=function-controller.d.ts.map

@@ -39,2 +39,5 @@ import { createFuture } from '../future';

},
get resourceTask() {
return delegate === null || delegate === void 0 ? void 0 : delegate.resourceTask;
},
future,

@@ -41,0 +44,0 @@ start,

@@ -8,4 +8,4 @@ import { Controller, Options } from './controller';

}
export declare function createIteratorController<TOut>(task: Task<TOut>, iterator: OperationIterator<TOut> & Claimable, options?: Options): Controller<TOut>;
export declare function createIteratorController<TOut>(task: Task<TOut>, iterator: OperationIterator<TOut> & Claimable, options: Options): Controller<TOut>;
export {};
//# sourceMappingURL=iterator-controller.d.ts.map
import { createTask } from '../task';
import { createFuture } from '../future';
import { createRunLoop } from '../run-loop';
const claimed = Symbol.for('effection/v2/iterator-controller/claimed');
export function createIteratorController(task, iterator, options = {}) {
export function createIteratorController(task, iterator, options) {
let didHalt = false;
let yieldingTo;
let { produce, future } = createFuture();
let runLoop = createRunLoop();
function start() {

@@ -22,3 +20,3 @@ if (iterator[claimed]) {

function resume(iter) {
runLoop.run(() => {
options.runLoop.run(() => {
let next;

@@ -41,3 +39,3 @@ try {

else {
yieldingTo = createTask(next.value, { resourceScope: options.resourceScope || task, ignoreError: true });
yieldingTo = createTask(next.value, { scope: task.options.yieldScope || task, ignoreError: true });
yieldingTo.consume(trap);

@@ -44,0 +42,0 @@ yieldingTo.start();

@@ -1,30 +0,41 @@

import { createIteratorController } from './iterator-controller';
import { createFuture } from '../future';
export function createResourceController(task, resource) {
let delegate;
let { resourceScope } = task.options;
let resourceTask;
let initTask;
let { scope } = task.options;
let { produce, future } = createFuture();
function start() {
if (!resourceScope) {
var _a;
if (!scope) {
throw new Error('cannot spawn resource in task which has no resource scope');
}
let init;
try {
init = resource.init(resourceScope, task);
}
catch (error) {
produce({ state: 'errored', error });
return;
}
delegate = createIteratorController(task, init, { resourceScope });
delegate.future.consume((value) => {
let name = resource.name || ((_a = resource.labels) === null || _a === void 0 ? void 0 : _a.name) || 'resource';
let labels = resource.labels || {};
resourceTask = scope.run(undefined, { type: 'resource', labels: { ...labels, name } });
initTask = resourceTask.run((task) => resource.init(resourceTask, task), {
yieldScope: resourceTask,
labels: { name: 'init' },
ignoreError: true,
});
initTask.consume((value) => {
if (value.state !== 'completed') {
resourceTask.halt();
}
produce(value);
});
delegate.start();
}
function halt() {
delegate.halt();
resourceTask === null || resourceTask === void 0 ? void 0 : resourceTask.halt();
}
return { start, halt, future, type: 'resource', operation: resource };
return {
type: 'resource constructor',
start,
halt,
future,
get resourceTask() {
return resourceTask;
},
operation: resource
};
}
//# sourceMappingURL=resource-controller.js.map

@@ -0,2 +1,5 @@

/**
* @hidden
*/
export declare function deprecated<TThis, TArgs extends unknown[], TReturn>(message: string, fn: (this: TThis, ...args: TArgs) => TReturn): (this: TThis, ...args: TArgs) => TReturn;
//# sourceMappingURL=deprecated.d.ts.map

@@ -0,1 +1,4 @@

/**
* @hidden
*/
export function deprecated(message, fn) {

@@ -2,0 +5,0 @@ return function (...args) {

import { Task } from './task';
/**
* The `Effection` constant provides access to some global properties in
* Effection. Most importantly, `Effection` has a {@link root} task, which all
* tasks spawned by {@link run} or `main` are spawned under. You normally do
* not need to work with this task directly, but it is useful for debugging and
* testing.
*/
export declare const Effection: {
/**
* All tasks spawned by {@link run} or `main` are spawned as children of this
* task. You normally do not need to work with this task directly, but it is
* useful for debugging and testing.
*/
root: Task<unknown>;
/**
* Completely halt all running Effection tasks and create a new root task.
*/
reset(): Promise<void>;
/**
* Completely halt all running Effection tasks.
*/
halt(): Promise<void>;
};
//# sourceMappingURL=effection.d.ts.map

@@ -5,3 +5,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */

// @ts-ignore
import { version } from '../package.json';
import packageInfo from '../package.json';
const { version } = packageInfo;
const MAJOR_VERSION = version.split('.')[0];

@@ -22,9 +23,27 @@ const GLOBAL_PROPERTY = `__effectionV${MAJOR_VERSION}`;

}
/**
* The `Effection` constant provides access to some global properties in
* Effection. Most importantly, `Effection` has a {@link root} task, which all
* tasks spawned by {@link run} or `main` are spawned under. You normally do
* not need to work with this task directly, but it is useful for debugging and
* testing.
*/
export const Effection = {
/**
* All tasks spawned by {@link run} or `main` are spawned as children of this
* task. You normally do not need to work with this task directly, but it is
* useful for debugging and testing.
*/
get root() {
return getGlobalConfig().root;
},
/**
* Set the root task to a new task.
*/
set root(value) {
getGlobalConfig().root = value;
},
/**
* Completely halt all running Effection tasks and create a new root task.
*/
async reset() {

@@ -34,2 +53,5 @@ await Effection.root.halt();

},
/**
* Completely halt all running Effection tasks.
*/
async halt() {

@@ -36,0 +58,0 @@ await Effection.root.halt();

import type { Task, TaskInfo } from './task';
/**
* @hidden
*/
export interface HasEffectionTrace {

@@ -3,0 +6,0 @@ effectionTrace: TaskInfo[];

@@ -8,9 +8,9 @@ export function addTrace(error, task) {

};
let properties = Object.getOwnPropertyDescriptors(error);
properties.effectionTrace = {
value: [...(error.effectionTrace || []), info],
enumerable: true,
};
return Object.create(Object.getPrototypeOf(error), properties);
return Object.create(error, {
effectionTrace: {
value: [...(error.effectionTrace || []), info],
enumerable: true,
}
});
}
//# sourceMappingURL=error.js.map

@@ -0,1 +1,2 @@

import { RunLoop } from './run-loop';
export declare type State = 'pending' | 'errored' | 'completed' | 'halted';

@@ -14,5 +15,29 @@ export declare type Value<T> = {

}
/**
* `FutureLike` is a slimmed down interface, similar to `PromiseLink` which
* does not make equally strong requirements on the implementor. In Effection,
* {@link Task} implements `FutureLike`, but not {@linkcode Future}.
*
* See [the Futures guide](https://frontside.com/effection/docs/guides/futures)
* for a more detailed description of futures and how they work.
*/
export interface FutureLike<T> {
consume(consumer: Consumer<T>): void;
}
/**
* A Future represents a value which may or may not be available yet. It is
* similar to a JavaScript Promise, except that it can resolve synchronously.
*
* See [the Futures guide](https://frontside.com/effection/docs/guides/futures)
* for a more detailed description of futures and how they work.
*
* A Future may resolve to *three* different states. A Future can either become
* `completed` with a value, it can become `errored` with an Error or it can
* become `halted`, meaning it was prematurely cancelled.
*
* A Future can be created via the {@link createFuture} function, or via the
* The `resolve`, `reject` and `halt` functions on {@link Future}.
*
* See also the slimmed down {@link FutureLike} interface.
*/
export interface Future<T> extends Promise<T>, FutureLike<T> {

@@ -24,6 +49,66 @@ state: State;

produce(value: Value<T>): void;
/** @deprecated Use produce(value) instead */
resolve(value: Value<T>): void;
resolve(value: T): void;
reject(error: Error): void;
halt(): void;
}
/**
* Create a new Future. This returns an object which contains the future itself
* as well as a function to produce a value for the future, and also shortcuts
* to resolve/reject/halt the future.
*
* ### Example
*
* ```typescript
* import { createFuture } from 'effection';
*
* let { future, produce } = createFuture<number>();
*
* // later...
* produce({ state: 'completed', value: 100 });
*
* future.consume((value) => console.log(value)) // => { state: 'completed', value: 100 }
* ```
*
* ### Example using shortcut
*
* ```typescript
* import { createFuture } from 'effection';
*
* let { future, resolve } = createFuture<number>();
*
* // later...
* resolve(100);
*
* future.consume((value) => console.log(value)) // => { state: 'completed', value: 100 }
* ```
*/
export declare function createFuture<T>(): NewFuture<T>;
export declare function createFutureOnRunLoop<T>(runLoop: RunLoop): NewFuture<T>;
/**
* Namespace for `Future` functions to create resolved futures.
*
* See [Future](./interfaces/Future.html).
*/
export declare const Future: {
/**
* Create a future which has resolved successfully to a `completed` state.
*
* @param value The value to resolve to
* @typeParam T The type that the future resolves to
*/
resolve<T>(value: T): Future<T>;
/**
* Create a future which has been rejected and is in an `errored` state.
*
* @param error The error that the future rejects with
* @typeParam T The type that the future resolves to
*/
reject<T_1 = unknown>(error: Error): Future<T_1>;
/**
* Create a future which has been halted and is in a `halted` state.
*
* @typeParam T The type that the future resolves to
*/
halt<T_2 = unknown>(): Future<T_2>;
};
//# sourceMappingURL=future.d.ts.map
import { HaltError } from './halt-error';
import { createRunLoop } from './run-loop';
/**
* Create a new Future. This returns an object which contains the future itself
* as well as a function to produce a value for the future, and also shortcuts
* to resolve/reject/halt the future.
*
* ### Example
*
* ```typescript
* import { createFuture } from 'effection';
*
* let { future, produce } = createFuture<number>();
*
* // later...
* produce({ state: 'completed', value: 100 });
*
* future.consume((value) => console.log(value)) // => { state: 'completed', value: 100 }
* ```
*
* ### Example using shortcut
*
* ```typescript
* import { createFuture } from 'effection';
*
* let { future, resolve } = createFuture<number>();
*
* // later...
* resolve(100);
*
* future.consume((value) => console.log(value)) // => { state: 'completed', value: 100 }
* ```
*/
export function createFuture() {
let runLoop = createRunLoop();
return createFutureOnRunLoop(createRunLoop('future'));
}
export function createFutureOnRunLoop(runLoop) {
let consumers = [];

@@ -30,6 +63,2 @@ let result;

}
function resolve(value) {
console.warn(`DEPRECATED: resolve() is deprecated and will be changed or removed prior to the release of effection 2.0\nuse produce() instead`);
produce(value);
}
let promise;

@@ -61,5 +90,46 @@ function getPromise() {

},
resolve
resolve: (value) => produce({ state: 'completed', value }),
reject: (error) => produce({ state: 'errored', error }),
halt: () => produce({ state: 'halted' }),
};
}
/**
* Namespace for `Future` functions to create resolved futures.
*
* See [Future](./interfaces/Future.html).
*/
export const Future = {
/**
* Create a future which has resolved successfully to a `completed` state.
*
* @param value The value to resolve to
* @typeParam T The type that the future resolves to
*/
resolve(value) {
let { future, resolve } = createFuture();
resolve(value);
return future;
},
/**
* Create a future which has been rejected and is in an `errored` state.
*
* @param error The error that the future rejects with
* @typeParam T The type that the future resolves to
*/
reject(error) {
let { future, reject } = createFuture();
reject(error);
return future;
},
/**
* Create a future which has been halted and is in a `halted` state.
*
* @typeParam T The type that the future resolves to
*/
halt() {
let { future, halt } = createFuture();
halt();
return future;
},
};
//# sourceMappingURL=future.js.map

@@ -9,3 +9,3 @@ export class HaltError extends Error {

}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
export function isHaltError(value) {

@@ -12,0 +12,0 @@ return !!(value && value.__isEffectionHaltError);

@@ -8,3 +8,3 @@ import { Operation } from './operation';

export { deprecated } from './deprecated';
export { Labels, withLabels } from './labels';
export { Labels, withLabels, setLabels } from './labels';
export { HasEffectionTrace } from './error';

@@ -20,3 +20,27 @@ export { createFuture, Future, FutureLike } from './future';

export { label } from './operations/label';
/**
* Run takes an operation and runs it in a task. It returns the task it created.
*
* Run is an entry point into Effection, and is especially useful when
* embedding Effection code into existing code. If you are writing your whole
* program using Effection, you should prefer using `main`.
*
* ### Example
*
* ```
* import { run, fetch } from 'effection';
*
* async function fetchExample() {
* await run(function*() {
* let response = yield fetch('http://www.example.com');
* yield response.text();
* });
* });
* ```
*
* @param operation the operation to run
* @param options the options to configure the task with
* @returns the new task
*/
export declare function run<TOut>(operation?: Operation<TOut>, options?: TaskOptions): Task<TOut>;
//# sourceMappingURL=index.d.ts.map

@@ -5,4 +5,4 @@ import { Effection } from './effection';

export { deprecated } from './deprecated';
export { withLabels } from './labels';
export { createFuture } from './future';
export { withLabels, setLabels } from './labels';
export { createFuture, Future } from './future';
export { sleep } from './operations/sleep';

@@ -16,2 +16,26 @@ export { ensure } from './operations/ensure';

export { label } from './operations/label';
/**
* Run takes an operation and runs it in a task. It returns the task it created.
*
* Run is an entry point into Effection, and is especially useful when
* embedding Effection code into existing code. If you are writing your whole
* program using Effection, you should prefer using `main`.
*
* ### Example
*
* ```
* import { run, fetch } from 'effection';
*
* async function fetchExample() {
* await run(function*() {
* let response = yield fetch('http://www.example.com');
* yield response.text();
* });
* });
* ```
*
* @param operation the operation to run
* @param options the options to configure the task with
* @returns the new task
*/
export function run(operation, options) {

@@ -18,0 +42,0 @@ return Effection.root.run(operation, options);

import { Operation } from './operation';
/**
* A map of labels. Each label is a key/value pair, where the key must be a
* string and the value may be a string, number or boolean.
*/
export declare type Labels = Record<string, string | number | boolean>;
/**
* Apply the given labels to an operation. When the operation is run as a task,
* using {@link run} or {@link spawn}, the labels get applied to the task as
* well.
*
* If the task operation already has labels, the existing labels are not
* removed. See {@link setLabels} if you want to replace the existing labels
* entirely.
*/
export declare function withLabels<T>(operation: Operation<T>, labels: Labels): Operation<T>;
/**
* Like {@link withLabels}, but replaces the existing labels entirely.
*/
export declare function setLabels<T>(operation: Operation<T>, labels: Labels): Operation<T>;
//# sourceMappingURL=labels.d.ts.map

@@ -0,1 +1,10 @@

/**
* Apply the given labels to an operation. When the operation is run as a task,
* using {@link run} or {@link spawn}, the labels get applied to the task as
* well.
*
* If the task operation already has labels, the existing labels are not
* removed. See {@link setLabels} if you want to replace the existing labels
* entirely.
*/
export function withLabels(operation, labels) {

@@ -7,2 +16,11 @@ if (operation) {

}
/**
* Like {@link withLabels}, but replaces the existing labels entirely.
*/
export function setLabels(operation, labels) {
if (operation) {
operation.labels = labels;
}
return operation;
}
//# sourceMappingURL=labels.js.map

@@ -12,9 +12,21 @@ import type { Task } from './task';

}
export interface OperationResolution<TOut> extends Labelled {
perform(resolve: (value: TOut) => void, reject: (err: Error) => void): void | (() => void);
}
export interface OperationFuture<TOut> extends FutureLike<TOut>, Labelled {
}
/**
* An `Resource` in Effection represents a long running computation which also
* provides some means of interaction while it is running.
*
* See [the Resource guide](https://frontside.com/effection/docs/guides/resources) for more information.
*
*/
export interface Resource<TOut> extends Labelled {
init(scope: Task, local: Task): OperationIterator<TOut>;
/**
* A resource's `init` method is called when the resource is initialized. It
* runs inside the resource's resource task, and is able to spawn tasks and
* other resources directly under the resource task.
*
* @param resourceTask a handle to the resource task that the resource is running
* @param initTask a handle to the task of the `init` itself
*/
init(resourceTask: Task, initTask: Task): OperationIterator<TOut>;
}

@@ -24,3 +36,10 @@ export interface OperationFunction<TOut> extends Labelled {

}
export declare type Operation<TOut> = OperationPromise<TOut> | OperationIterator<TOut> | OperationResolution<TOut> | OperationFuture<TOut> | OperationFunction<TOut> | Resource<TOut> | undefined;
/**
* An `Operation` in Effection describes an abstract computation, that is a
* computation which is not currently running.
*
* See [the Task and Operations guide](https://frontside.com/effection/docs/guides/tasks) for more information.
*
*/
export declare type Operation<TOut> = OperationPromise<TOut> | OperationIterator<TOut> | OperationFuture<TOut> | OperationFunction<TOut> | Resource<TOut> | undefined;
//# sourceMappingURL=operation.d.ts.map

@@ -5,4 +5,26 @@ import type { Operation } from '../operation';

};
/**
* Block and wait for all of the given operations to complete. Returns an array of
* values that the given operations evaluated to. This has the same purpose as
* [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all).
*
* If any of the operations become errored, then `all` will also become errored.
*
* ### Example
*
* ```typescript
* import { main, all, fetch } from 'effection';
*
* main(function*() {
* let [google, bing] = yield all([fetch('http://google.com').text(), fetch('http://bing.com').text()]);
* // ...
* });
* ```
*
* @typeParam T the type of the array of options, this can a heterogenous array
* @param operations a list of operations to wait for
* @returns the list of values that the operations evaluate to, in the order they were given
*/
export declare function all<T extends Operation<any>[]>(operations: T): Operation<All<T>>;
export {};
//# sourceMappingURL=all.d.ts.map
import { withLabels } from '../labels';
import { spawn } from './spawn';
/**
* Block and wait for all of the given operations to complete. Returns an array of
* values that the given operations evaluated to. This has the same purpose as
* [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all).
*
* If any of the operations become errored, then `all` will also become errored.
*
* ### Example
*
* ```typescript
* import { main, all, fetch } from 'effection';
*
* main(function*() {
* let [google, bing] = yield all([fetch('http://google.com').text(), fetch('http://bing.com').text()]);
* // ...
* });
* ```
*
* @typeParam T the type of the array of options, this can a heterogenous array
* @param operations a list of operations to wait for
* @returns the list of values that the operations evaluate to, in the order they were given
*/
export function all(operations) {
return withLabels(function* (scope) {
return withLabels(function* (task) {
let tasks = [];
let results = [];
for (let operation of operations) {
if (scope.state === 'running') {
tasks.push(scope.run(operation));
try {
yield function* () {
for (let operation of operations) {
tasks.push(yield spawn(operation, { scope: task.options.scope }));
}
for (let task of tasks) {
results.push(yield task);
}
};
}
catch (err) {
for (let task of tasks) {
task.halt();
}
throw (err);
}
for (let task of tasks) {
results.push(yield task);
}
return results;

@@ -15,0 +46,0 @@ }, { name: 'all', count: operations.length });

@@ -1,3 +0,46 @@

import { Operation, Resource } from '../operation';
export declare function ensure<T>(fn: () => Operation<T> | void): Resource<undefined>;
import { Operation } from '../operation';
/**
* Run the given function or operation when the current task shuts down. This
* is equivalent to running the function or operation in a finally block, but
* it can help you avoid rightward drift.
*
* ### Example using function
*
* Using a function:
*
* ```typescript
* import { main, ensure } from 'effection';
* import { createServer } from 'http';
*
* main(function*() {
* let server = createServer(...);
* yield ensure(() => { server.close() });
* });
* ```
*
* Note that you should wrap the function body in braces, so the function
* returns `undefined`.
*
* ### Example using operation
*
* ```typescript
* import { main, ensure, once } from 'effection';
* import { createServer } from 'some-library';
*
* main(function*() {
* let server = createServer(...);
* yield ensure(function*() {
* server.close();
* yield once(server, 'closed');
* });
* });
* ```
*
* Here we're using some library where server shutdown is asynchronous, we can
* block and wait for the shutdown to complete inside of ensure by using an
* operation.
*
* @param fn a function which returns an operation or void
*/
export declare function ensure<T>(fn: () => Operation<T> | void): Operation<undefined>;
//# sourceMappingURL=ensure.d.ts.map

@@ -1,21 +0,65 @@

import { spawn } from './spawn';
import { Future } from '../future';
/**
* Run the given function or operation when the current task shuts down. This
* is equivalent to running the function or operation in a finally block, but
* it can help you avoid rightward drift.
*
* ### Example using function
*
* Using a function:
*
* ```typescript
* import { main, ensure } from 'effection';
* import { createServer } from 'http';
*
* main(function*() {
* let server = createServer(...);
* yield ensure(() => { server.close() });
* });
* ```
*
* Note that you should wrap the function body in braces, so the function
* returns `undefined`.
*
* ### Example using operation
*
* ```typescript
* import { main, ensure, once } from 'effection';
* import { createServer } from 'some-library';
*
* main(function*() {
* let server = createServer(...);
* yield ensure(function*() {
* server.close();
* yield once(server, 'closed');
* });
* });
* ```
*
* Here we're using some library where server shutdown is asynchronous, we can
* block and wait for the shutdown to complete inside of ensure by using an
* operation.
*
* @param fn a function which returns an operation or void
*/
export function ensure(fn) {
return {
name: 'ensure',
*init() {
yield spawn(function* () {
try {
yield;
return function ensure(task) {
let { scope } = task.options;
if (!scope) {
throw new Error('cannot run `ensure` on a task without scope');
}
scope.run(function* ensureHandler() {
try {
yield;
}
finally {
let result = fn();
if (result) {
yield result;
}
finally {
let result = fn();
if (result) {
yield result;
}
}
}, { labels: { name: 'ensureHandler' } });
return undefined;
}
}
});
return Future.resolve(undefined);
};
}
//# sourceMappingURL=ensure.js.map

@@ -1,4 +0,41 @@

import { Resource } from '../operation';
import { Operation } from '../operation';
import { Labels } from '../labels';
export declare function label(labels: Labels): Resource<void>;
/**
* Apply the given labels to the current task. This can be used to dynamically
* set labels, for example as the state of the operation changes, or as more
* information becomes available.
*
* If possible you should prefer to set labels statically using {@link withLabels},
* or by passing them as options to {@link run} or {@link spawn}.
*
* Existing labels are not removed, but may be overwritten if the key is the
* same.
*
* ### Example
*
* ```typescript
* import { main, label } from 'effection';
*
* function createSocket(url) {
* // use `withLabels` for information which is known statically
* return withLabels(function*() {
* let socket = new WebSocket(url);
* yield once(socket, 'open');
*
* // state has changed dynamically, use `label` to update labels
* yield label({ state: 'open'});
*
* let initMessage = yield once(socket, 'message');
*
* // we received a handshake message with some new information
* yield label({ state: 'initialized', connectionId: initMessage.data.connectionId });
*
* // ...
* }, { labels: { name: 'createSocket', state: 'pending' }});
* });
* ```
*
* @param labels the labels to apply
*/
export declare function label(labels: Labels): Operation<void>;
//# sourceMappingURL=label.d.ts.map

@@ -0,9 +1,50 @@

import { Future } from '../future';
import { withLabels } from '../labels';
/**
* Apply the given labels to the current task. This can be used to dynamically
* set labels, for example as the state of the operation changes, or as more
* information becomes available.
*
* If possible you should prefer to set labels statically using {@link withLabels},
* or by passing them as options to {@link run} or {@link spawn}.
*
* Existing labels are not removed, but may be overwritten if the key is the
* same.
*
* ### Example
*
* ```typescript
* import { main, label } from 'effection';
*
* function createSocket(url) {
* // use `withLabels` for information which is known statically
* return withLabels(function*() {
* let socket = new WebSocket(url);
* yield once(socket, 'open');
*
* // state has changed dynamically, use `label` to update labels
* yield label({ state: 'open'});
*
* let initMessage = yield once(socket, 'message');
*
* // we received a handshake message with some new information
* yield label({ state: 'initialized', connectionId: initMessage.data.connectionId });
*
* // ...
* }, { labels: { name: 'createSocket', state: 'pending' }});
* });
* ```
*
* @param labels the labels to apply
*/
export function label(labels) {
return {
name: 'label',
*init(scope) {
scope.setLabels(labels);
return withLabels((task) => {
let { scope } = task.options;
if (!scope) {
throw new Error('cannot run `label` on a task without scope');
}
};
scope.setLabels(labels);
return Future.resolve(undefined);
}, { name: 'label' });
}
//# sourceMappingURL=label.js.map
import type { Operation } from '../operation';
/**
* Race the given operations against each other and return the value of
* whichever operation returns first. This has the same purpose as
* [Promise.race](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race).
*
* If an operation become errored first, then `race` will fail with this error.
* After the first operation wins the race, all other operations will become
* halted and therefore cannot throw any further errors.
*
* ### Example
*
* ```typescript
* import { main, race, fetch } from 'effection';
*
* main(function*() {
* let fastest = yield race([fetch('http://google.com').text(), fetch('http://bing.com').text()]);
* // ...
* });
* ```
*
* @typeParam T the type of the operations that race against each other
* @param operations a list of operations to race against each other
* @returns the value of the fastest operation
*/
export declare function race<T>(operations: Operation<T>[]): Operation<T>;
//# sourceMappingURL=race.d.ts.map
import { withLabels } from '../labels';
import { createFuture } from '../future';
/**
* Race the given operations against each other and return the value of
* whichever operation returns first. This has the same purpose as
* [Promise.race](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race).
*
* If an operation become errored first, then `race` will fail with this error.
* After the first operation wins the race, all other operations will become
* halted and therefore cannot throw any further errors.
*
* ### Example
*
* ```typescript
* import { main, race, fetch } from 'effection';
*
* main(function*() {
* let fastest = yield race([fetch('http://google.com').text(), fetch('http://bing.com').text()]);
* // ...
* });
* ```
*
* @typeParam T the type of the operations that race against each other
* @param operations a list of operations to race against each other
* @returns the value of the fastest operation
*/
export function race(operations) {
return withLabels((scope) => ({
perform: (resolve, reject) => {
for (let operation of operations) {
if (scope.state === 'running') {
scope.run(function* () {
try {
resolve(yield operation);
}
catch (e) {
reject(e);
}
});
}
return withLabels(function* (task) {
let { resolve, future } = createFuture();
let tasks = [];
for (let operation of operations) {
if (task.state === 'running') {
let child = task.run(operation, { ignoreError: true, scope: task.options.scope });
child.consume(() => resolve(child));
tasks.push(child);
}
}
}), { name: 'race', count: operations.length });
let resultChild = yield future;
for (let child of tasks) {
if (child !== resultChild) {
child.halt();
}
}
return yield resultChild;
}, { name: 'race', count: operations.length });
}
//# sourceMappingURL=race.js.map
import { Operation } from '../operation';
/**
* Sleep for the given amount of milliseconds. If no duration is given, then
* sleep indefinitely.
*
* ### Example
*
* ```typescript
* import { main, sleep } from 'effection';
*
* main(function*() {
* yield sleep(2000);
* console.log("Hello lazy world!");
* });
* ```
*
* @param duration the number of milliseconds to sleep
*/
export declare function sleep(duration?: number): Operation<void>;
//# sourceMappingURL=sleep.d.ts.map

@@ -0,12 +1,30 @@

import { createFuture } from '../future';
import { withLabels } from '../labels';
/**
* Sleep for the given amount of milliseconds. If no duration is given, then
* sleep indefinitely.
*
* ### Example
*
* ```typescript
* import { main, sleep } from 'effection';
*
* main(function*() {
* yield sleep(2000);
* console.log("Hello lazy world!");
* });
* ```
*
* @param duration the number of milliseconds to sleep
*/
export function sleep(duration) {
return {
labels: { name: 'sleep', duration: (duration != null) ? duration : 'forever' },
perform(resolve) {
if (duration != null) {
let timeoutId = setTimeout(resolve, duration);
return () => clearTimeout(timeoutId);
}
return withLabels((task) => {
let { future, resolve } = createFuture();
if (duration != null) {
let timeoutId = setTimeout(resolve, duration);
task.consume(() => clearTimeout(timeoutId));
}
};
return future;
}, { name: 'sleep', duration: (duration != null) ? duration : 'forever' });
}
//# sourceMappingURL=sleep.js.map

@@ -1,8 +0,38 @@

import { Operation, Resource } from '../operation';
import { Operation, OperationFunction } from '../operation';
import type { Task, TaskOptions } from '../task';
interface Spawn<T> extends Resource<Task<T>> {
within(scope: Task): Resource<Task<T>>;
interface Spawn<T> extends OperationFunction<Task<T>> {
within(scope: Task): Operation<Task<T>>;
}
/**
* An operation which spawns the given operation as a child of the current task.
*
* You should prefer using the spawn operation over calling `task.run` from
* within Effection code. The reason being that a synchronous failure in the
* spawned task will not be caught until the next yield point when using `run`,
* which can be confusing. Additionally, using `spawn` is usually more
* ergonomic.
*
* ### Example
*
* ```typescript
* import { main, sleep, spawn } from 'effection';
*
* main(function*() {
* yield spawn(function*() {
* yield sleep(1000);
* console.log("hello");
* });
* yield spawn(function*() {
* yield sleep(2000);
* console.log("world");
* });
* yield;
* });
* ```
*
* @param operation the operation to run as a child of the current task
* @typeParam T the type that the spawned task evaluates to
*/
export declare function spawn<T>(operation?: Operation<T>, options?: TaskOptions): Spawn<T>;
export {};
//# sourceMappingURL=spawn.d.ts.map

@@ -0,12 +1,46 @@

import { Future } from '../future';
/**
* An operation which spawns the given operation as a child of the current task.
*
* You should prefer using the spawn operation over calling `task.run` from
* within Effection code. The reason being that a synchronous failure in the
* spawned task will not be caught until the next yield point when using `run`,
* which can be confusing. Additionally, using `spawn` is usually more
* ergonomic.
*
* ### Example
*
* ```typescript
* import { main, sleep, spawn } from 'effection';
*
* main(function*() {
* yield spawn(function*() {
* yield sleep(1000);
* console.log("hello");
* });
* yield spawn(function*() {
* yield sleep(2000);
* console.log("world");
* });
* yield;
* });
* ```
*
* @param operation the operation to run as a child of the current task
* @typeParam T the type that the spawned task evaluates to
*/
export function spawn(operation, options) {
function* init(scope) {
return scope.run(operation, options);
function spawn(task) {
let { scope } = task.options;
if (!scope) {
throw new Error('cannot run `spawn` on a task without scope');
}
let result = scope.run(operation, options);
return Future.resolve(result);
}
function within(scope) {
return {
init: () => init(scope)
};
return scope.spawn(operation, options);
}
return { init, within, name: 'spawn' };
return Object.assign(spawn, { within });
}
//# sourceMappingURL=spawn.js.map
import { Operation } from '../operation';
/**
* Throw an error after the given amount of milliseconds. See also {@link withTimeout}.
*
* ### Example
*
* ```typescript
* import { main, spawn, timeout } from 'effection';
*
* main(function*() {
* yield spawn(timeout(2000));
* yield fetch('http://google.com');
* });
* ```
*
* @param duration the timeout duration in milliseconds
*/
export declare function timeout(duration: number): Operation<never>;
//# sourceMappingURL=timeout.d.ts.map

@@ -9,2 +9,18 @@ import { sleep } from './sleep';

}
/**
* Throw an error after the given amount of milliseconds. See also {@link withTimeout}.
*
* ### Example
*
* ```typescript
* import { main, spawn, timeout } from 'effection';
*
* main(function*() {
* yield spawn(timeout(2000));
* yield fetch('http://google.com');
* });
* ```
*
* @param duration the timeout duration in milliseconds
*/
export function timeout(duration) {

@@ -11,0 +27,0 @@ return withLabels(function* () {

import { Operation } from '../operation';
/**
* Runs the given operation and throws an error if it takes more than the given
* number of milliseconds. See also {@link timeout}.
*
* ### Example
*
* ```typescript
* import { main, spawn, withTimeout } from 'effection';
*
* main(function*() {
* yield withTimeout(2000, fetch('http://google.com'));
* });
* ```
*
* @param duration the timeout duration in milliseconds
*/
export declare function withTimeout<T>(duration: number, operation: Operation<T>): Operation<T>;
//# sourceMappingURL=with-timeout.d.ts.map
import { timeout } from './timeout';
import { spawn } from './spawn';
import { withLabels } from '../labels';
import { race } from './race';
import { setLabels } from '../labels';
/**
* Runs the given operation and throws an error if it takes more than the given
* number of milliseconds. See also {@link timeout}.
*
* ### Example
*
* ```typescript
* import { main, spawn, withTimeout } from 'effection';
*
* main(function*() {
* yield withTimeout(2000, fetch('http://google.com'));
* });
* ```
*
* @param duration the timeout duration in milliseconds
*/
export function withTimeout(duration, operation) {
return withLabels(function* () {
yield spawn(timeout(duration));
return yield operation;
}, { name: 'withTimeout', duration });
return setLabels(race([timeout(duration), operation]), { name: 'withTimeout', duration });
}
//# sourceMappingURL=with-timeout.js.map

@@ -1,8 +0,7 @@

import type { OperationResolution, Resource } from "./operation";
import type { Resource } from "./operation";
import type { FutureLike } from "./future";
export declare function isPromise(value: any): value is PromiseLike<unknown>;
export declare function isGenerator(value: any): value is Iterator<unknown>;
export declare function isResolution<T>(value: any): value is OperationResolution<T>;
export declare function isFuture<T>(value: any): value is FutureLike<T>;
export declare function isResource<TOut>(value: any): value is Resource<TOut>;
//# sourceMappingURL=predicates.d.ts.map

@@ -9,5 +9,2 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */

}
export function isResolution(value) {
return value && typeof (value.perform) === 'function';
}
export function isFuture(value) {

@@ -14,0 +11,0 @@ return value && typeof (value.consume) === 'function';

@@ -5,3 +5,3 @@ export declare type Runnable = () => void;

}
export declare function createRunLoop(): RunLoop;
export declare function createRunLoop(name?: string): RunLoop;
//# sourceMappingURL=run-loop.d.ts.map

@@ -5,3 +5,3 @@ // A run loop protects against reentrant code, where synchronous callbacks end

// of synchronous mutex
export function createRunLoop() {
export function createRunLoop(name) {
let didEnter = false;

@@ -21,3 +21,3 @@ let runnables = [];

catch (e) {
console.error("Caught error in run loop:");
console.error(`Caught error in run loop \`${name}\`:`);
console.error(e);

@@ -24,0 +24,0 @@ }

/// <reference types="node" />
import { EventEmitter } from 'events';
/**
* The state of a Task can be one of the following:
*
* - `pending`: the task has not been started yet
* - `running`: the task is currently running
* - `halting`: the task has been halted, but halting is not finished yet
* - `halted`: the task is fully halted
* - `erroring`: the task or one of its children has failed and is in the process of shutting down
* - `errored`: the task has fully failed and evaluates to an Error
* - `completing`: the task is completing and shutting down its children
* - `completed`: the task is fully complete and evaluates to a value
*/
export declare type State = 'pending' | 'running' | 'halting' | 'halted' | 'erroring' | 'errored' | 'completing' | 'completed';
/**
* @hidden
*/
export declare type StateTransition = {

@@ -13,2 +28,3 @@ from: State;

private transition;
get isFinalized(): boolean;
start(): void;

@@ -15,0 +31,0 @@ completing(): void;

@@ -17,2 +17,5 @@ function f(value) { return JSON.stringify(value); }

}
get isFinalized() {
return this.current === 'errored' || this.current === 'completed' || this.current === 'halted';
}
start() {

@@ -19,0 +22,0 @@ this.transition('start', {

@@ -7,2 +7,5 @@ /// <reference types="node" />

import { Future, FutureLike } from './future';
/**
* @hidden
*/
export interface TaskInfo {

@@ -14,2 +17,5 @@ id: number;

}
/**
* @hidden
*/
export interface TaskTree extends TaskInfo {

@@ -19,30 +25,277 @@ yieldingTo?: TaskTree;

}
/**
* Task options can be used to configure the behaviour of a task. Options can
* be passed to {@link run}, `main`, {@link spawn} and when creating a
* task manually through {@link createTask}.
*/
export interface TaskOptions {
readonly resourceScope?: Task;
/**
* Normally a task's type is inferred from its operation, but it can also be
* explicitly set via this option.
*/
readonly type?: string;
/**
* The scope of a task describes the scope in which this task creates resource
* tasks.
*
* Usually this is the parent task, but in some instances it can be useful to
* change the scope of a task. This is for example often the case with
* "higher-order operations", that is operations which take another operation
* as an argument, such as the {@link all} operation.
*
* This is a very advanced feature and great care should be taken when using
* it as it can lead to possibly breaking some of the guarantees that
* Effection makes.
*/
readonly scope?: Task;
/**
* @internal
*
* The yieldScope of a task describes the scope in which this task's yield
* points create resources. This is normally the task itself. It is currently
* only changed for resource's `init` tasks.
*
* This is an internal API and you should not set this option yourself.
*/
readonly yieldScope?: Task;
/**
* Normally when a task finishes its operation, it halts all of its children.
* When setting this option to `true` on a child, it will cause the parent
* task to wait for the child to complete after it finishes its operation.
* Essentially it allows a child to block the completion of its parent.
*/
readonly blockParent?: boolean;
/**
* When a child task becomes errored it normally causes its parent task to
* become errored as well. When setting this option to `true` on a parent
* task, an error in its children will be ignored. This is useful for some
* tasks which want to have finer grained control over their children, such
* as supervisors or root tasks.
*/
readonly ignoreChildErrors?: boolean;
/**
* When a child task becomes errored it normally causes its parent task to
* become errored as well. When setting this option to `true` on a child
* task, then an error in the child will not cause the parent to become
* errored. This is useful for some tasks which want finer grained control
* over error conditions.
*/
readonly ignoreError?: boolean;
/**
* Set the given labels on the task.
*/
readonly labels?: Labels;
}
/**
* A `Task` in Effection has many responsibilities. Fundamentally it represents
* a computation which at some point will result in either a value or an error,
* or become halted. It can also have children, which are also tasks.
*
* See [the Task and Operations guide](https://frontside.com/effection/docs/guides/tasks) for more information.
*
*/
export interface Task<TOut = unknown> extends Promise<TOut>, FutureLike<TOut> {
/**
* Each `Task` has a unique id
*/
readonly id: number;
/**
* The type of a `Task` is usually determined by the operation that the task
* is running, but it can also be adjusted via the `type` option.
*/
readonly type: string;
/**
* Returns the {@link State} that the task is currently in.
*/
readonly state: State;
/**
* Returns the options that the task was created with
*/
readonly options: TaskOptions;
/**
* Returns a map of {@link Labels} which provide additional information about
* the task, such as its name, or any other metadata that the task wants to
* provide.
*/
readonly labels: Labels;
/**
* Returns a list of the task's children
*/
readonly children: Task[];
/**
* Returns a {@link Future} for this task. The task itself can act as a
* Future, so usually you do not need to access this property explicitly.
*/
readonly future: Future<TOut>;
/**
* Returns the task that this task is currently yielding to. When using a
* generator with a task, a task ends up processing one operation at a time,
* each such operation runs in its own task, and can be accessed via this
* property.
*
* ### Example
*
* ``` typescript
* import { run, sleep } from 'effection'
*
* let task = run(function*() {
* yield sleep(2000);
* });
*
* console.log(task.yieldingTo); // => logs the sleep task
* ```
*/
readonly yieldingTo: Task | undefined;
/**
* Tasks which construct a resource create a special task in the background called
* a resource task, which groups everything running inside the resource. This property
* provides access to such a task.
*
* ### Example
*
* ``` typescript
* import { main, spawn, fetch } from 'effection'
*
* main(function*() {
* let fetchTask = yield spawn(fetch('http://www.example.com'));
* console.log(fetchTask.resourceTask); // => logs the resource task of the fetch
* });
* ```
*/
readonly resourceTask: Task | undefined;
/**
* When using a task as a `Promise`, it would usually be rejected when the task
* is halted. Using `catchHalt()` we can treat a `halt` as the promise
* resolving to `undefined`, rather than being rejected.
*
* ### Example
*
* ``` typescript
* import { run, sleep } from 'effection'
*
* let task = run(function*() {
* yield sleep(2000);
* return "done!";
* });
*
* task.halt();
*
* task.then((value) => console.log(value)); // => log "undefined"
*/
catchHalt(): Promise<TOut | undefined>;
/**
* Sets the given {@link Labels} on the task. This only adds new labels or
* overwrites labels with the same key, but does not remove other labels.
*
* See also the {@link label} operation for setting labels dynamically.
*
* ### Example
*
* ``` typescript
* import { run } from 'effection'
*
* let task = run();
* task.setLabels({ name: 'myTask', florb: 123 });
* ```
*/
setLabels(labels: Labels): void;
run<R>(operation?: Operation<R>, options?: TaskOptions): Task<R>;
/** @deprecated Use run() instead */
spawn<R>(operation?: Operation<R>, options?: TaskOptions): Task<R>;
/**
* Run the given operation as a child of this task.
*
* This is similar to {@link spawn}, but it is *not* an operation and
* therefore should *not* be used with `yield`. If you are inside Effection
* code, you should generally prefer {@link spawn}. `run` is useful when you
* are creating children from outside of Effection.
*
* ### Example
*
* ``` typescript
* import { run } from 'effection'
*
* let task = run();
* task.run(function*() { ... }) // run a child of this task
* ```
*
* @typeParam TOutChild the type that the child resolves to
*/
run<TOutChild>(operation?: Operation<TOutChild>, options?: TaskOptions): Task<TOutChild>;
/**
* An operation to run the given operation as child of this task.
*
* This is similar to {@link run}, but it is an operation and therefore must
* be used with `yield` or `run`. If you are inside Effection code, you
* should generally prefer `spawn`. {@link run} is useful when you are
* creating children from outside of Effection.
*
* ### Example
*
* ``` typescript
* import { main, spawn } from 'effection'
*
* main(function*(mainTask) {
* yield function*() {
* yield mainTask.spawn(function*() { ... }); // => run as child of `main`.
* }
* });
* ```
*
* @param operation the operation that the child runs
* @typeParam TOutChild the type that the child resolves to
*/
spawn<TOutChild>(operation?: Operation<TOutChild>, options?: TaskOptions): Operation<Task<TOutChild>>;
/**
* Cause this task to halt. This will halt the task itself, as well as any task
* it is currently {@link yieldingTo} and its {@link children}.
*/
halt(): Promise<void>;
/**
* Starts running the task if it has not yet started, does nothing otherwise.
* You will only need to call this if you created your task manually through
* {@link createTask}.
*
* ### Example
*
* ``` typescript
* import { createTask } from 'effection'
*
* let task = createTask(someOperation);
* console.log(task.state) // => 'pending';
*
* task.start();
* console.log(task.state) // => 'running';
* ```
*/
start(): void;
/**
* Serializes information about the task into an object. This will include
* the task's {@link id}, {@link state}, {@link type}, {@link labels} as well
* as the task it is {@link yieldingTo} and its {@link children}.
*/
toJSON(): TaskTree;
/**
* Returns a readable description of the task and its children. This is
* useful for debugging. The output can be improved by applying {@link labels}.
* For more advanced debugging you can also use the
* [inspector](https://frontside.com/effection/docs/guides/inspector).
*/
toString(): string;
/**
* @hidden
*/
on: EventEmitter['on'];
/**
* @hidden
*/
off: EventEmitter['off'];
}
/**
* Low level interface to create a task which does not have a parent. Normally all
* tasks are spawned as children of {@link Effection}.root, but on rare occasions it is necessary
* to create a task outside the normal task hierarchy.
*
* @param operation the operation that the task runs
* @param options the options that the task is configured with
* @returns the new task
*/
export declare function createTask<TOut = unknown>(operation: Operation<TOut>, options?: TaskOptions): Task<TOut>;
//# sourceMappingURL=task.d.ts.map

@@ -7,5 +7,14 @@ /* eslint-disable @typescript-eslint/no-explicit-any */

import { addTrace } from './error';
import { createFuture } from './future';
import { createFutureOnRunLoop } from './future';
import { createRunLoop } from './run-loop';
let COUNTER = 0;
/**
* Low level interface to create a task which does not have a parent. Normally all
* tasks are spawned as children of {@link Effection}.root, but on rare occasions it is necessary
* to create a task outside the normal task hierarchy.
*
* @param operation the operation that the task runs
* @param options the options that the task is configured with
* @returns the new task
*/
export function createTask(operation, options = {}) {

@@ -17,5 +26,5 @@ let id = ++COUNTER;

let stateMachine = new StateMachine(emitter);
let { produce, future } = createFuture();
let result;
let runLoop = createRunLoop();
let runLoop = createRunLoop(`task ${id}`);
let { produce, future } = createFutureOnRunLoop(runLoop);
let controller;

@@ -38,5 +47,6 @@ let labels = { ...operation === null || operation === void 0 ? void 0 : operation.labels, ...options.labels };

get state() { return stateMachine.current; },
get type() { return controller.type; },
get type() { return options.type || controller.type; },
get children() { return Array.from(children); },
get yieldingTo() { return yieldingTo; },
get resourceTask() { return controller.resourceTask; },
catchHalt() {

@@ -53,3 +63,3 @@ return future.catch(swallowHalt);

}
let child = createTask(operation, { resourceScope: task, ...options });
let child = createTask(operation, { scope: task, ...options });
link(child);

@@ -60,17 +70,24 @@ child.start();

spawn(operation, options = {}) {
console.warn(`DEPRECATED: task.spawn() is deprecated and will be changed or removed prior to the release of effection 2.0\nuse task.run() instead`);
return task.run(operation, options);
return {
name: 'spawn',
*init() {
return task.run(operation, options);
}
};
},
start() {
if (stateMachine.current === 'pending') {
stateMachine.start();
controller.start();
}
runLoop.run(() => {
if (stateMachine.current === 'pending') {
stateMachine.start();
controller.start();
}
});
},
async halt() {
if (stateMachine.current === 'running' || stateMachine.current === 'completing') {
controller.halt();
if (stateMachine.current === 'running') {
stateMachine.halting();
result = { state: 'halted' };
shutdown(true);
}
shutdown(true);
await future.catch(() => {

@@ -84,3 +101,3 @@ // TODO: should this catch all errors, or only halt errors?

id: id,
type: controller.type,
type: task.type,
labels: labels,

@@ -92,2 +109,10 @@ state: stateMachine.current,

},
toString() {
let formattedLabels = Object.entries(labels).filter(([key]) => key !== 'name' && key !== 'expand').map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(' ');
return [
[labels.name || 'task', formattedLabels, `[${task.type} ${id}]`].filter(Boolean).join(' '),
yieldingTo && yieldingTo.toString().split('\n').map(l => '┃ ' + l).join('\n').replace(/^┃ /, `┣ yield `),
...Array.from(children).map((c) => c.toString().split('\n').map(l => '┃ ' + l).join('\n').replace(/^┃/, '┣'))
].filter(Boolean).join('\n');
},
on: (...args) => emitter.on(...args),

@@ -102,2 +127,3 @@ off: (...args) => emitter.off(...args),

controller = createController(task, operation, {
runLoop,
onYieldingToChange(value) {

@@ -109,2 +135,4 @@ yieldingTo = value;

controller.future.consume((value) => {
if (stateMachine.isFinalized)
return;
if (value.state === 'completed') {

@@ -122,2 +150,7 @@ stateMachine.completing();

}
else if (value.state === 'halted' && stateMachine.current !== 'erroring') {
stateMachine.halting();
result = { state: 'halted' };
shutdown(true);
}
finalize();

@@ -128,12 +161,17 @@ });

child.consume((value) => {
if (value.state === 'errored' && !child.options.ignoreError && !options.ignoreChildErrors) {
stateMachine.erroring();
result = { state: 'errored', error: addTrace(value.error, task) };
shutdown(true);
}
if (children.has(child)) {
children.delete(child);
emitter.emit('unlink', child);
}
finalize();
runLoop.run(() => {
if (stateMachine.isFinalized)
return;
if (value.state === 'errored' && !child.options.ignoreError && !options.ignoreChildErrors) {
stateMachine.erroring();
result = { state: 'errored', error: addTrace(value.error, task) };
controller.halt();
shutdown(true);
}
if (children.has(child)) {
children.delete(child);
emitter.emit('unlink', child);
}
finalize();
});
});

@@ -145,3 +183,2 @@ children.add(child);

function shutdown(force) {
controller.halt();
controller.future.consume(() => {

@@ -164,12 +201,10 @@ let nextChild;

function finalize() {
runLoop.run(() => {
if (Array.from(children).length !== 0)
return;
if (controller.future.state === 'pending')
return;
if (future.state !== 'pending')
return;
stateMachine.finish();
produce(result);
});
if (Array.from(children).length !== 0)
return;
if (controller.future.state === 'pending')
return;
if (future.state !== 'pending')
return;
stateMachine.finish();
produce(result);
}

@@ -176,0 +211,0 @@ return task;

{
"name": "@effection/core",
"version": "2.0.0-side-effects.1628189696867",
"version": "2.0.0-v2-writable-unification.1633595877341",
"main": "dist-cjs/index.js",

@@ -10,3 +10,3 @@ "module": "dist-esm/index.js",

"license": "MIT",
"homepage": "https://github.com/thefrontside/effection",
"homepage": "https://frontside.com/effection",
"repository": {

@@ -35,3 +35,2 @@ "type": "git",

"eslint": "^7.30.0",
"eslint-plugin-tree-shaking": "^1.9.2",
"expect": "^25.3.0",

@@ -38,0 +37,0 @@ "mocha": "^8.3.1",

import type { Task } from '../task';
import type { RunLoop } from '../run-loop';
import type { Operation } from '../operation';
import { isResource, isResolution, isFuture, isPromise, isGenerator } from '../predicates';
import { isResource, isFuture, isPromise, isGenerator } from '../predicates';
import { createFunctionController } from './function-controller';

@@ -8,3 +9,2 @@ import { createSuspendController } from './suspend-controller';

import { createIteratorController } from './iterator-controller';
import { createResolutionController } from './resolution-controller';
import { createFutureController } from './future-controller';

@@ -17,2 +17,3 @@ import { createResourceController } from './resource-controller';

operation: Operation<TOut>;
resourceTask?: Task;
start(): void;

@@ -24,7 +25,7 @@ halt(): void;

export type Options = {
resourceScope?: Task;
runLoop: RunLoop;
onYieldingToChange?: (task: Task | undefined) => void;
}
export function createController<T>(task: Task<T>, operation: Operation<T>, options: Options = {}): Controller<T> {
export function createController<T>(task: Task<T>, operation: Operation<T>, options: Options): Controller<T> {
if (typeof(operation) === 'function') {

@@ -38,4 +39,2 @@ return createFunctionController(task, operation, () => createController(task, operation(task), options));

return createFutureController(task, operation);
} else if (isResolution<T>(operation)) {
return createResolutionController(task, operation);
} else if(isPromise(operation)) {

@@ -45,5 +44,5 @@ return createPromiseController(task, operation);

return createIteratorController(task, operation, options);
} else {
return createFutureController(task, Future.reject(new Error(`unkown type of operation: ${operation}`)));
}
throw new Error(`unkown type of operation: ${operation}`);
}
import { Task } from '../task';
import { Controller } from './controller';
import { createFuture, Future } from '../future';
import { Operation, OperationFunction } from '../operation';
import { createFuture } from '../future';
import { OperationFunction } from '../operation';
interface FunctionController<TOut> {
readonly type: string;
readonly operation: Operation<TOut>;
future: Future<TOut>;
start: () => void;
halt: () => void;
}
export function createFunctionController<TOut>(task: Task<TOut>, fn: OperationFunction<TOut>, createController: () => Controller<TOut>): FunctionController<TOut> {
export function createFunctionController<TOut>(task: Task<TOut>, fn: OperationFunction<TOut>, createController: () => Controller<TOut>): Controller<TOut> {
let delegate: Controller<TOut>;

@@ -51,2 +43,5 @@ let { produce, future } = createFuture<TOut>();

},
get resourceTask() {
return delegate?.resourceTask;
},
future,

@@ -53,0 +48,0 @@ start,

@@ -6,3 +6,2 @@ import { Controller, Options } from './controller';

import { createFuture, Value } from '../future';
import { createRunLoop } from '../run-loop';

@@ -17,3 +16,3 @@ const claimed = Symbol.for('effection/v2/iterator-controller/claimed');

export function createIteratorController<TOut>(task: Task<TOut>, iterator: OperationIterator<TOut> & Claimable, options: Options = {}): Controller<TOut> {
export function createIteratorController<TOut>(task: Task<TOut>, iterator: OperationIterator<TOut> & Claimable, options: Options): Controller<TOut> {
let didHalt = false;

@@ -23,3 +22,2 @@ let yieldingTo: Task | undefined;

let { produce, future } = createFuture<TOut>();
let runLoop = createRunLoop();

@@ -38,3 +36,3 @@ function start() {

function resume(iter: NextFn) {
runLoop.run(() => {
options.runLoop.run(() => {
let next;

@@ -54,3 +52,3 @@ try {

} else {
yieldingTo = createTask(next.value, { resourceScope: options.resourceScope || task, ignoreError: true });
yieldingTo = createTask(next.value, { scope: task.options.yieldScope || task, ignoreError: true });
yieldingTo.consume(trap);

@@ -57,0 +55,0 @@ yieldingTo.start();

import { Controller } from './controller';
import { createIteratorController } from './iterator-controller';
import { Resource } from '../operation';

@@ -8,29 +7,44 @@ import { Task } from '../task';

export function createResourceController<TOut>(task: Task<TOut>, resource: Resource<TOut>): Controller<TOut> {
let delegate: Controller<TOut>;
let { resourceScope } = task.options;
let resourceTask: Task;
let initTask: Task<TOut>;
let { scope } = task.options;
let { produce, future } = createFuture<TOut>();
function start() {
if(!resourceScope) {
if(!scope) {
throw new Error('cannot spawn resource in task which has no resource scope');
}
let init;
try {
init = resource.init(resourceScope, task);
} catch(error) {
produce({ state: 'errored', error });
return;
}
delegate = createIteratorController(task, init, { resourceScope });
delegate.future.consume((value) => {
let name = resource.name || resource.labels?.name || 'resource';
let labels = resource.labels || {};
resourceTask = scope.run(undefined, { type: 'resource', labels: { ...labels, name } });
initTask = resourceTask.run((task) => resource.init(resourceTask, task), {
yieldScope: resourceTask,
labels: { name: 'init' },
ignoreError: true,
});
initTask.consume((value) => {
if(value.state !== 'completed') {
resourceTask.halt();
}
produce(value);
});
delegate.start();
}
function halt() {
delegate.halt();
resourceTask?.halt();
}
return { start, halt, future, type: 'resource', operation: resource };
return {
type: 'resource constructor',
start,
halt,
future,
get resourceTask() {
return resourceTask;
},
operation: resource
};
}

@@ -0,1 +1,4 @@

/**
* @hidden
*/
export function deprecated<TThis, TArgs extends unknown[], TReturn>(message: string, fn: (this: TThis, ...args: TArgs) => TReturn): (this: TThis, ...args: TArgs) => TReturn {

@@ -2,0 +5,0 @@ return function(...args) {

@@ -5,4 +5,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */

// @ts-ignore
import { version } from '../package.json';
import packageInfo from '../package.json';
const { version } = packageInfo;
const MAJOR_VERSION = version.split('.')[0];

@@ -30,3 +32,15 @@ const GLOBAL_PROPERTY = `__effectionV${MAJOR_VERSION}`;

/**
* The `Effection` constant provides access to some global properties in
* Effection. Most importantly, `Effection` has a {@link root} task, which all
* tasks spawned by {@link run} or `main` are spawned under. You normally do
* not need to work with this task directly, but it is useful for debugging and
* testing.
*/
export const Effection = {
/**
* All tasks spawned by {@link run} or `main` are spawned as children of this
* task. You normally do not need to work with this task directly, but it is
* useful for debugging and testing.
*/
get root(): Task {

@@ -36,2 +50,5 @@ return getGlobalConfig().root;

/**
* Set the root task to a new task.
*/
set root(value: Task){

@@ -41,2 +58,5 @@ getGlobalConfig().root = value;

/**
* Completely halt all running Effection tasks and create a new root task.
*/
async reset(): Promise<void> {

@@ -47,2 +67,5 @@ await Effection.root.halt();

/**
* Completely halt all running Effection tasks.
*/
async halt(): Promise<void> {

@@ -49,0 +72,0 @@ await Effection.root.halt();

import type { Task, TaskInfo } from './task';
/**
* @hidden
*/
export interface HasEffectionTrace {

@@ -15,8 +18,8 @@ effectionTrace: TaskInfo[];

let properties = Object.getOwnPropertyDescriptors(error);
properties.effectionTrace = {
value: [...(error.effectionTrace || []), info],
enumerable: true,
};
return Object.create(Object.getPrototypeOf(error), properties);
return Object.create(error, {
effectionTrace: {
value: [...(error.effectionTrace || []), info],
enumerable: true,
}
});
}
import { HaltError } from './halt-error';
import { createRunLoop } from './run-loop';
import { createRunLoop, RunLoop } from './run-loop';

@@ -15,2 +15,10 @@ export type State = 'pending' | 'errored' | 'completed' | 'halted';

/**
* `FutureLike` is a slimmed down interface, similar to `PromiseLink` which
* does not make equally strong requirements on the implementor. In Effection,
* {@link Task} implements `FutureLike`, but not {@linkcode Future}.
*
* See [the Futures guide](https://frontside.com/effection/docs/guides/futures)
* for a more detailed description of futures and how they work.
*/
export interface FutureLike<T> {

@@ -20,2 +28,18 @@ consume(consumer: Consumer<T>): void;

/**
* A Future represents a value which may or may not be available yet. It is
* similar to a JavaScript Promise, except that it can resolve synchronously.
*
* See [the Futures guide](https://frontside.com/effection/docs/guides/futures)
* for a more detailed description of futures and how they work.
*
* A Future may resolve to *three* different states. A Future can either become
* `completed` with a value, it can become `errored` with an Error or it can
* become `halted`, meaning it was prematurely cancelled.
*
* A Future can be created via the {@link createFuture} function, or via the
* The `resolve`, `reject` and `halt` functions on {@link Future}.
*
* See also the slimmed down {@link FutureLike} interface.
*/
export interface Future<T> extends Promise<T>, FutureLike<T> {

@@ -28,8 +52,43 @@ state: State;

produce(value: Value<T>): void;
/** @deprecated Use produce(value) instead */
resolve(value: Value<T>): void;
resolve(value: T): void;
reject(error: Error): void;
halt(): void;
}
/**
* Create a new Future. This returns an object which contains the future itself
* as well as a function to produce a value for the future, and also shortcuts
* to resolve/reject/halt the future.
*
* ### Example
*
* ```typescript
* import { createFuture } from 'effection';
*
* let { future, produce } = createFuture<number>();
*
* // later...
* produce({ state: 'completed', value: 100 });
*
* future.consume((value) => console.log(value)) // => { state: 'completed', value: 100 }
* ```
*
* ### Example using shortcut
*
* ```typescript
* import { createFuture } from 'effection';
*
* let { future, resolve } = createFuture<number>();
*
* // later...
* resolve(100);
*
* future.consume((value) => console.log(value)) // => { state: 'completed', value: 100 }
* ```
*/
export function createFuture<T>(): NewFuture<T> {
let runLoop = createRunLoop();
return createFutureOnRunLoop(createRunLoop('future'));
}
export function createFutureOnRunLoop<T>(runLoop: RunLoop): NewFuture<T> {
let consumers: Consumer<T>[] = [];

@@ -63,8 +122,2 @@ let result: Value<T>;

function resolve(value: Value<T>) {
console.warn(`DEPRECATED: resolve() is deprecated and will be changed or removed prior to the release of effection 2.0\nuse produce() instead`);
produce(value);
}
let promise: Promise<T>;

@@ -96,4 +149,49 @@

},
resolve
resolve: (value: T) => produce({ state: 'completed', value }),
reject: (error: Error) => produce({ state: 'errored', error }),
halt: () => produce({ state: 'halted' }),
};
}
/**
* Namespace for `Future` functions to create resolved futures.
*
* See [Future](./interfaces/Future.html).
*/
export const Future = {
/**
* Create a future which has resolved successfully to a `completed` state.
*
* @param value The value to resolve to
* @typeParam T The type that the future resolves to
*/
resolve<T>(value: T): Future<T> {
let { future, resolve } = createFuture<T>();
resolve(value);
return future;
},
/**
* Create a future which has been rejected and is in an `errored` state.
*
* @param error The error that the future rejects with
* @typeParam T The type that the future resolves to
*/
reject<T = unknown>(error: Error): Future<T> {
let { future, reject } = createFuture<T>();
reject(error);
return future;
},
/**
* Create a future which has been halted and is in a `halted` state.
*
* @typeParam T The type that the future resolves to
*/
halt<T = unknown>(): Future<T> {
let { future, halt } = createFuture<T>();
halt();
return future;
},
};

@@ -11,3 +11,3 @@ export class HaltError extends Error {

// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
export function isHaltError(value: any): value is HaltError {

@@ -14,0 +14,0 @@ return !!(value && value.__isEffectionHaltError);

@@ -10,3 +10,3 @@ import { Operation } from './operation';

export { deprecated } from './deprecated';
export { Labels, withLabels } from './labels';
export { Labels, withLabels, setLabels } from './labels';
export { HasEffectionTrace } from './error';

@@ -24,4 +24,28 @@ export { createFuture, Future, FutureLike } from './future';

/**
* Run takes an operation and runs it in a task. It returns the task it created.
*
* Run is an entry point into Effection, and is especially useful when
* embedding Effection code into existing code. If you are writing your whole
* program using Effection, you should prefer using `main`.
*
* ### Example
*
* ```
* import { run, fetch } from 'effection';
*
* async function fetchExample() {
* await run(function*() {
* let response = yield fetch('http://www.example.com');
* yield response.text();
* });
* });
* ```
*
* @param operation the operation to run
* @param options the options to configure the task with
* @returns the new task
*/
export function run<TOut>(operation?: Operation<TOut>, options?: TaskOptions): Task<TOut> {
return Effection.root.run(operation, options);
}
import { Operation } from './operation';
/**
* A map of labels. Each label is a key/value pair, where the key must be a
* string and the value may be a string, number or boolean.
*/
export type Labels = Record<string, string | number | boolean>;
/**
* Apply the given labels to an operation. When the operation is run as a task,
* using {@link run} or {@link spawn}, the labels get applied to the task as
* well.
*
* If the task operation already has labels, the existing labels are not
* removed. See {@link setLabels} if you want to replace the existing labels
* entirely.
*/
export function withLabels<T>(operation: Operation<T>, labels: Labels): Operation<T> {

@@ -11,1 +24,11 @@ if(operation) {

}
/**
* Like {@link withLabels}, but replaces the existing labels entirely.
*/
export function setLabels<T>(operation: Operation<T>, labels: Labels): Operation<T> {
if(operation) {
operation.labels = labels;
}
return operation;
}

@@ -17,11 +17,22 @@ /* eslint-disable @typescript-eslint/no-explicit-any */

export interface OperationResolution<TOut> extends Labelled {
perform(resolve: (value: TOut) => void, reject: (err: Error) => void): void | (() => void);
}
export interface OperationFuture<TOut> extends FutureLike<TOut>, Labelled {
}
/**
* An `Resource` in Effection represents a long running computation which also
* provides some means of interaction while it is running.
*
* See [the Resource guide](https://frontside.com/effection/docs/guides/resources) for more information.
*
*/
export interface Resource<TOut> extends Labelled {
init(scope: Task, local: Task): OperationIterator<TOut>;
/**
* A resource's `init` method is called when the resource is initialized. It
* runs inside the resource's resource task, and is able to spawn tasks and
* other resources directly under the resource task.
*
* @param resourceTask a handle to the resource task that the resource is running
* @param initTask a handle to the task of the `init` itself
*/
init(resourceTask: Task, initTask: Task): OperationIterator<TOut>;
}

@@ -33,6 +44,12 @@

/**
* An `Operation` in Effection describes an abstract computation, that is a
* computation which is not currently running.
*
* See [the Task and Operations guide](https://frontside.com/effection/docs/guides/tasks) for more information.
*
*/
export type Operation<TOut> =
OperationPromise<TOut> |
OperationIterator<TOut> |
OperationResolution<TOut> |
OperationFuture<TOut> |

@@ -39,0 +56,0 @@ OperationFunction<TOut> |

@@ -5,2 +5,3 @@ /* eslint-disable @typescript-eslint/no-explicit-any */

import { withLabels } from '../labels';
import { spawn } from './spawn';

@@ -11,16 +12,45 @@ type All<T extends Operation<any>[]> = {

/**
* Block and wait for all of the given operations to complete. Returns an array of
* values that the given operations evaluated to. This has the same purpose as
* [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all).
*
* If any of the operations become errored, then `all` will also become errored.
*
* ### Example
*
* ```typescript
* import { main, all, fetch } from 'effection';
*
* main(function*() {
* let [google, bing] = yield all([fetch('http://google.com').text(), fetch('http://bing.com').text()]);
* // ...
* });
* ```
*
* @typeParam T the type of the array of options, this can a heterogenous array
* @param operations a list of operations to wait for
* @returns the list of values that the operations evaluate to, in the order they were given
*/
export function all<T extends Operation<any>[]>(operations: T): Operation<All<T>> {
return withLabels(function*(scope) {
return withLabels(function*(task) {
let tasks: Task<unknown>[] = [];
let results: unknown[] = [];
for (let operation of operations) {
if(scope.state === 'running') {
tasks.push(scope.run(operation));
try {
yield function*() {
for (let operation of operations) {
tasks.push(yield spawn(operation, { scope: task.options.scope }));
}
for (let task of tasks) {
results.push(yield task);
}
};
} catch(err) {
for (let task of tasks) {
task.halt();
}
throw(err);
}
for (let task of tasks) {
results.push(yield task);
}
return results as All<T>;
}, { name: 'all', count: operations.length });
}

@@ -1,22 +0,66 @@

import { Operation, Resource } from '../operation';
import { spawn } from './spawn';
import { Operation } from '../operation';
import { Future } from '../future';
/**
* Run the given function or operation when the current task shuts down. This
* is equivalent to running the function or operation in a finally block, but
* it can help you avoid rightward drift.
*
* ### Example using function
*
* Using a function:
*
* ```typescript
* import { main, ensure } from 'effection';
* import { createServer } from 'http';
*
* main(function*() {
* let server = createServer(...);
* yield ensure(() => { server.close() });
* });
* ```
*
* Note that you should wrap the function body in braces, so the function
* returns `undefined`.
*
* ### Example using operation
*
* ```typescript
* import { main, ensure, once } from 'effection';
* import { createServer } from 'some-library';
*
* main(function*() {
* let server = createServer(...);
* yield ensure(function*() {
* server.close();
* yield once(server, 'closed');
* });
* });
* ```
*
* Here we're using some library where server shutdown is asynchronous, we can
* block and wait for the shutdown to complete inside of ensure by using an
* operation.
*
* @param fn a function which returns an operation or void
*/
export function ensure<T>(fn: () => Operation<T> | void): Operation<undefined> {
return function ensure(task) {
let { scope } = task.options;
if(!scope) {
throw new Error('cannot run `ensure` on a task without scope');
}
scope.run(function* ensureHandler() {
try {
yield;
} finally {
let result = fn();
if(result) {
yield result;
}
}
});
export function ensure<T>(fn: () => Operation<T> | void): Resource<undefined> {
return {
name: 'ensure',
*init() {
yield spawn(function*() {
try {
yield;
} finally {
let result = fn();
if(result) {
yield result;
}
}
}, { labels: { name: 'ensureHandler' } });
return undefined;
}
return Future.resolve(undefined);
};
}

@@ -1,11 +0,53 @@

import { Resource } from '../operation';
import { Operation } from '../operation';
import { Labels } from '../labels';
import { Future } from '../future';
import { withLabels } from '../labels';
export function label(labels: Labels): Resource<void> {
return {
name: 'label',
*init(scope) {
scope.setLabels(labels);
/**
* Apply the given labels to the current task. This can be used to dynamically
* set labels, for example as the state of the operation changes, or as more
* information becomes available.
*
* If possible you should prefer to set labels statically using {@link withLabels},
* or by passing them as options to {@link run} or {@link spawn}.
*
* Existing labels are not removed, but may be overwritten if the key is the
* same.
*
* ### Example
*
* ```typescript
* import { main, label } from 'effection';
*
* function createSocket(url) {
* // use `withLabels` for information which is known statically
* return withLabels(function*() {
* let socket = new WebSocket(url);
* yield once(socket, 'open');
*
* // state has changed dynamically, use `label` to update labels
* yield label({ state: 'open'});
*
* let initMessage = yield once(socket, 'message');
*
* // we received a handshake message with some new information
* yield label({ state: 'initialized', connectionId: initMessage.data.connectionId });
*
* // ...
* }, { labels: { name: 'createSocket', state: 'pending' }});
* });
* ```
*
* @param labels the labels to apply
*/
export function label(labels: Labels): Operation<void> {
return withLabels((task) => {
let { scope } = task.options;
if(!scope) {
throw new Error('cannot run `label` on a task without scope');
}
};
scope.setLabels(labels);
return Future.resolve(undefined);
}, { name: 'label' });
}
import type { Operation } from '../operation';
import type { Task } from '../task';
import { withLabels } from '../labels';
import { createFuture } from '../future';
/**
* Race the given operations against each other and return the value of
* whichever operation returns first. This has the same purpose as
* [Promise.race](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race).
*
* If an operation become errored first, then `race` will fail with this error.
* After the first operation wins the race, all other operations will become
* halted and therefore cannot throw any further errors.
*
* ### Example
*
* ```typescript
* import { main, race, fetch } from 'effection';
*
* main(function*() {
* let fastest = yield race([fetch('http://google.com').text(), fetch('http://bing.com').text()]);
* // ...
* });
* ```
*
* @typeParam T the type of the operations that race against each other
* @param operations a list of operations to race against each other
* @returns the value of the fastest operation
*/
export function race<T>(operations: Operation<T>[]): Operation<T> {
return withLabels((scope) => ({
perform: (resolve, reject) => {
for (let operation of operations) {
if(scope.state === 'running') {
scope.run(function*() {
try {
resolve(yield operation);
} catch (e) {
reject(e);
}
});
}
return withLabels(function*(task) {
let { resolve, future } = createFuture<Task>();
let tasks: Task[] = [];
for (let operation of operations) {
if(task.state === 'running') {
let child = task.run(operation, { ignoreError: true, scope: task.options.scope });
child.consume(() => resolve(child));
tasks.push(child);
}
}
}), { name: 'race', count: operations.length });
let resultChild = yield future;
for(let child of tasks) {
if(child !== resultChild) {
child.halt();
}
}
return yield resultChild;
}, { name: 'race', count: operations.length });
}
import { Operation } from '../operation';
import { createFuture } from '../future';
import { withLabels } from '../labels';
/**
* Sleep for the given amount of milliseconds. If no duration is given, then
* sleep indefinitely.
*
* ### Example
*
* ```typescript
* import { main, sleep } from 'effection';
*
* main(function*() {
* yield sleep(2000);
* console.log("Hello lazy world!");
* });
* ```
*
* @param duration the number of milliseconds to sleep
*/
export function sleep(duration?: number): Operation<void> {
return {
labels: { name: 'sleep', duration: (duration != null) ? duration : 'forever' },
perform(resolve) {
if(duration != null) {
let timeoutId = setTimeout(resolve, duration);
return () => clearTimeout(timeoutId);
}
return withLabels((task) => {
let { future, resolve } = createFuture<void>();
if(duration != null) {
let timeoutId = setTimeout(resolve, duration);
task.consume(() => clearTimeout(timeoutId));
}
};
return future;
}, { name: 'sleep', duration: (duration != null) ? duration : 'forever' });
}

@@ -1,20 +0,54 @@

import { Operation, Resource } from '../operation';
import { Operation, OperationFunction } from '../operation';
import { Future } from '../future';
import type { Task, TaskOptions } from '../task';
interface Spawn<T> extends Resource<Task<T>> {
within(scope: Task): Resource<Task<T>>;
interface Spawn<T> extends OperationFunction<Task<T>> {
within(scope: Task): Operation<Task<T>>;
}
/**
* An operation which spawns the given operation as a child of the current task.
*
* You should prefer using the spawn operation over calling `task.run` from
* within Effection code. The reason being that a synchronous failure in the
* spawned task will not be caught until the next yield point when using `run`,
* which can be confusing. Additionally, using `spawn` is usually more
* ergonomic.
*
* ### Example
*
* ```typescript
* import { main, sleep, spawn } from 'effection';
*
* main(function*() {
* yield spawn(function*() {
* yield sleep(1000);
* console.log("hello");
* });
* yield spawn(function*() {
* yield sleep(2000);
* console.log("world");
* });
* yield;
* });
* ```
*
* @param operation the operation to run as a child of the current task
* @typeParam T the type that the spawned task evaluates to
*/
export function spawn<T>(operation?: Operation<T>, options?: TaskOptions): Spawn<T> {
function* init(scope: Task) {
return scope.run(operation, options);
function spawn(task: Task) {
let { scope } = task.options;
if(!scope) {
throw new Error('cannot run `spawn` on a task without scope');
}
let result = scope.run(operation, options);
return Future.resolve(result);
}
function within(scope: Task) {
return {
init: () => init(scope)
};
return scope.spawn(operation, options);
}
return { init, within, name: 'spawn' };
return Object.assign(spawn, { within });
}

@@ -9,2 +9,18 @@ import { Operation } from '../operation';

/**
* Throw an error after the given amount of milliseconds. See also {@link withTimeout}.
*
* ### Example
*
* ```typescript
* import { main, spawn, timeout } from 'effection';
*
* main(function*() {
* yield spawn(timeout(2000));
* yield fetch('http://google.com');
* });
* ```
*
* @param duration the timeout duration in milliseconds
*/
export function timeout(duration: number): Operation<never> {

@@ -11,0 +27,0 @@ return withLabels(function*() {

import { Operation } from '../operation';
import { timeout } from './timeout';
import { spawn } from './spawn';
import { withLabels } from '../labels';
import { race } from './race';
import { setLabels } from '../labels';
/**
* Runs the given operation and throws an error if it takes more than the given
* number of milliseconds. See also {@link timeout}.
*
* ### Example
*
* ```typescript
* import { main, spawn, withTimeout } from 'effection';
*
* main(function*() {
* yield withTimeout(2000, fetch('http://google.com'));
* });
* ```
*
* @param duration the timeout duration in milliseconds
*/
export function withTimeout<T>(duration: number, operation: Operation<T>): Operation<T> {
return withLabels(function*() {
yield spawn(timeout(duration));
return yield operation;
}, { name: 'withTimeout', duration });
return setLabels(
race([timeout(duration) as Operation<T>, operation]),
{ name: 'withTimeout', duration }
);
}
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { OperationResolution, Resource } from "./operation";
import type { Resource } from "./operation";
import type { FutureLike } from "./future";

@@ -15,6 +15,2 @@

export function isResolution<T>(value: any): value is OperationResolution<T> {
return value && typeof(value.perform) === 'function';
}
export function isFuture<T>(value: any): value is FutureLike<T> {

@@ -21,0 +17,0 @@ return value && typeof(value.consume) === 'function';

@@ -11,3 +11,3 @@ export type Runnable = () => void;

// of synchronous mutex
export function createRunLoop(): RunLoop {
export function createRunLoop(name?: string): RunLoop {
let didEnter = false;

@@ -27,3 +27,3 @@ let runnables: Runnable[] = [];

} catch(e) {
console.error("Caught error in run loop:");
console.error(`Caught error in run loop \`${name}\`:`);
console.error(e);

@@ -30,0 +30,0 @@ }

import { EventEmitter } from 'events';
/**
* The state of a Task can be one of the following:
*
* - `pending`: the task has not been started yet
* - `running`: the task is currently running
* - `halting`: the task has been halted, but halting is not finished yet
* - `halted`: the task is fully halted
* - `erroring`: the task or one of its children has failed and is in the process of shutting down
* - `errored`: the task has fully failed and evaluates to an Error
* - `completing`: the task is completing and shutting down its children
* - `completed`: the task is fully complete and evaluates to a value
*/
export type State = 'pending' | 'running' | 'halting' | 'halted' | 'erroring' | 'errored' | 'completing' | 'completed';

@@ -7,2 +19,5 @@

/**
* @hidden
*/
export type StateTransition = {

@@ -31,2 +46,6 @@ from: State;

get isFinalized(): boolean {
return this.current === 'errored' || this.current === 'completed' || this.current === 'halted';
}
start(): void {

@@ -33,0 +52,0 @@ this.transition('start', {

@@ -9,3 +9,3 @@ /* eslint-disable @typescript-eslint/no-explicit-any */

import { addTrace } from './error';
import { createFuture, Future, FutureLike, Value } from './future';
import { createFutureOnRunLoop, Future, FutureLike, Value } from './future';
import { createRunLoop } from './run-loop';

@@ -15,2 +15,5 @@

/**
* @hidden
*/
export interface TaskInfo {

@@ -23,2 +26,5 @@ id: number;

/**
* @hidden
*/
export interface TaskTree extends TaskInfo {

@@ -29,31 +35,280 @@ yieldingTo?: TaskTree;

/**
* Task options can be used to configure the behaviour of a task. Options can
* be passed to {@link run}, `main`, {@link spawn} and when creating a
* task manually through {@link createTask}.
*/
export interface TaskOptions {
readonly resourceScope?: Task;
/**
* Normally a task's type is inferred from its operation, but it can also be
* explicitly set via this option.
*/
readonly type?: string;
/**
* The scope of a task describes the scope in which this task creates resource
* tasks.
*
* Usually this is the parent task, but in some instances it can be useful to
* change the scope of a task. This is for example often the case with
* "higher-order operations", that is operations which take another operation
* as an argument, such as the {@link all} operation.
*
* This is a very advanced feature and great care should be taken when using
* it as it can lead to possibly breaking some of the guarantees that
* Effection makes.
*/
readonly scope?: Task;
/**
* @internal
*
* The yieldScope of a task describes the scope in which this task's yield
* points create resources. This is normally the task itself. It is currently
* only changed for resource's `init` tasks.
*
* This is an internal API and you should not set this option yourself.
*/
readonly yieldScope?: Task;
/**
* Normally when a task finishes its operation, it halts all of its children.
* When setting this option to `true` on a child, it will cause the parent
* task to wait for the child to complete after it finishes its operation.
* Essentially it allows a child to block the completion of its parent.
*/
readonly blockParent?: boolean;
/**
* When a child task becomes errored it normally causes its parent task to
* become errored as well. When setting this option to `true` on a parent
* task, an error in its children will be ignored. This is useful for some
* tasks which want to have finer grained control over their children, such
* as supervisors or root tasks.
*/
readonly ignoreChildErrors?: boolean;
/**
* When a child task becomes errored it normally causes its parent task to
* become errored as well. When setting this option to `true` on a child
* task, then an error in the child will not cause the parent to become
* errored. This is useful for some tasks which want finer grained control
* over error conditions.
*/
readonly ignoreError?: boolean;
/**
* Set the given labels on the task.
*/
readonly labels?: Labels;
}
/**
* A `Task` in Effection has many responsibilities. Fundamentally it represents
* a computation which at some point will result in either a value or an error,
* or become halted. It can also have children, which are also tasks.
*
* See [the Task and Operations guide](https://frontside.com/effection/docs/guides/tasks) for more information.
*
*/
export interface Task<TOut = unknown> extends Promise<TOut>, FutureLike<TOut> {
/**
* Each `Task` has a unique id
*/
readonly id: number;
/**
* The type of a `Task` is usually determined by the operation that the task
* is running, but it can also be adjusted via the `type` option.
*/
readonly type: string;
/**
* Returns the {@link State} that the task is currently in.
*/
readonly state: State;
/**
* Returns the options that the task was created with
*/
readonly options: TaskOptions;
/**
* Returns a map of {@link Labels} which provide additional information about
* the task, such as its name, or any other metadata that the task wants to
* provide.
*/
readonly labels: Labels;
/**
* Returns a list of the task's children
*/
readonly children: Task[];
/**
* Returns a {@link Future} for this task. The task itself can act as a
* Future, so usually you do not need to access this property explicitly.
*/
readonly future: Future<TOut>;
/**
* Returns the task that this task is currently yielding to. When using a
* generator with a task, a task ends up processing one operation at a time,
* each such operation runs in its own task, and can be accessed via this
* property.
*
* ### Example
*
* ``` typescript
* import { run, sleep } from 'effection'
*
* let task = run(function*() {
* yield sleep(2000);
* });
*
* console.log(task.yieldingTo); // => logs the sleep task
* ```
*/
readonly yieldingTo: Task | undefined;
/**
* Tasks which construct a resource create a special task in the background called
* a resource task, which groups everything running inside the resource. This property
* provides access to such a task.
*
* ### Example
*
* ``` typescript
* import { main, spawn, fetch } from 'effection'
*
* main(function*() {
* let fetchTask = yield spawn(fetch('http://www.example.com'));
* console.log(fetchTask.resourceTask); // => logs the resource task of the fetch
* });
* ```
*/
readonly resourceTask: Task | undefined;
/**
* When using a task as a `Promise`, it would usually be rejected when the task
* is halted. Using `catchHalt()` we can treat a `halt` as the promise
* resolving to `undefined`, rather than being rejected.
*
* ### Example
*
* ``` typescript
* import { run, sleep } from 'effection'
*
* let task = run(function*() {
* yield sleep(2000);
* return "done!";
* });
*
* task.halt();
*
* task.then((value) => console.log(value)); // => log "undefined"
*/
catchHalt(): Promise<TOut | undefined>;
/**
* Sets the given {@link Labels} on the task. This only adds new labels or
* overwrites labels with the same key, but does not remove other labels.
*
* See also the {@link label} operation for setting labels dynamically.
*
* ### Example
*
* ``` typescript
* import { run } from 'effection'
*
* let task = run();
* task.setLabels({ name: 'myTask', florb: 123 });
* ```
*/
setLabels(labels: Labels): void;
run<R>(operation?: Operation<R>, options?: TaskOptions): Task<R>;
/** @deprecated Use run() instead */
spawn<R>(operation?: Operation<R>, options?: TaskOptions): Task<R>;
/**
* Run the given operation as a child of this task.
*
* This is similar to {@link spawn}, but it is *not* an operation and
* therefore should *not* be used with `yield`. If you are inside Effection
* code, you should generally prefer {@link spawn}. `run` is useful when you
* are creating children from outside of Effection.
*
* ### Example
*
* ``` typescript
* import { run } from 'effection'
*
* let task = run();
* task.run(function*() { ... }) // run a child of this task
* ```
*
* @typeParam TOutChild the type that the child resolves to
*/
run<TOutChild>(operation?: Operation<TOutChild>, options?: TaskOptions): Task<TOutChild>;
/**
* An operation to run the given operation as child of this task.
*
* This is similar to {@link run}, but it is an operation and therefore must
* be used with `yield` or `run`. If you are inside Effection code, you
* should generally prefer `spawn`. {@link run} is useful when you are
* creating children from outside of Effection.
*
* ### Example
*
* ``` typescript
* import { main, spawn } from 'effection'
*
* main(function*(mainTask) {
* yield function*() {
* yield mainTask.spawn(function*() { ... }); // => run as child of `main`.
* }
* });
* ```
*
* @param operation the operation that the child runs
* @typeParam TOutChild the type that the child resolves to
*/
spawn<TOutChild>(operation?: Operation<TOutChild>, options?: TaskOptions): Operation<Task<TOutChild>>;
/**
* Cause this task to halt. This will halt the task itself, as well as any task
* it is currently {@link yieldingTo} and its {@link children}.
*/
halt(): Promise<void>;
/**
* Starts running the task if it has not yet started, does nothing otherwise.
* You will only need to call this if you created your task manually through
* {@link createTask}.
*
* ### Example
*
* ``` typescript
* import { createTask } from 'effection'
*
* let task = createTask(someOperation);
* console.log(task.state) // => 'pending';
*
* task.start();
* console.log(task.state) // => 'running';
* ```
*/
start(): void;
/**
* Serializes information about the task into an object. This will include
* the task's {@link id}, {@link state}, {@link type}, {@link labels} as well
* as the task it is {@link yieldingTo} and its {@link children}.
*/
toJSON(): TaskTree;
/**
* Returns a readable description of the task and its children. This is
* useful for debugging. The output can be improved by applying {@link labels}.
* For more advanced debugging you can also use the
* [inspector](https://frontside.com/effection/docs/guides/inspector).
*/
toString(): string;
/**
* @hidden
*/
on: EventEmitter['on'];
/**
* @hidden
*/
off: EventEmitter['off'];
}
/**
* Low level interface to create a task which does not have a parent. Normally all
* tasks are spawned as children of {@link Effection}.root, but on rare occasions it is necessary
* to create a task outside the normal task hierarchy.
*
* @param operation the operation that the task runs
* @param options the options that the task is configured with
* @returns the new task
*/
export function createTask<TOut = unknown>(operation: Operation<TOut>, options: TaskOptions = {}): Task<TOut> {

@@ -69,5 +324,5 @@ let id = ++COUNTER;

let { produce, future } = createFuture<TOut>();
let result: Value<TOut>;
let runLoop = createRunLoop();
let runLoop = createRunLoop(`task ${id}`);
let { produce, future } = createFutureOnRunLoop<TOut>(runLoop);

@@ -79,3 +334,2 @@ let controller: Controller<TOut>;

if (!labels.name) {

@@ -100,3 +354,3 @@ if (operation?.name) {

get type() { return controller.type },
get type() { return options.type || controller.type },

@@ -107,2 +361,4 @@ get children() { return Array.from(children) },

get resourceTask() { return controller.resourceTask },
catchHalt() {

@@ -121,3 +377,3 @@ return future.catch(swallowHalt);

}
let child = createTask(operation, { resourceScope: task, ...options });
let child = createTask(operation, { scope: task, ...options });
link(child as Task);

@@ -129,19 +385,26 @@ child.start();

spawn(operation?, options = {}) {
console.warn(`DEPRECATED: task.spawn() is deprecated and will be changed or removed prior to the release of effection 2.0\nuse task.run() instead`);
return task.run(operation, options);
return {
name: 'spawn',
*init() {
return task.run(operation, options);
}
};
},
start() {
if(stateMachine.current === 'pending') {
stateMachine.start();
controller.start();
}
runLoop.run(() => {
if(stateMachine.current === 'pending') {
stateMachine.start();
controller.start();
}
});
},
async halt() {
if(stateMachine.current === 'running' || stateMachine.current === 'completing') {
controller.halt();
if(stateMachine.current === 'running') {
stateMachine.halting();
result = { state: 'halted' };
shutdown(true);
}
shutdown(true);
await future.catch(() => {

@@ -156,3 +419,3 @@ // TODO: should this catch all errors, or only halt errors?

id: id,
type: controller.type,
type: task.type,
labels: labels,

@@ -165,2 +428,11 @@ state: stateMachine.current,

toString() {
let formattedLabels = Object.entries(labels).filter(([key]) => key !== 'name' && key !== 'expand').map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(' ');
return [
[labels.name || 'task', formattedLabels, `[${task.type} ${id}]`].filter(Boolean).join(' '),
yieldingTo && yieldingTo.toString().split('\n').map(l => '┃ ' + l).join('\n').replace(/^┃ /, `┣ yield `),
...Array.from(children).map((c) => c.toString().split('\n').map(l => '┃ ' + l).join('\n').replace(/^┃/, '┣'),)
].filter(Boolean).join('\n');
},
on: (...args) => emitter.on(...args),

@@ -176,2 +448,3 @@ off: (...args) => emitter.off(...args),

controller = createController(task, operation, {
runLoop,
onYieldingToChange(value) {

@@ -184,2 +457,3 @@ yieldingTo = value;

controller.future.consume((value) => {
if(stateMachine.isFinalized) return;
if(value.state === 'completed') {

@@ -195,2 +469,6 @@ stateMachine.completing();

shutdown(true);
} else if(value.state === 'halted' && stateMachine.current !== 'erroring') {
stateMachine.halting();
result = { state: 'halted' };
shutdown(true);
}

@@ -203,13 +481,17 @@ finalize();

child.consume((value) => {
if(value.state === 'errored' && !child.options.ignoreError && !options.ignoreChildErrors) {
stateMachine.erroring();
result = { state: 'errored', error: addTrace(value.error, task) };
runLoop.run(() => {
if(stateMachine.isFinalized) return;
if(value.state === 'errored' && !child.options.ignoreError && !options.ignoreChildErrors) {
stateMachine.erroring();
result = { state: 'errored', error: addTrace(value.error, task) };
shutdown(true);
}
if(children.has(child)) {
children.delete(child);
emitter.emit('unlink', child);
}
finalize();
controller.halt();
shutdown(true);
}
if(children.has(child)) {
children.delete(child);
emitter.emit('unlink', child);
}
finalize();
});
});

@@ -222,3 +504,2 @@ children.add(child);

function shutdown(force: boolean) {
controller.halt();
controller.future.consume(() => {

@@ -243,10 +524,8 @@ let nextChild: Task | undefined;

function finalize() {
runLoop.run(() => {
if(Array.from(children).length !== 0) return;
if(controller.future.state === 'pending') return;
if(future.state !== 'pending') return;
if(Array.from(children).length !== 0) return;
if(controller.future.state === 'pending') return;
if(future.state !== 'pending') return;
stateMachine.finish();
produce(result);
});
stateMachine.finish();
produce(result);
}

@@ -253,0 +532,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc