Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/queue-initial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@solid-primitives/queue": minor
---

Initial release of `@solid-primitives/queue`

Six primitives for managing queues:

- **`makeQueue<T>(initialValues?)`** — non-reactive FIFO queue backed by a plain array.
- **`createQueue<T>(initialValues?)`** — reactive FIFO queue backed by Solid signals. Exposes reactive accessors (`queue`, `first`, `last`, `size`, `isEmpty`) and imperative methods (`add`, `remove`, `clear`).
- **`makePriorityQueue<T>(comparator, initialValues?)`** — non-reactive priority queue; items are dequeued in comparator order rather than insertion order.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update makePriorityQueue signature in changeset text.

Line 11 documents makePriorityQueue<T>(comparator, initialValues?), but the PR contract describes a queue modifier form (makePriorityQueue(q, comparator)). This should be corrected before release notes are generated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/queue-initial.md at line 11, The changeset incorrectly documents
makePriorityQueue with the old constructor signature; update the changeset text
to reflect the actual API used in the PR by changing the documented form from
makePriorityQueue<T>(comparator, initialValues?) to the modifier form
makePriorityQueue(q, comparator) (or the exact PR contract signature), and
ensure the description mentions it modifies an existing queue rather than
returning a non-reactive standalone queue; reference makePriorityQueue and the
queue modifier form in your edit so release notes match the implemented API.

- **`createPriorityQueue<T>(comparator, initialValues?)`** — reactive priority queue; same interface as `createQueue`.
- **`createTaskQueue<T>()`** — reactive async task queue. Tasks execute one at a time in FIFO order. `enqueue(task)` returns a `Promise<T>`. Exposes reactive `size` (pending count) and `active` (`boolean`).
- **`createConcurrentTaskQueue<T>(concurrency)`** — reactive async task queue running up to `concurrency` tasks simultaneously. `active` is a count (`Accessor<number>`).
12 changes: 12 additions & 0 deletions packages/queue/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# @solid-primitives/queue

## 0.1.0

### Initial release

- `makeQueue<T>(initialValues?)` — non-reactive FIFO queue backed by a plain array
- `createQueue<T>(initialValues?)` — reactive FIFO queue backed by Solid signals; exposes `first`, `last`, `size`, `isEmpty`, `add`, `remove`, `clear`
- `makePriorityQueue<T>(comparator, initialValues?)` — non-reactive priority queue; items dequeued in comparator order
- `createPriorityQueue<T>(comparator, initialValues?)` — reactive priority queue; same interface as `createQueue`
Comment on lines +8 to +10
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Correct API docs to match released signatures.

Line 8 and Line 9 appear out of sync with the PR’s described API: createQueue should include queue in exposed accessors, and makePriorityQueue is described as a modifier form (makePriorityQueue(q, comparator)), not a (comparator, initialValues?) factory signature. Please align the changelog so users don’t implement against the wrong contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/queue/CHANGELOG.md` around lines 8 - 10, Update the CHANGELOG
entries to reflect the actual exported signatures: change the createQueue line
to document createQueue<T>(initialValues?) — reactive FIFO queue backed by Solid
signals; exposes queue, first, last, size, isEmpty, add, remove, clear (include
the `queue` accessor), update makePriorityQueue to describe the modifier form
makePriorityQueue<T>(q, comparator) (not a factory), and keep
createPriorityQueue<T>(comparator, initialValues?) as-is; adjust wording so the
listed symbols (`createQueue`, `makePriorityQueue`, `createPriorityQueue`) match
their real function signatures and exposed members.

- `createTaskQueue<T>()` — reactive async task queue; tasks execute one at a time in FIFO order; `enqueue(task)` returns a Promise for the task's result; exposes reactive `size` and `active`
- `createConcurrentTaskQueue<T>(concurrency)` — reactive async task queue running up to `concurrency` tasks simultaneously; `active` is a count (`Accessor<number>`)
21 changes: 21 additions & 0 deletions packages/queue/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 Solid Primitives Working Group

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
276 changes: 276 additions & 0 deletions packages/queue/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
<p>
<img width="100%" src="https://assets.solidjs.com/banner?type=Primitives&background=tiles&project=queue" alt="Solid Primitives queue">
</p>

# @solid-primitives/queue

[![size](https://img.shields.io/bundlephobia/minzip/@solid-primitives/queue?style=for-the-badge&label=size)](https://bundlephobia.com/package/@solid-primitives/queue)
[![version](https://img.shields.io/npm/v/@solid-primitives/queue?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/queue)
[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-0.json)](https://github.com/solidjs-community/solid-primitives#contribution-process)

Queue primitives for Solid.js.

- **`makeQueue`** — non-reactive FIFO queue backed by a plain array. No Solid lifecycle hooks; suitable for non-reactive contexts.
- **`createQueue`** — reactive FIFO queue backed by Solid signals. All accessor properties (`queue`, `first`, `last`, `size`, `isEmpty`) track reactively.
- **`makePriorityQueue`** — non-reactive priority queue. Items are dequeued by comparator order rather than insertion order.
- **`createPriorityQueue`** — reactive priority queue backed by Solid signals.
- **`createTaskQueue`** — reactive queue of async tasks that execute one at a time in FIFO order. Each `enqueue` call returns a Promise.
- **`createConcurrentTaskQueue`** — like `createTaskQueue` but runs up to `concurrency` tasks simultaneously.

## Installation

```bash
npm install @solid-primitives/queue
# or
yarn add @solid-primitives/queue
# or
pnpm add @solid-primitives/queue
```

## `makeQueue`

Creates a plain, non-reactive FIFO queue.

```ts
import { makeQueue } from "@solid-primitives/queue";

