vue-reactive-queue 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +276 -0
- package/dist/index.d.mts +128 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +290 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# vue-reactive-queue
|
|
2
|
+
|
|
3
|
+
Reactive async task queue for Vue 3 with concurrency control.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- π **Concurrency Control** - Limit parallel task execution
|
|
8
|
+
- π **Reactive State** - Track task status in real-time with Vue reactivity
|
|
9
|
+
- βΈοΈ **Pause/Resume** - Control queue execution flow
|
|
10
|
+
- π **Retry** - Retry failed tasks with one call
|
|
11
|
+
- π **Task History** - Keep track of completed tasks
|
|
12
|
+
- π― **Manual Execution** - Start specific tasks on demand
|
|
13
|
+
- πͺ **TypeScript** - Full type support with generics
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install vue-reactive-queue
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Basic
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { useQueue } from 'vue-reactive-queue'
|
|
27
|
+
|
|
28
|
+
const queue = useQueue()
|
|
29
|
+
|
|
30
|
+
// Add tasks to the queue
|
|
31
|
+
queue.add(async () => {
|
|
32
|
+
await processFile('document.pdf')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
queue.add(async () => {
|
|
36
|
+
await processFile('image.png')
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Concurrency Control
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const queue = useQueue({ concurrency: 3 })
|
|
44
|
+
|
|
45
|
+
// Only 3 tasks will run in parallel
|
|
46
|
+
queue.add(() => fetchUser(1))
|
|
47
|
+
queue.add(() => fetchUser(2))
|
|
48
|
+
queue.add(() => fetchUser(3))
|
|
49
|
+
queue.add(() => fetchUser(4)) // waits for a slot
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Reactive State
|
|
53
|
+
|
|
54
|
+
```vue
|
|
55
|
+
<script setup>
|
|
56
|
+
import { useQueue } from 'vue-reactive-queue'
|
|
57
|
+
|
|
58
|
+
const queue = useQueue({ concurrency: 2 })
|
|
59
|
+
|
|
60
|
+
function addTask() {
|
|
61
|
+
queue.add(async () => {
|
|
62
|
+
await new Promise(r => setTimeout(r, 1000))
|
|
63
|
+
return 'done'
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<template>
|
|
69
|
+
<div>
|
|
70
|
+
<p>Pending: {{ queue.stats.value.pending }}</p>
|
|
71
|
+
<p>Running: {{ queue.stats.value.running }}</p>
|
|
72
|
+
<p>Completed: {{ queue.stats.value.completed }}</p>
|
|
73
|
+
|
|
74
|
+
<button @click="addTask">Add Task</button>
|
|
75
|
+
|
|
76
|
+
<div v-for="task in queue.tasks.value" :key="task.id">
|
|
77
|
+
<span>Task #{{ task.id }}: {{ task.status }}</span>
|
|
78
|
+
<button v-if="task.status === 'rejected'" @click="queue.retry(task.id)">
|
|
79
|
+
Retry
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</template>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Pause and Resume
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const queue = useQueue()
|
|
90
|
+
|
|
91
|
+
queue.add(() => task1())
|
|
92
|
+
queue.add(() => task2())
|
|
93
|
+
|
|
94
|
+
queue.pause() // Stop executing new tasks
|
|
95
|
+
queue.resume() // Continue execution
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Start Paused
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const queue = useQueue({ immediate: false })
|
|
102
|
+
|
|
103
|
+
queue.add(() => task1())
|
|
104
|
+
queue.add(() => task2())
|
|
105
|
+
|
|
106
|
+
// Tasks are pending, not running
|
|
107
|
+
console.log(queue.stats.value.isPaused) // true
|
|
108
|
+
|
|
109
|
+
// Start when ready
|
|
110
|
+
queue.resume()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Manual Task Execution
|
|
114
|
+
|
|
115
|
+
Start a specific task immediately, ignoring the pause state:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const queue = useQueue({ immediate: false })
|
|
119
|
+
|
|
120
|
+
const { id: idA } = queue.add(() => taskA())
|
|
121
|
+
const { id: idB } = queue.add(() => taskB())
|
|
122
|
+
const { id: idC } = queue.add(() => taskC())
|
|
123
|
+
|
|
124
|
+
// Only execute taskB, leave others pending
|
|
125
|
+
await queue.start(idB)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Retry Failed Tasks
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const queue = useQueue()
|
|
132
|
+
|
|
133
|
+
const { id } = queue.add(async () => {
|
|
134
|
+
await uploadFile(file) // might fail due to network issues
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Later, if the task failed
|
|
138
|
+
const result = queue.retry(id)
|
|
139
|
+
if (result.success) {
|
|
140
|
+
await result.promise
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Task Metadata
|
|
145
|
+
|
|
146
|
+
Attach custom data to tasks for UI rendering:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
interface TaskMeta {
|
|
150
|
+
filename: string
|
|
151
|
+
size: number
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const queue = useQueue<TaskMeta>()
|
|
155
|
+
|
|
156
|
+
queue.add(
|
|
157
|
+
() => uploadFile(file),
|
|
158
|
+
{ filename: file.name, size: file.size }
|
|
159
|
+
)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```vue
|
|
163
|
+
<template>
|
|
164
|
+
<div v-for="task in queue.tasks.value" :key="task.id">
|
|
165
|
+
<span>{{ task.meta?.filename }}</span>
|
|
166
|
+
<span>{{ task.status }}</span>
|
|
167
|
+
</div>
|
|
168
|
+
</template>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Callbacks
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
const queue = useQueue({
|
|
175
|
+
onSuccess(task) {
|
|
176
|
+
console.log('Task completed:', task.result)
|
|
177
|
+
},
|
|
178
|
+
onError(task, error) {
|
|
179
|
+
console.error('Task failed:', error)
|
|
180
|
+
},
|
|
181
|
+
onFinished() {
|
|
182
|
+
console.log('All tasks done!')
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Limit History
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
// Only keep the last 10 completed tasks
|
|
191
|
+
const queue = useQueue({ maxHistory: 10 })
|
|
192
|
+
|
|
193
|
+
// Keep no history (only pending/running tasks visible)
|
|
194
|
+
const queue = useQueue({ maxHistory: 0 })
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Wait for Idle
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
const queue = useQueue()
|
|
201
|
+
|
|
202
|
+
queue.add(() => task1())
|
|
203
|
+
queue.add(() => task2())
|
|
204
|
+
queue.add(() => task3())
|
|
205
|
+
|
|
206
|
+
await queue.waitForIdle()
|
|
207
|
+
console.log('All tasks completed!')
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Dynamic Concurrency
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { ref } from 'vue'
|
|
214
|
+
|
|
215
|
+
const limit = ref(2)
|
|
216
|
+
const queue = useQueue({ concurrency: limit })
|
|
217
|
+
|
|
218
|
+
// Reactively update concurrency
|
|
219
|
+
limit.value = 5
|
|
220
|
+
|
|
221
|
+
// Or use setConcurrency
|
|
222
|
+
queue.setConcurrency(10)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Type Declarations
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
export interface QueueTask<T = unknown, M = unknown> {
|
|
229
|
+
readonly id: number
|
|
230
|
+
status: 'pending' | 'running' | 'fulfilled' | 'rejected'
|
|
231
|
+
readonly createdAt: number
|
|
232
|
+
startedAt?: number
|
|
233
|
+
finishedAt?: number
|
|
234
|
+
result?: T
|
|
235
|
+
error?: unknown
|
|
236
|
+
readonly meta?: M
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface QueueStats {
|
|
240
|
+
readonly pending: number
|
|
241
|
+
readonly running: number
|
|
242
|
+
readonly completed: number
|
|
243
|
+
readonly failed: number
|
|
244
|
+
readonly total: number
|
|
245
|
+
readonly isIdle: boolean
|
|
246
|
+
readonly isPaused: boolean
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface UseQueueOptions<M = unknown> {
|
|
250
|
+
concurrency?: MaybeRefOrGetter<number> // default: 1
|
|
251
|
+
immediate?: boolean // default: true
|
|
252
|
+
maxHistory?: number // default: undefined (keep all)
|
|
253
|
+
onSuccess?: (task: QueueTask<unknown, M>) => void
|
|
254
|
+
onError?: (task: QueueTask<unknown, M>, error: unknown) => void
|
|
255
|
+
onFinished?: () => void
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface UseQueueReturn<M = unknown> {
|
|
259
|
+
tasks: Readonly<ShallowRef<QueueTask<unknown, M>[]>>
|
|
260
|
+
stats: ComputedRef<QueueStats>
|
|
261
|
+
concurrency: Readonly<ShallowRef<number>>
|
|
262
|
+
add: <T>(fn: () => T | Promise<T>, meta?: M) => { id: number; task: QueueTask<T, M>; promise: Promise<T> }
|
|
263
|
+
start: (id: number) => Promise<unknown> | undefined
|
|
264
|
+
retry: (id: number) => RetryResult
|
|
265
|
+
remove: (id: number) => boolean
|
|
266
|
+
clear: () => void
|
|
267
|
+
pause: () => void
|
|
268
|
+
resume: () => void
|
|
269
|
+
setConcurrency: (value: number) => void
|
|
270
|
+
waitForIdle: () => Promise<void>
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ComputedRef, MaybeRefOrGetter, ShallowRef } from "vue";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
type TaskStatus = "pending" | "running" | "fulfilled" | "rejected";
|
|
5
|
+
type TaskFunction<T = unknown> = () => T | Promise<T>;
|
|
6
|
+
interface QueueTask<T = unknown, M = unknown> {
|
|
7
|
+
readonly id: number;
|
|
8
|
+
status: TaskStatus;
|
|
9
|
+
readonly createdAt: number;
|
|
10
|
+
startedAt?: number;
|
|
11
|
+
finishedAt?: number;
|
|
12
|
+
result?: T;
|
|
13
|
+
error?: unknown;
|
|
14
|
+
readonly run: TaskFunction<T>;
|
|
15
|
+
readonly meta?: M;
|
|
16
|
+
}
|
|
17
|
+
interface QueueStats {
|
|
18
|
+
readonly pending: number;
|
|
19
|
+
readonly running: number;
|
|
20
|
+
readonly completed: number;
|
|
21
|
+
readonly failed: number;
|
|
22
|
+
readonly total: number;
|
|
23
|
+
readonly isIdle: boolean;
|
|
24
|
+
readonly isPaused: boolean;
|
|
25
|
+
}
|
|
26
|
+
interface UseQueueOptions<M = unknown> {
|
|
27
|
+
/**
|
|
28
|
+
* Maximum number of concurrent tasks
|
|
29
|
+
*
|
|
30
|
+
* @default 1
|
|
31
|
+
*/
|
|
32
|
+
concurrency?: MaybeRefOrGetter<number>;
|
|
33
|
+
/**
|
|
34
|
+
* Start the queue immediately
|
|
35
|
+
*
|
|
36
|
+
* @default true
|
|
37
|
+
*/
|
|
38
|
+
immediate?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Maximum number of completed tasks to keep in history
|
|
41
|
+
* Set to 0 to keep no history, undefined to keep all
|
|
42
|
+
*
|
|
43
|
+
* @default undefined
|
|
44
|
+
*/
|
|
45
|
+
maxHistory?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Callback when a task completes successfully
|
|
48
|
+
*/
|
|
49
|
+
onSuccess?: (task: QueueTask<unknown, M>) => void;
|
|
50
|
+
/**
|
|
51
|
+
* Callback when a task fails
|
|
52
|
+
*/
|
|
53
|
+
onError?: (task: QueueTask<unknown, M>, error: unknown) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Callback when all tasks are finished and queue becomes idle
|
|
56
|
+
*/
|
|
57
|
+
onFinished?: () => void;
|
|
58
|
+
}
|
|
59
|
+
type RetryResult = {
|
|
60
|
+
success: true;
|
|
61
|
+
promise: Promise<unknown>;
|
|
62
|
+
} | {
|
|
63
|
+
success: false;
|
|
64
|
+
reason: "not-found" | "not-rejected" | "already-queued";
|
|
65
|
+
};
|
|
66
|
+
interface UseQueueReturn<M = unknown> {
|
|
67
|
+
/**
|
|
68
|
+
* All tasks in the queue (including history)
|
|
69
|
+
*/
|
|
70
|
+
tasks: Readonly<ShallowRef<QueueTask<unknown, M>[]>>;
|
|
71
|
+
/**
|
|
72
|
+
* Queue statistics
|
|
73
|
+
*/
|
|
74
|
+
stats: ComputedRef<QueueStats>;
|
|
75
|
+
/**
|
|
76
|
+
* Current concurrency limit
|
|
77
|
+
*/
|
|
78
|
+
concurrency: Readonly<ShallowRef<number>>;
|
|
79
|
+
/**
|
|
80
|
+
* Add a task to the queue
|
|
81
|
+
*/
|
|
82
|
+
add: <T = unknown>(fn: TaskFunction<T>, meta?: M) => {
|
|
83
|
+
id: number;
|
|
84
|
+
task: QueueTask<T, M>;
|
|
85
|
+
promise: Promise<T>;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Start a specific pending task immediately (ignores pause state)
|
|
89
|
+
*/
|
|
90
|
+
start: (id: number) => Promise<unknown> | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Retry a failed task
|
|
93
|
+
*/
|
|
94
|
+
retry: (id: number) => RetryResult;
|
|
95
|
+
/**
|
|
96
|
+
* Remove a pending task from the queue
|
|
97
|
+
*/
|
|
98
|
+
remove: (id: number) => boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Clear all pending tasks
|
|
101
|
+
*/
|
|
102
|
+
clear: () => void;
|
|
103
|
+
/**
|
|
104
|
+
* Pause the queue (running tasks will continue)
|
|
105
|
+
*/
|
|
106
|
+
pause: () => void;
|
|
107
|
+
/**
|
|
108
|
+
* Resume the queue
|
|
109
|
+
*/
|
|
110
|
+
resume: () => void;
|
|
111
|
+
/**
|
|
112
|
+
* Set the concurrency limit
|
|
113
|
+
*/
|
|
114
|
+
setConcurrency: (value: number) => void;
|
|
115
|
+
/**
|
|
116
|
+
* Wait for the queue to become idle
|
|
117
|
+
*/
|
|
118
|
+
waitForIdle: () => Promise<void>;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Reactive async task queue with concurrency control.
|
|
122
|
+
*
|
|
123
|
+
* @see https://github.com/ruibaby/vue-reactive-queue
|
|
124
|
+
*/
|
|
125
|
+
declare function useQueue<M = unknown>(options?: UseQueueOptions<M>): UseQueueReturn<M>;
|
|
126
|
+
//#endregion
|
|
127
|
+
export { type MaybeRefOrGetter, QueueStats, QueueTask, RetryResult, TaskFunction, TaskStatus, UseQueueOptions, UseQueueReturn, useQueue };
|
|
128
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;KAcY,UAAA;AAAA,KAEA,YAAA,sBAAkC,CAAA,GAAI,OAAA,CAAQ,CAAA;AAAA,UAEzC,SAAA;EAAA,SACN,EAAA;EACT,MAAA,EAAQ,UAAA;EAAA,SACC,SAAA;EACT,SAAA;EACA,UAAA;EACA,MAAA,GAAS,CAAA;EACT,KAAA;EAAA,SACS,GAAA,EAAK,YAAA,CAAa,CAAA;EAAA,SAClB,IAAA,GAAO,CAAA;AAAA;AAAA,UAGD,UAAA;EAAA,SACN,OAAA;EAAA,SACA,OAAA;EAAA,SACA,SAAA;EAAA,SACA,MAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA;EAAA,SACA,QAAA;AAAA;AAAA,UAGM,eAAA;EAtBS;;;;;EA4BxB,WAAA,GAAc,gBAAA;EAnBG;;;;;EA0BjB,SAAA;EAjCQ;;;;;;EAyCR,UAAA;EAnCS;;;EAwCT,SAAA,IAAa,IAAA,EAAM,SAAA,UAAmB,CAAA;EAvCtB;;;EA4ChB,OAAA,IAAW,IAAA,EAAM,SAAA,UAAmB,CAAA,GAAI,KAAA;EAzCf;;;EA8CzB,UAAA;AAAA;AAAA,KAGU,WAAA;EACN,OAAA;EAAe,OAAA,EAAS,OAAA;AAAA;EACxB,OAAA;EAAgB,MAAA;AAAA;AAAA,UAEL,cAAA;EA3Ce;;;EA+C9B,KAAA,EAAO,QAAA,CAAS,UAAA,CAAW,SAAA,UAAmB,CAAA;EArB3B;;;EA0BnB,KAAA,EAAO,WAAA,CAAY,UAAA;EArBO;;;EA0B1B,WAAA,EAAa,QAAA,CAAS,UAAA;EA5CtB;;;EAiDA,GAAA,gBACE,EAAA,EAAI,YAAA,CAAa,CAAA,GACjB,IAAA,GAAO,CAAA;IAEP,EAAA;IACA,IAAA,EAAM,SAAA,CAAU,CAAA,EAAG,CAAA;IACnB,OAAA,EAAS,OAAA,CAAQ,CAAA;EAAA;EArCiB;;;EA2CpC,KAAA,GAAQ,EAAA,aAAe,OAAA;EAtCb;;AAGZ;EAwCE,KAAA,GAAQ,EAAA,aAAe,WAAA;;;;EAKvB,MAAA,GAAS,EAAA;EA5CmB;;;EAiD5B,KAAA;EAhD0B;AAE5B;;EAmDE,KAAA;EA/C8C;;;EAoD9C,MAAA;EA/CmB;;;EAoDnB,cAAA,GAAiB,KAAA;EAzCE;;;EA8CnB,WAAA,QAAmB,OAAA;AAAA;;;;;;iBAiCL,QAAA,aAAA,CACd,OAAA,GAAS,eAAA,CAAgB,CAAA,IACxB,cAAA,CAAe,CAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { computed, shallowReadonly, shallowRef, toValue, triggerRef, watch } from "vue";
|
|
2
|
+
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
function clampConcurrency(value) {
|
|
5
|
+
if (!Number.isFinite(value)) return 1;
|
|
6
|
+
return Math.max(1, Math.floor(value));
|
|
7
|
+
}
|
|
8
|
+
function createDeferred() {
|
|
9
|
+
let resolve;
|
|
10
|
+
let reject;
|
|
11
|
+
return {
|
|
12
|
+
promise: new Promise((res, rej) => {
|
|
13
|
+
resolve = res;
|
|
14
|
+
reject = rej;
|
|
15
|
+
}),
|
|
16
|
+
resolve,
|
|
17
|
+
reject
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Reactive async task queue with concurrency control.
|
|
22
|
+
*
|
|
23
|
+
* @see https://github.com/ruibaby/vue-reactive-queue
|
|
24
|
+
*/
|
|
25
|
+
function useQueue(options = {}) {
|
|
26
|
+
const { concurrency: initialConcurrency = 1, immediate = true, maxHistory, onSuccess, onError, onFinished } = options;
|
|
27
|
+
const concurrency = shallowRef(clampConcurrency(toValue(initialConcurrency)));
|
|
28
|
+
const isPaused = shallowRef(!immediate);
|
|
29
|
+
const tasks = shallowRef([]);
|
|
30
|
+
const pendingQueue = shallowRef([]);
|
|
31
|
+
const deferredMap = /* @__PURE__ */ new Map();
|
|
32
|
+
let taskId = 0;
|
|
33
|
+
const runningCount = shallowRef(0);
|
|
34
|
+
const pendingCount = shallowRef(0);
|
|
35
|
+
const completedCount = shallowRef(0);
|
|
36
|
+
const failedCount = shallowRef(0);
|
|
37
|
+
const stats = computed(() => ({
|
|
38
|
+
pending: pendingCount.value,
|
|
39
|
+
running: runningCount.value,
|
|
40
|
+
completed: completedCount.value,
|
|
41
|
+
failed: failedCount.value,
|
|
42
|
+
total: tasks.value.length,
|
|
43
|
+
isPaused: isPaused.value,
|
|
44
|
+
isIdle: pendingCount.value === 0 && runningCount.value === 0
|
|
45
|
+
}));
|
|
46
|
+
function touchTasks() {
|
|
47
|
+
triggerRef(tasks);
|
|
48
|
+
}
|
|
49
|
+
function touchQueue() {
|
|
50
|
+
triggerRef(pendingQueue);
|
|
51
|
+
}
|
|
52
|
+
function trimHistory() {
|
|
53
|
+
if (maxHistory === void 0 || maxHistory < 0) return;
|
|
54
|
+
const completedTasks = tasks.value.filter((t) => t.status === "fulfilled" || t.status === "rejected");
|
|
55
|
+
if (completedTasks.length <= maxHistory) return;
|
|
56
|
+
const toRemove = completedTasks.length - maxHistory;
|
|
57
|
+
const idsToRemove = new Set(completedTasks.slice(0, toRemove).map((t) => t.id));
|
|
58
|
+
tasks.value = tasks.value.filter((t) => !idsToRemove.has(t.id));
|
|
59
|
+
touchTasks();
|
|
60
|
+
}
|
|
61
|
+
function checkIdle() {
|
|
62
|
+
if (stats.value.isIdle) onFinished?.();
|
|
63
|
+
}
|
|
64
|
+
function executeTask(task, deferred) {
|
|
65
|
+
runningCount.value += 1;
|
|
66
|
+
task.status = "running";
|
|
67
|
+
task.startedAt = Date.now();
|
|
68
|
+
touchTasks();
|
|
69
|
+
let runPromise;
|
|
70
|
+
try {
|
|
71
|
+
runPromise = Promise.resolve(task.run());
|
|
72
|
+
} catch (error) {
|
|
73
|
+
runPromise = Promise.reject(error);
|
|
74
|
+
}
|
|
75
|
+
runPromise.then((result) => {
|
|
76
|
+
task.status = "fulfilled";
|
|
77
|
+
task.result = result;
|
|
78
|
+
task.finishedAt = Date.now();
|
|
79
|
+
completedCount.value += 1;
|
|
80
|
+
runningCount.value -= 1;
|
|
81
|
+
touchTasks();
|
|
82
|
+
trimHistory();
|
|
83
|
+
deferred?.resolve(result);
|
|
84
|
+
onSuccess?.(task);
|
|
85
|
+
checkIdle();
|
|
86
|
+
}).catch((error) => {
|
|
87
|
+
task.status = "rejected";
|
|
88
|
+
task.error = error;
|
|
89
|
+
task.finishedAt = Date.now();
|
|
90
|
+
failedCount.value += 1;
|
|
91
|
+
runningCount.value -= 1;
|
|
92
|
+
touchTasks();
|
|
93
|
+
trimHistory();
|
|
94
|
+
deferred?.reject(error);
|
|
95
|
+
onError?.(task, error);
|
|
96
|
+
checkIdle();
|
|
97
|
+
}).finally(() => {
|
|
98
|
+
deferredMap.delete(task.id);
|
|
99
|
+
schedule();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function schedule() {
|
|
103
|
+
if (isPaused.value) return;
|
|
104
|
+
while (runningCount.value < concurrency.value && pendingQueue.value.length > 0) {
|
|
105
|
+
const nextTask = pendingQueue.value.shift();
|
|
106
|
+
touchQueue();
|
|
107
|
+
if (!nextTask) break;
|
|
108
|
+
if (nextTask.status === "pending") pendingCount.value -= 1;
|
|
109
|
+
executeTask(nextTask, deferredMap.get(nextTask.id));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function add(fn, meta) {
|
|
113
|
+
const id = ++taskId;
|
|
114
|
+
const task = {
|
|
115
|
+
id,
|
|
116
|
+
status: "pending",
|
|
117
|
+
createdAt: Date.now(),
|
|
118
|
+
run: fn,
|
|
119
|
+
meta
|
|
120
|
+
};
|
|
121
|
+
tasks.value.push(task);
|
|
122
|
+
triggerRef(tasks);
|
|
123
|
+
pendingQueue.value.push(task);
|
|
124
|
+
triggerRef(pendingQueue);
|
|
125
|
+
pendingCount.value += 1;
|
|
126
|
+
const deferred = createDeferred();
|
|
127
|
+
deferredMap.set(id, deferred);
|
|
128
|
+
schedule();
|
|
129
|
+
return {
|
|
130
|
+
id,
|
|
131
|
+
task,
|
|
132
|
+
promise: deferred.promise
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function start(id) {
|
|
136
|
+
const task = tasks.value.find((t) => t.id === id);
|
|
137
|
+
if (!task || task.status !== "pending") return void 0;
|
|
138
|
+
const queueIndex = pendingQueue.value.findIndex((t) => t.id === id);
|
|
139
|
+
if (queueIndex !== -1) {
|
|
140
|
+
pendingQueue.value.splice(queueIndex, 1);
|
|
141
|
+
touchQueue();
|
|
142
|
+
}
|
|
143
|
+
const existingDeferred = deferredMap.get(id);
|
|
144
|
+
deferredMap.delete(id);
|
|
145
|
+
pendingCount.value -= 1;
|
|
146
|
+
const deferred = createDeferred();
|
|
147
|
+
let runPromise;
|
|
148
|
+
try {
|
|
149
|
+
runPromise = Promise.resolve(task.run());
|
|
150
|
+
} catch (error) {
|
|
151
|
+
runPromise = Promise.reject(error);
|
|
152
|
+
}
|
|
153
|
+
runningCount.value += 1;
|
|
154
|
+
task.status = "running";
|
|
155
|
+
task.startedAt = Date.now();
|
|
156
|
+
touchTasks();
|
|
157
|
+
runPromise.then((result) => {
|
|
158
|
+
task.status = "fulfilled";
|
|
159
|
+
task.result = result;
|
|
160
|
+
task.finishedAt = Date.now();
|
|
161
|
+
completedCount.value += 1;
|
|
162
|
+
runningCount.value -= 1;
|
|
163
|
+
touchTasks();
|
|
164
|
+
trimHistory();
|
|
165
|
+
deferred.resolve(result);
|
|
166
|
+
existingDeferred?.resolve(result);
|
|
167
|
+
onSuccess?.(task);
|
|
168
|
+
checkIdle();
|
|
169
|
+
}).catch((error) => {
|
|
170
|
+
task.status = "rejected";
|
|
171
|
+
task.error = error;
|
|
172
|
+
task.finishedAt = Date.now();
|
|
173
|
+
failedCount.value += 1;
|
|
174
|
+
runningCount.value -= 1;
|
|
175
|
+
touchTasks();
|
|
176
|
+
trimHistory();
|
|
177
|
+
deferred.reject(error);
|
|
178
|
+
existingDeferred?.reject(error);
|
|
179
|
+
onError?.(task, error);
|
|
180
|
+
checkIdle();
|
|
181
|
+
});
|
|
182
|
+
return deferred.promise;
|
|
183
|
+
}
|
|
184
|
+
function retry(id) {
|
|
185
|
+
const task = tasks.value.find((t) => t.id === id);
|
|
186
|
+
if (!task) return {
|
|
187
|
+
success: false,
|
|
188
|
+
reason: "not-found"
|
|
189
|
+
};
|
|
190
|
+
if (task.status !== "rejected") return {
|
|
191
|
+
success: false,
|
|
192
|
+
reason: "not-rejected"
|
|
193
|
+
};
|
|
194
|
+
if (pendingQueue.value.some((t) => t.id === id)) return {
|
|
195
|
+
success: false,
|
|
196
|
+
reason: "already-queued"
|
|
197
|
+
};
|
|
198
|
+
failedCount.value -= 1;
|
|
199
|
+
pendingCount.value += 1;
|
|
200
|
+
task.status = "pending";
|
|
201
|
+
task.startedAt = void 0;
|
|
202
|
+
task.finishedAt = void 0;
|
|
203
|
+
task.result = void 0;
|
|
204
|
+
task.error = void 0;
|
|
205
|
+
touchTasks();
|
|
206
|
+
const deferred = createDeferred();
|
|
207
|
+
deferredMap.set(id, deferred);
|
|
208
|
+
pendingQueue.value.push(task);
|
|
209
|
+
triggerRef(pendingQueue);
|
|
210
|
+
schedule();
|
|
211
|
+
return {
|
|
212
|
+
success: true,
|
|
213
|
+
promise: deferred.promise
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function remove(id) {
|
|
217
|
+
const queueIndex = pendingQueue.value.findIndex((t) => t.id === id);
|
|
218
|
+
if (queueIndex === -1) return false;
|
|
219
|
+
pendingQueue.value.splice(queueIndex, 1);
|
|
220
|
+
touchQueue();
|
|
221
|
+
const taskIndex = tasks.value.findIndex((t) => t.id === id);
|
|
222
|
+
if (taskIndex !== -1) {
|
|
223
|
+
tasks.value.splice(taskIndex, 1);
|
|
224
|
+
touchTasks();
|
|
225
|
+
}
|
|
226
|
+
pendingCount.value -= 1;
|
|
227
|
+
deferredMap.get(id)?.reject(/* @__PURE__ */ new Error("Task removed"));
|
|
228
|
+
deferredMap.delete(id);
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
function clear() {
|
|
232
|
+
if (pendingQueue.value.length === 0) return;
|
|
233
|
+
const pendingIds = new Set(pendingQueue.value.map((t) => t.id));
|
|
234
|
+
const clearedCount = pendingIds.size;
|
|
235
|
+
pendingQueue.value = [];
|
|
236
|
+
triggerRef(pendingQueue);
|
|
237
|
+
tasks.value = tasks.value.filter((t) => !pendingIds.has(t.id));
|
|
238
|
+
triggerRef(tasks);
|
|
239
|
+
pendingCount.value -= clearedCount;
|
|
240
|
+
for (const id of pendingIds) {
|
|
241
|
+
deferredMap.get(id)?.reject(/* @__PURE__ */ new Error("Queue cleared"));
|
|
242
|
+
deferredMap.delete(id);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function pause() {
|
|
246
|
+
isPaused.value = true;
|
|
247
|
+
}
|
|
248
|
+
function resume() {
|
|
249
|
+
if (!isPaused.value) return;
|
|
250
|
+
isPaused.value = false;
|
|
251
|
+
schedule();
|
|
252
|
+
}
|
|
253
|
+
function setConcurrency(value) {
|
|
254
|
+
concurrency.value = clampConcurrency(value);
|
|
255
|
+
schedule();
|
|
256
|
+
}
|
|
257
|
+
function waitForIdle() {
|
|
258
|
+
if (stats.value.isIdle) return Promise.resolve();
|
|
259
|
+
return new Promise((resolve) => {
|
|
260
|
+
const stop = watch(() => stats.value.isIdle, (idle) => {
|
|
261
|
+
if (idle) {
|
|
262
|
+
stop();
|
|
263
|
+
resolve();
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
if (typeof initialConcurrency === "function" || typeof initialConcurrency === "object") watch(() => toValue(initialConcurrency), (value) => {
|
|
269
|
+
concurrency.value = clampConcurrency(value);
|
|
270
|
+
schedule();
|
|
271
|
+
});
|
|
272
|
+
return {
|
|
273
|
+
tasks: shallowReadonly(tasks),
|
|
274
|
+
stats,
|
|
275
|
+
concurrency: shallowReadonly(concurrency),
|
|
276
|
+
add,
|
|
277
|
+
start,
|
|
278
|
+
retry,
|
|
279
|
+
remove,
|
|
280
|
+
clear,
|
|
281
|
+
pause,
|
|
282
|
+
resume,
|
|
283
|
+
setConcurrency,
|
|
284
|
+
waitForIdle
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
//#endregion
|
|
289
|
+
export { useQueue };
|
|
290
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { ComputedRef, MaybeRefOrGetter, ShallowRef } from \"vue\";\nimport {\n computed,\n shallowReadonly,\n shallowRef,\n toValue,\n triggerRef,\n watch,\n} from \"vue\";\n\n// =====================\n// Types\n// =====================\n\nexport type TaskStatus = \"pending\" | \"running\" | \"fulfilled\" | \"rejected\";\n\nexport type TaskFunction<T = unknown> = () => T | Promise<T>;\n\nexport interface QueueTask<T = unknown, M = unknown> {\n readonly id: number;\n status: TaskStatus;\n readonly createdAt: number;\n startedAt?: number;\n finishedAt?: number;\n result?: T;\n error?: unknown;\n readonly run: TaskFunction<T>;\n readonly meta?: M;\n}\n\nexport interface QueueStats {\n readonly pending: number;\n readonly running: number;\n readonly completed: number;\n readonly failed: number;\n readonly total: number;\n readonly isIdle: boolean;\n readonly isPaused: boolean;\n}\n\nexport interface UseQueueOptions<M = unknown> {\n /**\n * Maximum number of concurrent tasks\n *\n * @default 1\n */\n concurrency?: MaybeRefOrGetter<number>;\n\n /**\n * Start the queue immediately\n *\n * @default true\n */\n immediate?: boolean;\n\n /**\n * Maximum number of completed tasks to keep in history\n * Set to 0 to keep no history, undefined to keep all\n *\n * @default undefined\n */\n maxHistory?: number;\n\n /**\n * Callback when a task completes successfully\n */\n onSuccess?: (task: QueueTask<unknown, M>) => void;\n\n /**\n * Callback when a task fails\n */\n onError?: (task: QueueTask<unknown, M>, error: unknown) => void;\n\n /**\n * Callback when all tasks are finished and queue becomes idle\n */\n onFinished?: () => void;\n}\n\nexport type RetryResult =\n | { success: true; promise: Promise<unknown> }\n | { success: false; reason: \"not-found\" | \"not-rejected\" | \"already-queued\" };\n\nexport interface UseQueueReturn<M = unknown> {\n /**\n * All tasks in the queue (including history)\n */\n tasks: Readonly<ShallowRef<QueueTask<unknown, M>[]>>;\n\n /**\n * Queue statistics\n */\n stats: ComputedRef<QueueStats>;\n\n /**\n * Current concurrency limit\n */\n concurrency: Readonly<ShallowRef<number>>;\n\n /**\n * Add a task to the queue\n */\n add: <T = unknown>(\n fn: TaskFunction<T>,\n meta?: M,\n ) => {\n id: number;\n task: QueueTask<T, M>;\n promise: Promise<T>;\n };\n\n /**\n * Start a specific pending task immediately (ignores pause state)\n */\n start: (id: number) => Promise<unknown> | undefined;\n\n /**\n * Retry a failed task\n */\n retry: (id: number) => RetryResult;\n\n /**\n * Remove a pending task from the queue\n */\n remove: (id: number) => boolean;\n\n /**\n * Clear all pending tasks\n */\n clear: () => void;\n\n /**\n * Pause the queue (running tasks will continue)\n */\n pause: () => void;\n\n /**\n * Resume the queue\n */\n resume: () => void;\n\n /**\n * Set the concurrency limit\n */\n setConcurrency: (value: number) => void;\n\n /**\n * Wait for the queue to become idle\n */\n waitForIdle: () => Promise<void>;\n}\n\n// =====================\n// Implementation\n// =====================\n\ninterface Deferred<T> {\n resolve: (value: T) => void;\n reject: (reason?: unknown) => void;\n promise: Promise<T>;\n}\n\nfunction clampConcurrency(value: number): number {\n if (!Number.isFinite(value)) return 1;\n return Math.max(1, Math.floor(value));\n}\n\nfunction createDeferred<T>(): Deferred<T> {\n let resolve: (value: T) => void;\n let reject: (reason?: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n return { promise, resolve: resolve!, reject: reject! };\n}\n\n/**\n * Reactive async task queue with concurrency control.\n *\n * @see https://github.com/ruibaby/vue-reactive-queue\n */\nexport function useQueue<M = unknown>(\n options: UseQueueOptions<M> = {},\n): UseQueueReturn<M> {\n const {\n concurrency: initialConcurrency = 1,\n immediate = true,\n maxHistory,\n onSuccess,\n onError,\n onFinished,\n } = options;\n\n const concurrency = shallowRef(clampConcurrency(toValue(initialConcurrency)));\n const isPaused = shallowRef(!immediate);\n const tasks = shallowRef<QueueTask<unknown, M>[]>([]);\n const pendingQueue = shallowRef<QueueTask<unknown, M>[]>([]);\n const deferredMap = new Map<number, Deferred<unknown>>();\n\n let taskId = 0;\n const runningCount = shallowRef(0);\n const pendingCount = shallowRef(0);\n const completedCount = shallowRef(0);\n const failedCount = shallowRef(0);\n\n const stats = computed<QueueStats>(() => ({\n pending: pendingCount.value,\n running: runningCount.value,\n completed: completedCount.value,\n failed: failedCount.value,\n total: tasks.value.length,\n isPaused: isPaused.value,\n isIdle: pendingCount.value === 0 && runningCount.value === 0,\n }));\n\n function touchTasks() {\n triggerRef(tasks);\n }\n\n function touchQueue() {\n triggerRef(pendingQueue);\n }\n\n function trimHistory() {\n if (maxHistory === undefined || maxHistory < 0) return;\n\n const completedTasks = tasks.value.filter(\n (t) => t.status === \"fulfilled\" || t.status === \"rejected\",\n );\n if (completedTasks.length <= maxHistory) return;\n\n const toRemove = completedTasks.length - maxHistory;\n const idsToRemove = new Set(\n completedTasks.slice(0, toRemove).map((t) => t.id),\n );\n tasks.value = tasks.value.filter((t) => !idsToRemove.has(t.id));\n touchTasks();\n }\n\n function checkIdle() {\n if (stats.value.isIdle) onFinished?.();\n }\n\n function executeTask(\n task: QueueTask<unknown, M>,\n deferred?: Deferred<unknown>,\n ) {\n runningCount.value += 1;\n task.status = \"running\";\n task.startedAt = Date.now();\n touchTasks();\n\n let runPromise: Promise<unknown>;\n try {\n runPromise = Promise.resolve(task.run());\n } catch (error) {\n runPromise = Promise.reject(error);\n }\n\n runPromise\n .then((result) => {\n task.status = \"fulfilled\";\n task.result = result;\n task.finishedAt = Date.now();\n completedCount.value += 1;\n runningCount.value -= 1;\n touchTasks();\n trimHistory();\n deferred?.resolve(result);\n onSuccess?.(task);\n checkIdle();\n })\n .catch((error) => {\n task.status = \"rejected\";\n task.error = error;\n task.finishedAt = Date.now();\n failedCount.value += 1;\n runningCount.value -= 1;\n touchTasks();\n trimHistory();\n deferred?.reject(error);\n onError?.(task, error);\n checkIdle();\n })\n .finally(() => {\n deferredMap.delete(task.id);\n schedule();\n });\n }\n\n function schedule() {\n if (isPaused.value) return;\n\n while (\n runningCount.value < concurrency.value &&\n pendingQueue.value.length > 0\n ) {\n const nextTask = pendingQueue.value.shift();\n touchQueue();\n if (!nextTask) break;\n\n if (nextTask.status === \"pending\") pendingCount.value -= 1;\n\n const deferred = deferredMap.get(nextTask.id);\n executeTask(nextTask, deferred);\n }\n }\n\n function add<T>(fn: TaskFunction<T>, meta?: M) {\n const id = ++taskId;\n const task: QueueTask<T, M> = {\n id,\n status: \"pending\",\n createdAt: Date.now(),\n run: fn,\n meta,\n };\n\n tasks.value.push(task);\n triggerRef(tasks);\n pendingQueue.value.push(task);\n triggerRef(pendingQueue);\n pendingCount.value += 1;\n\n const deferred = createDeferred<T>();\n deferredMap.set(id, deferred as Deferred<unknown>);\n\n schedule();\n\n return { id, task, promise: deferred.promise };\n }\n\n function start(id: number): Promise<unknown> | undefined {\n const task = tasks.value.find((t) => t.id === id);\n if (!task || task.status !== \"pending\") return undefined;\n\n const queueIndex = pendingQueue.value.findIndex((t) => t.id === id);\n if (queueIndex !== -1) {\n pendingQueue.value.splice(queueIndex, 1);\n touchQueue();\n }\n\n const existingDeferred = deferredMap.get(id);\n deferredMap.delete(id);\n\n pendingCount.value -= 1;\n\n const deferred = createDeferred<unknown>();\n\n let runPromise: Promise<unknown>;\n try {\n runPromise = Promise.resolve(task.run());\n } catch (error) {\n runPromise = Promise.reject(error);\n }\n\n runningCount.value += 1;\n task.status = \"running\";\n task.startedAt = Date.now();\n touchTasks();\n\n runPromise\n .then((result) => {\n task.status = \"fulfilled\";\n task.result = result;\n task.finishedAt = Date.now();\n completedCount.value += 1;\n runningCount.value -= 1;\n touchTasks();\n trimHistory();\n deferred.resolve(result);\n existingDeferred?.resolve(result);\n onSuccess?.(task);\n checkIdle();\n })\n .catch((error) => {\n task.status = \"rejected\";\n task.error = error;\n task.finishedAt = Date.now();\n failedCount.value += 1;\n runningCount.value -= 1;\n touchTasks();\n trimHistory();\n deferred.reject(error);\n existingDeferred?.reject(error);\n onError?.(task, error);\n checkIdle();\n });\n\n return deferred.promise;\n }\n\n function retry(id: number): RetryResult {\n const task = tasks.value.find((t) => t.id === id);\n if (!task) return { success: false, reason: \"not-found\" };\n\n if (task.status !== \"rejected\")\n return { success: false, reason: \"not-rejected\" };\n\n if (pendingQueue.value.some((t) => t.id === id))\n return { success: false, reason: \"already-queued\" };\n\n failedCount.value -= 1;\n pendingCount.value += 1;\n task.status = \"pending\";\n task.startedAt = undefined;\n task.finishedAt = undefined;\n task.result = undefined;\n task.error = undefined;\n touchTasks();\n\n const deferred = createDeferred<unknown>();\n deferredMap.set(id, deferred);\n pendingQueue.value.push(task);\n triggerRef(pendingQueue);\n schedule();\n\n return { success: true, promise: deferred.promise };\n }\n\n function remove(id: number): boolean {\n const queueIndex = pendingQueue.value.findIndex((t) => t.id === id);\n if (queueIndex === -1) return false;\n\n pendingQueue.value.splice(queueIndex, 1);\n touchQueue();\n\n const taskIndex = tasks.value.findIndex((t) => t.id === id);\n if (taskIndex !== -1) {\n tasks.value.splice(taskIndex, 1);\n touchTasks();\n }\n\n pendingCount.value -= 1;\n deferredMap.get(id)?.reject(new Error(\"Task removed\"));\n deferredMap.delete(id);\n\n return true;\n }\n\n function clear() {\n if (pendingQueue.value.length === 0) return;\n\n const pendingIds = new Set(pendingQueue.value.map((t) => t.id));\n const clearedCount = pendingIds.size;\n\n pendingQueue.value = [];\n triggerRef(pendingQueue);\n\n tasks.value = tasks.value.filter((t) => !pendingIds.has(t.id));\n triggerRef(tasks);\n\n pendingCount.value -= clearedCount;\n\n for (const id of pendingIds) {\n deferredMap.get(id)?.reject(new Error(\"Queue cleared\"));\n deferredMap.delete(id);\n }\n }\n\n function pause() {\n isPaused.value = true;\n }\n\n function resume() {\n if (!isPaused.value) return;\n isPaused.value = false;\n schedule();\n }\n\n function setConcurrency(value: number) {\n concurrency.value = clampConcurrency(value);\n schedule();\n }\n\n function waitForIdle(): Promise<void> {\n if (stats.value.isIdle) return Promise.resolve();\n\n return new Promise((resolve) => {\n const stop = watch(\n () => stats.value.isIdle,\n (idle) => {\n if (idle) {\n stop();\n resolve();\n }\n },\n );\n });\n }\n\n // Watch for dynamic concurrency changes\n if (\n typeof initialConcurrency === \"function\" ||\n typeof initialConcurrency === \"object\"\n ) {\n watch(\n () => toValue(initialConcurrency),\n (value) => {\n concurrency.value = clampConcurrency(value);\n schedule();\n },\n );\n }\n\n return {\n tasks: shallowReadonly(tasks),\n stats,\n concurrency: shallowReadonly(concurrency),\n add,\n start,\n retry,\n remove,\n clear,\n pause,\n resume,\n setConcurrency,\n waitForIdle,\n };\n}\n\nexport type { MaybeRefOrGetter };\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-reactive-queue",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "A Vue 3 composable for managing async task queues with reactive state, concurrency control, pause/resume, and retry support",
|
|
5
|
+
"homepage": "https://github.com/ruibaby/vue-reactive-queue",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/ruibaby/vue-reactive-queue/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/ruibaby/vue-reactive-queue"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./dist/index.mjs",
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"types": "./dist/index.d.mts",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@biomejs/biome": "^2.3.13",
|
|
25
|
+
"@types/node": "^22.19.7",
|
|
26
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
27
|
+
"tsdown": "^0.20.1",
|
|
28
|
+
"typescript": "^5.9.3",
|
|
29
|
+
"vitest": "^4.0.18"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"vue": ">=3.0.0"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsdown",
|
|
36
|
+
"dev": "tsdown --watch",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:coverage": "vitest run --coverage",
|
|
39
|
+
"test:watch": "vitest",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"check": "biome check . --write",
|
|
42
|
+
"prepublish": "pnpm run build"
|
|
43
|
+
}
|
|
44
|
+
}
|