Opinionated, type-safe, zero-dependency max/min priority queue for JavaScript and TypeScript projects.

Overview

qewe

npm version ci status bundle size

qewe is an opinionated, type-safe, zero-dependency max/min priority queue for JavaScript and TypeScript projects.

Installation

Add qewe to your project using your favorite package manager:

$ yarn add qewe

You can also import qewe with a script tag via unpkg:

<script src="//unpkg.com/qewe" type="text/javascript"></script>

Usage

import { Qewe } from 'qewe';

const queue = new Qewe();

queue.enqueue('hello', 1);
queue.enqueue('world', 2);

console.log(...queue); // [ 'world', 'hello' ]
console.log(queue.size); // 2

const dequeued = queue.dequeue();
console.log(dequeued); // 'world'
console.log(queue.size); // 1

Enqueueing

A Qewe instance's queue is a list of QeweEntry instances. The recommended way to enqueue new values is to use the enqueue method, passing in a value and a priority:

const myQueue = new Qewe();

myQueue.enqueue('my-value', 1);
console.log(myQueue.queue); // [ QeweEntry { value: 'my-value', priority: 1 } ]

If the priority of an entry can be inferred when enqueue is called then you can omit the priority argument and instead pass an inferValuePriority function to the constructor:

const myQueue = new Qewe<string>({
  inferValuePriority: (value) => value.length,
});

myQueue.enqueue('hello');
myQueue.enqueue('qewe');
console.log(myQueue.queue); // [ QeweEntry { value: 'hello', priority: 5 }, QeweEntry { value: 'qewe', priority: 4 } ]

Alternatively, you can create a QeweEntry instance yourself - either by using the new QeweEntry constructor or the createEntry method on a Qewe instance - and pass it to the enqueue method. This can be useful if you will need to requeue the same entry later.

const myQueue = new Qewe();

const firstEntry = queue.createEntry('my-value', 1);
const secondEntry = new QeweEntry('my-other-value', 2);

myQueue.enqueue(firstEntry);
myQueue.enqueue(secondEntry);
console.log(myQueue.queue); // [ QeweEntry { value: 'my-other-value', priority: 2 }, QeweEntry { value: 'my-value', priority: 1 } ]

Queue Behavior

Instances which have an empty queue will throw an error when a dequeue or dequeueEnd is attempted. It is recommended that you expect this error and handle it accordingly:

const queue = new Qewe();

try {
  const value = queue.dequeue();
} catch {
  // queue is empty - do something else
}

Alternatively, you can check if the queue is empty before you attempt to dequeue:

const queue = new Qewe();

if (!queue.isEmpty()) {
  const value = queue.dequeue();
} else {
  // queue is empty - do something else
}

Note: the peek and peekEnd properties of an instance do not throw an error when the queue is empty. Instead, they return undefined.

Qewe API

Constructor Options

You can customize a Qewe instance by passing a QeweOptions object to the constructor:

class Qewe<T>(options?: QeweOptions<T>);

type QueueType = 'min' | 'max';
interface QeweOptions<T> {
  queueType?: QueueType;
  maxSize?: number;
  inferValuePriority?: (value: T) => number;
  initialEntries?: QeweEntry<T>[];
  initialValues?: T[];
}
  • queueType: QueueType

    Indicate whether the queue should be a minimum or maximum priority queue.

    queueType is max by default.

  • maxSize: number

    Specify a maximum number of entries that can exist in the instance's priority queue.

    maxSize is Infinity by default.

  • inferValuePriority: (value: T) => number

    Define a function that will be used to infer the priority of a value when an entry is created. This can be useful when the priority is something that can be derived from the value itself.

    By providing this function you can omit the priority argument from the enqueue and createEntry.

    inferValuePriority is undefined by default, which means you always have to provide the priority when adding a value to the queue.

  • initialEntries: QeweEntry<T>

    Specify an array of QeweEntry instances to initialize the instance's priority queue. This uses Qewe.prototype.enqueue to add each entry.

  • initialValues: T[] | QeweEntry<T>[]

    Provide an array of values to initialize the queue with. This uses Qewe.prototype.enqueue to add each value.

    Note: The instance must also have an inferValuePriority function so that it can infer the priority of each value. If you cannot provide an inferValuePriority function you should instead use the initialEntries option to initialize the queue.

Instance Properties

// get the amount of entries of the queue.
Qewe.prototype.size: number;

// get the maximum amount of entries that the queue can hold.
Qewe.prototype.maxSize: number;

// get the current queue state.
Qewe.prototype.queue: QeweEntry<T>[];

// get the type (minimum or maximum) of the queue.
Qewe.prototype.queueType: QueueType;

// get the function used to infer the priority of a value to be enqueued
Qewe.prototype.inferValuePriority: ((value: T) => number) | null;

