replicate-predictions-downloader 2.0.2 → 2.0.3

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 (3) hide show
  1. package/example.js +5 -5
  2. package/index.js +101 -11
  3. package/package.json +12 -5
package/example.js CHANGED
@@ -17,19 +17,19 @@ async function example() {
17
17
 
18
18
  // Example 1: Download all predictions (default behavior)
19
19
  console.log('\nšŸ“„ Example 1: Download all predictions');
20
- const downloader1 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN);
20
+ const _downloader1 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN);
21
21
  // await downloader1.run(); // Uncomment to run
22
22
 
23
23
  // Example 2: Download predictions since a specific date
24
24
  console.log('\nšŸ“… Example 2: Download predictions since 2024-01-01');
25
- const downloader2 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
25
+ const _downloader2 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
26
26
  since: '2024-01-01'
27
27
  });
28
28
  // await downloader2.run(); // Uncomment to run
29
29
 
30
30
  // Example 3: Download predictions in a date range
31
31
  console.log('\nšŸ“… Example 3: Download predictions between 2024-01-01 and 2024-01-31');
32
- const downloader3 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
32
+ const _downloader3 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
33
33
  since: '2024-01-01',
34
34
  until: '2024-01-31'
35
35
  });
@@ -37,7 +37,7 @@ async function example() {
37
37
 
38
38
  // Example 4: Incremental download (since last run)
39
39
  console.log('\nšŸ”„ Example 4: Incremental download since last successful run');
40
- const downloader4 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
40
+ const _downloader4 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
41
41
  lastRun: true
42
42
  });
43
43
  // await downloader4.run(); // Uncomment to run
@@ -46,7 +46,7 @@ async function example() {
46
46
  console.log('\nšŸ“… Example 5: Download predictions from the last 7 days');
47
47
  const sevenDaysAgo = new Date();
48
48
  sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
49
- const downloader5 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
49
+ const _downloader5 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
50
50
  since: sevenDaysAgo.toISOString()
51
51
  });
52
52
  // await downloader5.run(); // Uncomment to run