const q = makeQueue([1, 2, 3]);

q.first; // 1
q.last; // 3
q.size; // 3
q.isEmpty; // false

q.add(4, 5);
q.remove(); // 1
q.first; // 2

q.clear();
q.isEmpty; // true
```

### Type

```ts
type Queue<T> = {
readonly first: T | undefined;
readonly last: T | undefined;
readonly size: number;
readonly isEmpty: boolean;
add: (...items: T[]) => void;
remove: () => T | undefined;
clear: () => void;
};
Comment on lines +55 to +63
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Queue/ReactiveQueue type docs omit push, which is part of the public API.

Both runtime implementations expose push, but the documented type shapes do not. This makes the API contract inconsistent for users.

Suggested README fix
 type Queue<T> = {
   readonly first: T | undefined;
   readonly last: T | undefined;
   readonly size: number;
   readonly isEmpty: boolean;
   add: (...items: T[]) => void;
+  push: (comparator: (a: T, b: T) => number, ...items: T[]) => void;
   remove: () => T | undefined;
   clear: () => void;
 };
 type ReactiveQueue<T> = {
   readonly queue: Accessor<T[]>;
   readonly first: Accessor<T | undefined>;
   readonly last: Accessor<T | undefined>;
   readonly size: Accessor<number>;
   readonly isEmpty: Accessor<boolean>;
   add: (...items: T[]) => void;
+  push: (comparator: (a: T, b: T) => number, ...items: T[]) => void;
   remove: () => T | undefined;
   clear: () => void;
 };

Also applies to: 97-106

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/queue/README.md` around lines 55 - 63, The README type definitions
for Queue and ReactiveQueue are missing the public push method; update the type
shapes (the Queue<T> and ReactiveQueue<T> entries) to include a push signature
that matches the runtime (e.g., push: (...items: T[]) => void), keeping it
consistent with the existing add method and other members (first, last, size,
isEmpty, add, remove, clear). Ensure both occurrences of the type doc (the block
around Queue<T> and the similar block later referenced) are updated so the
documented contract matches the runtime API.


function makeQueue<T>(initialValues?: T[]): Queue<T>;
```

## `createQueue`

Creates a reactive FIFO queue. All accessor properties establish reactive dependencies when read inside a tracking scope (JSX, `createMemo`, `createEffect`, etc.).

Mutations (`add`, `remove`, `clear`) are batched by Solid's scheduler and applied on the next microtask. In tests, call `flush()` after mutations before reading reactive values.

```ts
import { createQueue } from "@solid-primitives/queue";

const { queue, first, last, size, isEmpty, add, remove, clear } = createQueue(["a", "b", "c"]);

// Read reactive state
size(); // 3
first(); // "a"
isEmpty(); // false

// Mutate
add("d", "e");
remove(); // "a" — returned synchronously

// In JSX — updates automatically
<For each={queue()}>{item => <li>{item}</li>}</For>
<p>Next: {first()}</p>
<p>Remaining: {size()}</p>
```