Instance Methods

// returns a generator that yields the values in the queue. synonymous with Qewe.prototype.values.
Qewe.prototype[Symbol.Iterator](): T[];

// returns a generator that yields the queue's values.
Qewe.prototype.values(): Generator<T>;

// returns a generator that yields the queue's entries.
Qewe.prototype.entries(): Generator<QeweEntry<T>>;

// returns whether or not the queue contains a given value.
Qewe.prototype.contains(value: T): boolean;

// returns whether or not the queue is empty.
Qewe.prototype.isEmpty(): boolean;

// create a new entry which can be passed to `enqueue`. returns the entry.
// NOTE: the priority argument is only optional when when
// `options.inferValuePriority` is defined for the instance.
Qewe.prototype.createEntry(value: T, priority?: number): QeweEntry<T>;

// add a new value to the queue. returns the new queue entry.
// NOTE: if you are using the value/priority signature then the priority argument
// is only optional when when `options.inferValuePriority` is defined for the instance.
Qewe.prototype.enqueue(entry: QeweEntry<T>): QeweEntry<T>;
Qewe.prototype.enqueue(value: T, priority?: number): QeweEntry<T>;

// returns the first value in the queue (without removing its entry, like dequeue does).
Qewe.prototype.peek(): T | undefined;

// returns the last value in the queue (without removing its entry, like dequeueEnd does).
Qewe.prototype.peekEnd(): T | undefined;

// removes the first entry from the queue and returns its value.
Qewe.prototype.dequeue(): T;

// removes the last entry from the queue and returns its value.
Qewe.prototype.dequeueEnd(): T;

// removes a specified value or entry from the queue and returns the removed entry.
Qewe.prototype.remove(value: T): QeweEntry<T>;
Qewe.prototype.remove(entry: QeweEntry<T>): QeweEntry<T>;

// removes all entries from the queue and returns them.
Qewe.prototype.clear(): QeweEntry<T>[];

Errors

All errors thrown by a Qewe instance are members of the QeweError enum, which can be imported from the package.

Error Description
QeweError.NoPriorityValue Cannot enqueue - no priority value, or function to infer an entry's priority value, was provided.
QeweError.MaxQueueSizeReached Cannot enqueue - the queue is already at its max size.
QeweError.EmptyQueue Cannot dequeue - the queue is empty.
QeweError.NotFound Cannot remove - the value was not found in the queue.

QeweEntry API

Constructor Arguments

A new QeweEntry instance takes two arguments in its constructor:

class QeweEntry<T>(value: T, priority: number);
  • value: T

    The value of the entry.

  • priority: number

    The priority of the entry.

Instance Properties

// the value of the entry
QeweEntry.prototype.value: T;

