youtube-knowledge-mcp 2.2.0 → 3.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.
Files changed (192) hide show
  1. package/README.md +62 -18
  2. package/dist/harvest-schemas.d.ts +75 -0
  3. package/dist/harvest-schemas.d.ts.map +1 -0
  4. package/dist/harvest-schemas.js +106 -0
  5. package/dist/harvest-schemas.js.map +1 -0
  6. package/dist/registry/harvest-tools.d.ts +10 -0
  7. package/dist/registry/harvest-tools.d.ts.map +1 -0
  8. package/dist/registry/harvest-tools.js +109 -0
  9. package/dist/registry/harvest-tools.js.map +1 -0
  10. package/dist/registry/health-tools.d.ts.map +1 -1
  11. package/dist/registry/health-tools.js +18 -0
  12. package/dist/registry/health-tools.js.map +1 -1
  13. package/dist/registry/index.d.ts.map +1 -1
  14. package/dist/registry/index.js +2 -0
  15. package/dist/registry/index.js.map +1 -1
  16. package/dist/registry/video-tools.js +1 -1
  17. package/dist/registry/video-tools.js.map +1 -1
  18. package/dist/schemas.d.ts +239 -0
  19. package/dist/schemas.d.ts.map +1 -1
  20. package/dist/schemas.js +56 -0
  21. package/dist/schemas.js.map +1 -1
  22. package/dist/thumbnail-schemas.d.ts +28 -28
  23. package/dist/tools/ask-brain.js +1 -1
  24. package/dist/tools/ask-brain.js.map +1 -1
  25. package/dist/tools/brain-info.js +1 -1
  26. package/dist/tools/brain-info.js.map +1 -1
  27. package/dist/tools/check-health.d.ts +59 -0
  28. package/dist/tools/check-health.d.ts.map +1 -1
  29. package/dist/tools/check-health.js +85 -6
  30. package/dist/tools/check-health.js.map +1 -1
  31. package/dist/tools/fetch-channel-thumbnails.d.ts +8 -8
  32. package/dist/tools/fetch-videos.d.ts +15 -4
  33. package/dist/tools/fetch-videos.d.ts.map +1 -1
  34. package/dist/tools/fetch-videos.js +63 -22
  35. package/dist/tools/fetch-videos.js.map +1 -1
  36. package/dist/tools/get-comments.d.ts +238 -6
  37. package/dist/tools/get-comments.d.ts.map +1 -1
  38. package/dist/tools/get-comments.js +85 -21
  39. package/dist/tools/get-comments.js.map +1 -1
  40. package/dist/tools/get-coverage.d.ts +122 -0
  41. package/dist/tools/get-coverage.d.ts.map +1 -0
  42. package/dist/tools/get-coverage.js +105 -0
  43. package/dist/tools/get-coverage.js.map +1 -0
  44. package/dist/tools/harvest-channel.d.ts +92 -0
  45. package/dist/tools/harvest-channel.d.ts.map +1 -0
  46. package/dist/tools/harvest-channel.js +62 -0
  47. package/dist/tools/harvest-channel.js.map +1 -0
  48. package/dist/tools/harvest-comments.d.ts +81 -0
  49. package/dist/tools/harvest-comments.d.ts.map +1 -0
  50. package/dist/tools/harvest-comments.js +115 -0
  51. package/dist/tools/harvest-comments.js.map +1 -0
  52. package/dist/tools/library.js +1 -1
  53. package/dist/tools/library.js.map +1 -1
  54. package/dist/tools/list-channel-thumbnails.d.ts +10 -10
  55. package/dist/tools/list-channel-thumbnails.js +1 -1
  56. package/dist/tools/list-channel-thumbnails.js.map +1 -1
  57. package/dist/tools/list-library.js +1 -1
  58. package/dist/tools/list-library.js.map +1 -1
  59. package/dist/tools/prune-harvest.d.ts +59 -0
  60. package/dist/tools/prune-harvest.d.ts.map +1 -0
  61. package/dist/tools/prune-harvest.js +108 -0
  62. package/dist/tools/prune-harvest.js.map +1 -0
  63. package/dist/tools/query-comments.d.ts +125 -0
  64. package/dist/tools/query-comments.d.ts.map +1 -0
  65. package/dist/tools/query-comments.js +106 -0
  66. package/dist/tools/query-comments.js.map +1 -0
  67. package/dist/tools/query-videos.d.ts +118 -0
  68. package/dist/tools/query-videos.d.ts.map +1 -0
  69. package/dist/tools/query-videos.js +97 -0
  70. package/dist/tools/query-videos.js.map +1 -0
  71. package/dist/tools/repair-store.d.ts +34 -0
  72. package/dist/tools/repair-store.d.ts.map +1 -0
  73. package/dist/tools/repair-store.js +58 -0
  74. package/dist/tools/repair-store.js.map +1 -0
  75. package/dist/tools/search-channels.d.ts.map +1 -1
  76. package/dist/tools/search-channels.js +5 -1
  77. package/dist/tools/search-channels.js.map +1 -1
  78. package/dist/tools/search-videos.js +1 -1
  79. package/dist/tools/search-videos.js.map +1 -1
  80. package/dist/utils/comment-store.d.ts +71 -0
  81. package/dist/utils/comment-store.d.ts.map +1 -0
  82. package/dist/utils/comment-store.js +129 -0
  83. package/dist/utils/comment-store.js.map +1 -0
  84. package/dist/utils/comment-threads.d.ts +51 -0
  85. package/dist/utils/comment-threads.d.ts.map +1 -0
  86. package/dist/utils/comment-threads.js +76 -0
  87. package/dist/utils/comment-threads.js.map +1 -0
  88. package/dist/utils/coverage-text.d.ts +3 -0
  89. package/dist/utils/coverage-text.d.ts.map +1 -0
  90. package/dist/utils/coverage-text.js +62 -0
  91. package/dist/utils/coverage-text.js.map +1 -0
  92. package/dist/utils/coverage.d.ts +57 -0
  93. package/dist/utils/coverage.d.ts.map +1 -0
  94. package/dist/utils/coverage.js +142 -0
  95. package/dist/utils/coverage.js.map +1 -0
  96. package/dist/utils/errors.d.ts +1 -1
  97. package/dist/utils/errors.d.ts.map +1 -1
  98. package/dist/utils/errors.js +9 -1
  99. package/dist/utils/errors.js.map +1 -1
  100. package/dist/utils/format.d.ts +17 -1
  101. package/dist/utils/format.d.ts.map +1 -1
  102. package/dist/utils/format.js +1 -1
  103. package/dist/utils/format.js.map +1 -1
  104. package/dist/utils/harvest-catalog.d.ts +44 -0
  105. package/dist/utils/harvest-catalog.d.ts.map +1 -0
  106. package/dist/utils/harvest-catalog.js +164 -0
  107. package/dist/utils/harvest-catalog.js.map +1 -0
  108. package/dist/utils/harvest-lock.d.ts +3 -0
  109. package/dist/utils/harvest-lock.d.ts.map +1 -0
  110. package/dist/utils/harvest-lock.js +26 -0
  111. package/dist/utils/harvest-lock.js.map +1 -0
  112. package/dist/utils/harvest-receipts.d.ts +14 -0
  113. package/dist/utils/harvest-receipts.d.ts.map +1 -0
  114. package/dist/utils/harvest-receipts.js +110 -0
  115. package/dist/utils/harvest-receipts.js.map +1 -0
  116. package/dist/utils/listing-cursor.d.ts +31 -0
  117. package/dist/utils/listing-cursor.d.ts.map +1 -0
  118. package/dist/utils/listing-cursor.js +39 -0
  119. package/dist/utils/listing-cursor.js.map +1 -0
  120. package/dist/utils/pot-preflight.d.ts +53 -0
  121. package/dist/utils/pot-preflight.d.ts.map +1 -0
  122. package/dist/utils/pot-preflight.js +146 -0
  123. package/dist/utils/pot-preflight.js.map +1 -0
  124. package/dist/utils/sleep.d.ts +9 -0
  125. package/dist/utils/sleep.d.ts.map +1 -0
  126. package/dist/utils/sleep.js +25 -0
  127. package/dist/utils/sleep.js.map +1 -0
  128. package/dist/utils/store-health.d.ts +37 -0
  129. package/dist/utils/store-health.d.ts.map +1 -0
  130. package/dist/utils/store-health.js +92 -0
  131. package/dist/utils/store-health.js.map +1 -0
  132. package/dist/utils/store-migrations.d.ts +33 -0
  133. package/dist/utils/store-migrations.d.ts.map +1 -0
  134. package/dist/utils/store-migrations.js +41 -0
  135. package/dist/utils/store-migrations.js.map +1 -0
  136. package/dist/utils/store-paths.d.ts +33 -0
  137. package/dist/utils/store-paths.d.ts.map +1 -0
  138. package/dist/utils/store-paths.js +48 -0
  139. package/dist/utils/store-paths.js.map +1 -0
  140. package/dist/utils/store-rows.d.ts +22 -0
  141. package/dist/utils/store-rows.d.ts.map +1 -0
  142. package/dist/utils/store-rows.js +62 -0
  143. package/dist/utils/store-rows.js.map +1 -0
  144. package/dist/utils/store-schema.d.ts +16 -0
  145. package/dist/utils/store-schema.d.ts.map +1 -0
  146. package/dist/utils/store-schema.js +168 -0
  147. package/dist/utils/store-schema.js.map +1 -0
  148. package/dist/utils/store.d.ts +34 -0
  149. package/dist/utils/store.d.ts.map +1 -0
  150. package/dist/utils/store.js +157 -0
  151. package/dist/utils/store.js.map +1 -0
  152. package/dist/utils/video-stats-cache.d.ts +16 -0
  153. package/dist/utils/video-stats-cache.d.ts.map +1 -0
  154. package/dist/utils/video-stats-cache.js +81 -0
  155. package/dist/utils/video-stats-cache.js.map +1 -0
  156. package/dist/utils/video-stats.d.ts +186 -0
  157. package/dist/utils/video-stats.d.ts.map +1 -0
  158. package/dist/utils/video-stats.js +168 -0
  159. package/dist/utils/video-stats.js.map +1 -0
  160. package/dist/utils/video-store.d.ts +69 -0
  161. package/dist/utils/video-store.d.ts.map +1 -0
  162. package/dist/utils/video-store.js +77 -0
  163. package/dist/utils/video-store.js.map +1 -0
  164. package/dist/utils/youtube-channel.d.ts +9 -2
  165. package/dist/utils/youtube-channel.d.ts.map +1 -1
  166. package/dist/utils/youtube-channel.js +33 -3
  167. package/dist/utils/youtube-channel.js.map +1 -1
  168. package/dist/utils/youtube-schemas.d.ts +98 -0
  169. package/dist/utils/youtube-schemas.d.ts.map +1 -1
  170. package/dist/utils/youtube-schemas.js +30 -0
  171. package/dist/utils/youtube-schemas.js.map +1 -1
  172. package/dist/utils/youtube-video.d.ts +22 -1
  173. package/dist/utils/youtube-video.d.ts.map +1 -1
  174. package/dist/utils/youtube-video.js +40 -28
  175. package/dist/utils/youtube-video.js.map +1 -1
  176. package/dist/utils/ytdlp-limiter.d.ts +3 -0
  177. package/dist/utils/ytdlp-limiter.d.ts.map +1 -1
  178. package/dist/utils/ytdlp-limiter.js +19 -3
  179. package/dist/utils/ytdlp-limiter.js.map +1 -1
  180. package/dist/utils/ytdlp-pacer.d.ts +64 -0
  181. package/dist/utils/ytdlp-pacer.d.ts.map +1 -0
  182. package/dist/utils/ytdlp-pacer.js +165 -0
  183. package/dist/utils/ytdlp-pacer.js.map +1 -0
  184. package/dist/utils/ytdlp-stream.d.ts +32 -0
  185. package/dist/utils/ytdlp-stream.d.ts.map +1 -0
  186. package/dist/utils/ytdlp-stream.js +61 -0
  187. package/dist/utils/ytdlp-stream.js.map +1 -0
  188. package/dist/utils/ytdlp.d.ts +10 -0
  189. package/dist/utils/ytdlp.d.ts.map +1 -1
  190. package/dist/utils/ytdlp.js +36 -25
  191. package/dist/utils/ytdlp.js.map +1 -1
  192. package/package.json +2 -2