### Type

```ts
type ReactiveQueue<T> = {
readonly queue: Accessor<T[]>;
readonly first: Accessor<T | undefined>;
readonly last: Accessor<T | undefined>;
readonly size: Accessor<number>;
readonly isEmpty: Accessor<boolean>;
add: (...items: T[]) => void;
remove: () => T | undefined;
clear: () => void;
};

function createQueue<T>(initialValues?: T[]): ReactiveQueue<T>;
```

### Notes

- `remove()` returns the dequeued item **synchronously**, even though the reactive signal update is batched.
- Initial values are **copied** — the source array is never mutated.
- Calling `add` or `remove` inside a Solid reactive computation (memo, effect compute phase) will throw in development. Call mutations from event handlers or effect **apply** phases.

## `makePriorityQueue`

Creates a plain, non-reactive priority queue. Items are kept in sorted order; `remove()` always returns the highest-priority item (smallest by the comparator).

```ts
import { makePriorityQueue } from "@solid-primitives/queue";

const q = makePriorityQueue((a, b) => a - b, [3, 1, 2]);

Comment on lines +121 to +125
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

makePriorityQueue docs use the wrong function signature.

The README currently documents and demonstrates makePriorityQueue as if it creates a queue from (comparator, initialValues), but the implementation takes an existing queue first: (q, comparator). This will lead users to call a non-existent API.

Suggested README fix
-import { makePriorityQueue } from "`@solid-primitives/queue`";
+import { makePriorityQueue, makeQueue } from "`@solid-primitives/queue`";

-const q = makePriorityQueue((a, b) => a - b, [3, 1, 2]);
+const asc = (a, b) => a - b;
+const q = makePriorityQueue(makeQueue([3, 1, 2].sort(asc)), asc);
-function makePriorityQueue<T>(
-  comparator: (a: T, b: T) => number,
-  initialValues?: T[],
-): Queue<T>;
+function makePriorityQueue<T, Q extends { add: (...items: T[]) => void; push: (comparator: (a: T, b: T) => number, ...items: T[]) => void }>(
+  q: Q,
+  comparator: (a: T, b: T) => number,
+): Q;

Also applies to: 137-142

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/queue/README.md` around lines 121 - 125, The README demonstrates
makePriorityQueue with the wrong argument order; the implementation expects an
existing queue first and the comparator second. Update all examples and
descriptive text that show makePriorityQueue to pass the initial values/queue as
the first argument and the comparator function as the second (fix both example
blocks that currently show the reversed signature), and adjust any prose that
describes the function signature to reflect (q, comparator) instead of
(comparator, initialValues).

q.first; // 1
q.last; // 3
q.remove(); // 1
q.first; // 2

q.add(0);
q.first; // 0
```

### Type

```ts
function makePriorityQueue<T>(
comparator: (a: T, b: T) => number,
initialValues?: T[],
): Queue<T>;
```

## `createPriorityQueue`

Creates a reactive priority queue. All accessor properties establish reactive dependencies. Mutations are batched; call `flush()` in tests before reading reactive values.

```ts
import { createPriorityQueue } from "@solid-primitives/queue";

const { queue, first, size, add, remove } = createPriorityQueue(
(a, b) => a.priority - b.priority,
initialItems,
);

first(); // highest-priority item

add({ priority: 0, label: "urgent" });
// In JSX
<For each={queue()}>{item => <Task item={item} />}</For>
```

### Type

```ts
function createPriorityQueue<T>(
comparator: (a: T, b: T) => number,
initialValues?: T[],
): ReactiveQueue<T>;
```

### Notes

- `remove()` returns the dequeued item **synchronously**, even though the reactive signal update is batched.
- Initial values are **copied** — the source array is never mutated.
- `queue()` returns items in priority order (lowest comparator value first).

## `createTaskQueue`

Creates a reactive queue that runs async tasks one at a time in FIFO order.

Each task is a zero-argument function returning a plain value or a Promise. Tasks execute sequentially: the next task starts only after the current one resolves or rejects. `enqueue` returns a `Promise<T>` that settles with the task's result.

`size` counts tasks **waiting** (not including the one currently executing).
`active` is `true` while any task is running.

```ts
import { createTaskQueue } from "@solid-primitives/queue";

