@trieb.work/payload-plugin-backup-mongodb 0.1.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.
Files changed (165) hide show
  1. package/README.md +319 -0
  2. package/dist/backup-dashboard.css +1132 -0
  3. package/dist/client.d.ts +3 -0
  4. package/dist/client.js +3 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/collections/BackupSettings.d.ts +6 -0
  7. package/dist/collections/BackupSettings.js +91 -0
  8. package/dist/collections/BackupSettings.js.map +1 -0
  9. package/dist/collections/BackupTasks.d.ts +2 -0
  10. package/dist/collections/BackupTasks.js +56 -0
  11. package/dist/collections/BackupTasks.js.map +1 -0
  12. package/dist/components/BackupDashboard/BackupItemActions.client.d.ts +7 -0
  13. package/dist/components/BackupDashboard/BackupItemActions.client.js +90 -0
  14. package/dist/components/BackupDashboard/BackupItemActions.client.js.map +1 -0
  15. package/dist/components/BackupDashboard/BackupListCollapsible.client.d.ts +16 -0
  16. package/dist/components/BackupDashboard/BackupListCollapsible.client.js +597 -0
  17. package/dist/components/BackupDashboard/BackupListCollapsible.client.js.map +1 -0
  18. package/dist/components/BackupDashboard/BackupSettingsModal.client.d.ts +2 -0
  19. package/dist/components/BackupDashboard/BackupSettingsModal.client.js +939 -0
  20. package/dist/components/BackupDashboard/BackupSettingsModal.client.js.map +1 -0
  21. package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.d.ts +18 -0
  22. package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.js +267 -0
  23. package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.js.map +1 -0
  24. package/dist/components/BackupDashboard/ManualBackupDialog.client.d.ts +1 -0
  25. package/dist/components/BackupDashboard/ManualBackupDialog.client.js +276 -0
  26. package/dist/components/BackupDashboard/ManualBackupDialog.client.js.map +1 -0
  27. package/dist/components/BackupDashboard/RestoreBackupDialog.client.d.ts +6 -0
  28. package/dist/components/BackupDashboard/RestoreBackupDialog.client.js +551 -0
  29. package/dist/components/BackupDashboard/RestoreBackupDialog.client.js.map +1 -0
  30. package/dist/components/BackupDashboard/TaskActionButton.client.d.ts +29 -0
  31. package/dist/components/BackupDashboard/TaskActionButton.client.js +240 -0
  32. package/dist/components/BackupDashboard/TaskActionButton.client.js.map +1 -0
  33. package/dist/components/BackupDashboard/backupDashboardInlineCss.d.ts +1 -0
  34. package/dist/components/BackupDashboard/backupDashboardInlineCss.js +3 -0
  35. package/dist/components/BackupDashboard/backupDashboardInlineCss.js.map +1 -0
  36. package/dist/components/BackupDashboard/index.client.d.ts +8 -0
  37. package/dist/components/BackupDashboard/index.client.js +9 -0
  38. package/dist/components/BackupDashboard/index.client.js.map +1 -0
  39. package/dist/components/BackupDashboard/index.d.ts +8 -0
  40. package/dist/components/BackupDashboard/index.js +196 -0
  41. package/dist/components/BackupDashboard/index.js.map +1 -0
  42. package/dist/core/archive.d.ts +8 -0
  43. package/dist/core/archive.js +47 -0
  44. package/dist/core/archive.js.map +1 -0
  45. package/dist/core/backup.d.ts +41 -0
  46. package/dist/core/backup.js +188 -0
  47. package/dist/core/backup.js.map +1 -0
  48. package/dist/core/backupBlobIO.d.ts +54 -0
  49. package/dist/core/backupBlobIO.js +179 -0
  50. package/dist/core/backupBlobIO.js.map +1 -0
  51. package/dist/core/backupBlobTransfer.d.ts +29 -0
  52. package/dist/core/backupBlobTransfer.js +85 -0
  53. package/dist/core/backupBlobTransfer.js.map +1 -0
  54. package/dist/core/backupSettings.d.ts +35 -0
  55. package/dist/core/backupSettings.js +95 -0
  56. package/dist/core/backupSettings.js.map +1 -0
  57. package/dist/core/backupSourcePreview.d.ts +10 -0
  58. package/dist/core/backupSourcePreview.js +33 -0
  59. package/dist/core/backupSourcePreview.js.map +1 -0
  60. package/dist/core/blobTokenValidate.d.ts +14 -0
  61. package/dist/core/blobTokenValidate.js +75 -0
  62. package/dist/core/blobTokenValidate.js.map +1 -0
  63. package/dist/core/db.d.ts +24 -0
  64. package/dist/core/db.js +12 -0
  65. package/dist/core/db.js.map +1 -0
  66. package/dist/core/restore.d.ts +27 -0
  67. package/dist/core/restore.js +199 -0
  68. package/dist/core/restore.js.map +1 -0
  69. package/dist/core/restorePreview.d.ts +64 -0
  70. package/dist/core/restorePreview.js +266 -0
  71. package/dist/core/restorePreview.js.map +1 -0
  72. package/dist/core/taskProgress.d.ts +26 -0
  73. package/dist/core/taskProgress.js +73 -0
  74. package/dist/core/taskProgress.js.map +1 -0
  75. package/dist/core/vercelBackupCron.d.ts +8 -0
  76. package/dist/core/vercelBackupCron.js +38 -0
  77. package/dist/core/vercelBackupCron.js.map +1 -0
  78. package/dist/endpoints/createBackupEndpoints.d.ts +1 -0
  79. package/dist/endpoints/createBackupEndpoints.js +3 -0
  80. package/dist/endpoints/createBackupEndpoints.js.map +1 -0
  81. package/dist/endpoints/index.d.ts +3 -0
  82. package/dist/endpoints/index.js +34 -0
  83. package/dist/endpoints/index.js.map +1 -0
  84. package/dist/endpoints/paths/admin-backup-download.d.ts +8 -0
  85. package/dist/endpoints/paths/admin-backup-download.js +72 -0
  86. package/dist/endpoints/paths/admin-backup-download.js.map +1 -0
  87. package/dist/endpoints/paths/admin-delete.d.ts +3 -0
  88. package/dist/endpoints/paths/admin-delete.js +55 -0
  89. package/dist/endpoints/paths/admin-delete.js.map +1 -0
  90. package/dist/endpoints/paths/admin-manual.d.ts +3 -0
  91. package/dist/endpoints/paths/admin-manual.js +61 -0
  92. package/dist/endpoints/paths/admin-manual.js.map +1 -0
  93. package/dist/endpoints/paths/admin-preview.d.ts +6 -0
  94. package/dist/endpoints/paths/admin-preview.js +102 -0
  95. package/dist/endpoints/paths/admin-preview.js.map +1 -0
  96. package/dist/endpoints/paths/admin-restore.d.ts +3 -0
  97. package/dist/endpoints/paths/admin-restore.js +74 -0
  98. package/dist/endpoints/paths/admin-restore.js.map +1 -0
  99. package/dist/endpoints/paths/admin-seed.d.ts +3 -0
  100. package/dist/endpoints/paths/admin-seed.js +47 -0
  101. package/dist/endpoints/paths/admin-seed.js.map +1 -0
  102. package/dist/endpoints/paths/admin-settings.d.ts +3 -0
  103. package/dist/endpoints/paths/admin-settings.js +230 -0
  104. package/dist/endpoints/paths/admin-settings.js.map +1 -0
  105. package/dist/endpoints/paths/admin-task.d.ts +3 -0
  106. package/dist/endpoints/paths/admin-task.js +34 -0
  107. package/dist/endpoints/paths/admin-task.js.map +1 -0
  108. package/dist/endpoints/paths/admin-validate-blob-token.d.ts +8 -0
  109. package/dist/endpoints/paths/admin-validate-blob-token.js +26 -0
  110. package/dist/endpoints/paths/admin-validate-blob-token.js.map +1 -0
  111. package/dist/endpoints/paths/cron-list.d.ts +2 -0
  112. package/dist/endpoints/paths/cron-list.js +33 -0
  113. package/dist/endpoints/paths/cron-list.js.map +1 -0
  114. package/dist/endpoints/paths/cron-restore.d.ts +2 -0
  115. package/dist/endpoints/paths/cron-restore.js +61 -0
  116. package/dist/endpoints/paths/cron-restore.js.map +1 -0
  117. package/dist/endpoints/paths/cron-run.d.ts +3 -0
  118. package/dist/endpoints/paths/cron-run.js +45 -0
  119. package/dist/endpoints/paths/cron-run.js.map +1 -0
  120. package/dist/endpoints/shared.d.ts +13 -0
  121. package/dist/endpoints/shared.js +58 -0
  122. package/dist/endpoints/shared.js.map +1 -0
  123. package/dist/exports/client.d.ts +1 -0
  124. package/dist/exports/client.js +3 -0
  125. package/dist/exports/client.js.map +1 -0
  126. package/dist/exports/rsc.d.ts +1 -0
  127. package/dist/exports/rsc.js +3 -0
  128. package/dist/exports/rsc.js.map +1 -0
  129. package/dist/index.d.ts +18 -0
  130. package/dist/index.js +14 -0
  131. package/dist/index.js.map +1 -0
  132. package/dist/plugin.d.ts +3 -0
  133. package/dist/plugin.js +82 -0
  134. package/dist/plugin.js.map +1 -0
  135. package/dist/publicApiPaths.d.ts +19 -0
  136. package/dist/publicApiPaths.js +20 -0
  137. package/dist/publicApiPaths.js.map +1 -0
  138. package/dist/types.d.ts +21 -0
  139. package/dist/types.js +3 -0
  140. package/dist/types.js.map +1 -0
  141. package/dist/utils/backupSelection.d.ts +6 -0
  142. package/dist/utils/backupSelection.js +16 -0
  143. package/dist/utils/backupSelection.js.map +1 -0
  144. package/dist/utils/blobName.d.ts +32 -0
  145. package/dist/utils/blobName.js +99 -0
  146. package/dist/utils/blobName.js.map +1 -0
  147. package/dist/utils/dashboardRoleAccess.d.ts +23 -0
  148. package/dist/utils/dashboardRoleAccess.js +64 -0
  149. package/dist/utils/dashboardRoleAccess.js.map +1 -0
  150. package/dist/utils/dialogBackdrop.d.ts +9 -0
  151. package/dist/utils/dialogBackdrop.js +17 -0
  152. package/dist/utils/dialogBackdrop.js.map +1 -0
  153. package/dist/utils/formatBytes.d.ts +2 -0
  154. package/dist/utils/formatBytes.js +20 -0
  155. package/dist/utils/formatBytes.js.map +1 -0
  156. package/dist/utils/hostname.d.ts +2 -0
  157. package/dist/utils/hostname.js +17 -0
  158. package/dist/utils/hostname.js.map +1 -0
  159. package/dist/utils/index.d.ts +5 -0
  160. package/dist/utils/index.js +7 -0
  161. package/dist/utils/index.js.map +1 -0
  162. package/dist/utils/maskBlobToken.d.ts +11 -0
  163. package/dist/utils/maskBlobToken.js +42 -0
  164. package/dist/utils/maskBlobToken.js.map +1 -0
  165. package/package.json +140 -0