@@ -0,0 +1,110 @@
1
+ import { z } from 'zod';
2
+ import { coverageSchema, harvestScopeSchema } from '../harvest-schemas.js';
3
+ import { assertCoverageConsistent } from './coverage.js';
4
+ import { queryRow, queryRows, sqliteBoolean } from './store-rows.js';
5
+ /**
6
+ * Persisting completeness receipts.
7
+ *
8
+ * Written in the same transaction as the rows they describe. That is the whole
9
+ * reason this store is a database: there is no interval in which a receipt can
10
+ * claim something the store does not hold, so the reconciliation dance that
11
+ * `brain-build.ts` performs between its manifest and its chunks has no
12
+ * analogue here.
13
+ */
14
+ const receiptRowSchema = z.object({
15
+ scope: harvestScopeSchema,
16
+ target_id: z.string(),
17
+ state: z.string(),
18
+ reason: z.string(),
19
+ have: z.number(),
20
+ expected: z.number().nullable(),
21
+ expected_source: z.string().nullable(),
22
+ source: z.string(),
23
+ limit_applied: z.number().nullable(),
24
+ sort_applied: z.string().nullable(),
25
+ ran_to_exhaustion: sqliteBoolean.nullable(),
26
+ resume_token: z.string().nullable(),
27
+ started_at: z.number(),
28
+ finished_at: z.number().nullable(),
29
+ attempts: z.number(),
30
+ error_code: z.string().nullable(),
31
+ note: z.string().nullable(),
32
+ });
33
+ const UPSERT = `
34
+ INSERT INTO harvest_receipt (
35
+ scope, target_id, state, reason, have, expected, expected_source, source,
36
+ limit_applied, sort_applied, ran_to_exhaustion, resume_token,
37
+ started_at, finished_at, attempts, error_code, note
38
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
39
+ ON CONFLICT(scope, target_id) DO UPDATE SET
40
+ state = excluded.state, reason = excluded.reason, have = excluded.have,
41
+ expected = excluded.expected, expected_source = excluded.expected_source,
42
+ source = excluded.source, limit_applied = excluded.limit_applied,
43
+ sort_applied = excluded.sort_applied, ran_to_exhaustion = excluded.ran_to_exhaustion,
44
+ resume_token = excluded.resume_token, finished_at = excluded.finished_at,
45
+ attempts = harvest_receipt.attempts + 1, error_code = excluded.error_code,
46
+ note = excluded.note`;
47
+ /** `complete` is the receipt's own word; the rest describe how it ended. */
48
+ function stateOf(coverage) {
49
+ if (coverage.complete)
50
+ return 'complete';
51
+ if (coverage.reason === 'SOURCE_REFUSED' || coverage.reason === 'TIMEOUT')
52
+ return 'failed';
53
+ if (coverage.reason === 'NOT_ATTEMPTED')
54
+ return 'running';
55
+ return 'partial';
56
+ }
57
+ function toRow(coverage) {
58
+ // Re-checked on the way to disk as well as on the way out of coverageOf: a
59
+ // receipt that reaches storage wrong is one that will be believed later.
60
+ assertCoverageConsistent(coverage);
61
+ return coverage;
62
+ }
63
+ export function saveReceipt(database, coverage, options = {}) {
64
+ const checked = toRow(coverage);
65
+ const finishedAt = Date.parse(checked.harvestedAt);
66
+ database
67
+ .prepare(UPSERT)
68
+ .run(checked.scope, checked.targetId, stateOf(checked), checked.reason, checked.have, checked.expected ?? null, checked.expectedSource ?? null, checked.source, checked.limitApplied ?? null, checked.sortApplied ?? null, checked.ranToExhaustion === undefined ? null : Number(checked.ranToExhaustion), checked.resumeToken ?? null, options.startedAt ?? finishedAt, finishedAt, 0, options.errorCode ?? null, checked.note ?? null);
69
+ database
70
+ .prepare('INSERT INTO harvest_event (scope, target_id, at, state, delta, note) VALUES (?,?,?,?,?,?)')
71
+ .run(checked.scope, checked.targetId, finishedAt, stateOf(checked), checked.have, checked.reason);
72
+ }
73
+ function fromRow(row) {
74
+ const expected = row.expected;
75
+ const expectedSource = row.expected_source;
76
+ return coverageSchema.parse({
77
+ scope: row.scope,
78
+ targetId: row.target_id,
79
+ complete: row.state === 'complete',
80
+ reason: row.reason,
81
+ have: row.have,
82
+ ...(expected === null || expectedSource === null ? {} : { expected, expectedSource }),
83
+ ...(row.ran_to_exhaustion === null ? {} : { ranToExhaustion: row.ran_to_exhaustion }),
84
+ source: row.source,
85
+ harvestedAt: new Date(row.finished_at ?? row.started_at).toISOString(),
86
+ ...(row.limit_applied === null ? {} : { limitApplied: row.limit_applied }),
87
+ ...(row.sort_applied === null ? {} : { sortApplied: row.sort_applied }),
88
+ ...(row.resume_token === null ? {} : { resumeToken: row.resume_token }),
89
+ ...(row.note === null ? {} : { note: row.note }),
90
+ });
91
+ }
92
+ export function readReceipt(database, scope, targetId) {
93
+ const row = queryRow(database.prepare('SELECT * FROM harvest_receipt WHERE scope = ? AND target_id = ?'), receiptRowSchema, scope, targetId);
94
+ return row === undefined ? undefined : fromRow(row);
95
+ }
96
+ export function listReceipts(database, options = {}) {
97
+ const limit = options.limit ?? 50;
98
+ const offset = options.offset ?? 0;
99
+ const rows = options.scope === undefined
100
+ ? queryRows(database.prepare('SELECT * FROM harvest_receipt ORDER BY finished_at DESC, target_id LIMIT ? OFFSET ?'), receiptRowSchema, limit, offset)
101
+ : queryRows(database.prepare('SELECT * FROM harvest_receipt WHERE scope = ? ORDER BY finished_at DESC, target_id LIMIT ? OFFSET ?'), receiptRowSchema, options.scope, limit, offset);
102
+ return rows.map(fromRow);
103
+ }
104
+ export function countReceipts(database, scope) {
105
+ const row = scope === undefined
106
+ ? queryRow(database.prepare('SELECT COUNT(*) AS n FROM harvest_receipt'), z.object({ n: z.number() }))
107
+ : queryRow(database.prepare('SELECT COUNT(*) AS n FROM harvest_receipt WHERE scope = ?'), z.object({ n: z.number() }), scope);
108
+ return row?.n ?? 0;
109
+ }
110
+ //# sourceMappingURL=harvest-receipts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harvest-receipts.js","sourceRoot":"","sources":["../../src/utils/harvest-receipts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * The cursor `fetch_videos` hands back.
4
+ *
5
+ * YouTube's listings are continuation-paginated, so `--playlist-items 101:120`
6
+ * still walks from the start: paging server-side is O(n) per page and O(n^2)
7
+ * overall. It is also not a cursor in any useful sense — a new upload shifts
8
+ * every index by one, so an offset silently re-reads or skips at the seam.
9
+ *
10
+ * So the cursor carries the next index *and* the id of the last item already
11
+ * returned. If that id reappears on the next page the listing moved under us,
12
+ * and the duplicate is dropped rather than handed over twice.
13
+ */
14
+ declare const cursorSchema: z.ZodObject<{
15
+ v: z.ZodLiteral<1>;
16
+ start: z.ZodNumber;
17
+ anchor: z.ZodString;
18
+ }, "strip", z.ZodTypeAny, {
19
+ anchor: string;
20
+ start: number;
21
+ v: 1;
22
+ }, {
23
+ anchor: string;
24
+ start: number;
25
+ v: 1;
26
+ }>;
27
+ export type ListingCursor = z.infer<typeof cursorSchema>;
28
+ export declare function encodeCursor(cursor: ListingCursor): string;
29
+ export declare function decodeCursor(token: string): ListingCursor;
30
+ export {};
31
+ //# sourceMappingURL=listing-cursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listing-cursor.d.ts","sourceRoot":"","sources":["../../src/utils/listing-cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;GAWG;AAEH,QAAA,MAAM,YAAY;;;;;;;;;;;;EAIhB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAEzD,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAE1D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAezD"}
@@ -0,0 +1,39 @@
1
+ import { z } from 'zod';
2
+ import { YouTubeError } from './errors.js';
3
+ /**
4
+ * The cursor `fetch_videos` hands back.
5
+ *
6
+ * YouTube's listings are continuation-paginated, so `--playlist-items 101:120`
7
+ * still walks from the start: paging server-side is O(n) per page and O(n^2)
8
+ * overall. It is also not a cursor in any useful sense — a new upload shifts
9
+ * every index by one, so an offset silently re-reads or skips at the seam.
10
+ *
11
+ * So the cursor carries the next index *and* the id of the last item already
12
+ * returned. If that id reappears on the next page the listing moved under us,
13
+ * and the duplicate is dropped rather than handed over twice.
14
+ */
15
+ const cursorSchema = z.object({
16
+ v: z.literal(1),
17
+ start: z.number().int().min(1),
18
+ anchor: z.string(),
19
+ });
20
+ export function encodeCursor(cursor) {
21
+ return Buffer.from(JSON.stringify(cursor), 'utf8').toString('base64url');
22
+ }
23
+ export function decodeCursor(token) {
24
+ const parsed = (() => {
25
+ try {
26
+ return JSON.parse(Buffer.from(token, 'base64url').toString('utf8'));
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ })();
32
+ const result = cursorSchema.safeParse(parsed);
33
+ if (result.success)
34
+ return result.data;
35
+ throw new YouTubeError('INVALID_INPUT', 'That cursor is not one this server issued.', {
36
+ nextStep: 'Omit `cursor` to start from the first page.',
37
+ });
38
+ }
39
+ //# sourceMappingURL=listing-cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listing-cursor.js","sourceRoot":"","sources":["../../src/utils/listing-cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;GAWG;AAEH,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5B,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACf,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;CACnB,CAAC,CAAC;AAIH,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,MAAM,GAAG,CAAC,GAAY,EAAE;QAC5B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IAEL,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC;IAEvC,MAAM,IAAI,YAAY,CAAC,eAAe,EAAE,4CAA4C,EAAE;QACpF,QAAQ,EAAE,6CAA6C;KACxD,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Session preflight: what yt-dlp can prove about itself before we ask YouTube
3
+ * anything.
4
+ *
5
+ * Measured on 2026-09-06: with no PO token provider installed, every *player*
6
+ * request from this host failed with "Sign in to confirm you're not a bot",
7
+ * while flat listings were never affected. Rotating `player_client` across six
8
+ * clients and waiting out a 15-minute cooldown both failed. The cause was not
9
+ * rate limiting and not the address alone — `yt-dlp -v` reported
10
+ * `PO Token Providers: none`, so every player request went out without the
11
+ * integrity token a browser attaches.
12
+ *
13
+ * That diagnosis is only useful if the server can see it, so this module reads
14
+ * it out of yt-dlp's own debug output rather than guessing. It is deliberately
15
+ * provider-agnostic: it reports what yt-dlp says it has, and never looks for a
16
+ * particular plugin by name.
17
+ */
18
+ export interface ProviderStatus {
19
+ name: string;
20
+ available: boolean;
21
+ /** Flags yt-dlp printed in parentheses, e.g. "external". */
22
+ flags: string[];
23
+ }
24
+ export interface SessionReport {
25
+ /** False when yt-dlp reports no usable PO token provider. */
26
+ potAvailable: boolean;
27
+ potProviders: ProviderStatus[];
28
+ jsRuntimes: ProviderStatus[];
29
+ impersonateTargets: string[];
30
+ /** Set when the probe itself could not run; the report is then unknown, not empty. */
31
+ probeFailed?: string;
32
+ }
33
+ /**
34
+ * Split on commas that separate entries, not the ones inside a parenthesised
35
+ * flag list. yt-dlp prints `bgutil:http-1.3.2 (external, unavailable)` — a
36
+ * plain `split(',')` tears that entry in half and loses the flag that matters.
37
+ */
38
+ export declare function splitTopLevel(value: string): string[];
39
+ /** Parses one `Providers: ...` payload. `none` is an empty list, not a provider. */
40
+ export declare function parseProviderList(payload: string): ProviderStatus[];
41
+ /**
42
+ * Reads `yt-dlp --list-impersonate-targets`, keeping only rows that are
43
+ * actually usable. Every target is unavailable without curl_cffi, and passing
44
+ * `--impersonate` to a build that lacks it fails every spawn — so an
45
+ * unavailable target must be visible here rather than discovered per request.
46
+ */
47
+ export declare function parseImpersonateTargets(stdout: string): string[];
48
+ export declare function runSessionPreflight(options?: {
49
+ force?: boolean;
50
+ }): Promise<SessionReport>;
51
+ /** Test seam — the report is cached for the process lifetime in normal use. */
52
+ export declare function resetSessionPreflightCache(): void;
53
+ //# sourceMappingURL=pot-preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pot-preflight.d.ts","sourceRoot":"","sources":["../../src/utils/pot-preflight.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAyBD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBrD;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAenE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBhE;AAqBD,wBAAsB,mBAAmB,CACvC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAChC,OAAO,CAAC,aAAa,CAAC,CAwCxB;AAED,+EAA+E;AAC/E,wBAAgB,0BAA0B,IAAI,IAAI,CAGjD"}
@@ -0,0 +1,146 @@
1
+ import { execa } from 'execa';
2
+ const PROBE_TIMEOUT_MS = 30_000;
3
+ const FAILED_REPORT_TTL_MS = 60_000;
4
+ /**
5
+ * The `[youtube] [pot]` and `[youtube] [jsc]` debug lines are emitted only when
6
+ * the *video* extractor initialises. Measured 2026-09-06: `--version`,
7
+ * `ytsearch0:` and a `--flat-playlist` channel walk all produce neither line —
8
+ * the listing plane never touches either subsystem.
9
+ *
10
+ * So the probe uses a syntactically valid but nonexistent video id. yt-dlp
11
+ * initialises the extractor, prints both lines, then fails the lookup. That
12
+ * failure is expected and ignored: it costs one request, depends on no real
13
+ * video continuing to exist, and needs no fixture to be kept up to date.
14
+ */
15
+ const PROBE_VIDEO_URL = 'https://www.youtube.com/watch?v=00000000000';
16
+ const PROBE_ARGS = ['-v', '--simulate', '--skip-download', PROBE_VIDEO_URL];
17
+ const POT_LINE = /PO Token Providers:\s*(.+)$/;
18
+ const JSC_LINE = /JS Challenge Providers:\s*(.+)$/;
19
+ let cached;
20
+ let cachedAt = 0;
21
+ /**
22
+ * Split on commas that separate entries, not the ones inside a parenthesised
23
+ * flag list. yt-dlp prints `bgutil:http-1.3.2 (external, unavailable)` — a
24
+ * plain `split(',')` tears that entry in half and loses the flag that matters.
25
+ */
26
+ export function splitTopLevel(value) {
27
+ const parts = [];
28
+ let depth = 0;
29
+ let start = 0;
30
+ for (let i = 0; i < value.length; i += 1) {
31
+ const char = value[i];
32
+ if (char === '(')
33
+ depth += 1;
34
+ else if (char === ')')
35
+ depth = Math.max(0, depth - 1);
36
+ else if (char === ',' && depth === 0) {
37
+ parts.push(value.slice(start, i));
38
+ start = i + 1;
39
+ }
40
+ }
41
+ parts.push(value.slice(start));
42
+ return parts.map((part) => part.trim()).filter((part) => part.length > 0);
43
+ }
44
+ /** Parses one `Providers: ...` payload. `none` is an empty list, not a provider. */
45
+ export function parseProviderList(payload) {
46
+ const trimmed = payload.trim();
47
+ if (trimmed === '' || trimmed.toLowerCase() === 'none')
48
+ return [];
49
+ return splitTopLevel(trimmed).map((entry) => {
50
+ const match = /^(.*?)\s*\((.*)\)$/.exec(entry);
51
+ if (match === null)
52
+ return { name: entry, available: true, flags: [] };
53
+ const flags = splitTopLevel(match[2] ?? '').map((flag) => flag.toLowerCase());
54
+ return {
55
+ name: (match[1] ?? '').trim(),
56
+ available: !flags.includes('unavailable'),
57
+ flags,
58
+ };
59
+ });
60
+ }
61
+ /**
62
+ * Reads `yt-dlp --list-impersonate-targets`, keeping only rows that are
63
+ * actually usable. Every target is unavailable without curl_cffi, and passing
64
+ * `--impersonate` to a build that lacks it fails every spawn — so an
65
+ * unavailable target must be visible here rather than discovered per request.
66
+ */
67
+ export function parseImpersonateTargets(stdout) {
68
+ const targets = [];
69
+ for (const raw of stdout.split('\n')) {
70
+ const line = raw.trim();
71
+ if (line === '' || line.startsWith('[') || line.startsWith('-'))
72
+ continue;
73
+ if (line.toLowerCase().startsWith('client'))
74
+ continue;
75
+ if (line.toLowerCase().includes('(unavailable)'))
76
+ continue;
77
+ const columns = line.split(/\s{2,}/).filter((column) => column.length > 0);
78
+ const client = columns[0]?.trim();
79
+ if (client === undefined || client === '')
80
+ continue;
81
+ const os = columns[1]?.trim();
82
+ targets.push(os === undefined || os === '' || os === '-' ? client : `${client}:${os}`);
83
+ }
84
+ return targets;
85
+ }
86
+ async function probe(args) {
87
+ try {
88
+ // yt-dlp writes debug lines to stderr and exits non-zero on some probes;
89
+ // `reject: false` keeps both streams instead of throwing them away.
90
+ const result = await execa('yt-dlp', args, { timeout: PROBE_TIMEOUT_MS, reject: false });
91
+ return { stdout: result.stdout, stderr: result.stderr };
92
+ }
93
+ catch {
94
+ return undefined;
95
+ }
96
+ }
97
+ function readProviders(output, pattern) {
98
+ for (const line of output.split('\n')) {
99
+ const match = pattern.exec(line);
100
+ if (match !== null)
101
+ return parseProviderList(match[1] ?? '');
102
+ }
103
+ return [];
104
+ }
105
+ export async function runSessionPreflight(options = {}) {
106
+ // Mirrors runPreflight: a good answer holds for the process, a bad one gets a
107
+ // short life so one slow spawn does not pin the diagnosis for good.
108
+ if (cached !== undefined && !options.force) {
109
+ if (cached.probeFailed === undefined || Date.now() - cachedAt < FAILED_REPORT_TTL_MS) {
110
+ return cached;
111
+ }
112
+ }
113
+ const [session, impersonate] = await Promise.all([
114
+ probe(PROBE_ARGS),
115
+ probe(['--list-impersonate-targets']),
116
+ ]);
117
+ if (session === undefined) {
118
+ cached = {
119
+ potAvailable: false,
120
+ potProviders: [],
121
+ jsRuntimes: [],
122
+ impersonateTargets: [],
123
+ probeFailed: 'yt-dlp could not be probed for session capabilities.',
124
+ };
125
+ cachedAt = Date.now();
126
+ return cached;
127
+ }
128
+ const output = `${session.stderr}\n${session.stdout}`;
129
+ const potProviders = readProviders(output, POT_LINE);
130
+ cached = {
131
+ potAvailable: potProviders.some((provider) => provider.available),
132
+ potProviders,
133
+ jsRuntimes: readProviders(output, JSC_LINE),
134
+ impersonateTargets: impersonate === undefined
135
+ ? []
136
+ : parseImpersonateTargets(`${impersonate.stdout}\n${impersonate.stderr}`),
137
+ };
138
+ cachedAt = Date.now();
139
+ return cached;
140
+ }
141
+ /** Test seam — the report is cached for the process lifetime in normal use. */
142
+ export function resetSessionPreflightCache() {
143
+ cached = undefined;
144
+ cachedAt = 0;
145
+ }
146
+ //# sourceMappingURL=pot-preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pot-preflight.js","sourceRoot":"","sources":["../../src/utils/pot-preflight.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A cancellable pause.
3
+ *
4
+ * Shared by the retry loop and the pacer so a cooling-down server still
5
+ * answers a client that gives up: an uncancellable sleep is how a cooldown
6
+ * turns into a wedge.
7
+ */
8
+ export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
9
+ //# sourceMappingURL=sleep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep.d.ts","sourceRoot":"","sources":["../../src/utils/sleep.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB3E"}
@@ -0,0 +1,25 @@
1
+ import { YouTubeError } from './errors.js';
2
+ /**
3
+ * A cancellable pause.
4
+ *
5
+ * Shared by the retry loop and the pacer so a cooling-down server still
6
+ * answers a client that gives up: an uncancellable sleep is how a cooldown
7
+ * turns into a wedge.
8
+ */
9
+ export async function sleep(ms, signal) {
10
+ if (signal?.aborted === true) {
11
+ throw new YouTubeError('CANCELLED', 'The request was cancelled.');
12
+ }
13
+ await new Promise((resolve, reject) => {
14
+ const timer = setTimeout(() => {
15
+ signal?.removeEventListener('abort', onAbort);
16
+ resolve();
17
+ }, ms);
18
+ const onAbort = () => {
19
+ clearTimeout(timer);
20
+ reject(new YouTubeError('CANCELLED', 'The request was cancelled.'));
21
+ };
22
+ signal?.addEventListener('abort', onAbort, { once: true });
23
+ });
24
+ }
25
+ //# sourceMappingURL=sleep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../src/utils/sleep.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,EAAU,EAAE,MAAoB;IAC1D,IAAI,MAAM,EAAE,OAAO,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,IAAI,YAAY,CAAC,WAAW,EAAE,4BAA4B,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC;QACZ,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,IAAI,YAAY,CAAC,WAAW,EAAE,4BAA4B,CAAC,CAAC,CAAC;QACtE,CAAC,CAAC;QACF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * What can be said about the store without harvesting anything.
3
+ *
4
+ * Integrity is checked here and nowhere else on the hot path: `quick_check` is
5
+ * bounded and catches the realistic failures, while the full `integrity_check`
6
+ * is O(database) and would add seconds to every server start on a store that
7
+ * is measured in gigabytes.
8
+ */
9
+ export interface StoreHealth {
10
+ enabled: boolean;
11
+ path: string;
12
+ exists: boolean;
13
+ storeVersion?: number;
14
+ sizeBytes: number;
15
+ walBytes: number;
16
+ integrity: 'ok' | 'failed' | 'unchecked';
17
+ channels: number;
18
+ videos: number;
19
+ comments: number;
20
+ receipts: {
21
+ complete: number;
22
+ partial: number;
23
+ running: number;
24
+ failed: number;
25
+ };
26
+ error?: string;
27
+ }
28
+ export declare function readStoreHealth(): Promise<StoreHealth>;
29
+ /**
30
+ * Whether the store should make an overall health report say "not ready".
31
+ *
32
+ * A missing store must not: a fresh install has none, and check_health's job
33
+ * is diagnosing the external binaries. A store that fails its integrity check,
34
+ * or claims a version this build does not understand, must.
35
+ */
36
+ export declare function storeFailsHealth(health: StoreHealth): boolean;
37
+ //# sourceMappingURL=store-health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-health.d.ts","sourceRoot":"","sources":["../../src/utils/store-health.ts"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AAEH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,GAAG,QAAQ,GAAG,WAAW,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAiBD,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC,CA0D5D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAG7D"}
@@ -0,0 +1,92 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import { z } from 'zod';
3
+ import { countRows, queryRow } from './store-rows.js';
4
+ import { getStore, readUserVersion } from './store.js';
5
+ import { storeDatabasePath } from './store-paths.js';
6
+ import { STORE_VERSION } from './store-schema.js';
7
+ const quickCheckSchema = z.object({ quick_check: z.string() }).passthrough();
8
+ const stateCountSchema = z.object({ state: z.string(), n: z.number() }).passthrough();
9
+ async function sizeOf(path) {
10
+ try {
11
+ return (await stat(path)).size;
12
+ }
13
+ catch {
14
+ return 0;
15
+ }
16
+ }
17
+ function emptyReceipts() {
18
+ return { complete: 0, partial: 0, running: 0, failed: 0 };
19
+ }
20
+ export async function readStoreHealth() {
21
+ const path = storeDatabasePath();
22
+ const [sizeBytes, walBytes] = await Promise.all([sizeOf(path), sizeOf(`${path}-wal`)]);
23
+ const base = {
24
+ enabled: true,
25
+ path,
26
+ exists: sizeBytes > 0,
27
+ sizeBytes,
28
+ // A -wal file that never shrinks means a reader is pinning it, which is
29
+ // worth seeing before it becomes a disk-space surprise.
30
+ walBytes,
31
+ integrity: 'unchecked',
32
+ channels: 0,
33
+ videos: 0,
34
+ comments: 0,
35
+ receipts: emptyReceipts(),
36
+ };
37
+ try {
38
+ const store = await getStore();
39
+ const quick = queryRow(store.prepare('PRAGMA quick_check'), quickCheckSchema);
40
+ const receipts = emptyReceipts();
41
+ for (const row of store
42
+ .prepare('SELECT state, COUNT(*) AS n FROM harvest_receipt GROUP BY state')
43
+ .all()) {
44
+ const parsed = stateCountSchema.safeParse(row);
45
+ if (!parsed.success)
46
+ continue;
47
+ // A `state` this build does not know about is counted nowhere rather
48
+ // than crashing: the enum is allowed to grow ahead of this reader.
49
+ const { state, n } = parsed.data;
50
+ if (state === 'complete')
51
+ receipts.complete = n;
52
+ else if (state === 'partial')
53
+ receipts.partial = n;
54
+ else if (state === 'running')
55
+ receipts.running = n;
56
+ else if (state === 'failed')
57
+ receipts.failed = n;
58
+ }
59
+ return {
60
+ ...base,
61
+ storeVersion: readUserVersion(store),
62
+ integrity: quick?.quick_check === 'ok' ? 'ok' : 'failed',
63
+ channels: countRows(store.prepare('SELECT COUNT(*) FROM channel')),
64
+ videos: countRows(store.prepare('SELECT COUNT(*) FROM video')),
65
+ comments: countRows(store.prepare('SELECT COUNT(*) FROM comment')),
66
+ receipts,
67
+ };
68
+ }
69
+ catch (error) {
70
+ // A store that cannot be opened is a reportable condition, not a reason
71
+ // for check_health itself to fail: the binaries it exists to diagnose are
72
+ // unaffected.
73
+ return {
74
+ ...base,
75
+ enabled: false,
76
+ error: error instanceof Error ? error.message : String(error),
77
+ };
78
+ }
79
+ }
80
+ /**
81
+ * Whether the store should make an overall health report say "not ready".
82
+ *
83
+ * A missing store must not: a fresh install has none, and check_health's job
84
+ * is diagnosing the external binaries. A store that fails its integrity check,
85
+ * or claims a version this build does not understand, must.
86
+ */
87
+ export function storeFailsHealth(health) {
88
+ if (health.integrity === 'failed')
89
+ return true;
90
+ return health.storeVersion !== undefined && health.storeVersion > STORE_VERSION;
91
+ }
92
+ //# sourceMappingURL=store-health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-health.js","sourceRoot":"","sources":["../../src/utils/store-health.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ /**
3
+ * Ordered schema migrations for the harvested store.
4
+ *
5
+ * `PRAGMA user_version` carries the store's version, deliberately independent
6
+ * of the server's semver: the schema changes far less often than the code, and
7
+ * tying them together would force an empty migration on every release.
8
+ *
9
+ * Version 0 means "no store yet". Creating the tables is `STORE_SCHEMA`'s job,
10
+ * not a migration's, so the first run only stamps the version.
11
+ */
12
+ export interface StoreMigration {
13
+ /** The version this migration produces. */
14
+ to: number;
15
+ apply: (database: DatabaseSync) => void;
16
+ }
17
+ /**
18
+ * Empty on purpose at v1: `STORE_SCHEMA` is `CREATE TABLE IF NOT EXISTS`
19
+ * throughout, so a fresh database and an existing v1 database converge without
20
+ * one. The list exists so the second schema change is a one-line addition
21
+ * rather than a retrofit.
22
+ */
23
+ export declare const STORE_MIGRATIONS: readonly StoreMigration[];
24
+ /**
25
+ * Brings a database up to STORE_VERSION.
26
+ *
27
+ * Each migration runs inside its own transaction so a failure leaves the
28
+ * store at the last version that fully applied, rather than half-way through
29
+ * one. A newer-than-known version is rejected before this is reached, in
30
+ * `store.ts`.
31
+ */
32
+ export declare function runStoreMigrations(database: DatabaseSync, fromVersion: number): number;
33
+ //# sourceMappingURL=store-migrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-migrations.d.ts","sourceRoot":"","sources":["../../src/utils/store-migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD;;;;;;;;;GASG;AAEH,MAAM,WAAW,cAAc;IAC7B,2CAA2C;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;CACzC;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAAO,CAAC;AAE9D;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAwBtF"}