const { enqueue, size, active } = createTaskQueue<User>();

// Each call runs after the previous one finishes
const [alice, bob] = await Promise.all([
enqueue(() => fetchUser("alice")),
enqueue(() => fetchUser("bob")),
]);

// In JSX
<Show when={active()}>
<p>Processing… ({size()} remaining)</p>
</Show>
```

### Type

```ts
type Task<T> = () => Promise<T> | T;

type ReactiveTaskQueue<T> = {
/** Number of tasks waiting to start (excludes the task currently executing). */
readonly size: Accessor<number>;
/** `true` while a task is executing. */
readonly active: Accessor<boolean>;
/** Adds a task to the back of the queue; resolves/rejects with its result. */
enqueue: (task: Task<T>) => Promise<T>;
/**
* Removes all waiting tasks and rejects their Promises with `"Queue cleared"`.
* The currently-executing task (if any) runs to completion unaffected.
*/
clear: () => void;
};

function createTaskQueue<T>(): ReactiveTaskQueue<T>;
```

### Notes

- Tasks added while the queue is draining are picked up automatically — `enqueue` never restarts the drain.
- `clear()` does **not** cancel the active task; only unstarted tasks are rejected.
- All tasks share the same return type `T`. For heterogeneous task types use `createTaskQueue<unknown>()`.

## `createConcurrentTaskQueue`

Creates a reactive task queue that runs up to `concurrency` tasks at a time. Tasks beyond the limit wait until a slot opens.

`size` counts tasks **waiting** (not including those executing).
`active` is the **number** of tasks currently executing (0 when idle).

```ts
import { createConcurrentTaskQueue } from "@solid-primitives/queue";

const { enqueue, active, size } = createConcurrentTaskQueue<Response>(3);

// Up to 3 fetches run at once; the rest wait
urls.forEach(url => enqueue(() => fetch(url)));

// In JSX
<Show when={active() > 0}>
<p>Fetching… ({active()} active, {size()} waiting)</p>
</Show>
```

### Type

```ts
type ReactiveConcurrentTaskQueue<T> = {
/** Number of tasks waiting to start (excludes tasks currently executing). */
readonly size: Accessor<number>;
/** Number of tasks currently executing (0 when idle). */
readonly active: Accessor<number>;
enqueue: (task: Task<T>) => Promise<T>;
clear: () => void;
};

function createConcurrentTaskQueue<T>(concurrency: number): ReactiveConcurrentTaskQueue<T>;
```

### Notes

- `active` is a **count** (`Accessor<number>`), unlike `createTaskQueue` where it is a boolean.
- `clear()` rejects all **waiting** tasks; tasks currently executing run to completion.
- For heterogeneous task types use `createConcurrentTaskQueue<unknown>()`.

## Changelog

See [CHANGELOG.md](./CHANGELOG.md)
53 changes: 53 additions & 0 deletions packages/queue/dev/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { type Component, createSignal } from "solid-js";
import { For } from "@solidjs/web";
import { createQueue } from "../src/index.js";

const App: Component = () => {
const [input, setInput] = createSignal("");
const { queue, first, size, isEmpty, add, remove, clear } = createQueue<string>();

const enqueue = () => {
const val = input().trim();
if (val) {
add(val);
setInput("");
}
};

return (
<div class="box-border flex min-h-screen w-full flex-col items-center justify-center space-y-4 bg-gray-800 p-24 text-white">
<div class="wrapper-v">
<h4>Queue Primitive</h4>
<p class="caption">FIFO queue — items added to back, removed from front</p>
<div class="flex gap-2">
<input
class="input"
placeholder="Add item..."
value={input()}
onInput={e => setInput(e.currentTarget.value)}
onKeyDown={e => e.key === "Enter" && enqueue()}
/>
<button class="btn" onClick={enqueue}>
Enqueue
</button>
</div>
<div class="flex gap-2">
<button class="btn" onClick={remove} disabled={isEmpty()}>
Dequeue
</button>
<button class="btn" onClick={clear} disabled={isEmpty()}>
Clear
</button>
</div>
<p>
Size: <strong>{size()}</strong> | Next up: <strong>{first() ?? "—"}</strong>
</p>
<ul>
<For each={queue()}>{item => <li>{item}</li>}</For>
</ul>
</div>
</div>
);
};

export default App;
Loading