@effection/core
Advanced tools
Comparing version 2.0.0-beta.11 to 2.0.0-beta.12
# @effection/core | ||
## \[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] | ||
@@ -4,0 +15,0 @@ |
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 |
@@ -12,3 +12,3 @@ "use strict"; | ||
const resource_controller_1 = require("./resource-controller"); | ||
function createController(task, operation, options = {}) { | ||
function createController(task, operation, options) { | ||
if (typeof (operation) === 'function') { | ||
@@ -15,0 +15,0 @@ return function_controller_1.createFunctionController(task, operation, () => createController(task, operation(task), options)); |
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) => { | ||
produce(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' } | ||
}); | ||
delegate.start(); | ||
initTask.consume(produce); | ||
} | ||
function halt() { | ||
delegate.halt(); | ||
initTask === null || initTask === void 0 ? void 0 : initTask.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,1 +1,2 @@ | ||
import { RunLoop } from './run-loop'; | ||
export declare type State = 'pending' | 'errored' | 'completed' | 'halted'; | ||
@@ -27,2 +28,3 @@ export declare type Value<T> = { | ||
export declare function createFuture<T>(): NewFuture<T>; | ||
export declare function createFutureOnRunLoop<T>(runLoop: RunLoop): NewFuture<T>; | ||
//# sourceMappingURL=future.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createFuture = void 0; | ||
exports.createFutureOnRunLoop = exports.createFuture = void 0; | ||
const halt_error_1 = require("./halt-error"); | ||
const run_loop_1 = require("./run-loop"); | ||
function createFuture() { | ||
let runLoop = run_loop_1.createRunLoop(); | ||
return createFutureOnRunLoop(run_loop_1.createRunLoop('future')); | ||
} | ||
exports.createFuture = createFuture; | ||
function createFutureOnRunLoop(runLoop) { | ||
let consumers = []; | ||
@@ -66,3 +69,3 @@ let result; | ||
} | ||
exports.createFuture = createFuture; | ||
exports.createFutureOnRunLoop = createFutureOnRunLoop; | ||
//# sourceMappingURL=future.js.map |
@@ -1,3 +0,3 @@ | ||
import { Operation, Resource } from '../operation'; | ||
export declare function ensure<T>(fn: () => Operation<T> | void): Resource<undefined>; | ||
import { Operation } from '../operation'; | ||
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"); | ||
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; | ||
} | ||
} | ||
}); | ||
let { future, produce } = future_1.createFuture(); | ||
produce({ state: 'completed', value: undefined }); | ||
return future; | ||
}; | ||
@@ -23,0 +26,0 @@ } |
@@ -1,4 +0,4 @@ | ||
import { Resource } from '../operation'; | ||
import { Operation } from '../operation'; | ||
import { Labels } from '../labels'; | ||
export declare function label(labels: Labels): Resource<void>; | ||
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"); | ||
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); | ||
let { future, produce } = future_1.createFuture(); | ||
produce({ state: 'completed', value: undefined }); | ||
return future; | ||
}, { name: 'label' }); | ||
} | ||
exports.label = label; | ||
//# sourceMappingURL=label.js.map |
@@ -1,4 +0,4 @@ | ||
import { Operation, Resource } from '../operation'; | ||
import { Operation, OperationFunction } from '../operation'; | ||
import type { Task, TaskOptions } from '../task'; | ||
interface Spawn<T> extends Resource<Task<T>> { | ||
interface Spawn<T> extends OperationFunction<Task<T>> { | ||
within(scope: Task): Operation<Task<T>>; | ||
@@ -5,0 +5,0 @@ } |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.spawn = void 0; | ||
const future_1 = require("../future"); | ||
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); | ||
let { future, produce } = future_1.createFuture(); | ||
produce({ state: 'completed', value: result }); | ||
return future; | ||
} | ||
@@ -11,5 +19,5 @@ function within(scope) { | ||
} | ||
return { init, within, name: 'spawn' }; | ||
return Object.assign(spawn, { within }); | ||
} | ||
exports.spawn = spawn; | ||
//# sourceMappingURL=spawn.js.map |
@@ -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 @@ } |
@@ -13,2 +13,3 @@ /// <reference types="node" /> | ||
private transition; | ||
get isFinalized(): boolean; | ||
start(): void; | ||
@@ -15,0 +16,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', { |
@@ -18,3 +18,5 @@ /// <reference types="node" /> | ||
export interface TaskOptions { | ||
readonly resourceScope?: Task; | ||
readonly type?: string; | ||
readonly scope?: Task; | ||
readonly yieldScope?: Task; | ||
readonly blockParent?: boolean; | ||
@@ -34,2 +36,3 @@ readonly ignoreChildErrors?: boolean; | ||
readonly yieldingTo: Task | undefined; | ||
readonly resourceTask: Task | undefined; | ||
catchHalt(): Promise<TOut | undefined>; | ||
@@ -42,2 +45,3 @@ setLabels(labels: Labels): void; | ||
toJSON(): TaskTree; | ||
toString(): string; | ||
on: EventEmitter['on']; | ||
@@ -44,0 +48,0 @@ off: EventEmitter['off']; |
@@ -19,5 +19,5 @@ "use strict"; | ||
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; | ||
@@ -40,5 +40,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() { | ||
@@ -55,3 +56,3 @@ return future.catch(halt_error_1.swallowHalt); | ||
} | ||
let child = createTask(operation, { resourceScope: task, ...options }); | ||
let child = createTask(operation, { scope: task, ...options }); | ||
link(child); | ||
@@ -70,9 +71,11 @@ child.start(); | ||
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') { | ||
if (stateMachine.current === 'running') { | ||
stateMachine.halting(); | ||
@@ -90,3 +93,3 @@ result = { state: 'halted' }; | ||
id: id, | ||
type: controller.type, | ||
type: task.type, | ||
labels: labels, | ||
@@ -98,2 +101,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), | ||
@@ -108,2 +119,3 @@ off: (...args) => emitter.off(...args), | ||
controller = controller_1.createController(task, operation, { | ||
runLoop, | ||
onYieldingToChange(value) { | ||
@@ -115,2 +127,4 @@ yieldingTo = value; | ||
controller.future.consume((value) => { | ||
if (stateMachine.isFinalized) | ||
return; | ||
if (value.state === 'completed') { | ||
@@ -128,2 +142,7 @@ stateMachine.completing(); | ||
} | ||
else if (value.state === 'halted' && stateMachine.current !== 'erroring') { | ||
stateMachine.halting(); | ||
result = { state: 'halted' }; | ||
shutdown(true); | ||
} | ||
finalize(); | ||
@@ -134,12 +153,16 @@ }); | ||
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) }; | ||
shutdown(true); | ||
} | ||
if (children.has(child)) { | ||
children.delete(child); | ||
emitter.emit('unlink', child); | ||
} | ||
finalize(); | ||
}); | ||
}); | ||
@@ -169,12 +192,10 @@ children.add(child); | ||
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); | ||
} | ||
@@ -181,0 +202,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 |
@@ -9,3 +9,3 @@ import { isResource, isResolution, isFuture, isPromise, isGenerator } from '../predicates'; | ||
import { createResourceController } from './resource-controller'; | ||
export function createController(task, operation, options = {}) { | ||
export function createController(task, operation, options) { | ||
if (typeof (operation) === 'function') { | ||
@@ -12,0 +12,0 @@ return createFunctionController(task, operation, () => createController(task, operation(task), options)); |
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,35 @@ | ||
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) => { | ||
produce(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' } | ||
}); | ||
delegate.start(); | ||
initTask.consume(produce); | ||
} | ||
function halt() { | ||
delegate.halt(); | ||
initTask === null || initTask === void 0 ? void 0 : initTask.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,1 +1,2 @@ | ||
import { RunLoop } from './run-loop'; | ||
export declare type State = 'pending' | 'errored' | 'completed' | 'halted'; | ||
@@ -27,2 +28,3 @@ export declare type Value<T> = { | ||
export declare function createFuture<T>(): NewFuture<T>; | ||
export declare function createFutureOnRunLoop<T>(runLoop: RunLoop): NewFuture<T>; | ||
//# sourceMappingURL=future.d.ts.map |
import { HaltError } from './halt-error'; | ||
import { createRunLoop } from './run-loop'; | ||
export function createFuture() { | ||
let runLoop = createRunLoop(); | ||
return createFutureOnRunLoop(createRunLoop('future')); | ||
} | ||
export function createFutureOnRunLoop(runLoop) { | ||
let consumers = []; | ||
@@ -6,0 +8,0 @@ let result; |
@@ -1,3 +0,3 @@ | ||
import { Operation, Resource } from '../operation'; | ||
export declare function ensure<T>(fn: () => Operation<T> | void): Resource<undefined>; | ||
import { Operation } from '../operation'; | ||
export declare function ensure<T>(fn: () => Operation<T> | void): Operation<undefined>; | ||
//# sourceMappingURL=ensure.d.ts.map |
@@ -1,21 +0,24 @@ | ||
import { spawn } from './spawn'; | ||
import { createFuture } from '../future'; | ||
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; | ||
} | ||
} | ||
}); | ||
let { future, produce } = createFuture(); | ||
produce({ state: 'completed', value: undefined }); | ||
return future; | ||
}; | ||
} | ||
//# sourceMappingURL=ensure.js.map |
@@ -1,4 +0,4 @@ | ||
import { Resource } from '../operation'; | ||
import { Operation } from '../operation'; | ||
import { Labels } from '../labels'; | ||
export declare function label(labels: Labels): Resource<void>; | ||
export declare function label(labels: Labels): Operation<void>; | ||
//# sourceMappingURL=label.d.ts.map |
@@ -0,9 +1,15 @@ | ||
import { createFuture } from '../future'; | ||
import { withLabels } from '../labels'; | ||
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); | ||
let { future, produce } = createFuture(); | ||
produce({ state: 'completed', value: undefined }); | ||
return future; | ||
}, { name: 'label' }); | ||
} | ||
//# sourceMappingURL=label.js.map |
@@ -1,4 +0,4 @@ | ||
import { Operation, Resource } from '../operation'; | ||
import { Operation, OperationFunction } from '../operation'; | ||
import type { Task, TaskOptions } from '../task'; | ||
interface Spawn<T> extends Resource<Task<T>> { | ||
interface Spawn<T> extends OperationFunction<Task<T>> { | ||
within(scope: Task): Operation<Task<T>>; | ||
@@ -5,0 +5,0 @@ } |
@@ -0,4 +1,12 @@ | ||
import { createFuture } from '../future'; | ||
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); | ||
let { future, produce } = createFuture(); | ||
produce({ state: 'completed', value: result }); | ||
return future; | ||
} | ||
@@ -8,4 +16,4 @@ function within(scope) { | ||
} | ||
return { init, within, name: 'spawn' }; | ||
return Object.assign(spawn, { within }); | ||
} | ||
//# sourceMappingURL=spawn.js.map |
@@ -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 @@ } |
@@ -13,2 +13,3 @@ /// <reference types="node" /> | ||
private transition; | ||
get isFinalized(): boolean; | ||
start(): void; | ||
@@ -15,0 +16,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', { |
@@ -18,3 +18,5 @@ /// <reference types="node" /> | ||
export interface TaskOptions { | ||
readonly resourceScope?: Task; | ||
readonly type?: string; | ||
readonly scope?: Task; | ||
readonly yieldScope?: Task; | ||
readonly blockParent?: boolean; | ||
@@ -34,2 +36,3 @@ readonly ignoreChildErrors?: boolean; | ||
readonly yieldingTo: Task | undefined; | ||
readonly resourceTask: Task | undefined; | ||
catchHalt(): Promise<TOut | undefined>; | ||
@@ -42,2 +45,3 @@ setLabels(labels: Labels): void; | ||
toJSON(): TaskTree; | ||
toString(): string; | ||
on: EventEmitter['on']; | ||
@@ -44,0 +48,0 @@ off: EventEmitter['off']; |
@@ -7,3 +7,3 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import { addTrace } from './error'; | ||
import { createFuture } from './future'; | ||
import { createFutureOnRunLoop } from './future'; | ||
import { createRunLoop } from './run-loop'; | ||
@@ -17,5 +17,5 @@ let COUNTER = 0; | ||
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 +38,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 +54,3 @@ return future.catch(swallowHalt); | ||
} | ||
let child = createTask(operation, { resourceScope: task, ...options }); | ||
let child = createTask(operation, { scope: task, ...options }); | ||
link(child); | ||
@@ -68,9 +69,11 @@ child.start(); | ||
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') { | ||
if (stateMachine.current === 'running') { | ||
stateMachine.halting(); | ||
@@ -88,3 +91,3 @@ result = { state: 'halted' }; | ||
id: id, | ||
type: controller.type, | ||
type: task.type, | ||
labels: labels, | ||
@@ -96,2 +99,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), | ||
@@ -106,2 +117,3 @@ off: (...args) => emitter.off(...args), | ||
controller = createController(task, operation, { | ||
runLoop, | ||
onYieldingToChange(value) { | ||
@@ -113,2 +125,4 @@ yieldingTo = value; | ||
controller.future.consume((value) => { | ||
if (stateMachine.isFinalized) | ||
return; | ||
if (value.state === 'completed') { | ||
@@ -126,2 +140,7 @@ stateMachine.completing(); | ||
} | ||
else if (value.state === 'halted' && stateMachine.current !== 'erroring') { | ||
stateMachine.halting(); | ||
result = { state: 'halted' }; | ||
shutdown(true); | ||
} | ||
finalize(); | ||
@@ -132,12 +151,16 @@ }); | ||
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) }; | ||
shutdown(true); | ||
} | ||
if (children.has(child)) { | ||
children.delete(child); | ||
emitter.emit('unlink', child); | ||
} | ||
finalize(); | ||
}); | ||
}); | ||
@@ -167,12 +190,10 @@ children.add(child); | ||
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 +200,0 @@ return task; |
{ | ||
"name": "@effection/core", | ||
"version": "2.0.0-beta.11", | ||
"version": "2.0.0-beta.12", | ||
"main": "dist-cjs/index.js", | ||
@@ -5,0 +5,0 @@ "module": "dist-esm/index.js", |
import type { Task } from '../task'; | ||
import type { RunLoop } from '../run-loop'; | ||
import type { Operation } from '../operation'; | ||
@@ -16,2 +17,3 @@ import { isResource, isResolution, isFuture, isPromise, isGenerator } from '../predicates'; | ||
operation: Operation<TOut>; | ||
resourceTask?: Task; | ||
start(): void; | ||
@@ -23,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') { | ||
@@ -30,0 +32,0 @@ return createFunctionController(task, operation, () => createController(task, operation(task), options)); |
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,38 @@ 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) => { | ||
produce(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' } | ||
}); | ||
delegate.start(); | ||
initTask.consume(produce); | ||
} | ||
function halt() { | ||
delegate.halt(); | ||
initTask?.halt(); | ||
} | ||
return { start, halt, future, type: 'resource', operation: resource }; | ||
return { | ||
type: 'resource constructor', | ||
start, | ||
halt, | ||
future, | ||
get resourceTask() { | ||
return resourceTask; | ||
}, | ||
operation: resource | ||
}; | ||
} |
import { HaltError } from './halt-error'; | ||
import { createRunLoop } from './run-loop'; | ||
import { createRunLoop, RunLoop } from './run-loop'; | ||
@@ -30,4 +30,8 @@ export type State = 'pending' | 'errored' | 'completed' | 'halted'; | ||
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>[] = []; | ||
@@ -34,0 +38,0 @@ let result: Value<T>; |
@@ -1,22 +0,25 @@ | ||
import { Operation, Resource } from '../operation'; | ||
import { spawn } from './spawn'; | ||
import { Operation } from '../operation'; | ||
import { createFuture } from '../future'; | ||
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; | ||
} | ||
let { future, produce } = createFuture<undefined>(); | ||
produce({ state: 'completed', value: undefined }); | ||
return future; | ||
}; | ||
} |
@@ -1,11 +0,17 @@ | ||
import { Resource } from '../operation'; | ||
import { Operation } from '../operation'; | ||
import { Labels } from '../labels'; | ||
import { createFuture } from '../future'; | ||
import { withLabels } from '../labels'; | ||
export function label(labels: Labels): Resource<void> { | ||
return { | ||
name: 'label', | ||
*init(scope) { | ||
scope.setLabels(labels); | ||
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); | ||
let { future, produce } = createFuture<undefined>(); | ||
produce({ state: 'completed', value: undefined }); | ||
return future; | ||
}, { name: 'label' }); | ||
} |
@@ -1,5 +0,6 @@ | ||
import { Operation, Resource } from '../operation'; | ||
import { Operation, OperationFunction } from '../operation'; | ||
import { createFuture } from '../future'; | ||
import type { Task, TaskOptions } from '../task'; | ||
interface Spawn<T> extends Resource<Task<T>> { | ||
interface Spawn<T> extends OperationFunction<Task<T>> { | ||
within(scope: Task): Operation<Task<T>>; | ||
@@ -9,4 +10,11 @@ } | ||
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); | ||
let { future, produce } = createFuture<Task<T>>(); | ||
produce({ state: 'completed', value: result }); | ||
return future; | ||
} | ||
@@ -18,3 +26,3 @@ | ||
return { init, within, name: 'spawn' }; | ||
return Object.assign(spawn, { within }); | ||
} |
@@ -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 @@ } |
@@ -30,2 +30,6 @@ import { EventEmitter } from 'events'; | ||
get isFinalized(): boolean { | ||
return this.current === 'errored' || this.current === 'completed' || this.current === 'halted'; | ||
} | ||
start(): void { | ||
@@ -32,0 +36,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'; | ||
@@ -28,3 +28,5 @@ | ||
export interface TaskOptions { | ||
readonly resourceScope?: Task; | ||
readonly type?: string; | ||
readonly scope?: Task; | ||
readonly yieldScope?: Task; | ||
readonly blockParent?: boolean; | ||
@@ -45,2 +47,3 @@ readonly ignoreChildErrors?: boolean; | ||
readonly yieldingTo: Task | undefined; | ||
readonly resourceTask: Task | undefined; | ||
catchHalt(): Promise<TOut | undefined>; | ||
@@ -53,2 +56,3 @@ setLabels(labels: Labels): void; | ||
toJSON(): TaskTree; | ||
toString(): string; | ||
on: EventEmitter['on']; | ||
@@ -68,5 +72,5 @@ off: EventEmitter['off']; | ||
let { produce, future } = createFuture<TOut>(); | ||
let result: Value<TOut>; | ||
let runLoop = createRunLoop(); | ||
let runLoop = createRunLoop(`task ${id}`); | ||
let { produce, future } = createFutureOnRunLoop<TOut>(runLoop); | ||
@@ -78,3 +82,2 @@ let controller: Controller<TOut>; | ||
if (!labels.name) { | ||
@@ -99,3 +102,3 @@ if (operation?.name) { | ||
get type() { return controller.type }, | ||
get type() { return options.type || controller.type }, | ||
@@ -106,2 +109,4 @@ get children() { return Array.from(children) }, | ||
get resourceTask() { return controller.resourceTask }, | ||
catchHalt() { | ||
@@ -120,3 +125,3 @@ return future.catch(swallowHalt); | ||
} | ||
let child = createTask(operation, { resourceScope: task, ...options }); | ||
let child = createTask(operation, { scope: task, ...options }); | ||
link(child as Task); | ||
@@ -137,10 +142,12 @@ child.start(); | ||
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') { | ||
if(stateMachine.current === 'running') { | ||
stateMachine.halting(); | ||
@@ -159,3 +166,3 @@ result = { state: 'halted' }; | ||
id: id, | ||
type: controller.type, | ||
type: task.type, | ||
labels: labels, | ||
@@ -168,2 +175,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), | ||
@@ -179,2 +195,3 @@ off: (...args) => emitter.off(...args), | ||
controller = createController(task, operation, { | ||
runLoop, | ||
onYieldingToChange(value) { | ||
@@ -187,2 +204,3 @@ yieldingTo = value; | ||
controller.future.consume((value) => { | ||
if(stateMachine.isFinalized) return; | ||
if(value.state === 'completed') { | ||
@@ -198,2 +216,6 @@ stateMachine.completing(); | ||
shutdown(true); | ||
} else if(value.state === 'halted' && stateMachine.current !== 'erroring') { | ||
stateMachine.halting(); | ||
result = { state: 'halted' }; | ||
shutdown(true); | ||
} | ||
@@ -206,13 +228,16 @@ 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(); | ||
shutdown(true); | ||
} | ||
if(children.has(child)) { | ||
children.delete(child); | ||
emitter.emit('unlink', child); | ||
} | ||
finalize(); | ||
}); | ||
}); | ||
@@ -245,10 +270,8 @@ children.add(child); | ||
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); | ||
} | ||
@@ -255,0 +278,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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
215730
3248