-
Notifications
You must be signed in to change notification settings - Fork 152
new: queue package for Solid 2.0 #918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
davedbase
wants to merge
7
commits into
solidjs-community:next
Choose a base branch
from
davedbase:v2/queue
base: next
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
e4c9991
Initial concept
davedbase ba0cf24
Added priority and task queue
davedbase c2a2a29
Added range error and better docs
davedbase 8d6686e
Updated changelog
davedbase 89406a1
Merge branch 'next' into v2/queue
davedbase 39ca7fa
Merge branch 'next' into v2/queue
davedbase 441d4ea
Added stories
davedbase File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, Q>(q, comparator)`** — queue modifier that turns any existing queue into a priority queue by overriding its `add` method to maintain comparator-sorted order. Returns the same queue object with `add` patched in place. | ||
| - **`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>`). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 `queue`, `first`, `last`, `size`, `isEmpty`, `add`, `remove`, `clear` | ||
| - `makePriorityQueue<T>(q, comparator)` — queue modifier; patches an existing queue's `add` method to maintain comparator-sorted order; returns the same queue | ||
| - `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 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>`) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,280 @@ | ||
| <p> | ||
| <img width="100%" src="https://assets.solidjs.com/banner?type=Primitives&background=tiles&project=queue" alt="Solid Primitives queue"> | ||
| </p> | ||
|
|
||
| # @solid-primitives/queue | ||
|
|
||
| [](https://primitives.solidjs.community/package/queue) | ||
| [](https://bundlephobia.com/package/@solid-primitives/queue) | ||
| [](https://www.npmjs.com/package/@solid-primitives/queue) | ||
| [](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; | ||
| push: (comparator: (a: T, b: T) => number, ...items: T[]) => void; | ||
| remove: () => T | undefined; | ||
| clear: () => void; | ||
| }; | ||
|
|
||
| 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; | ||
| push: (comparator: (a: T, b: T) => number, ...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` | ||
|
|
||
| Modifies an existing queue in place so that every `add` call maintains comparator-sorted order. Returns the same queue object with its `add` method patched; `remove()` always returns the highest-priority item (smallest by the comparator). | ||
|
|
||
| ```ts | ||
| import { makeQueue, makePriorityQueue } from "@solid-primitives/queue"; | ||
|
|
||
| const cmp = (a: number, b: number) => a - b; | ||
| const q = makePriorityQueue(makeQueue([3, 1, 2].sort(cmp)), cmp); | ||
|
|
||
|
davedbase marked this conversation as resolved.
|
||
| q.first; // 1 | ||
| q.last; // 3 | ||
| q.remove(); // 1 | ||
| q.first; // 2 | ||
|
|
||
| q.add(0); | ||
| q.first; // 0 | ||
| ``` | ||
|
|
||
| ### Type | ||
|
|
||
| ```ts | ||
| function makePriorityQueue<T, Q extends Queue<T>>( | ||
| q: Q, | ||
| comparator: (a: T, b: T) => number, | ||
| ): Q; | ||
| ``` | ||
|
|
||
| ## `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) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.