offcourse 1.0.0 → 1.0.1
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/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/inspect.js +1 -1
- package/dist/cli/commands/inspect.js.map +1 -1
- package/dist/cli/commands/sync.d.ts +1 -2
- package/dist/cli/commands/sync.d.ts.map +1 -1
- package/dist/cli/commands/sync.js +13 -14
- package/dist/cli/commands/sync.js.map +1 -1
- package/dist/cli/commands/syncHighLevel.d.ts +1 -2
- package/dist/cli/commands/syncHighLevel.d.ts.map +1 -1
- package/dist/cli/commands/syncHighLevel.js +4 -8
- package/dist/cli/commands/syncHighLevel.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/config/configManager.d.ts.map +1 -1
- package/dist/config/configManager.js +4 -0
- package/dist/config/configManager.js.map +1 -1
- package/dist/downloader/hlsDownloader.d.ts.map +1 -1
- package/dist/downloader/hlsDownloader.js +23 -14
- package/dist/downloader/hlsDownloader.js.map +1 -1
- package/dist/downloader/hlsValidator.d.ts.map +1 -1
- package/dist/downloader/hlsValidator.js +6 -2
- package/dist/downloader/hlsValidator.js.map +1 -1
- package/dist/downloader/index.d.ts +3 -0
- package/dist/downloader/index.d.ts.map +1 -1
- package/dist/downloader/index.js +3 -0
- package/dist/downloader/index.js.map +1 -1
- package/dist/downloader/loomDownloader.d.ts.map +1 -1
- package/dist/downloader/loomDownloader.js +23 -20
- package/dist/downloader/loomDownloader.js.map +1 -1
- package/dist/downloader/queue.d.ts +4 -4
- package/dist/downloader/queue.d.ts.map +1 -1
- package/dist/downloader/queue.js.map +1 -1
- package/dist/downloader/vimeoDownloader.d.ts.map +1 -1
- package/dist/downloader/vimeoDownloader.js +7 -3
- package/dist/downloader/vimeoDownloader.js.map +1 -1
- package/dist/scraper/extractor.d.ts +4 -0
- package/dist/scraper/extractor.d.ts.map +1 -1
- package/dist/scraper/extractor.js +79 -79
- package/dist/scraper/extractor.js.map +1 -1
- package/dist/scraper/highlevel/extractor.d.ts +11 -19
- package/dist/scraper/highlevel/extractor.d.ts.map +1 -1
- package/dist/scraper/highlevel/extractor.js +72 -85
- package/dist/scraper/highlevel/extractor.js.map +1 -1
- package/dist/scraper/highlevel/navigator.d.ts +3 -10
- package/dist/scraper/highlevel/navigator.d.ts.map +1 -1
- package/dist/scraper/highlevel/navigator.js +140 -127
- package/dist/scraper/highlevel/navigator.js.map +1 -1
- package/dist/scraper/highlevel/schemas.d.ts +188 -0
- package/dist/scraper/highlevel/schemas.d.ts.map +1 -0
- package/dist/scraper/highlevel/schemas.js +139 -0
- package/dist/scraper/highlevel/schemas.js.map +1 -0
- package/dist/scraper/navigator.d.ts +14 -11
- package/dist/scraper/navigator.d.ts.map +1 -1
- package/dist/scraper/navigator.js +61 -104
- package/dist/scraper/navigator.js.map +1 -1
- package/dist/scraper/schemas.d.ts +57 -0
- package/dist/scraper/schemas.d.ts.map +1 -0
- package/dist/scraper/schemas.js +135 -0
- package/dist/scraper/schemas.js.map +1 -0
- package/dist/scraper/videoInterceptor.d.ts +4 -0
- package/dist/scraper/videoInterceptor.d.ts.map +1 -1
- package/dist/scraper/videoInterceptor.js +66 -51
- package/dist/scraper/videoInterceptor.js.map +1 -1
- package/dist/shared/auth.d.ts +9 -9
- package/dist/shared/auth.d.ts.map +1 -1
- package/dist/shared/auth.js +24 -38
- package/dist/shared/auth.js.map +1 -1
- package/dist/shared/firebase.d.ts +60 -0
- package/dist/shared/firebase.d.ts.map +1 -0
- package/dist/shared/firebase.js +102 -0
- package/dist/shared/firebase.js.map +1 -0
- package/dist/shared/fs.d.ts.map +1 -1
- package/dist/shared/fs.js +4 -0
- package/dist/shared/fs.js.map +1 -1
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +3 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/slug.d.ts +11 -0
- package/dist/shared/slug.d.ts.map +1 -0
- package/{src/shared/slug.ts → dist/shared/slug.js} +10 -11
- package/dist/shared/slug.js.map +1 -0
- package/dist/shared/url.d.ts +43 -0
- package/dist/shared/url.d.ts.map +1 -0
- package/{src/shared/url.ts → dist/shared/url.js} +12 -15
- package/dist/shared/url.js.map +1 -0
- package/dist/state/database.d.ts +1 -0
- package/dist/state/database.d.ts.map +1 -1
- package/dist/state/database.js +3 -0
- package/dist/state/database.js.map +1 -1
- package/dist/storage/fileSystem.d.ts +17 -17
- package/dist/storage/fileSystem.d.ts.map +1 -1
- package/dist/storage/fileSystem.js +39 -31
- package/dist/storage/fileSystem.js.map +1 -1
- package/package.json +5 -2
- package/.github/workflows/ci.yml +0 -50
- package/.husky/commit-msg +0 -2
- package/.husky/pre-commit +0 -1
- package/.husky/pre-push +0 -3
- package/.prettierrc +0 -8
- package/.release-it.json +0 -23
- package/ARCHITECTURE.md +0 -233
- package/CHANGELOG.md +0 -78
- package/commitlint.config.js +0 -4
- package/dist/ai/openRouter.d.ts +0 -47
- package/dist/ai/openRouter.d.ts.map +0 -1
- package/dist/ai/openRouter.js +0 -116
- package/dist/ai/openRouter.js.map +0 -1
- package/dist/ai/transcriptPolisher.d.ts +0 -24
- package/dist/ai/transcriptPolisher.d.ts.map +0 -1
- package/dist/ai/transcriptPolisher.js +0 -89
- package/dist/ai/transcriptPolisher.js.map +0 -1
- package/dist/cli/commands/enrich.d.ts +0 -14
- package/dist/cli/commands/enrich.d.ts.map +0 -1
- package/dist/cli/commands/enrich.js +0 -271
- package/dist/cli/commands/enrich.js.map +0 -1
- package/dist/cli/commands/syncGhl.d.ts +0 -20
- package/dist/cli/commands/syncGhl.d.ts.map +0 -1
- package/dist/cli/commands/syncGhl.js +0 -483
- package/dist/cli/commands/syncGhl.js.map +0 -1
- package/dist/cli/commands/syncHighLevel.test.d.ts +0 -2
- package/dist/cli/commands/syncHighLevel.test.d.ts.map +0 -1
- package/dist/cli/commands/syncHighLevel.test.js +0 -102
- package/dist/cli/commands/syncHighLevel.test.js.map +0 -1
- package/dist/config/paths.test.d.ts +0 -2
- package/dist/config/paths.test.d.ts.map +0 -1
- package/dist/config/paths.test.js +0 -70
- package/dist/config/paths.test.js.map +0 -1
- package/dist/config/schema.test.d.ts +0 -2
- package/dist/config/schema.test.d.ts.map +0 -1
- package/dist/config/schema.test.js +0 -151
- package/dist/config/schema.test.js.map +0 -1
- package/dist/downloader/hlsDownloader.test.d.ts +0 -2
- package/dist/downloader/hlsDownloader.test.d.ts.map +0 -1
- package/dist/downloader/hlsDownloader.test.js +0 -116
- package/dist/downloader/hlsDownloader.test.js.map +0 -1
- package/dist/downloader/loomDownloader.test.d.ts +0 -2
- package/dist/downloader/loomDownloader.test.d.ts.map +0 -1
- package/dist/downloader/loomDownloader.test.js +0 -36
- package/dist/downloader/loomDownloader.test.js.map +0 -1
- package/dist/downloader/queue.test.d.ts +0 -2
- package/dist/downloader/queue.test.d.ts.map +0 -1
- package/dist/downloader/queue.test.js +0 -158
- package/dist/downloader/queue.test.js.map +0 -1
- package/dist/downloader/videoDownloader.d.ts +0 -32
- package/dist/downloader/videoDownloader.d.ts.map +0 -1
- package/dist/downloader/videoDownloader.js +0 -173
- package/dist/downloader/videoDownloader.js.map +0 -1
- package/dist/downloader/vimeoDownloader.test.d.ts +0 -2
- package/dist/downloader/vimeoDownloader.test.d.ts.map +0 -1
- package/dist/downloader/vimeoDownloader.test.js +0 -51
- package/dist/downloader/vimeoDownloader.test.js.map +0 -1
- package/dist/scraper/auth.d.ts +0 -29
- package/dist/scraper/auth.d.ts.map +0 -1
- package/dist/scraper/auth.js +0 -115
- package/dist/scraper/auth.js.map +0 -1
- package/dist/scraper/extractor.test.d.ts +0 -2
- package/dist/scraper/extractor.test.d.ts.map +0 -1
- package/dist/scraper/extractor.test.js +0 -65
- package/dist/scraper/extractor.test.js.map +0 -1
- package/dist/scraper/ghl/auth.d.ts +0 -25
- package/dist/scraper/ghl/auth.d.ts.map +0 -1
- package/dist/scraper/ghl/auth.js +0 -187
- package/dist/scraper/ghl/auth.js.map +0 -1
- package/dist/scraper/ghl/extractor.d.ts +0 -96
- package/dist/scraper/ghl/extractor.d.ts.map +0 -1
- package/dist/scraper/ghl/extractor.js +0 -345
- package/dist/scraper/ghl/extractor.js.map +0 -1
- package/dist/scraper/ghl/index.d.ts +0 -4
- package/dist/scraper/ghl/index.d.ts.map +0 -1
- package/dist/scraper/ghl/index.js +0 -4
- package/dist/scraper/ghl/index.js.map +0 -1
- package/dist/scraper/ghl/navigator.d.ts +0 -93
- package/dist/scraper/ghl/navigator.d.ts.map +0 -1
- package/dist/scraper/ghl/navigator.js +0 -447
- package/dist/scraper/ghl/navigator.js.map +0 -1
- package/dist/scraper/highlevel/auth.d.ts +0 -25
- package/dist/scraper/highlevel/auth.d.ts.map +0 -1
- package/dist/scraper/highlevel/auth.js +0 -189
- package/dist/scraper/highlevel/auth.js.map +0 -1
- package/dist/scraper/highlevel/extractor.test.d.ts +0 -2
- package/dist/scraper/highlevel/extractor.test.d.ts.map +0 -1
- package/dist/scraper/highlevel/extractor.test.js +0 -101
- package/dist/scraper/highlevel/extractor.test.js.map +0 -1
- package/dist/scraper/highlevel/navigator.test.d.ts +0 -2
- package/dist/scraper/highlevel/navigator.test.d.ts.map +0 -1
- package/dist/scraper/highlevel/navigator.test.js +0 -78
- package/dist/scraper/highlevel/navigator.test.js.map +0 -1
- package/dist/scraper/navigator.test.d.ts +0 -2
- package/dist/scraper/navigator.test.d.ts.map +0 -1
- package/dist/scraper/navigator.test.js +0 -63
- package/dist/scraper/navigator.test.js.map +0 -1
- package/dist/scraper/skoolApi.d.ts +0 -17
- package/dist/scraper/skoolApi.d.ts.map +0 -1
- package/dist/scraper/skoolApi.js +0 -72
- package/dist/scraper/skoolApi.js.map +0 -1
- package/dist/state/database.test.d.ts +0 -2
- package/dist/state/database.test.d.ts.map +0 -1
- package/dist/state/database.test.js +0 -34
- package/dist/state/database.test.js.map +0 -1
- package/dist/transcription/whisperService.d.ts +0 -27
- package/dist/transcription/whisperService.d.ts.map +0 -1
- package/dist/transcription/whisperService.js +0 -102
- package/dist/transcription/whisperService.js.map +0 -1
- package/eslint.config.js +0 -55
- package/src/__fixtures__/highlevel-post-response.json +0 -68
- package/src/__fixtures__/hls-master-playlist.m3u8 +0 -24
- package/src/cli/commands/__snapshots__/syncHighLevel.test.ts.snap +0 -38
- package/src/cli/commands/config.ts +0 -74
- package/src/cli/commands/inspect.ts +0 -441
- package/src/cli/commands/login.ts +0 -68
- package/src/cli/commands/status.ts +0 -147
- package/src/cli/commands/sync.ts +0 -1235
- package/src/cli/commands/syncHighLevel.test.ts +0 -144
- package/src/cli/commands/syncHighLevel.ts +0 -639
- package/src/cli/index.ts +0 -121
- package/src/config/configManager.ts +0 -75
- package/src/config/paths.test.ts +0 -83
- package/src/config/paths.ts +0 -36
- package/src/config/schema.test.ts +0 -173
- package/src/config/schema.ts +0 -65
- package/src/downloader/hlsDownloader.test.ts +0 -148
- package/src/downloader/hlsDownloader.ts +0 -327
- package/src/downloader/hlsValidator.ts +0 -196
- package/src/downloader/index.ts +0 -122
- package/src/downloader/loomDownloader.test.ts +0 -43
- package/src/downloader/loomDownloader.ts +0 -742
- package/src/downloader/queue.test.ts +0 -199
- package/src/downloader/queue.ts +0 -118
- package/src/downloader/vimeoDownloader.test.ts +0 -62
- package/src/downloader/vimeoDownloader.ts +0 -722
- package/src/scraper/extractor.test.ts +0 -124
- package/src/scraper/extractor.ts +0 -757
- package/src/scraper/highlevel/__snapshots__/extractor.test.ts.snap +0 -41
- package/src/scraper/highlevel/extractor.test.ts +0 -134
- package/src/scraper/highlevel/extractor.ts +0 -537
- package/src/scraper/highlevel/index.ts +0 -2
- package/src/scraper/highlevel/navigator.test.ts +0 -110
- package/src/scraper/highlevel/navigator.ts +0 -668
- package/src/scraper/highlevel/schemas.ts +0 -183
- package/src/scraper/navigator.test.ts +0 -122
- package/src/scraper/navigator.ts +0 -355
- package/src/scraper/schemas.ts +0 -177
- package/src/scraper/videoInterceptor.ts +0 -435
- package/src/shared/auth.test.ts +0 -58
- package/src/shared/auth.ts +0 -251
- package/src/shared/firebase.ts +0 -151
- package/src/shared/fs.ts +0 -80
- package/src/shared/http.ts +0 -34
- package/src/shared/index.ts +0 -6
- package/src/shared/url.test.ts +0 -122
- package/src/state/database.test.ts +0 -49
- package/src/state/database.ts +0 -919
- package/src/state/index.ts +0 -14
- package/src/storage/fileSystem.test.ts +0 -64
- package/src/storage/fileSystem.ts +0 -175
- package/tsconfig.json +0 -28
- package/vitest.config.ts +0 -29
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { AsyncQueue } from "./queue.js";
|
|
3
|
-
|
|
4
|
-
describe("AsyncQueue", () => {
|
|
5
|
-
it("processes items in order", async () => {
|
|
6
|
-
const processed: string[] = [];
|
|
7
|
-
const queue = new AsyncQueue<string>({
|
|
8
|
-
concurrency: 1,
|
|
9
|
-
maxRetries: 0,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
queue.add("1", "first");
|
|
13
|
-
queue.add("2", "second");
|
|
14
|
-
queue.add("3", "third");
|
|
15
|
-
|
|
16
|
-
await queue.process(async (item) => {
|
|
17
|
-
processed.push(item);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
expect(processed).toEqual(["first", "second", "third"]);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("tracks completed and failed counts", async () => {
|
|
24
|
-
const queue = new AsyncQueue<string>({
|
|
25
|
-
concurrency: 1,
|
|
26
|
-
maxRetries: 0,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
queue.add("1", "success");
|
|
30
|
-
queue.add("2", "fail");
|
|
31
|
-
queue.add("3", "success");
|
|
32
|
-
|
|
33
|
-
const result = await queue.process(async (item) => {
|
|
34
|
-
if (item === "fail") {
|
|
35
|
-
throw new Error("Intentional failure");
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
expect(result.completed).toBe(2);
|
|
40
|
-
expect(result.failed).toBe(1);
|
|
41
|
-
expect(result.errors).toHaveLength(1);
|
|
42
|
-
expect(result.errors[0]?.id).toBe("2");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("retries failed items up to maxRetries", async () => {
|
|
46
|
-
let attempts = 0;
|
|
47
|
-
const queue = new AsyncQueue<string>({
|
|
48
|
-
concurrency: 1,
|
|
49
|
-
maxRetries: 3,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
queue.add("1", "retry-test");
|
|
53
|
-
|
|
54
|
-
await queue.process(async () => {
|
|
55
|
-
attempts++;
|
|
56
|
-
if (attempts < 3) {
|
|
57
|
-
throw new Error("Not yet");
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
expect(attempts).toBe(3);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("adds multiple items with addAll", async () => {
|
|
65
|
-
const queue = new AsyncQueue<number>({
|
|
66
|
-
concurrency: 1,
|
|
67
|
-
maxRetries: 0,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
queue.addAll([
|
|
71
|
-
{ id: "a", data: 1 },
|
|
72
|
-
{ id: "b", data: 2 },
|
|
73
|
-
{ id: "c", data: 3 },
|
|
74
|
-
]);
|
|
75
|
-
|
|
76
|
-
const sum = { value: 0 };
|
|
77
|
-
await queue.process(async (item) => {
|
|
78
|
-
sum.value += item;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
expect(sum.value).toBe(6);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("reports progress during processing", async () => {
|
|
85
|
-
const progressCalls: { completed: number; total: number }[] = [];
|
|
86
|
-
|
|
87
|
-
const queue = new AsyncQueue<string>({
|
|
88
|
-
concurrency: 1,
|
|
89
|
-
maxRetries: 0,
|
|
90
|
-
onProgress: (completed, total) => {
|
|
91
|
-
progressCalls.push({ completed, total });
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
queue.add("1", "a");
|
|
96
|
-
queue.add("2", "b");
|
|
97
|
-
queue.add("3", "c");
|
|
98
|
-
|
|
99
|
-
await queue.process(async () => {
|
|
100
|
-
// no-op
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
expect(progressCalls.length).toBeGreaterThan(0);
|
|
104
|
-
// Last call should have all completed
|
|
105
|
-
const lastCall = progressCalls.at(-1);
|
|
106
|
-
expect(lastCall?.completed).toBe(3);
|
|
107
|
-
expect(lastCall?.total).toBe(3);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("returns correct status", async () => {
|
|
111
|
-
const queue = new AsyncQueue<string>({
|
|
112
|
-
concurrency: 1,
|
|
113
|
-
maxRetries: 0,
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
queue.add("1", "a");
|
|
117
|
-
queue.add("2", "b");
|
|
118
|
-
|
|
119
|
-
const beforeStatus = queue.getStatus();
|
|
120
|
-
expect(beforeStatus.pending).toBe(2);
|
|
121
|
-
expect(beforeStatus.completed).toBe(0);
|
|
122
|
-
|
|
123
|
-
await queue.process(async () => {});
|
|
124
|
-
|
|
125
|
-
const afterStatus = queue.getStatus();
|
|
126
|
-
expect(afterStatus.pending).toBe(0);
|
|
127
|
-
expect(afterStatus.completed).toBe(2);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it("handles concurrent processing", async () => {
|
|
131
|
-
const processing = new Set<string>();
|
|
132
|
-
let maxConcurrent = 0;
|
|
133
|
-
|
|
134
|
-
const queue = new AsyncQueue<string>({
|
|
135
|
-
concurrency: 2,
|
|
136
|
-
maxRetries: 0,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
queue.addAll([
|
|
140
|
-
{ id: "1", data: "a" },
|
|
141
|
-
{ id: "2", data: "b" },
|
|
142
|
-
{ id: "3", data: "c" },
|
|
143
|
-
{ id: "4", data: "d" },
|
|
144
|
-
]);
|
|
145
|
-
|
|
146
|
-
await queue.process(async (_item, id) => {
|
|
147
|
-
processing.add(id);
|
|
148
|
-
maxConcurrent = Math.max(maxConcurrent, processing.size);
|
|
149
|
-
// Simulate async work
|
|
150
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
151
|
-
processing.delete(id);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
expect(maxConcurrent).toBeLessThanOrEqual(2);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("handles empty queue", async () => {
|
|
158
|
-
const queue = new AsyncQueue<string>({
|
|
159
|
-
concurrency: 1,
|
|
160
|
-
maxRetries: 0,
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
const result = await queue.process(async () => {});
|
|
164
|
-
|
|
165
|
-
expect(result.completed).toBe(0);
|
|
166
|
-
expect(result.failed).toBe(0);
|
|
167
|
-
expect(result.errors).toEqual([]);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it("captures error messages correctly", async () => {
|
|
171
|
-
const queue = new AsyncQueue<string>({
|
|
172
|
-
concurrency: 1,
|
|
173
|
-
maxRetries: 0,
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
queue.add("1", "test");
|
|
177
|
-
|
|
178
|
-
const result = await queue.process(async () => {
|
|
179
|
-
throw new Error("Specific error message");
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
expect(result.errors[0]?.error).toBe("Specific error message");
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it("handles non-Error throws", async () => {
|
|
186
|
-
const queue = new AsyncQueue<string>({
|
|
187
|
-
concurrency: 1,
|
|
188
|
-
maxRetries: 0,
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
queue.add("1", "test");
|
|
192
|
-
|
|
193
|
-
const result = await queue.process(async () => {
|
|
194
|
-
throw "String error";
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
expect(result.errors[0]?.error).toBe("String error");
|
|
198
|
-
});
|
|
199
|
-
});
|
package/src/downloader/queue.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import PQueue from "p-queue";
|
|
2
|
-
|
|
3
|
-
export interface QueueItem<T> {
|
|
4
|
-
id: string;
|
|
5
|
-
data: T;
|
|
6
|
-
status: "pending" | "processing" | "completed" | "failed";
|
|
7
|
-
error?: string;
|
|
8
|
-
retries: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface QueueOptions {
|
|
12
|
-
concurrency: number;
|
|
13
|
-
maxRetries: number;
|
|
14
|
-
onProgress?: ((completed: number, total: number, current?: string) => void) | undefined;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* An async queue for processing items with concurrency control.
|
|
19
|
-
* Uses p-queue internally for battle-tested concurrency handling.
|
|
20
|
-
*/
|
|
21
|
-
export class AsyncQueue<T> {
|
|
22
|
-
private items: QueueItem<T>[] = [];
|
|
23
|
-
private readonly queue: PQueue;
|
|
24
|
-
private readonly maxRetries: number;
|
|
25
|
-
private readonly onProgress:
|
|
26
|
-
| ((completed: number, total: number, current?: string) => void)
|
|
27
|
-
| undefined;
|
|
28
|
-
|
|
29
|
-
constructor(options: QueueOptions) {
|
|
30
|
-
this.queue = new PQueue({ concurrency: options.concurrency });
|
|
31
|
-
this.maxRetries = options.maxRetries;
|
|
32
|
-
this.onProgress = options.onProgress;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Adds an item to the queue.
|
|
37
|
-
*/
|
|
38
|
-
add(id: string, data: T): void {
|
|
39
|
-
this.items.push({
|
|
40
|
-
id,
|
|
41
|
-
data,
|
|
42
|
-
status: "pending",
|
|
43
|
-
retries: 0,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Adds multiple items to the queue.
|
|
49
|
-
*/
|
|
50
|
-
addAll(items: { id: string; data: T }[]): void {
|
|
51
|
-
for (const item of items) {
|
|
52
|
-
this.add(item.id, item.data);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Processes all items in the queue using the provided handler.
|
|
58
|
-
*/
|
|
59
|
-
async process(
|
|
60
|
-
handler: (item: T, id: string) => Promise<void>
|
|
61
|
-
): Promise<{ completed: number; failed: number; errors: { id: string; error: string }[] }> {
|
|
62
|
-
const errors: { id: string; error: string }[] = [];
|
|
63
|
-
|
|
64
|
-
const processItem = async (item: QueueItem<T>): Promise<void> => {
|
|
65
|
-
item.status = "processing";
|
|
66
|
-
|
|
67
|
-
// maxRetries=0 means try once, maxRetries=3 means try up to 3 times total
|
|
68
|
-
const maxAttempts = Math.max(1, this.maxRetries);
|
|
69
|
-
|
|
70
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
71
|
-
try {
|
|
72
|
-
await handler(item.data, item.id);
|
|
73
|
-
item.status = "completed";
|
|
74
|
-
this.reportProgress();
|
|
75
|
-
return;
|
|
76
|
-
} catch (error) {
|
|
77
|
-
item.retries = attempt + 1;
|
|
78
|
-
if (attempt >= maxAttempts - 1) {
|
|
79
|
-
item.status = "failed";
|
|
80
|
-
item.error = error instanceof Error ? error.message : String(error);
|
|
81
|
-
errors.push({ id: item.id, error: item.error });
|
|
82
|
-
this.reportProgress();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// Add all items to the p-queue
|
|
89
|
-
await this.queue.addAll(this.items.map((item) => () => processItem(item)));
|
|
90
|
-
|
|
91
|
-
const completed = this.items.filter((i) => i.status === "completed").length;
|
|
92
|
-
const failed = this.items.filter((i) => i.status === "failed").length;
|
|
93
|
-
|
|
94
|
-
return { completed, failed, errors };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private reportProgress(): void {
|
|
98
|
-
if (!this.onProgress) return;
|
|
99
|
-
|
|
100
|
-
const completed = this.items.filter((i) => i.status === "completed").length;
|
|
101
|
-
const total = this.items.length;
|
|
102
|
-
const current = this.items.find((i) => i.status === "processing");
|
|
103
|
-
|
|
104
|
-
this.onProgress(completed, total, current?.id);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Gets the current queue status.
|
|
109
|
-
*/
|
|
110
|
-
getStatus(): { pending: number; processing: number; completed: number; failed: number } {
|
|
111
|
-
return {
|
|
112
|
-
pending: this.items.filter((i) => i.status === "pending").length,
|
|
113
|
-
processing: this.items.filter((i) => i.status === "processing").length,
|
|
114
|
-
completed: this.items.filter((i) => i.status === "completed").length,
|
|
115
|
-
failed: this.items.filter((i) => i.status === "failed").length,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { extractVimeoId } from "./vimeoDownloader.js";
|
|
3
|
-
|
|
4
|
-
describe("extractVimeoId", () => {
|
|
5
|
-
it("extracts ID from standard vimeo.com URL", () => {
|
|
6
|
-
const url = "https://vimeo.com/123456789";
|
|
7
|
-
expect(extractVimeoId(url)).toBe("123456789");
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it("extracts ID from vimeo.com/video URL", () => {
|
|
11
|
-
const url = "https://vimeo.com/video/123456789";
|
|
12
|
-
expect(extractVimeoId(url)).toBe("123456789");
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("extracts ID from player.vimeo.com URL", () => {
|
|
16
|
-
const url = "https://player.vimeo.com/video/987654321";
|
|
17
|
-
expect(extractVimeoId(url)).toBe("987654321");
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("extracts ID from channel URL", () => {
|
|
21
|
-
const url = "https://vimeo.com/channels/staffpicks/123456789";
|
|
22
|
-
expect(extractVimeoId(url)).toBe("123456789");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("extracts ID from groups URL", () => {
|
|
26
|
-
const url = "https://vimeo.com/groups/shortfilms/videos/123456789";
|
|
27
|
-
expect(extractVimeoId(url)).toBe("123456789");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("extracts ID from URL with query params", () => {
|
|
31
|
-
const url = "https://vimeo.com/123456789?share=copy&autoplay=1";
|
|
32
|
-
expect(extractVimeoId(url)).toBe("123456789");
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("extracts ID from URL with hash for unlisted videos", () => {
|
|
36
|
-
const url = "https://vimeo.com/123456789/abcdef1234";
|
|
37
|
-
expect(extractVimeoId(url)).toBe("123456789");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("extracts ID from player URL with h parameter", () => {
|
|
41
|
-
const url = "https://player.vimeo.com/video/123456789?h=abcdef1234";
|
|
42
|
-
expect(extractVimeoId(url)).toBe("123456789");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("returns null for non-Vimeo URL", () => {
|
|
46
|
-
expect(extractVimeoId("https://youtube.com/watch?v=abc123")).toBeNull();
|
|
47
|
-
expect(extractVimeoId("https://loom.com/embed/abc123")).toBeNull();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("returns null for Vimeo homepage", () => {
|
|
51
|
-
expect(extractVimeoId("https://vimeo.com")).toBeNull();
|
|
52
|
-
expect(extractVimeoId("https://vimeo.com/")).toBeNull();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("returns null for empty string", () => {
|
|
56
|
-
expect(extractVimeoId("")).toBeNull();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("returns null for invalid string", () => {
|
|
60
|
-
expect(extractVimeoId("not-a-url")).toBeNull();
|
|
61
|
-
});
|
|
62
|
-
});
|