pax_global_header 0000666 0000000 0000000 00000000064 14235343117 0014515 g ustar 00root root 0000000 0000000 52 comment=29525d44e080c8033bd3e25eae89d9111b60f09f
teamwork-6.0.0/ 0000775 0000000 0000000 00000000000 14235343117 0013351 5 ustar 00root root 0000000 0000000 teamwork-6.0.0/.github/ 0000775 0000000 0000000 00000000000 14235343117 0014711 5 ustar 00root root 0000000 0000000 teamwork-6.0.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14235343117 0016746 5 ustar 00root root 0000000 0000000 teamwork-6.0.0/.github/workflows/ci-module.yml 0000664 0000000 0000000 00000000314 14235343117 0021345 0 ustar 00root root 0000000 0000000 name: ci
on:
push:
branches:
- master
pull_request:
workflow_dispatch:
jobs:
test:
uses: hapijs/.github/.github/workflows/ci-module.yml@master
with:
min-node-version: 14
teamwork-6.0.0/.gitignore 0000775 0000000 0000000 00000000154 14235343117 0015344 0 ustar 00root root 0000000 0000000 **/node_modules
**/package-lock.json
coverage.*
**/.DS_Store
**/._*
**/*.pem
**/.vs
**/.vscode
**/.idea
teamwork-6.0.0/LICENSE.md 0000775 0000000 0000000 00000002717 14235343117 0014767 0 ustar 00root root 0000000 0000000 Copyright (c) 2015-2022, Project contributors
Copyright (c) 2015-2020, Sideway Inc
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* The names of any contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
teamwork-6.0.0/README.md 0000775 0000000 0000000 00000001713 14235343117 0014635 0 ustar 00root root 0000000 0000000
# @hapi/teamwork
#### Wait for multiple callbacks
**teamwork** is part of the **hapi** ecosystem and was designed to work seamlessly with the [hapi web framework](https://hapi.dev) and its other components (but works great on its own or with other frameworks). If you are using a different web framework and find this module useful, check out [hapi](https://hapi.dev) – they work even better together.
### Visit the [hapi.dev](https://hapi.dev) Developer Portal for tutorials, documentation, and support
## Useful resources
- [Version status](https://hapi.dev/resources/status/#teamwork) (builds, dependencies, node versions, licenses, eol)
- [Changelog](https://hapi.dev/family/teamwork/changelog/)
- [Project policies](https://hapi.dev/policies/)
- [Free and commercial support options](https://hapi.dev/support/)
teamwork-6.0.0/lib/ 0000775 0000000 0000000 00000000000 14235343117 0014117 5 ustar 00root root 0000000 0000000 teamwork-6.0.0/lib/index.d.ts 0000775 0000000 0000000 00000004406 14235343117 0016027 0 ustar 00root root 0000000 0000000 /**
* Team bridges between callbacks and promises. Used to convert callback-based
* interfaces to a promise-based result including support for collecting multiple
* callback events into a single promise.
*/
export class Team {
/**
* Start a new team work.
*
* @param options Configuration of the team work.
*/
constructor(options?: Team.Options);
/**
* Resulting work when all the meetings are done.
*/
work: Promise;
/**
* Attend a single meeting.
*
* @param note An optional note that will be included in the work's results. If an error is provided, the work will be immediately rejected with that error.
*/
attend(note?: Error | Team.ElementOf): void;
/**
* Wait for the current work to be done and start another team work.
*
* @param options New configuration of the team work.
*
* @returns a promise that resolves when the current work is done.
*/
regroup(options?: Team.Options) : Promise;
}
export namespace Team {
/**
* Configuration of the team work.
*/
export interface Options {
/**
* Number of meetings this team should attend before delivering work.
*
* @default 1
*/
readonly meetings?: number;
/**
* Throws when the team attends more than the expected number of `meetings`.
*
* @default false
*/
readonly strict?: boolean;
}
type ElementOf = T extends (infer E)[] ? E : T;
}
/**
* Events emitter via an async iterator interface.
*/
export class Events {
/**
* Returns a standard async iterator interface object.
*
* @returns async iterator interface object.
*/
iterator(): Events.Iterator;
/**
* Emits an event to be consumed via the iterator.
*
* @param value
*/
emit(value: T): void;
/**
* Informs the iterator that no new events will be emitted.
*/
end(): void;
}
export namespace Events {
class Iterator implements AsyncIterator {
constructor(events: Events);
[Symbol.asyncIterator](): AsyncIterator;
next(): Promise>;
}
}
teamwork-6.0.0/lib/index.js 0000775 0000000 0000000 00000004503 14235343117 0015571 0 ustar 00root root 0000000 0000000 'use strict';
const internals = {};
exports.Team = class {
#meetings = null;
#count = null;
#notes = null;
#done = false;
#strict = false;
constructor(options) {
this._init(options);
}
static _notes(instance) {
return instance.#notes;
}
_init(options = {}) {
this.work = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
const meetings = options.meetings || 1;
this.#meetings = meetings;
this.#count = meetings;
this.#notes = [];
this.#done = false;
this.#strict = options.strict;
}
attend(note) {
if (this.#strict && this.#done) {
throw new Error('Unscheduled meeting');
}
else if (this.#done) {
return;
}
if (note instanceof Error) {
this.#done = true;
this.#notes = null;
return this._reject(note);
}
this.#notes.push(note);
if (--this.#count) {
return;
}
this.#done = true;
this._resolve(this.#meetings === 1 ? this.#notes[0] : [...this.#notes]);
this.#notes = null;
}
async regroup(options) {
await this.work;
this._init(options);
}
};
exports.Events = class {
#pending = null;
#queue = [];
static isIterator(iterator) {
return iterator instanceof internals.EventsIterator;
}
iterator() {
return new internals.EventsIterator(this);
}
emit(value) {
this._queue({ value, done: false });
}
end() {
this._queue({ done: true });
}
_next() {
if (this.#queue.length) {
return Promise.resolve(this.#queue.shift());
}
this.#pending = new exports.Team();
return this.#pending.work;
}
_queue(item) {
if (this.#pending) {
this.#pending.attend(item);
this.#pending = null;
}
else {
this.#queue.push(item);
}
}
};
internals.EventsIterator = class {
#events = null;
constructor(events) {
this.#events = events;
}
[Symbol.asyncIterator]() {
return this;
}
next() {
return this.#events._next();
}
};
teamwork-6.0.0/package.json 0000775 0000000 0000000 00000001366 14235343117 0015650 0 ustar 00root root 0000000 0000000 {
"name": "@hapi/teamwork",
"description": "Wait for multiple callback",
"version": "6.0.0",
"repository": "git://github.com/hapijs/teamwork",
"main": "lib/index.js",
"files": [
"lib"
],
"keywords": [
"async",
"flow control",
"callback"
],
"types": "lib/index.d.ts",
"engines": {
"node": ">=14.0.0"
},
"eslintConfig": {
"extends": [
"plugin:@hapi/module"
]
},
"devDependencies": {
"@hapi/code": "^9.0.0",
"@hapi/eslint-plugin": "*",
"@hapi/lab": "^25.0.0",
"@types/node": "^17.0.31",
"typescript": "^4.6.4"
},
"scripts": {
"test": "lab -a @hapi/code -t 100 -L -Y",
"test-cov-html": "lab -a @hapi/code -r html -o coverage.html"
},
"license": "BSD-3-Clause"
}
teamwork-6.0.0/test/ 0000775 0000000 0000000 00000000000 14235343117 0014330 5 ustar 00root root 0000000 0000000 teamwork-6.0.0/test/index.js 0000775 0000000 0000000 00000012071 14235343117 0016001 0 ustar 00root root 0000000 0000000 'use strict';
const Code = require('@hapi/code');
const Lab = require('@hapi/lab');
const Teamwork = require('..');
const internals = {};
const { describe, it } = exports.lab = Lab.script();
const expect = Code.expect;
describe('Team', () => {
it('resolves when meeting is attended', async () => {
const team = new Teamwork.Team();
setTimeout(() => {
team.attend();
}, 100);
await team.work;
});
it('resolves when all meetings are attended', async () => {
const team = new Teamwork.Team({ meetings: 2 });
let count = '';
setTimeout(() => {
count += '1';
team.attend();
}, 100);
setTimeout(() => {
count += '2';
team.attend();
}, 150);
await team.work;
expect(count).to.equal('12');
expect(() => team.attend()).not.to.throw();
expect(() => team.attend(new Error())).not.to.throw();
});
it('throws when too many meetings attended', async () => {
const team = new Teamwork.Team({ meetings: 2, strict: true });
team.attend();
team.attend();
await team.work;
expect(() => team.attend()).to.throw('Unscheduled meeting');
expect(() => team.attend(new Error())).to.throw('Unscheduled meeting');
});
it('resolves with a note', async () => {
const team = new Teamwork.Team();
setTimeout(() => {
team.attend('1');
}, 100);
const note = await team.work;
expect(note).to.equal('1');
});
it('resolves with notes', async () => {
const team = new Teamwork.Team({ meetings: 2 });
setTimeout(() => {
team.attend('1');
}, 100);
setTimeout(() => {
team.attend('2');
}, 150);
const notes = await team.work;
expect(notes).to.equal(['1', '2']);
});
it('rejects on first error', async () => {
const team = new Teamwork.Team({ meetings: 2 });
setTimeout(() => {
team.attend(new Error('boom'));
}, 100);
setTimeout(() => {
team.attend('2');
}, 150);
await expect(team.work).to.reject('boom');
});
it('resets condition after initial condition met', async () => {
const team = new Teamwork.Team({ meetings: 2, strict: true });
let count = '';
setTimeout(() => {
count += '1';
team.attend();
}, 100);
setTimeout(() => {
count += '2';
team.attend();
}, 150);
await team.regroup({ strict: true });
expect(count).to.equal('12');
setTimeout(() => {
count += '3';
team.attend();
}, 150);
await team.work;
expect(count).to.equal('123');
});
it('exposes private notes as a static accessor', async () => {
const team = new Teamwork.Team({ meetings: 2 });
team.attend('1');
const internalNotes = Teamwork.Team._notes(team);
expect(internalNotes).to.equal(['1']);
team.attend('2');
await team.work;
});
it('clears notes and doesn\'t add new notes when meeting is done', async () => {
const team = new Teamwork.Team({ meetings: 2 });
setTimeout(() => {
team.attend('1');
}, 100);
setTimeout(() => {
team.attend('2');
}, 150);
const notes = await team.work;
expect(notes).to.equal(['1', '2']);
expect(Teamwork.Team._notes(team)).to.be.null();
expect(() => team.attend('3')).to.not.throw();
expect(Teamwork.Team._notes(team)).to.be.null();
});
it('clears notes when attending with an error', async () => {
const team = new Teamwork.Team({ meetings: 2 });
setTimeout(() => {
team.attend('1');
}, 100);
setTimeout(() => {
team.attend(new Error());
}, 150);
await expect(team.work).to.reject();
expect(Teamwork.Team._notes(team)).to.be.null();
});
});
describe('Events', () => {
it('iterates over events', async () => {
const events = new Teamwork.Events();
const iterator = events.iterator();
expect(Teamwork.Events.isIterator(iterator)).to.be.true();
const collect = new Promise(async (resolve) => {
const items = [];
for await (const item of iterator) {
items.push(item);
}
resolve(items);
});
events.emit(1);
events.emit(2);
events.emit(3);
events.end();
expect(await collect).to.equal([1, 2, 3]);
});
it('iterates over events (queued)', async () => {
const events = new Teamwork.Events();
events.emit(1);
events.emit(2);
events.emit(3);
events.end();
const items = [];
for await (const item of events.iterator()) {
items.push(item);
}
expect(items).to.equal([1, 2, 3]);
});
});
teamwork-6.0.0/test/index.ts 0000775 0000000 0000000 00000003527 14235343117 0016021 0 ustar 00root root 0000000 0000000 import * as Code from '@hapi/code';
import * as Lab from '@hapi/lab';
import * as Teamwork from '..';
const { expect } = Lab.types;
// Team
// Constructor tests
expect.type(new Teamwork.Team());
expect.type(new Teamwork.Team({ meetings: 2 }));
expect.type(new Teamwork.Team({ strict: true }));
expect.error(new Teamwork.Team({ foo: true }));
expect.error(new Teamwork.Team({ meetings: 'foo' }));
expect.type>(new Teamwork.Team().work);
// Attend tests
expect.type(new Teamwork.Team().attend());
expect.type(new Teamwork.Team().attend(new Error()));
expect.type(new Teamwork.Team().attend('foo'));
expect.type(new Teamwork.Team().attend('foo'));
expect.error(new Teamwork.Team().attend('foo'));
expect.error(new Teamwork.Team().attend('foo'));
expect.type(new Teamwork.Team<[boolean, string]>().attend('foo'));
// Work tests
expect.type>(new Teamwork.Team().work);
expect.type>(new Teamwork.Team().work);
expect.type>(new Teamwork.Team().work);
// Regroup tests
expect.type>(new Teamwork.Team().regroup());
expect.type>(new Teamwork.Team().regroup({ meetings: 2 }));
expect.type>(new Teamwork.Team().regroup({ strict: true }));
// Events
const events = new Teamwork.Events();
expect.error(events.emit(1));
const iterator = events.iterator();
expect.type>(iterator);
const test = async function () {
const events = new Teamwork.Events();
events.emit(1);
events.emit(2);
events.emit(3);
events.end();
const items = [];
for await (const item of events.iterator()) {
items.push(item);
}
Code.expect(items).to.equal([1, 2, 3]);
};
test();