package/index.js CHANGED
@@ -66,17 +66,54 @@ class ReplicateDownloader {
66
66
  // Parse date string to ISO format
67
67
  parseDate(dateStr) {
68
68
  if (!dateStr) return null;
69
-
70
- // Handle various date formats
69
+
70
+ // Support common relative phrases
71
+ const input = String(dateStr).trim().toLowerCase();
72
+ const nowMs = Date.now();
73
+
74
+ // Yesterday
75
+ if (input === 'yesterday') {
76
+ return new Date(nowMs - 24 * 60 * 60 * 1000).toISOString();
77
+ }
78
+
79
+ // "N <unit> ago" where unit ∈ minutes/hours/days/weeks/months/years
80
+ const relMatch = input.match(/^(\d+)\s*(minute|minutes|hour|hours|day|days|week|weeks|month|months|year|years)\s+ago$/i);
81
+ if (relMatch) {
82
+ const amount = parseInt(relMatch[1], 10);
83
+ const unit = relMatch[2];
84
+
85
+ const unitToMs = {
86
+ minute: 60 * 1000,
87
+ minutes: 60 * 1000,
88
+ hour: 60 * 60 * 1000,
89
+ hours: 60 * 60 * 1000,
90
+ day: 24 * 60 * 60 * 1000,
91
+ days: 24 * 60 * 60 * 1000,
92
+ week: 7 * 24 * 60 * 60 * 1000,
93
+ weeks: 7 * 24 * 60 * 60 * 1000,
94
+ // Approximations for broader ranges
95
+ month: 30 * 24 * 60 * 60 * 1000,
96
+ months: 30 * 24 * 60 * 60 * 1000,
97
+ year: 365 * 24 * 60 * 60 * 1000,
98
+ years: 365 * 24 * 60 * 60 * 1000
99
+ };
100
+
101
+ const deltaMs = amount * (unitToMs[unit] || 0);
102
+ if (deltaMs > 0) {
103
+ return new Date(nowMs - deltaMs).toISOString();
104
+ }
105
+ }
106
+
107
+ // Fallback to native parser (ISO, RFC, etc.)
71
108
  const date = new Date(dateStr);
72
109
  if (isNaN(date.getTime())) {
73
110
  throw new Error(`Invalid date format: ${dateStr}. Use formats like "2024-01-15", "2024-01-15T10:30:00Z", or "2 days ago"`);
74
111
  }
75
-
112
+
76
113
  return date.toISOString();
77
114
  }
78
115
 
79
- // Get date filter parameters for API
116
+ // Get date filter parameters for API (server may ignore these)
80
117
  getDateFilterParams() {
81
118
  const params = new URLSearchParams();
82
119
 
@@ -108,6 +145,28 @@ class ReplicateDownloader {
108
145
  return params.toString();
109
146
  }
110
147
 
148
+ // Compute client-side date bounds for filtering
149
+ getDateFilterBounds() {
150
+ let sinceDate = this.dateFilters.since;
151
+ if (this.dateFilters.lastRun) {
152
+ const state = this.loadState();
153
+ if (state.lastSuccessfulRun) {
154
+ sinceDate = state.lastSuccessfulRun;
155
+ }
156
+ }
157
+ let sinceIsoBuffer = null;
158
+ if (sinceDate) {
159
+ const parsedSince = this.parseDate(sinceDate);
160
+ const sinceBuffer = new Date(new Date(parsedSince).getTime() - 1000);
161
+ sinceIsoBuffer = sinceBuffer.toISOString();
162
+ }
163
+ let untilIso = null;
164
+ if (this.dateFilters.until) {
165
+ untilIso = this.parseDate(this.dateFilters.until);
166
+ }
167
+ return { sinceIsoBuffer, untilIso };
168
+ }
169
+
111
170
  // Make authenticated request to Replicate API
112
171
  async makeRequest(url) {
113
172
  return new Promise((resolve, reject) => {
@@ -172,7 +231,7 @@ class ReplicateDownloader {
172
231
  }
173
232
 
174
233
  // Try to find any string input that looks like a prompt
175
- for (const [key, value] of Object.entries(input)) {
234
+ for (const [_key, value] of Object.entries(input)) {
176
235
  if (typeof value === 'string' && value.length > 5 && value.length < 200) {
177
236
  return this.sanitizeFilename(value);
178
237
  }
@@ -219,11 +278,17 @@ class ReplicateDownloader {
219
278
  let nextUrl = `${this.baseUrl}/predictions`;
220
279
  let page = 1;
221
280
 
222
- // Apply date filtering
281
+ // Apply date filtering (server may ignore these; we also filter client-side)
223
282
  const dateParams = this.getDateFilterParams();
224
283
  if (dateParams) {
225
284
  nextUrl += `?${dateParams}`;
226
- console.log('Applying date filters:', dateParams);
285
+ console.log('Applying date filters (server or client-side):', dateParams);
286
+ }
287
+
288
+ // Client-side bounds
289
+ const { sinceIsoBuffer, untilIso } = this.getDateFilterBounds();
290
+ if (sinceIsoBuffer || untilIso) {
291
+ console.log('Client-side date bounds:', { sinceIsoBuffer, untilIso });
227
292
  }
228
293
 
229
294
  console.log('Starting to fetch predictions...');
@@ -235,9 +300,19 @@ class ReplicateDownloader {
235
300
  const response = await this.makeRequest(nextUrl);
236
301
 
237
302
  if (response.results) {
238
- this.allPredictions.push(...response.results);
239
- console.log(`Found ${response.results.length} predictions on page ${page}`);
240
- console.log(`Total so far: ${this.allPredictions.length}`);
303
+ const pageResults = response.results;
304
+ this.allPredictions.push(...pageResults);
305
+ console.log(`Found ${pageResults.length} predictions on page ${page}`);
306
+ console.log(`Total so far (pre-filter): ${this.allPredictions.length}`);
307
+
308
+ // Heuristic early stop: if all items are older than since bound, stop paginating
309
+ if (sinceIsoBuffer && pageResults.length > 0) {
310
+ const allOlder = pageResults.every(p => p.created_at && p.created_at < sinceIsoBuffer);
311
+ if (allOlder) {
312
+ console.log('Reached items older than since bound; stopping pagination early');
313
+ nextUrl = null;
314
+ }
315
+ }
241
316
  }
242
317
 
243
318
  // Handle pagination with date filters
@@ -264,7 +339,22 @@ class ReplicateDownloader {
264
339
  }
265
340
  }
266
341
 
267
- console.log(`\nTotal predictions fetched: ${this.allPredictions.length}`);
342
+ // Final client-side filter within bounds
343
+ if (this.allPredictions.length > 0) {
344
+ const before = this.allPredictions.length;
345
+ this.allPredictions = this.allPredictions.filter(p => {
346
+ const created = p.created_at;
347
+ if (!created) return true;
348
+ if (sinceIsoBuffer && created < sinceIsoBuffer) return false;
349
+ if (untilIso && created > untilIso) return false;
350
+ return true;
351
+ });
352
+ if (sinceIsoBuffer || untilIso) {
353
+ console.log(`Applied client-side date filter: ${before} -> ${this.allPredictions.length}`);
354
+ }
355
+ }
356
+
357
+ console.log(`\nTotal predictions fetched (post-filter): ${this.allPredictions.length}`);
268
358
  return this.allPredictions;
269
359
  }
270
360
 
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "replicate-predictions-downloader",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "A Node.js script to download and organize all your Replicate predictions, including images, metadata, and other outputs",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "start": "node index.js",
9
9
  "download": "node index.js",
10
- "example": "node example.js"
10
+ "example": "node example.js",
11
+ "lint": "eslint .",
12
+ "lint:fix": "eslint . --fix"
11
13
  },
12
14
  "keywords": [
13
15
  "replicate",
@@ -41,13 +43,18 @@
41
43
  },
42
44
  "dependencies": {
43
45
  "archiver": "^5.3.1",
44
- "dotenv": "^16.3.1",
45
- "commander": "^11.0.0"
46
+ "commander": "^11.0.0",
47
+ "dotenv": "^16.3.1"
46
48
  },
47
49
  "files": [
48
50
  "index.js",
49
51
  "example.js",
50
52
  "README.md",
51
53
  "LICENSE.md"
52
- ]
54
+ ],
55
+ "devDependencies": {
56
+ "@eslint/js": "^9.35.0",
57
+ "eslint": "^9.13.0",
58
+ "globals": "^16.3.0"
59
+ }
53
60
  }