offcourse 1.0.0 → 1.1.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.
Files changed (280) hide show
  1. package/README.md +107 -8
  2. package/dist/cli/commands/config.js.map +1 -1
  3. package/dist/cli/commands/inspect.js +1 -1
  4. package/dist/cli/commands/inspect.js.map +1 -1
  5. package/dist/cli/commands/sync.d.ts +1 -2
  6. package/dist/cli/commands/sync.d.ts.map +1 -1
  7. package/dist/cli/commands/sync.js +17 -15
  8. package/dist/cli/commands/sync.js.map +1 -1
  9. package/dist/cli/commands/syncHighLevel.d.ts +1 -2
  10. package/dist/cli/commands/syncHighLevel.d.ts.map +1 -1
  11. package/dist/cli/commands/syncHighLevel.js +8 -9
  12. package/dist/cli/commands/syncHighLevel.js.map +1 -1
  13. package/dist/cli/commands/syncLearningSuite.d.ts +35 -0
  14. package/dist/cli/commands/syncLearningSuite.d.ts.map +1 -0
  15. package/dist/cli/commands/syncLearningSuite.js +765 -0
  16. package/dist/cli/commands/syncLearningSuite.js.map +1 -0
  17. package/dist/cli/index.js +39 -1
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/config/configManager.d.ts.map +1 -1
  20. package/dist/config/configManager.js +4 -0
  21. package/dist/config/configManager.js.map +1 -1
  22. package/dist/downloader/hlsDownloader.d.ts +10 -4
  23. package/dist/downloader/hlsDownloader.d.ts.map +1 -1
  24. package/dist/downloader/hlsDownloader.js +60 -29
  25. package/dist/downloader/hlsDownloader.js.map +1 -1
  26. package/dist/downloader/hlsValidator.d.ts.map +1 -1
  27. package/dist/downloader/hlsValidator.js +6 -2
  28. package/dist/downloader/hlsValidator.js.map +1 -1
  29. package/dist/downloader/index.d.ts +7 -0
  30. package/dist/downloader/index.d.ts.map +1 -1
  31. package/dist/downloader/index.js +9 -6
  32. package/dist/downloader/index.js.map +1 -1
  33. package/dist/downloader/loomDownloader.d.ts +1 -1
  34. package/dist/downloader/loomDownloader.d.ts.map +1 -1
  35. package/dist/downloader/loomDownloader.js +32 -27
  36. package/dist/downloader/loomDownloader.js.map +1 -1
  37. package/dist/downloader/queue.d.ts +4 -4
  38. package/dist/downloader/queue.d.ts.map +1 -1
  39. package/dist/downloader/queue.js.map +1 -1
  40. package/dist/downloader/vimeoDownloader.d.ts.map +1 -1
  41. package/dist/downloader/vimeoDownloader.js +7 -3
  42. package/dist/downloader/vimeoDownloader.js.map +1 -1
  43. package/dist/scraper/extractor.d.ts +4 -0
  44. package/dist/scraper/extractor.d.ts.map +1 -1
  45. package/dist/scraper/extractor.js +79 -79
  46. package/dist/scraper/extractor.js.map +1 -1
  47. package/dist/scraper/highlevel/extractor.d.ts +11 -19
  48. package/dist/scraper/highlevel/extractor.d.ts.map +1 -1
  49. package/dist/scraper/highlevel/extractor.js +72 -85
  50. package/dist/scraper/highlevel/extractor.js.map +1 -1
  51. package/dist/scraper/highlevel/navigator.d.ts +3 -10
  52. package/dist/scraper/highlevel/navigator.d.ts.map +1 -1
  53. package/dist/scraper/highlevel/navigator.js +140 -127
  54. package/dist/scraper/highlevel/navigator.js.map +1 -1
  55. package/dist/scraper/highlevel/schemas.d.ts +188 -0
  56. package/dist/scraper/highlevel/schemas.d.ts.map +1 -0
  57. package/dist/scraper/highlevel/schemas.js +139 -0
  58. package/dist/scraper/highlevel/schemas.js.map +1 -0
  59. package/dist/scraper/learningsuite/extractor.d.ts +50 -0
  60. package/dist/scraper/learningsuite/extractor.d.ts.map +1 -0
  61. package/dist/scraper/learningsuite/extractor.js +429 -0
  62. package/dist/scraper/learningsuite/extractor.js.map +1 -0
  63. package/dist/scraper/learningsuite/index.d.ts +4 -0
  64. package/dist/scraper/learningsuite/index.d.ts.map +1 -0
  65. package/dist/scraper/{ghl → learningsuite}/index.js +1 -1
  66. package/dist/scraper/learningsuite/index.js.map +1 -0
  67. package/dist/scraper/learningsuite/navigator.d.ts +122 -0
  68. package/dist/scraper/learningsuite/navigator.d.ts.map +1 -0
  69. package/dist/scraper/learningsuite/navigator.js +736 -0
  70. package/dist/scraper/learningsuite/navigator.js.map +1 -0
  71. package/dist/scraper/learningsuite/schemas.d.ts +270 -0
  72. package/dist/scraper/learningsuite/schemas.d.ts.map +1 -0
  73. package/dist/scraper/learningsuite/schemas.js +147 -0
  74. package/dist/scraper/learningsuite/schemas.js.map +1 -0
  75. package/dist/scraper/navigator.d.ts +14 -11
  76. package/dist/scraper/navigator.d.ts.map +1 -1
  77. package/dist/scraper/navigator.js +61 -104
  78. package/dist/scraper/navigator.js.map +1 -1
  79. package/dist/scraper/schemas.d.ts +57 -0
  80. package/dist/scraper/schemas.d.ts.map +1 -0
  81. package/dist/scraper/schemas.js +135 -0
  82. package/dist/scraper/schemas.js.map +1 -0
  83. package/dist/scraper/videoInterceptor.d.ts +4 -0
  84. package/dist/scraper/videoInterceptor.d.ts.map +1 -1
  85. package/dist/scraper/videoInterceptor.js +66 -51
  86. package/dist/scraper/videoInterceptor.js.map +1 -1
  87. package/dist/shared/auth.d.ts +9 -9
  88. package/dist/shared/auth.d.ts.map +1 -1
  89. package/dist/shared/auth.js +24 -38
  90. package/dist/shared/auth.js.map +1 -1
  91. package/dist/shared/firebase.d.ts +60 -0
  92. package/dist/shared/firebase.d.ts.map +1 -0
  93. package/dist/shared/firebase.js +102 -0
  94. package/dist/shared/firebase.js.map +1 -0
  95. package/dist/shared/fs.d.ts.map +1 -1
  96. package/dist/shared/fs.js +4 -0
  97. package/dist/shared/fs.js.map +1 -1
  98. package/dist/shared/index.d.ts +3 -0
  99. package/dist/shared/index.d.ts.map +1 -1
  100. package/dist/shared/index.js +3 -0
  101. package/dist/shared/index.js.map +1 -1
  102. package/dist/shared/slug.d.ts +11 -0
  103. package/dist/shared/slug.d.ts.map +1 -0
  104. package/{src/shared/slug.ts → dist/shared/slug.js} +10 -11
  105. package/dist/shared/slug.js.map +1 -0
  106. package/dist/shared/url.d.ts +43 -0
  107. package/dist/shared/url.d.ts.map +1 -0
  108. package/{src/shared/url.ts → dist/shared/url.js} +12 -15
  109. package/dist/shared/url.js.map +1 -0
  110. package/dist/state/database.d.ts +1 -0
  111. package/dist/state/database.d.ts.map +1 -1
  112. package/dist/state/database.js +3 -0
  113. package/dist/state/database.js.map +1 -1
  114. package/dist/storage/fileSystem.d.ts +17 -17
  115. package/dist/storage/fileSystem.d.ts.map +1 -1
  116. package/dist/storage/fileSystem.js +39 -31
  117. package/dist/storage/fileSystem.js.map +1 -1
  118. package/package.json +5 -2
  119. package/.github/workflows/ci.yml +0 -50
  120. package/.husky/commit-msg +0 -2
  121. package/.husky/pre-commit +0 -1
  122. package/.husky/pre-push +0 -3
  123. package/.prettierrc +0 -8
  124. package/.release-it.json +0 -23
  125. package/ARCHITECTURE.md +0 -233
  126. package/CHANGELOG.md +0 -78
  127. package/commitlint.config.js +0 -4
  128. package/dist/ai/openRouter.d.ts +0 -47
  129. package/dist/ai/openRouter.d.ts.map +0 -1
  130. package/dist/ai/openRouter.js +0 -116
  131. package/dist/ai/openRouter.js.map +0 -1
  132. package/dist/ai/transcriptPolisher.d.ts +0 -24
  133. package/dist/ai/transcriptPolisher.d.ts.map +0 -1
  134. package/dist/ai/transcriptPolisher.js +0 -89
  135. package/dist/ai/transcriptPolisher.js.map +0 -1
  136. package/dist/cli/commands/enrich.d.ts +0 -14
  137. package/dist/cli/commands/enrich.d.ts.map +0 -1
  138. package/dist/cli/commands/enrich.js +0 -271
  139. package/dist/cli/commands/enrich.js.map +0 -1
  140. package/dist/cli/commands/syncGhl.d.ts +0 -20
  141. package/dist/cli/commands/syncGhl.d.ts.map +0 -1
  142. package/dist/cli/commands/syncGhl.js +0 -483
  143. package/dist/cli/commands/syncGhl.js.map +0 -1
  144. package/dist/cli/commands/syncHighLevel.test.d.ts +0 -2
  145. package/dist/cli/commands/syncHighLevel.test.d.ts.map +0 -1
  146. package/dist/cli/commands/syncHighLevel.test.js +0 -102
  147. package/dist/cli/commands/syncHighLevel.test.js.map +0 -1
  148. package/dist/config/paths.test.d.ts +0 -2
  149. package/dist/config/paths.test.d.ts.map +0 -1
  150. package/dist/config/paths.test.js +0 -70
  151. package/dist/config/paths.test.js.map +0 -1
  152. package/dist/config/schema.test.d.ts +0 -2
  153. package/dist/config/schema.test.d.ts.map +0 -1
  154. package/dist/config/schema.test.js +0 -151
  155. package/dist/config/schema.test.js.map +0 -1
  156. package/dist/downloader/hlsDownloader.test.d.ts +0 -2
  157. package/dist/downloader/hlsDownloader.test.d.ts.map +0 -1
  158. package/dist/downloader/hlsDownloader.test.js +0 -116
  159. package/dist/downloader/hlsDownloader.test.js.map +0 -1
  160. package/dist/downloader/loomDownloader.test.d.ts +0 -2
  161. package/dist/downloader/loomDownloader.test.d.ts.map +0 -1
  162. package/dist/downloader/loomDownloader.test.js +0 -36
  163. package/dist/downloader/loomDownloader.test.js.map +0 -1
  164. package/dist/downloader/queue.test.d.ts +0 -2
  165. package/dist/downloader/queue.test.d.ts.map +0 -1
  166. package/dist/downloader/queue.test.js +0 -158
  167. package/dist/downloader/queue.test.js.map +0 -1
  168. package/dist/downloader/videoDownloader.d.ts +0 -32
  169. package/dist/downloader/videoDownloader.d.ts.map +0 -1
  170. package/dist/downloader/videoDownloader.js +0 -173
  171. package/dist/downloader/videoDownloader.js.map +0 -1
  172. package/dist/downloader/vimeoDownloader.test.d.ts +0 -2
  173. package/dist/downloader/vimeoDownloader.test.d.ts.map +0 -1
  174. package/dist/downloader/vimeoDownloader.test.js +0 -51
  175. package/dist/downloader/vimeoDownloader.test.js.map +0 -1
  176. package/dist/scraper/auth.d.ts +0 -29
  177. package/dist/scraper/auth.d.ts.map +0 -1
  178. package/dist/scraper/auth.js +0 -115
  179. package/dist/scraper/auth.js.map +0 -1
  180. package/dist/scraper/extractor.test.d.ts +0 -2
  181. package/dist/scraper/extractor.test.d.ts.map +0 -1
  182. package/dist/scraper/extractor.test.js +0 -65
  183. package/dist/scraper/extractor.test.js.map +0 -1
  184. package/dist/scraper/ghl/auth.d.ts +0 -25
  185. package/dist/scraper/ghl/auth.d.ts.map +0 -1
  186. package/dist/scraper/ghl/auth.js +0 -187
  187. package/dist/scraper/ghl/auth.js.map +0 -1
  188. package/dist/scraper/ghl/extractor.d.ts +0 -96
  189. package/dist/scraper/ghl/extractor.d.ts.map +0 -1
  190. package/dist/scraper/ghl/extractor.js +0 -345
  191. package/dist/scraper/ghl/extractor.js.map +0 -1
  192. package/dist/scraper/ghl/index.d.ts +0 -4
  193. package/dist/scraper/ghl/index.d.ts.map +0 -1
  194. package/dist/scraper/ghl/index.js.map +0 -1
  195. package/dist/scraper/ghl/navigator.d.ts +0 -93
  196. package/dist/scraper/ghl/navigator.d.ts.map +0 -1
  197. package/dist/scraper/ghl/navigator.js +0 -447
  198. package/dist/scraper/ghl/navigator.js.map +0 -1
  199. package/dist/scraper/highlevel/auth.d.ts +0 -25
  200. package/dist/scraper/highlevel/auth.d.ts.map +0 -1
  201. package/dist/scraper/highlevel/auth.js +0 -189
  202. package/dist/scraper/highlevel/auth.js.map +0 -1
  203. package/dist/scraper/highlevel/extractor.test.d.ts +0 -2
  204. package/dist/scraper/highlevel/extractor.test.d.ts.map +0 -1
  205. package/dist/scraper/highlevel/extractor.test.js +0 -101
  206. package/dist/scraper/highlevel/extractor.test.js.map +0 -1
  207. package/dist/scraper/highlevel/navigator.test.d.ts +0 -2
  208. package/dist/scraper/highlevel/navigator.test.d.ts.map +0 -1
  209. package/dist/scraper/highlevel/navigator.test.js +0 -78
  210. package/dist/scraper/highlevel/navigator.test.js.map +0 -1
  211. package/dist/scraper/navigator.test.d.ts +0 -2
  212. package/dist/scraper/navigator.test.d.ts.map +0 -1
  213. package/dist/scraper/navigator.test.js +0 -63
  214. package/dist/scraper/navigator.test.js.map +0 -1
  215. package/dist/scraper/skoolApi.d.ts +0 -17
  216. package/dist/scraper/skoolApi.d.ts.map +0 -1
  217. package/dist/scraper/skoolApi.js +0 -72
  218. package/dist/scraper/skoolApi.js.map +0 -1
  219. package/dist/state/database.test.d.ts +0 -2
  220. package/dist/state/database.test.d.ts.map +0 -1
  221. package/dist/state/database.test.js +0 -34
  222. package/dist/state/database.test.js.map +0 -1
  223. package/dist/transcription/whisperService.d.ts +0 -27
  224. package/dist/transcription/whisperService.d.ts.map +0 -1
  225. package/dist/transcription/whisperService.js +0 -102
  226. package/dist/transcription/whisperService.js.map +0 -1
  227. package/eslint.config.js +0 -55
  228. package/src/__fixtures__/highlevel-post-response.json +0 -68
  229. package/src/__fixtures__/hls-master-playlist.m3u8 +0 -24
  230. package/src/cli/commands/__snapshots__/syncHighLevel.test.ts.snap +0 -38
  231. package/src/cli/commands/config.ts +0 -74
  232. package/src/cli/commands/inspect.ts +0 -441
  233. package/src/cli/commands/login.ts +0 -68
  234. package/src/cli/commands/status.ts +0 -147
  235. package/src/cli/commands/sync.ts +0 -1235
  236. package/src/cli/commands/syncHighLevel.test.ts +0 -144
  237. package/src/cli/commands/syncHighLevel.ts +0 -639
  238. package/src/cli/index.ts +0 -121
  239. package/src/config/configManager.ts +0 -75
  240. package/src/config/paths.test.ts +0 -83
  241. package/src/config/paths.ts +0 -36
  242. package/src/config/schema.test.ts +0 -173
  243. package/src/config/schema.ts +0 -65
  244. package/src/downloader/hlsDownloader.test.ts +0 -148
  245. package/src/downloader/hlsDownloader.ts +0 -327
  246. package/src/downloader/hlsValidator.ts +0 -196
  247. package/src/downloader/index.ts +0 -122
  248. package/src/downloader/loomDownloader.test.ts +0 -43
  249. package/src/downloader/loomDownloader.ts +0 -742
  250. package/src/downloader/queue.test.ts +0 -199
  251. package/src/downloader/queue.ts +0 -118
  252. package/src/downloader/vimeoDownloader.test.ts +0 -62
  253. package/src/downloader/vimeoDownloader.ts +0 -722
  254. package/src/scraper/extractor.test.ts +0 -124
  255. package/src/scraper/extractor.ts +0 -757
  256. package/src/scraper/highlevel/__snapshots__/extractor.test.ts.snap +0 -41
  257. package/src/scraper/highlevel/extractor.test.ts +0 -134
  258. package/src/scraper/highlevel/extractor.ts +0 -537
  259. package/src/scraper/highlevel/index.ts +0 -2
  260. package/src/scraper/highlevel/navigator.test.ts +0 -110
  261. package/src/scraper/highlevel/navigator.ts +0 -668
  262. package/src/scraper/highlevel/schemas.ts +0 -183
  263. package/src/scraper/navigator.test.ts +0 -122
  264. package/src/scraper/navigator.ts +0 -355
  265. package/src/scraper/schemas.ts +0 -177
  266. package/src/scraper/videoInterceptor.ts +0 -435
  267. package/src/shared/auth.test.ts +0 -58
  268. package/src/shared/auth.ts +0 -251
  269. package/src/shared/firebase.ts +0 -151
  270. package/src/shared/fs.ts +0 -80
  271. package/src/shared/http.ts +0 -34
  272. package/src/shared/index.ts +0 -6
  273. package/src/shared/url.test.ts +0 -122
  274. package/src/state/database.test.ts +0 -49
  275. package/src/state/database.ts +0 -919
  276. package/src/state/index.ts +0 -14
  277. package/src/storage/fileSystem.test.ts +0 -64
  278. package/src/storage/fileSystem.ts +0 -175
  279. package/tsconfig.json +0 -28
  280. 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
- });
@@ -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
- });