// the priority of the entry
QeweEntry.prototype.priority: number;
Comments
  • React to QeweEntry priority changes

    React to QeweEntry priority changes

    Suggested behaviour:

    const queue = new Qewe({ reactive: true });
    
    const firstEntry = queue.enqueue('hello', 1);
    const secondEntry = queue.enqueue('world', 2);
    
    console.log(...queue); // [ 'world', 'hello' ]
    
    firstEntry.priority = 3;
    
    console.log(...queue); // [ 'hello', 'world ]
    

    This should be possible by returning a Proxy from .enqueue that wraps the QeweEntry's priority setter.

    enhancement 
    opened by jgmcelwain 1
  • QeweEntry

    QeweEntry

    This PR, and subsequently release, adds a new QeweEntry class which is used by a Qewe instance to store values and their priorities.

    QeweEntry instances can be created outside of a Qewe instance - either using the new QeweEntry constructor or the .createEntry method on a Qewe instance. They can then be inserted programatically.

    const queue = new Qewe<string>();
    
    const entry = new QeweEntry('hello', 1);
    queue.enqueue(entry);
    
    const anotherEntry = queue.createEntry('world', 2);
    queue.enqueue(anotherEntry);
    

    .enqueue still allows the (value: T, priority?: number) syntax:

    const queue = new Qewe<string>();
    queue.enqueue('hello', 1);
    queue.enqueue('world', 2);
    
    const inferrerdQueue = new Qewe<string>({ inferValuePriority: (value) => value.length });
    inferredQueue.enqueue('hello');
    inferredQueue.enqueue('world');
    

    This is, however, a breaking change. The initialValues constructor option now only allows raw values and requires the inferValuePriority to be set.

    A new constructor option, initialEntries, has been added, which takes an array of QeweEntry instances.

    // old behavior, no longer works
    const queue = new Qewe({
      initialValues: [
        { value: 'hello', priority: 1 },
        { value: 'world', priority: 2 }
      ]
    });
    
    // new behavior
    const firstEntry = new QeweEntry('hello', 1);
    const secondEntry = new QeweEntry('world', 2);
    const queue = new Qewe({ initialEntries: [firstEntry, secondEntry] });
    
    // also works 
    const queue = new Qewe({
      inferValuePriority: (value) => value.length,
      initialValues: ['hello', 'world']
    });
    
    enhancement 
    opened by jgmcelwain 0
  • Max Queue Size, Explicit Queue Type, Updated Docs

    Max Queue Size, Explicit Queue Type, Updated Docs

    Max Queue Size

    const queue = new Qewe({ maximumQueueSize: 3 });
    
    queue.enqueue('a', 1); // ✅
    queue.enqueue('b', 1); // ✅
    queue.enqueue('c', 1); // ✅
    queue.enqueue('d', 4); // ❌ Error
    

    Explicit Queue Type

    // before
    const queue = new Qewe({ minQueue: true });
    
    // after
    const queue = new Qewe({ queueType: 'min' });
    
    opened by jgmcelwain 0
  • Errors when attempting to dequeue/dequeueEnd on an empty queue

    Errors when attempting to dequeue/dequeueEnd on an empty queue

    This PR addresses #1.

    dequeue and dequeueEnd now throw an error if they are called when the queue is empty:

    dequeue(): T {
      const entry = this.queue.shift();
    
      if (entry !== undefined) {
        return entry.value;
      } else {
        throw new Error('Dequeue failed - the queue is empty.');
      }
    }
    

    The documentation in the README file has been updated to suggest users wrap their dequeue/dequeueEnd calls in a try/catch block. It also provides an alternative suggestion, noted in the original issue, where users can instead check for themselves if the queue is empty before attempting a dequeue.

    opened by jgmcelwain 0
  • Initialize instance with values

    Initialize instance with values

    The first value(s) of a queue are often known when the instance is created. It would be beneficial if these values could be included in the initialization process, instead of having to add them afterwards.

    enhancement good first issue 
    opened by jgmcelwain 0
  • dequeue and dequeueEnd empty queue behavior

    dequeue and dequeueEnd empty queue behavior

    Currently both dequeue and dequeueEnd methods return null if the queue is empty when they're called. This is a fairly unobtrusive implementation but does have two fairly significant drawbacks:

    • Instances will indefinitely return null whilst their queue is empty. This means that users must manually check to see if their latest dequeue/dequeueEnd call emptied the queue.
    • If the user adds null to the queue they have no way of knowing if the null that dequeue/dequeueEnd returns is an inserted value or the "queue is empty" value. Again, they must manually check if the queue is empty instead.

    Proposal

    A potential solution is to instead throw an error when dequeue/dequeueEnd are called when the queue is empty. This solves both above issues. The instance will inform the user when the queue is empty, since they will have to deal with an error, and null values returned can safely be assumed to be values in the queue.

    const queue = new Qewe();
    
    try {
      const value = queue.dequeue();
      // do something with value
    } catch {
      // queue is empty, do something else
    }
    

    Drawbacks

    This behavior change does come with a little overhead. Users would have to handle errors, likely with a try/catch block, or their application would crash.

    Alternative

    An alternative is to suggest users check if the queue is empty before they call dequeue/dequeueEnd.

    const queue = new Qewe();
    
    if (queue.isEmpty === false) {
      const value = queue.dequeue();
      // do something with value
    } else {
      // queue is empty, do something else
    }
    
    enhancement good first issue 
    opened by jgmcelwain 0
  • Reactive Entries

    Reactive Entries

    Current Implementation

    const queue = new Qewe();
    
    const a = queue.enqueue('hello', 1);
    const b = queue.enqueue('world', 2);
    
    console.log(...queue); // [ 'world', 'hello' ]
    
    a.priority = 3;
    
    console.log(...queue); // [ 'hello', 'world' ]
    

    This is achieved by wrapping the QeweEntry created in .enqueue in a Proxy that runs a sort method on the queue when the entry's priority value is changed.

    Problems

    There is an inconsistency with the current implementation's behaviour if you pass a QeweEntry directly into enqueue:

    const queue = new Qewe();
    
    const a = new QeweEntry('hello', 1);
    const b = new QeweEntry('world', 2);
    queue.enqueue(a);
    queue.enqueue(b);
    
    console.log(...queue); // [ 'world', 'hello' ]
    
    a.priority = 3;
    
    console.log(...queue); // [ 'world', 'hello' ]
    

    Because the original QeweEntry instance is being edited, and not the Proxied instance that enqueue creates, the Qewe instance is not aware of any priority changes and so does not react to the change.

    enhancement 
    opened by jgmcelwain 0
Releases(v1.1.1)
Owner
Jamie McElwain
Software Engineer (React, Vue, TypeScript). He/him.
Jamie McElwain
Yet another concurrent priority task queue, yay!