@@ -0,0 +1,179 @@
1
+ import { put } from '@vercel/blob';
2
+ /**
3
+ * Returns the blob pathname (no leading slash) for a trusted Vercel Blob HTTPS URL, or null.
4
+ * The URL's path component is percent-decoded once so the result matches the literal pathname
5
+ * stored by Vercel Blob (e.g. blobs whose own pathname contains `%2F` are served from a URL that
6
+ * double-encodes it to `%252F`).
7
+ */ export function vercelBlobPathnameFromUrl(urlStr) {
8
+ try {
9
+ const u = new URL(urlStr);
10
+ if (u.protocol !== 'https:') {
11
+ return null;
12
+ }
13
+ if (!u.hostname.endsWith('.blob.vercel-storage.com')) {
14
+ return null;
15
+ }
16
+ const raw = u.pathname.replace(/^\//, '');
17
+ if (raw.length === 0) {
18
+ return null;
19
+ }
20
+ try {
21
+ return decodeURIComponent(raw);
22
+ } catch {
23
+ return raw;
24
+ }
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+ export function isTrustedBackupBlobReference(urlStr, expectedPathname) {
30
+ const fromUrl = vercelBlobPathnameFromUrl(urlStr);
31
+ return Boolean(fromUrl && fromUrl === expectedPathname);
32
+ }
33
+ /** True when the URL points to any Vercel Blob host (public or private). */ function isVercelBlobHost(urlStr) {
34
+ try {
35
+ const u = new URL(urlStr);
36
+ return u.protocol === 'https:' && u.hostname.endsWith('.blob.vercel-storage.com');
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+ /**
42
+ * Fetches a Vercel Blob URL with the Bearer token attached when the host belongs to Vercel Blob.
43
+ * Works transparently for public stores (token is ignored) and private stores (token is required).
44
+ * Tokens are never sent to non-Vercel hosts.
45
+ */ async function fetchBackupBlobUrl(url, token) {
46
+ const init = token && isVercelBlobHost(url) ? {
47
+ headers: {
48
+ authorization: `Bearer ${token}`
49
+ }
50
+ } : undefined;
51
+ return fetch(url, init);
52
+ }
53
+ /**
54
+ * Opens a readable stream for a backup object (public or private). Prefers the `downloadUrl` /
55
+ * `blobUrl` returned by `list()` because Vercel Blob already encodes the pathname correctly there
56
+ * (some legacy backups store literal `%2F` sequences inside the pathname, which the SDK's
57
+ * pathname-to-URL construction would misinterpret).
58
+ */ export async function streamBackupBlobForDownload(options) {
59
+ const { blobUrl, downloadUrl, pathname, token } = options;
60
+ const candidates = [];
61
+ if (downloadUrl && isTrustedBackupBlobReference(downloadUrl, pathname)) {
62
+ candidates.push(downloadUrl);
63
+ }
64
+ if (blobUrl && isTrustedBackupBlobReference(blobUrl, pathname)) {
65
+ candidates.push(blobUrl);
66
+ }
67
+ for (const target of candidates){
68
+ try {
69
+ const res = await fetchBackupBlobUrl(target, token);
70
+ if (res.ok && res.body) {
71
+ return {
72
+ contentType: res.headers.get('content-type') || 'application/octet-stream',
73
+ stream: res.body
74
+ };
75
+ }
76
+ } catch {
77
+ /* try next candidate */ }
78
+ }
79
+ return null;
80
+ }
81
+ export async function bufferFromWebReadableStream(stream) {
82
+ const reader = stream.getReader();
83
+ const chunks = [];
84
+ while(true){
85
+ const { done, value } = await reader.read();
86
+ if (done) {
87
+ break;
88
+ }
89
+ if (value) {
90
+ chunks.push(Buffer.from(value));
91
+ }
92
+ }
93
+ return Buffer.concat(chunks);
94
+ }
95
+ /**
96
+ * Read a backup blob using the trusted `downloadUrl` returned by `list()`. The Bearer token is
97
+ * always attached for Vercel Blob hosts; this works for public blobs (token ignored) and is
98
+ * required for private blobs.
99
+ */ export async function readBackupBlobContent(options) {
100
+ const { downloadUrl, pathname, token } = options;
101
+ const res = await fetchBackupBlobUrl(downloadUrl, token);
102
+ if (res.ok) {
103
+ return Buffer.from(await res.arrayBuffer());
104
+ }
105
+ throw new Error(`Failed to read backup blob (${pathname}): HTTP ${res.status}`);
106
+ }
107
+ /**
108
+ * Flexible read used during restore/preview/transfer where the source store visibility may differ
109
+ * from the new one. Always tries authenticated access first (correct for private stores and
110
+ * harmless for public ones). Falls back to anonymous fetch only when the URL is not a Vercel
111
+ * Blob URL, so tokens never leak to arbitrary hosts.
112
+ */ export async function readBackupBlobContentFlexible(pathname, downloadUrl, token, _sourceAccessHint) {
113
+ try {
114
+ const res = await fetchBackupBlobUrl(downloadUrl, token);
115
+ if (res.ok) {
116
+ return Buffer.from(await res.arrayBuffer());
117
+ }
118
+ } catch {
119
+ /* try anonymous fallback */ }
120
+ if (!isVercelBlobHost(downloadUrl)) {
121
+ try {
122
+ const res = await fetch(downloadUrl);
123
+ if (res.ok) {
124
+ return Buffer.from(await res.arrayBuffer());
125
+ }
126
+ } catch {
127
+ /* fall through to error */ }
128
+ }
129
+ throw new Error(`Failed to read backup blob ${pathname}`);
130
+ }
131
+ /**
132
+ * Uploads a backup blob with the preferred access level and transparently retries the other level
133
+ * if the Vercel Blob store rejects the preferred one (e.g. legacy public-only stores reject
134
+ * `access: 'private'`). Returns the effective access level that succeeded so callers can log/track
135
+ * it; throws the original error if both attempts fail.
136
+ */ export async function putBackupBlobContent(pathname, body, token, access) {
137
+ const order = access === 'private' ? [
138
+ 'private',
139
+ 'public'
140
+ ] : [
141
+ 'public',
142
+ 'private'
143
+ ];
144
+ let firstError;
145
+ for (const attempt of order){
146
+ try {
147
+ await put(pathname, body, {
148
+ access: attempt,
149
+ addRandomSuffix: false,
150
+ allowOverwrite: true,
151
+ ...token ? {
152
+ token
153
+ } : {}
154
+ });
155
+ return attempt;
156
+ } catch (error) {
157
+ if (firstError === undefined) {
158
+ firstError = error;
159
+ }
160
+ if (!looksLikeAccessModeMismatch(error)) {
161
+ throw error;
162
+ }
163
+ }
164
+ }
165
+ throw firstError instanceof Error ? firstError : new Error('Failed to upload backup blob');
166
+ }
167
+ /** Heuristic for Vercel Blob errors that indicate the store rejects the chosen access level. */ function looksLikeAccessModeMismatch(error) {
168
+ const msg = error instanceof Error ? error.message : typeof error === 'string' ? error : error == null ? '' : (()=>{
169
+ try {
170
+ return JSON.stringify(error);
171
+ } catch {
172
+ return 'unknown';
173
+ }
174
+ })();
175
+ const lower = msg.toLowerCase();
176
+ return lower.includes('access') && (lower.includes('public') || lower.includes('private') || lower.includes('not support') || lower.includes('invalid'));
177
+ }
178
+
179
+ //# sourceMappingURL=backupBlobIO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/backupBlobIO.ts"],"sourcesContent":["import { put } from '@vercel/blob'\n\n/** Vercel Blob access for backup archives under `backups/`. */\nexport type BackupBlobAccessLevel = 'private' | 'public'\n\n/**\n * Returns the blob pathname (no leading slash) for a trusted Vercel Blob HTTPS URL, or null.\n * The URL's path component is percent-decoded once so the result matches the literal pathname\n * stored by Vercel Blob (e.g. blobs whose own pathname contains `%2F` are served from a URL that\n * double-encodes it to `%252F`).\n */\nexport function vercelBlobPathnameFromUrl(urlStr: string): null | string {\n try {\n const u = new URL(urlStr)\n if (u.protocol !== 'https:') {return null}\n if (!u.hostname.endsWith('.blob.vercel-storage.com')) {return null}\n const raw = u.pathname.replace(/^\\//, '')\n if (raw.length === 0) {return null}\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n } catch {\n return null\n }\n}\n\nexport function isTrustedBackupBlobReference(urlStr: string, expectedPathname: string): boolean {\n const fromUrl = vercelBlobPathnameFromUrl(urlStr)\n return Boolean(fromUrl && fromUrl === expectedPathname)\n}\n\n/** True when the URL points to any Vercel Blob host (public or private). */\nfunction isVercelBlobHost(urlStr: string): boolean {\n try {\n const u = new URL(urlStr)\n return u.protocol === 'https:' && u.hostname.endsWith('.blob.vercel-storage.com')\n } catch {\n return false\n }\n}\n\n/**\n * Fetches a Vercel Blob URL with the Bearer token attached when the host belongs to Vercel Blob.\n * Works transparently for public stores (token is ignored) and private stores (token is required).\n * Tokens are never sent to non-Vercel hosts.\n */\nasync function fetchBackupBlobUrl(url: string, token: string): Promise<Response> {\n const init: RequestInit | undefined =\n token && isVercelBlobHost(url)\n ? { headers: { authorization: `Bearer ${token}` } }\n : undefined\n return fetch(url, init)\n}\n\nexport type BackupBlobDownloadStream = {\n contentType: string\n stream: ReadableStream<Uint8Array>\n}\n\n/**\n * Opens a readable stream for a backup object (public or private). Prefers the `downloadUrl` /\n * `blobUrl` returned by `list()` because Vercel Blob already encodes the pathname correctly there\n * (some legacy backups store literal `%2F` sequences inside the pathname, which the SDK's\n * pathname-to-URL construction would misinterpret).\n */\nexport async function streamBackupBlobForDownload(options: {\n blobUrl?: null | string\n downloadUrl?: null | string\n pathname: string\n preferredAccess: BackupBlobAccessLevel\n token: string\n}): Promise<BackupBlobDownloadStream | null> {\n const { blobUrl, downloadUrl, pathname, token } = options\n const candidates: string[] = []\n if (downloadUrl && isTrustedBackupBlobReference(downloadUrl, pathname)) {\n candidates.push(downloadUrl)\n }\n if (blobUrl && isTrustedBackupBlobReference(blobUrl, pathname)) {\n candidates.push(blobUrl)\n }\n\n for (const target of candidates) {\n try {\n const res = await fetchBackupBlobUrl(target, token)\n if (res.ok && res.body) {\n return {\n contentType: res.headers.get('content-type') || 'application/octet-stream',\n stream: res.body,\n }\n }\n } catch {\n /* try next candidate */\n }\n }\n\n return null\n}\n\nexport async function bufferFromWebReadableStream(\n stream: ReadableStream<Uint8Array>,\n): Promise<Buffer> {\n const reader = stream.getReader()\n const chunks: Buffer[] = []\n while (true) {\n const { done, value } = await reader.read()\n if (done) {break}\n if (value) {chunks.push(Buffer.from(value))}\n }\n return Buffer.concat(chunks)\n}\n\n/**\n * Read a backup blob using the trusted `downloadUrl` returned by `list()`. The Bearer token is\n * always attached for Vercel Blob hosts; this works for public blobs (token ignored) and is\n * required for private blobs.\n */\nexport async function readBackupBlobContent(options: {\n /** Kept for API compatibility — the fetch path is the same for public and private stores. */\n access: BackupBlobAccessLevel\n downloadUrl: string\n pathname: string\n token: string\n}): Promise<Buffer> {\n const { downloadUrl, pathname, token } = options\n const res = await fetchBackupBlobUrl(downloadUrl, token)\n if (res.ok) {\n return Buffer.from(await res.arrayBuffer())\n }\n throw new Error(`Failed to read backup blob (${pathname}): HTTP ${res.status}`)\n}\n\n/**\n * Flexible read used during restore/preview/transfer where the source store visibility may differ\n * from the new one. Always tries authenticated access first (correct for private stores and\n * harmless for public ones). Falls back to anonymous fetch only when the URL is not a Vercel\n * Blob URL, so tokens never leak to arbitrary hosts.\n */\nexport async function readBackupBlobContentFlexible(\n pathname: string,\n downloadUrl: string,\n token: string,\n _sourceAccessHint?: BackupBlobAccessLevel,\n): Promise<Buffer> {\n try {\n const res = await fetchBackupBlobUrl(downloadUrl, token)\n if (res.ok) {return Buffer.from(await res.arrayBuffer())}\n } catch {\n /* try anonymous fallback */\n }\n\n if (!isVercelBlobHost(downloadUrl)) {\n try {\n const res = await fetch(downloadUrl)\n if (res.ok) {return Buffer.from(await res.arrayBuffer())}\n } catch {\n /* fall through to error */\n }\n }\n\n throw new Error(`Failed to read backup blob ${pathname}`)\n}\n\n/**\n * Uploads a backup blob with the preferred access level and transparently retries the other level\n * if the Vercel Blob store rejects the preferred one (e.g. legacy public-only stores reject\n * `access: 'private'`). Returns the effective access level that succeeded so callers can log/track\n * it; throws the original error if both attempts fail.\n */\nexport async function putBackupBlobContent(\n pathname: string,\n body: Buffer | string | Uint8Array,\n token: string | undefined,\n access: BackupBlobAccessLevel,\n): Promise<BackupBlobAccessLevel> {\n const order: BackupBlobAccessLevel[] =\n access === 'private' ? ['private', 'public'] : ['public', 'private']\n let firstError: unknown\n for (const attempt of order) {\n try {\n await put(pathname, body, {\n access: attempt,\n addRandomSuffix: false,\n allowOverwrite: true,\n ...(token ? { token } : {}),\n })\n return attempt\n } catch (error) {\n if (firstError === undefined) {firstError = error}\n if (!looksLikeAccessModeMismatch(error)) {throw error}\n }\n }\n throw firstError instanceof Error ? firstError : new Error('Failed to upload backup blob')\n}\n\n/** Heuristic for Vercel Blob errors that indicate the store rejects the chosen access level. */\nfunction looksLikeAccessModeMismatch(error: unknown): boolean {\n const msg =\n error instanceof Error\n ? error.message\n : typeof error === 'string'\n ? error\n : error == null\n ? ''\n : (() => {\n try {\n return JSON.stringify(error)\n } catch {\n return 'unknown'\n }\n })()\n const lower = msg.toLowerCase()\n return (\n lower.includes('access') &&\n (lower.includes('public') ||\n lower.includes('private') ||\n lower.includes('not support') ||\n lower.includes('invalid'))\n )\n}\n"],"names":["put","vercelBlobPathnameFromUrl","urlStr","u","URL","protocol","hostname","endsWith","raw","pathname","replace","length","decodeURIComponent","isTrustedBackupBlobReference","expectedPathname","fromUrl","Boolean","isVercelBlobHost","fetchBackupBlobUrl","url","token","init","headers","authorization","undefined","fetch","streamBackupBlobForDownload","options","blobUrl","downloadUrl","candidates","push","target","res","ok","body","contentType","get","stream","bufferFromWebReadableStream","reader","getReader","chunks","done","value","read","Buffer","from","concat","readBackupBlobContent","arrayBuffer","Error","status","readBackupBlobContentFlexible","_sourceAccessHint","putBackupBlobContent","access","order","firstError","attempt","addRandomSuffix","allowOverwrite","error","looksLikeAccessModeMismatch","msg","message","JSON","stringify","lower","toLowerCase","includes"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { Payload } from 'payload';
2
+ import { type BackupBlobAccessLevel } from './backupBlobIO';
3
+ export interface BackupBlobTransferProgress {
4
+ failed: number;
5
+ index: number;
6
+ pathname: string;
7
+ total: number;
8
+ transferred: number;
9
+ }
10
+ export interface BackupBlobTransferSummary {
11
+ failed: number;
12
+ skipped: number;
13
+ total: number;
14
+ transferred: number;
15
+ }
16
+ export interface TransferBackupBlobsOptions {
17
+ /**
18
+ * When true, successfully copied blobs are removed from the source store.
19
+ * Defaults to false so the source remains intact unless the caller opts in.
20
+ */
21
+ deleteFromSource?: boolean;
22
+ onProgress?: (p: BackupBlobTransferProgress) => void;
23
+ /** When set, prefer this access when reading from the source (token rotation). */
24
+ sourceAccessHint?: BackupBlobAccessLevel;
25
+ /** Access level for blobs written to the target store. */
26
+ targetAccess?: BackupBlobAccessLevel;
27
+ taskId?: string;
28
+ }
29
+ export declare function transferBackupBlobsToToken(payload: Payload, sourceToken: string, targetToken: string, options?: TransferBackupBlobsOptions): Promise<BackupBlobTransferSummary>;
@@ -0,0 +1,85 @@
1
+ import { del, list } from '@vercel/blob';
2
+ import { putBackupBlobContent, readBackupBlobContentFlexible } from './backupBlobIO';
3
+ import { updateBackupTask } from './taskProgress';
4
+ function sameToken(a, b) {
5
+ return a.trim() !== '' && a.trim() === b.trim();
6
+ }
7
+ async function writeTransferProgress(payload, taskId, p) {
8
+ if (!taskId) {
9
+ return;
10
+ }
11
+ try {
12
+ await updateBackupTask(payload, taskId, {
13
+ message: JSON.stringify({
14
+ failed: p.failed,
15
+ pathname: p.pathname,
16
+ total: p.total,
17
+ transferred: p.transferred
18
+ }),
19
+ status: 'running'
20
+ });
21
+ } catch (err) {
22
+ payload.logger.warn({
23
+ err,
24
+ taskId
25
+ }, '[backup-transfer] Could not persist transfer progress');
26
+ }
27
+ }
28
+ export async function transferBackupBlobsToToken(payload, sourceToken, targetToken, options) {
29
+ if (sameToken(sourceToken, targetToken)) {
30
+ return {
31
+ failed: 0,
32
+ skipped: 0,
33
+ total: 0,
34
+ transferred: 0
35
+ };
36
+ }
37
+ const { blobs } = await list({
38
+ limit: 1000,
39
+ prefix: 'backups/',
40
+ token: sourceToken
41
+ });
42
+ const total = blobs.length;
43
+ let transferred = 0;
44
+ let failed = 0;
45
+ const taskId = options?.taskId;
46
+ const targetAccess = options?.targetAccess ?? 'public';
47
+ const sourceAccessHint = options?.sourceAccessHint;
48
+ const deleteFromSource = options?.deleteFromSource === true;
49
+ for(let index = 0; index < blobs.length; index += 1){
50
+ const blob = blobs[index];
51
+ try {
52
+ const data = await readBackupBlobContentFlexible(blob.pathname, blob.downloadUrl, sourceToken, sourceAccessHint);
53
+ await putBackupBlobContent(blob.pathname, data, targetToken, targetAccess);
54
+ if (deleteFromSource) {
55
+ await del(blob.url, {
56
+ token: sourceToken
57
+ });
58
+ }
59
+ transferred += 1;
60
+ } catch (err) {
61
+ failed += 1;
62
+ payload.logger.error({
63
+ err,
64
+ pathname: blob.pathname
65
+ }, '[backup-transfer] Failed to transfer backup blob');
66
+ }
67
+ const progress = {
68
+ failed,
69
+ index,
70
+ pathname: blob.pathname,
71
+ total,
72
+ transferred
73
+ };
74
+ options?.onProgress?.(progress);
75
+ await writeTransferProgress(payload, taskId, progress);
76
+ }
77
+ return {
78
+ failed,
79
+ skipped: 0,
80
+ total: blobs.length,
81
+ transferred
82
+ };
83
+ }
84
+
85
+ //# sourceMappingURL=backupBlobTransfer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/backupBlobTransfer.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\nimport { del, list } from '@vercel/blob'\n\nimport {\n type BackupBlobAccessLevel,\n putBackupBlobContent,\n readBackupBlobContentFlexible,\n} from './backupBlobIO'\nimport { updateBackupTask } from './taskProgress'\n\nexport interface BackupBlobTransferProgress {\n failed: number\n index: number\n pathname: string\n total: number\n transferred: number\n}\n\nexport interface BackupBlobTransferSummary {\n failed: number\n skipped: number\n total: number\n transferred: number\n}\n\nexport interface TransferBackupBlobsOptions {\n /**\n * When true, successfully copied blobs are removed from the source store.\n * Defaults to false so the source remains intact unless the caller opts in.\n */\n deleteFromSource?: boolean\n onProgress?: (p: BackupBlobTransferProgress) => void\n /** When set, prefer this access when reading from the source (token rotation). */\n sourceAccessHint?: BackupBlobAccessLevel\n /** Access level for blobs written to the target store. */\n targetAccess?: BackupBlobAccessLevel\n taskId?: string\n}\n\nfunction sameToken(a: string, b: string): boolean {\n return a.trim() !== '' && a.trim() === b.trim()\n}\n\nasync function writeTransferProgress(\n payload: Payload,\n taskId: string | undefined,\n p: BackupBlobTransferProgress,\n): Promise<void> {\n if (!taskId) {\n return\n }\n try {\n await updateBackupTask(payload, taskId, {\n message: JSON.stringify({\n failed: p.failed,\n pathname: p.pathname,\n total: p.total,\n transferred: p.transferred,\n }),\n status: 'running',\n })\n } catch (err) {\n payload.logger.warn({ err, taskId }, '[backup-transfer] Could not persist transfer progress')\n }\n}\n\nexport async function transferBackupBlobsToToken(\n payload: Payload,\n sourceToken: string,\n targetToken: string,\n options?: TransferBackupBlobsOptions,\n): Promise<BackupBlobTransferSummary> {\n if (sameToken(sourceToken, targetToken)) {\n return { failed: 0, skipped: 0, total: 0, transferred: 0 }\n }\n\n const { blobs } = await list({\n limit: 1000,\n prefix: 'backups/',\n token: sourceToken,\n })\n\n const total = blobs.length\n let transferred = 0\n let failed = 0\n const taskId = options?.taskId\n const targetAccess: BackupBlobAccessLevel = options?.targetAccess ?? 'public'\n const sourceAccessHint = options?.sourceAccessHint\n const deleteFromSource = options?.deleteFromSource === true\n\n for (let index = 0; index < blobs.length; index += 1) {\n const blob = blobs[index]\n try {\n const data = await readBackupBlobContentFlexible(\n blob.pathname,\n blob.downloadUrl,\n sourceToken,\n sourceAccessHint,\n )\n\n await putBackupBlobContent(blob.pathname, data, targetToken, targetAccess)\n if (deleteFromSource) {\n await del(blob.url, { token: sourceToken })\n }\n transferred += 1\n } catch (err) {\n failed += 1\n payload.logger.error(\n { err, pathname: blob.pathname },\n '[backup-transfer] Failed to transfer backup blob',\n )\n }\n\n const progress: BackupBlobTransferProgress = {\n failed,\n index,\n pathname: blob.pathname,\n total,\n transferred,\n }\n options?.onProgress?.(progress)\n await writeTransferProgress(payload, taskId, progress)\n }\n\n return {\n failed,\n skipped: 0,\n total: blobs.length,\n transferred,\n }\n}\n"],"names":["del","list","putBackupBlobContent","readBackupBlobContentFlexible","updateBackupTask","sameToken","a","b","trim","writeTransferProgress","payload","taskId","p","message","JSON","stringify","failed","pathname","total","transferred","status","err","logger","warn","transferBackupBlobsToToken","sourceToken","targetToken","options","skipped","blobs","limit","prefix","token","length","targetAccess","sourceAccessHint","deleteFromSource","index","blob","data","downloadUrl","url","error","progress","onProgress"],"mappings":"AAEA,SAASA,GAAG,EAAEC,IAAI,QAAQ,eAAc;AAExC,SAEEC,oBAAoB,EACpBC,6BAA6B,QACxB,iBAAgB;AACvB,SAASC,gBAAgB,QAAQ,iBAAgB;AA+BjD,SAASC,UAAUC,CAAS,EAAEC,CAAS;IACrC,OAAOD,EAAEE,IAAI,OAAO,MAAMF,EAAEE,IAAI,OAAOD,EAAEC,IAAI;AAC/C;AAEA,eAAeC,sBACbC,OAAgB,EAChBC,MAA0B,EAC1BC,CAA6B;IAE7B,IAAI,CAACD,QAAQ;QACX;IACF;IACA,IAAI;QACF,MAAMP,iBAAiBM,SAASC,QAAQ;YACtCE,SAASC,KAAKC,SAAS,CAAC;gBACtBC,QAAQJ,EAAEI,MAAM;gBAChBC,UAAUL,EAAEK,QAAQ;gBACpBC,OAAON,EAAEM,KAAK;gBACdC,aAAaP,EAAEO,WAAW;YAC5B;YACAC,QAAQ;QACV;IACF,EAAE,OAAOC,KAAK;QACZX,QAAQY,MAAM,CAACC,IAAI,CAAC;YAAEF;YAAKV;QAAO,GAAG;IACvC;AACF;AAEA,OAAO,eAAea,2BACpBd,OAAgB,EAChBe,WAAmB,EACnBC,WAAmB,EACnBC,OAAoC;IAEpC,IAAItB,UAAUoB,aAAaC,cAAc;QACvC,OAAO;YAAEV,QAAQ;YAAGY,SAAS;YAAGV,OAAO;YAAGC,aAAa;QAAE;IAC3D;IAEA,MAAM,EAAEU,KAAK,EAAE,GAAG,MAAM5B,KAAK;QAC3B6B,OAAO;QACPC,QAAQ;QACRC,OAAOP;IACT;IAEA,MAAMP,QAAQW,MAAMI,MAAM;IAC1B,IAAId,cAAc;IAClB,IAAIH,SAAS;IACb,MAAML,SAASgB,SAAShB;IACxB,MAAMuB,eAAsCP,SAASO,gBAAgB;IACrE,MAAMC,mBAAmBR,SAASQ;IAClC,MAAMC,mBAAmBT,SAASS,qBAAqB;IAEvD,IAAK,IAAIC,QAAQ,GAAGA,QAAQR,MAAMI,MAAM,EAAEI,SAAS,EAAG;QACpD,MAAMC,OAAOT,KAAK,CAACQ,MAAM;QACzB,IAAI;YACF,MAAME,OAAO,MAAMpC,8BACjBmC,KAAKrB,QAAQ,EACbqB,KAAKE,WAAW,EAChBf,aACAU;YAGF,MAAMjC,qBAAqBoC,KAAKrB,QAAQ,EAAEsB,MAAMb,aAAaQ;YAC7D,IAAIE,kBAAkB;gBACpB,MAAMpC,IAAIsC,KAAKG,GAAG,EAAE;oBAAET,OAAOP;gBAAY;YAC3C;YACAN,eAAe;QACjB,EAAE,OAAOE,KAAK;YACZL,UAAU;YACVN,QAAQY,MAAM,CAACoB,KAAK,CAClB;gBAAErB;gBAAKJ,UAAUqB,KAAKrB,QAAQ;YAAC,GAC/B;QAEJ;QAEA,MAAM0B,WAAuC;YAC3C3B;YACAqB;YACApB,UAAUqB,KAAKrB,QAAQ;YACvBC;YACAC;QACF;QACAQ,SAASiB,aAAaD;QACtB,MAAMlC,sBAAsBC,SAASC,QAAQgC;IAC/C;IAEA,OAAO;QACL3B;QACAY,SAAS;QACTV,OAAOW,MAAMI,MAAM;QACnBd;IACF;AACF"}
@@ -0,0 +1,35 @@
1
+ import type { Payload } from 'payload';
2
+ export declare const BACKUP_SETTINGS_SLUG = "backup-settings";
3
+ export interface ResolvedCronBackupSettings {
4
+ /**
5
+ * Validated access level of the current backup blob store, populated via
6
+ * `validateBackupBlobToken`. `null` when validation has never run (default token use is still
7
+ * treated as `public`, dedicated token without validation falls back to heuristic private).
8
+ */
9
+ backupBlobAccess: 'private' | 'public' | null;
10
+ backupBlobReadWriteToken: string;
11
+ backupsToKeep: number;
12
+ id: string;
13
+ includeMediaForCron: boolean;
14
+ skipMongoCollections: string[];
15
+ }
16
+ export declare function normalizeSkipMongoCollections(raw: unknown): string[];
17
+ export declare function toPayloadSkipRows(names: string[]): {
18
+ name: string;
19
+ }[];
20
+ export declare function getResolvedCronBackupSettings(payload: Payload): Promise<ResolvedCronBackupSettings>;
21
+ export declare function resolveBackupBlobToken(settings: ResolvedCronBackupSettings): string;
22
+ /**
23
+ * Vercel Blob `access` for new backup archives. Prefers the detected + persisted access level from
24
+ * {@link validateBackupBlobToken}; otherwise heuristically treats a dedicated backup token as
25
+ * `private` and the default env token as `public`.
26
+ */
27
+ export declare function resolveBackupBlobAccess(settings: ResolvedCronBackupSettings): 'private' | 'public';
28
+ /**
29
+ * When `pathname` is sent, restore/preview read via token + flexible SDK/fetch (works for public
30
+ * and private blobs). Returns undefined if pathname is missing or invalid.
31
+ */
32
+ export declare function resolveBackupArchiveRead(settings: ResolvedCronBackupSettings, pathname: unknown): {
33
+ pathname: string;
34
+ token: string;
35
+ } | undefined;
@@ -0,0 +1,95 @@
1
+ export const BACKUP_SETTINGS_SLUG = 'backup-settings';
2
+ function defaultBackupsToKeep() {
3
+ const n = Number(process.env.BACKUPS_TO_KEEP);
4
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 10;
5
+ }
6
+ export function normalizeSkipMongoCollections(raw) {
7
+ if (!Array.isArray(raw)) {
8
+ return [];
9
+ }
10
+ const out = [];
11
+ for (const entry of raw){
12
+ if (typeof entry === 'string' && entry.length > 0 && entry.length < 512) {
13
+ out.push(entry);
14
+ continue;
15
+ }
16
+ if (entry && typeof entry === 'object' && 'name' in entry) {
17
+ const name = entry.name;
18
+ if (typeof name === 'string' && name.length > 0 && name.length < 512) {
19
+ out.push(name);
20
+ }
21
+ }
22
+ }
23
+ return out;
24
+ }
25
+ export function toPayloadSkipRows(names) {
26
+ return names.map((name)=>({
27
+ name
28
+ }));
29
+ }
30
+ export async function getResolvedCronBackupSettings(payload) {
31
+ const res = await payload.find({
32
+ collection: BACKUP_SETTINGS_SLUG,
33
+ depth: 0,
34
+ limit: 1,
35
+ overrideAccess: true
36
+ });
37
+ const doc = res.docs[0];
38
+ if (!doc) {
39
+ return {
40
+ id: '',
41
+ backupBlobAccess: null,
42
+ backupBlobReadWriteToken: '',
43
+ backupsToKeep: defaultBackupsToKeep(),
44
+ includeMediaForCron: true,
45
+ skipMongoCollections: []
46
+ };
47
+ }
48
+ const rawKeep = doc.backupsToKeep;
49
+ const backupsToKeep = typeof rawKeep === 'number' && Number.isFinite(rawKeep) ? Math.min(365, Math.max(1, Math.floor(rawKeep))) : defaultBackupsToKeep();
50
+ const rawAccess = typeof doc.backupBlobAccess === 'string' ? doc.backupBlobAccess : null;
51
+ const backupBlobAccess = rawAccess === 'public' || rawAccess === 'private' ? rawAccess : null;
52
+ return {
53
+ id: String(doc.id),
54
+ backupBlobAccess,
55
+ backupBlobReadWriteToken: typeof doc.backupBlobReadWriteToken === 'string' ? doc.backupBlobReadWriteToken : '',
56
+ backupsToKeep,
57
+ includeMediaForCron: doc.includeMediaForCron === true,
58
+ skipMongoCollections: normalizeSkipMongoCollections(doc.skipMongoCollections)
59
+ };
60
+ }
61
+ export function resolveBackupBlobToken(settings) {
62
+ const fromSettings = settings.backupBlobReadWriteToken.trim();
63
+ if (fromSettings.length > 0) {
64
+ return fromSettings;
65
+ }
66
+ return process.env.BLOB_READ_WRITE_TOKEN || '';
67
+ }
68
+ /**
69
+ * Vercel Blob `access` for new backup archives. Prefers the detected + persisted access level from
70
+ * {@link validateBackupBlobToken}; otherwise heuristically treats a dedicated backup token as
71
+ * `private` and the default env token as `public`.
72
+ */ export function resolveBackupBlobAccess(settings) {
73
+ if (settings.backupBlobAccess === 'public' || settings.backupBlobAccess === 'private') {
74
+ return settings.backupBlobAccess;
75
+ }
76
+ return settings.backupBlobReadWriteToken.trim().length > 0 ? 'private' : 'public';
77
+ }
78
+ /**
79
+ * When `pathname` is sent, restore/preview read via token + flexible SDK/fetch (works for public
80
+ * and private blobs). Returns undefined if pathname is missing or invalid.
81
+ */ export function resolveBackupArchiveRead(settings, pathname) {
82
+ if (typeof pathname !== 'string' || !pathname.startsWith('backups/')) {
83
+ return undefined;
84
+ }
85
+ const token = resolveBackupBlobToken(settings).trim();
86
+ if (!token) {
87
+ return undefined;
88
+ }
89
+ return {
90
+ pathname,
91
+ token
92
+ };
93
+ }
94
+
95
+ //# sourceMappingURL=backupSettings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/backupSettings.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\nexport const BACKUP_SETTINGS_SLUG = 'backup-settings'\n\nexport interface ResolvedCronBackupSettings {\n /**\n * Validated access level of the current backup blob store, populated via\n * `validateBackupBlobToken`. `null` when validation has never run (default token use is still\n * treated as `public`, dedicated token without validation falls back to heuristic private).\n */\n backupBlobAccess: 'private' | 'public' | null\n backupBlobReadWriteToken: string\n backupsToKeep: number\n id: string\n includeMediaForCron: boolean\n skipMongoCollections: string[]\n}\n\nfunction defaultBackupsToKeep(): number {\n const n = Number(process.env.BACKUPS_TO_KEEP)\n return Number.isFinite(n) && n > 0 ? Math.floor(n) : 10\n}\n\nexport function normalizeSkipMongoCollections(raw: unknown): string[] {\n if (!Array.isArray(raw)) {return []}\n const out: string[] = []\n for (const entry of raw) {\n if (typeof entry === 'string' && entry.length > 0 && entry.length < 512) {\n out.push(entry)\n continue\n }\n if (entry && typeof entry === 'object' && 'name' in entry) {\n const name = (entry as { name?: unknown }).name\n if (typeof name === 'string' && name.length > 0 && name.length < 512) {out.push(name)}\n }\n }\n return out\n}\n\nexport function toPayloadSkipRows(names: string[]): { name: string }[] {\n return names.map((name) => ({ name }))\n}\n\nexport async function getResolvedCronBackupSettings(\n payload: Payload,\n): Promise<ResolvedCronBackupSettings> {\n const res = await payload.find({\n collection: BACKUP_SETTINGS_SLUG,\n depth: 0,\n limit: 1,\n overrideAccess: true,\n })\n const doc = res.docs[0] as unknown as Record<string, unknown> | undefined\n if (!doc) {\n return {\n id: '',\n backupBlobAccess: null,\n backupBlobReadWriteToken: '',\n backupsToKeep: defaultBackupsToKeep(),\n includeMediaForCron: true,\n skipMongoCollections: [],\n }\n }\n const rawKeep = doc.backupsToKeep\n const backupsToKeep =\n typeof rawKeep === 'number' && Number.isFinite(rawKeep)\n ? Math.min(365, Math.max(1, Math.floor(rawKeep)))\n : defaultBackupsToKeep()\n\n const rawAccess = typeof doc.backupBlobAccess === 'string' ? doc.backupBlobAccess : null\n const backupBlobAccess: 'private' | 'public' | null =\n rawAccess === 'public' || rawAccess === 'private' ? rawAccess : null\n\n return {\n id: String(doc.id),\n backupBlobAccess,\n backupBlobReadWriteToken:\n typeof doc.backupBlobReadWriteToken === 'string' ? doc.backupBlobReadWriteToken : '',\n backupsToKeep,\n includeMediaForCron: doc.includeMediaForCron === true,\n skipMongoCollections: normalizeSkipMongoCollections(doc.skipMongoCollections),\n }\n}\n\nexport function resolveBackupBlobToken(settings: ResolvedCronBackupSettings): string {\n const fromSettings = settings.backupBlobReadWriteToken.trim()\n if (fromSettings.length > 0) {return fromSettings}\n return process.env.BLOB_READ_WRITE_TOKEN || ''\n}\n\n/**\n * Vercel Blob `access` for new backup archives. Prefers the detected + persisted access level from\n * {@link validateBackupBlobToken}; otherwise heuristically treats a dedicated backup token as\n * `private` and the default env token as `public`.\n */\nexport function resolveBackupBlobAccess(\n settings: ResolvedCronBackupSettings,\n): 'private' | 'public' {\n if (settings.backupBlobAccess === 'public' || settings.backupBlobAccess === 'private') {\n return settings.backupBlobAccess\n }\n return settings.backupBlobReadWriteToken.trim().length > 0 ? 'private' : 'public'\n}\n\n/**\n * When `pathname` is sent, restore/preview read via token + flexible SDK/fetch (works for public\n * and private blobs). Returns undefined if pathname is missing or invalid.\n */\nexport function resolveBackupArchiveRead(\n settings: ResolvedCronBackupSettings,\n pathname: unknown,\n): { pathname: string; token: string } | undefined {\n if (typeof pathname !== 'string' || !pathname.startsWith('backups/')) {return undefined}\n const token = resolveBackupBlobToken(settings).trim()\n if (!token) {return undefined}\n return { pathname, token }\n}\n"],"names":["BACKUP_SETTINGS_SLUG","defaultBackupsToKeep","n","Number","process","env","BACKUPS_TO_KEEP","isFinite","Math","floor","normalizeSkipMongoCollections","raw","Array","isArray","out","entry","length","push","name","toPayloadSkipRows","names","map","getResolvedCronBackupSettings","payload","res","find","collection","depth","limit","overrideAccess","doc","docs","id","backupBlobAccess","backupBlobReadWriteToken","backupsToKeep","includeMediaForCron","skipMongoCollections","rawKeep","min","max","rawAccess","String","resolveBackupBlobToken","settings","fromSettings","trim","BLOB_READ_WRITE_TOKEN","resolveBackupBlobAccess","resolveBackupArchiveRead","pathname","startsWith","undefined","token"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Payload } from 'payload';
2
+ import type { RestorePreviewGroup } from './restorePreview';
3
+ export type BackupSourcePreviewResponse = {
4
+ groups: RestorePreviewGroup[];
5
+ /** Media docs with a filename (blob path candidates for tar backup). */
6
+ mediaBlobCandidates: number;
7
+ };
8
+ export declare function getBackupSourcePreviewForManual(payload: Payload, options?: {
9
+ preferredLocales?: string[];
10
+ }): Promise<BackupSourcePreviewResponse>;
@@ -0,0 +1,33 @@
1
+ import { getDb } from './db';
2
+ import { buildCollectionPreviewGroups } from './restorePreview';
3
+ export async function getBackupSourcePreviewForManual(payload, options = {}) {
4
+ const db = getDb(payload);
5
+ const cols = await db.listCollections().toArray();
6
+ const counts = {};
7
+ for (const { name } of cols){
8
+ counts[name] = await db.collection(name).countDocuments({});
9
+ }
10
+ const groups = buildCollectionPreviewGroups(payload, counts, {
11
+ includeEmptyCollections: true,
12
+ preferredLocales: options.preferredLocales,
13
+ sortLikeAdminNav: true
14
+ });
15
+ let mediaBlobCandidates = 0;
16
+ if ((counts['media'] ?? 0) > 0) {
17
+ mediaBlobCandidates = await db.collection('media').countDocuments({
18
+ filename: {
19
+ $exists: true,
20
+ $nin: [
21
+ null,
22
+ ''
23
+ ]
24
+ }
25
+ });
26
+ }
27
+ return {
28
+ groups,
29
+ mediaBlobCandidates
30
+ };
31
+ }
32
+
33
+ //# sourceMappingURL=backupSourcePreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/backupSourcePreview.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\nimport type { RestorePreviewGroup } from './restorePreview'\n\nimport { getDb } from './db'\nimport { buildCollectionPreviewGroups } from './restorePreview'\n\nexport type BackupSourcePreviewResponse = {\n groups: RestorePreviewGroup[]\n /** Media docs with a filename (blob path candidates for tar backup). */\n mediaBlobCandidates: number\n}\n\nexport async function getBackupSourcePreviewForManual(\n payload: Payload,\n options: { preferredLocales?: string[] } = {},\n): Promise<BackupSourcePreviewResponse> {\n const db = getDb(payload)\n const cols = await db.listCollections().toArray()\n const counts: Record<string, number> = {}\n for (const { name } of cols) {\n counts[name] = await db.collection(name).countDocuments({})\n }\n\n const groups = buildCollectionPreviewGroups(payload, counts, {\n includeEmptyCollections: true,\n preferredLocales: options.preferredLocales,\n sortLikeAdminNav: true,\n })\n\n let mediaBlobCandidates = 0\n if ((counts['media'] ?? 0) > 0) {\n mediaBlobCandidates = await db.collection('media').countDocuments({\n filename: { $exists: true, $nin: [null, ''] },\n })\n }\n\n return { groups, mediaBlobCandidates }\n}\n"],"names":["getDb","buildCollectionPreviewGroups","getBackupSourcePreviewForManual","payload","options","db","cols","listCollections","toArray","counts","name","collection","countDocuments","groups","includeEmptyCollections","preferredLocales","sortLikeAdminNav","mediaBlobCandidates","filename","$exists","$nin"],"mappings":"AAIA,SAASA,KAAK,QAAQ,OAAM;AAC5B,SAASC,4BAA4B,QAAQ,mBAAkB;AAQ/D,OAAO,eAAeC,gCACpBC,OAAgB,EAChBC,UAA2C,CAAC,CAAC;IAE7C,MAAMC,KAAKL,MAAMG;IACjB,MAAMG,OAAO,MAAMD,GAAGE,eAAe,GAAGC,OAAO;IAC/C,MAAMC,SAAiC,CAAC;IACxC,KAAK,MAAM,EAAEC,IAAI,EAAE,IAAIJ,KAAM;QAC3BG,MAAM,CAACC,KAAK,GAAG,MAAML,GAAGM,UAAU,CAACD,MAAME,cAAc,CAAC,CAAC;IAC3D;IAEA,MAAMC,SAASZ,6BAA6BE,SAASM,QAAQ;QAC3DK,yBAAyB;QACzBC,kBAAkBX,QAAQW,gBAAgB;QAC1CC,kBAAkB;IACpB;IAEA,IAAIC,sBAAsB;IAC1B,IAAI,AAACR,CAAAA,MAAM,CAAC,QAAQ,IAAI,CAAA,IAAK,GAAG;QAC9BQ,sBAAsB,MAAMZ,GAAGM,UAAU,CAAC,SAASC,cAAc,CAAC;YAChEM,UAAU;gBAAEC,SAAS;gBAAMC,MAAM;oBAAC;oBAAM;iBAAG;YAAC;QAC9C;IACF;IAEA,OAAO;QAAEP;QAAQI;IAAoB;AACvC"}
@@ -0,0 +1,14 @@
1
+ import type { BackupBlobAccessLevel } from './backupBlobIO';
2
+ export interface BackupBlobTokenValidation {
3
+ /** Detected access level the store accepts (set when ok). */
4
+ access?: BackupBlobAccessLevel;
5
+ /** Short error description when validation fails. */
6
+ error?: string;
7
+ ok: boolean;
8
+ }
9
+ /**
10
+ * Probes a Vercel Blob read/write token by uploading a tiny file and deleting it afterwards. Tries
11
+ * `private` first and falls back to `public` so the caller sees which access modes the store
12
+ * accepts. Non-access errors (auth, network, quota…) are reported as invalid.
13
+ */
14
+ export declare function validateBackupBlobToken(token: string): Promise<BackupBlobTokenValidation>;
@@ -0,0 +1,75 @@
1
+ import { del, put } from '@vercel/blob';
2
+ const PROBE_PATH_PREFIX = 'backups/.backup-mongodb-probe-';
3
+ function looksLikeAccessModeRejection(error) {
4
+ const msg = error instanceof Error ? error.message : typeof error === 'string' ? error : error == null ? '' : (()=>{
5
+ try {
6
+ return JSON.stringify(error);
7
+ } catch {
8
+ return 'unknown';
9
+ }
10
+ })();
11
+ const lower = msg.toLowerCase();
12
+ if (!lower.includes('access')) {
13
+ return false;
14
+ }
15
+ return lower.includes('public') || lower.includes('private') || lower.includes('not support') || lower.includes('invalid');
16
+ }
17
+ /**
18
+ * Probes a Vercel Blob read/write token by uploading a tiny file and deleting it afterwards. Tries
19
+ * `private` first and falls back to `public` so the caller sees which access modes the store
20
+ * accepts. Non-access errors (auth, network, quota…) are reported as invalid.
21
+ */ export async function validateBackupBlobToken(token) {
22
+ const trimmed = token.trim();
23
+ if (!trimmed) {
24
+ return {
25
+ error: 'Token is empty',
26
+ ok: false
27
+ };
28
+ }
29
+ const probePath = `${PROBE_PATH_PREFIX}${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}.txt`;
30
+ const probeBody = 'backup-mongodb-plugin-probe';
31
+ let firstError;
32
+ for (const access of [
33
+ 'private',
34
+ 'public'
35
+ ]){
36
+ let uploadedUrl;
37
+ try {
38
+ const result = await put(probePath, probeBody, {
39
+ access,
40
+ addRandomSuffix: false,
41
+ token: trimmed
42
+ });
43
+ uploadedUrl = result?.url;
44
+ return {
45
+ access,
46
+ ok: true
47
+ };
48
+ } catch (error) {
49
+ if (firstError === undefined) {
50
+ firstError = error;
51
+ }
52
+ if (!looksLikeAccessModeRejection(error)) {
53
+ return {
54
+ error: error instanceof Error ? error.message : 'Token rejected',
55
+ ok: false
56
+ };
57
+ }
58
+ } finally{
59
+ if (uploadedUrl) {
60
+ try {
61
+ await del(uploadedUrl, {
62
+ token: trimmed
63
+ });
64
+ } catch {
65
+ /* best-effort cleanup */ }
66
+ }
67
+ }
68
+ }
69
+ return {
70
+ error: firstError instanceof Error ? firstError.message : 'Token rejected for both public and private access',
71
+ ok: false
72
+ };
73
+ }
74
+
75
+ //# sourceMappingURL=blobTokenValidate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/blobTokenValidate.ts"],"sourcesContent":["import { del, put } from '@vercel/blob'\n\nimport type { BackupBlobAccessLevel } from './backupBlobIO'\n\nexport interface BackupBlobTokenValidation {\n /** Detected access level the store accepts (set when ok). */\n access?: BackupBlobAccessLevel\n /** Short error description when validation fails. */\n error?: string\n ok: boolean\n}\n\nconst PROBE_PATH_PREFIX = 'backups/.backup-mongodb-probe-'\n\nfunction looksLikeAccessModeRejection(error: unknown): boolean {\n const msg =\n error instanceof Error\n ? error.message\n : typeof error === 'string'\n ? error\n : error == null\n ? ''\n : (() => {\n try {\n return JSON.stringify(error)\n } catch {\n return 'unknown'\n }\n })()\n const lower = msg.toLowerCase()\n if (!lower.includes('access')) {\n return false\n }\n return (\n lower.includes('public') ||\n lower.includes('private') ||\n lower.includes('not support') ||\n lower.includes('invalid')\n )\n}\n\n/**\n * Probes a Vercel Blob read/write token by uploading a tiny file and deleting it afterwards. Tries\n * `private` first and falls back to `public` so the caller sees which access modes the store\n * accepts. Non-access errors (auth, network, quota…) are reported as invalid.\n */\nexport async function validateBackupBlobToken(token: string): Promise<BackupBlobTokenValidation> {\n const trimmed = token.trim()\n if (!trimmed) {\n return { error: 'Token is empty', ok: false }\n }\n\n const probePath = `${PROBE_PATH_PREFIX}${Date.now().toString(36)}-${Math.random()\n .toString(36)\n .slice(2, 10)}.txt`\n const probeBody = 'backup-mongodb-plugin-probe'\n\n let firstError: unknown\n for (const access of ['private', 'public'] as const) {\n let uploadedUrl: string | undefined\n try {\n const result = await put(probePath, probeBody, {\n access,\n addRandomSuffix: false,\n token: trimmed,\n })\n uploadedUrl = result?.url\n return { access, ok: true }\n } catch (error) {\n if (firstError === undefined) {\n firstError = error\n }\n if (!looksLikeAccessModeRejection(error)) {\n return { error: error instanceof Error ? error.message : 'Token rejected', ok: false }\n }\n } finally {\n if (uploadedUrl) {\n try {\n await del(uploadedUrl, { token: trimmed })\n } catch {\n /* best-effort cleanup */\n }\n }\n }\n }\n\n return {\n error:\n firstError instanceof Error\n ? firstError.message\n : 'Token rejected for both public and private access',\n ok: false,\n }\n}\n"],"names":["del","put","PROBE_PATH_PREFIX","looksLikeAccessModeRejection","error","msg","Error","message","JSON","stringify","lower","toLowerCase","includes","validateBackupBlobToken","token","trimmed","trim","ok","probePath","Date","now","toString","Math","random","slice","probeBody","firstError","access","uploadedUrl","result","addRandomSuffix","url","undefined"],"mappings":"AAAA,SAASA,GAAG,EAAEC,GAAG,QAAQ,eAAc;AAYvC,MAAMC,oBAAoB;AAE1B,SAASC,6BAA6BC,KAAc;IAClD,MAAMC,MACJD,iBAAiBE,QACbF,MAAMG,OAAO,GACb,OAAOH,UAAU,WACfA,QACAA,SAAS,OACP,KACA,AAAC,CAAA;QACC,IAAI;YACF,OAAOI,KAAKC,SAAS,CAACL;QACxB,EAAE,OAAM;YACN,OAAO;QACT;IACF,CAAA;IACV,MAAMM,QAAQL,IAAIM,WAAW;IAC7B,IAAI,CAACD,MAAME,QAAQ,CAAC,WAAW;QAC7B,OAAO;IACT;IACA,OACEF,MAAME,QAAQ,CAAC,aACfF,MAAME,QAAQ,CAAC,cACfF,MAAME,QAAQ,CAAC,kBACfF,MAAME,QAAQ,CAAC;AAEnB;AAEA;;;;CAIC,GACD,OAAO,eAAeC,wBAAwBC,KAAa;IACzD,MAAMC,UAAUD,MAAME,IAAI;IAC1B,IAAI,CAACD,SAAS;QACZ,OAAO;YAAEX,OAAO;YAAkBa,IAAI;QAAM;IAC9C;IAEA,MAAMC,YAAY,GAAGhB,oBAAoBiB,KAAKC,GAAG,GAAGC,QAAQ,CAAC,IAAI,CAAC,EAAEC,KAAKC,MAAM,GAC5EF,QAAQ,CAAC,IACTG,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC;IACrB,MAAMC,YAAY;IAElB,IAAIC;IACJ,KAAK,MAAMC,UAAU;QAAC;QAAW;KAAS,CAAW;QACnD,IAAIC;QACJ,IAAI;YACF,MAAMC,SAAS,MAAM5B,IAAIiB,WAAWO,WAAW;gBAC7CE;gBACAG,iBAAiB;gBACjBhB,OAAOC;YACT;YACAa,cAAcC,QAAQE;YACtB,OAAO;gBAAEJ;gBAAQV,IAAI;YAAK;QAC5B,EAAE,OAAOb,OAAO;YACd,IAAIsB,eAAeM,WAAW;gBAC5BN,aAAatB;YACf;YACA,IAAI,CAACD,6BAA6BC,QAAQ;gBACxC,OAAO;oBAAEA,OAAOA,iBAAiBE,QAAQF,MAAMG,OAAO,GAAG;oBAAkBU,IAAI;gBAAM;YACvF;QACF,SAAU;YACR,IAAIW,aAAa;gBACf,IAAI;oBACF,MAAM5B,IAAI4B,aAAa;wBAAEd,OAAOC;oBAAQ;gBAC1C,EAAE,OAAM;gBACN,uBAAuB,GACzB;YACF;QACF;IACF;IAEA,OAAO;QACLX,OACEsB,sBAAsBpB,QAClBoB,WAAWnB,OAAO,GAClB;QACNU,IAAI;IACN;AACF"}