YQueue Yet another concurrent priority task queue, yay! Install npm install yqueue Features Concurrency control Prioritized tasks Error handling for b

null 6 Apr 4, 2022
Type-safe and Promisified API for Web Worker and Iframe

?? You can help the author become a full-time open-source maintainer by sponsoring him on GitHub. typed-worker Install npm i typed-worker Usage Create

EGOIST 189 Dec 31, 2022
Full type-safe Redis PubSub with Zod

redis-pubsub Full type-safe Redis PubSub system with async iterators Features Type-safety with Zod Out-of-the-box support for Date/Map/Set/BigInt seri

null 12 Dec 21, 2022
Premium Queue package for handling distributed jobs and messages in NodeJS.

The fastest, most reliable, Redis-based queue for Node. Carefully written for rock solid stability and atomicity. Sponsors · Features · UIs · Install

null 13.5k Dec 31, 2022
Redis-backed task queue engine with advanced task control and eventual consistency

idoit Redis-backed task queue engine with advanced task control and eventual consistency. Task grouping, chaining, iterators for huge ranges. Postpone

Nodeca 65 Dec 15, 2022
A fast, robust and extensible distributed task/job queue for Node.js, powered by Redis.

Conveyor MQ A fast, robust and extensible distributed task/job queue for Node.js, powered by Redis. Introduction Conveyor MQ is a general purpose, dis

Conveyor MQ 45 Dec 15, 2022
A document based messaging queue for Mongo, DocumentDB, and others

DocMQ Messaging Queue for any document-friendly architectures (DocumentDB, Mongo, Postgres + JSONB, etc). Why Choose This DocMQ is a good choice if yo

Jakob Heuser 10 Dec 7, 2022
A simple, fast, robust job/task queue for Node.js, backed by Redis.

A simple, fast, robust job/task queue for Node.js, backed by Redis. Simple: ~1000 LOC, and minimal dependencies. Fast: maximizes throughput by minimiz

Bee Queue 3.1k Jan 5, 2023
Redis Simple Message Queue

Redis Simple Message Queue A lightweight message queue for Node.js that requires no dedicated queue server. Just a Redis server. tl;dr: If you run a R

Patrick Liess 1.6k Dec 27, 2022
BullMQ - Premium Message Queue for NodeJS based on Redis

The fastest, most reliable, Redis-based distributed queue for Node. Carefully written for rock solid stability and atomicity. Read the documentation F

Taskforce.sh Inc. 3.1k Dec 30, 2022
Better Queue for NodeJS

Better Queue - Powerful flow control Super simple to use Better Queue is designed to be simple to set up but still let you do complex things. Persiste

Diamond 415 Dec 17, 2022
A simple high-performance Redis message queue for Node.js.

RedisSMQ - Yet another simple Redis message queue A simple high-performance Redis message queue for Node.js. For more details about RedisSMQ design se

null 501 Dec 30, 2022
A client-friendly run queue

client-run-queue This package provides a RunQueue implementation for scheduling and managing async or time-consuming functions such that client-side i

Passfolio 6 Nov 22, 2022
A client-friendly run queue

client-run-queue This package provides a RunQueue implementation for scheduling and managing async or time-consuming functions such that client-side i

Passfolio 4 Jul 5, 2022
Template project for ComputerCraft programs written in TypeScript.

cc-tstl-template Template project for ComputerCraft programs written in TypeScript. Uses TypeScriptToLua to compile with ComputerCraft typing declarat

JackMacWindows 22 Dec 4, 2022
TypeScript Data Structures that you need!

TSDS TypeScript Data Structures that you need! Doc Website Introduction A data structure is a way to store and organize data in order to facilitate ac

Ehsan Samavati 25 Dec 8, 2022
Nanoservices in no time with seamless TypeScript support.

Nanolith Nanoservices in no time with seamless TypeScript support. Table of Contents About Defining a set of tasks Creating multiple sets of definitio

Matt Stephens 11 Dec 28, 2022
Bree is the best job scheduler for Node.js and JavaScript with cron, dates, ms, later, and human-friendly support.

The best job scheduler for Node.js and JavaScript with cron, dates, ms, later, and human-friendly support. Works in Node v10+ and browsers, uses workers to spawn sandboxed processes, and supports async/await, retries, throttling, concurrency, and graceful shutdown. Simple, fast, and lightweight. Made for @ForwardEmail and @ladjs.

Bree - The Best Node.js and JavaScript Job Scheduler 2.5k Dec 30, 2022
Challenge [Frontend Mentor] - In this challenge, JavaScript was used to filter jobs based on the selected categories. Technologies used: HTML5, CSS3 and React.

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

Rui Neto 11 Apr 13, 2022