@seedprotocol/feed 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1163 @@
1
+ import { client as $, getFeedItemsBySchemaName as _ } from "@seedprotocol/sdk";
2
+ import F from "pluralize";
3
+ import { generateRssFeed as L, generateJsonFeed as z, generateAtomFeed as R } from "feedsmith";
4
+ import { gql as x } from "graphql-request";
5
+ import { createHash as j } from "crypto";
6
+ import { promises as p } from "fs";
7
+ import { join as D } from "path";
8
+ import S from "image-size";
9
+ function k(d) {
10
+ return `"${j("md5").update(d).digest("hex").substring(0, 16)}"`;
11
+ }
12
+ function B(d, e, t, r) {
13
+ const n = `${d}-${e}-${t}-${r}`;
14
+ return k(n);
15
+ }
16
+ function O(d, e, t, r) {
17
+ const n = `${d}-${e}-${t}-${r}`;
18
+ return k(n);
19
+ }
20
+ function W(d, e) {
21
+ const t = (r) => r.replace(/^"|"$/g, "");
22
+ return t(d) === t(e);
23
+ }
24
+ function H(d) {
25
+ return d ? d.split(",").map((e) => e.trim()).filter((e) => e.length > 0) : [];
26
+ }
27
+ function G(d, e) {
28
+ const t = H(d);
29
+ return t.length === 0 ? !1 : t.includes("*") ? !0 : t.some((r) => W(r, e));
30
+ }
31
+ class X {
32
+ feedDataCache = /* @__PURE__ */ new Map();
33
+ feedContentCache = /* @__PURE__ */ new Map();
34
+ imageMetadataCache = /* @__PURE__ */ new Map();
35
+ config;
36
+ refreshLocks = /* @__PURE__ */ new Map();
37
+ constructor(e) {
38
+ this.config = e;
39
+ }
40
+ /**
41
+ * Get cached feed data for a schema
42
+ */
43
+ getFeedData(e) {
44
+ const t = this.feedDataCache.get(e);
45
+ return t ? Math.floor(Date.now() / 1e3) - t.lastUpdated > this.config.ttl ? (this.feedDataCache.delete(e), this.clearContentCache(e), null) : t : null;
46
+ }
47
+ /**
48
+ * Set cached feed data for a schema
49
+ */
50
+ setFeedData(e, t) {
51
+ const r = Math.floor(Date.now() / 1e3);
52
+ let n = 0, s = "";
53
+ for (const h of t) {
54
+ const f = h.timeCreated;
55
+ f && f > n && (n = f, s = h.id || h.seedUid || h.SeedUid || "");
56
+ }
57
+ n === 0 && (n = r);
58
+ const o = B(e, "data", n, t.length), l = {
59
+ items: [...t],
60
+ // Create a copy
61
+ lastProcessedTimestamp: n,
62
+ lastProcessedItemId: s,
63
+ lastUpdated: r,
64
+ etag: o
65
+ };
66
+ this.feedDataCache.set(e, l);
67
+ }
68
+ /**
69
+ * Get cached feed content for a schema and format
70
+ */
71
+ getFeedContent(e, t) {
72
+ const r = `${e}:${t}`, n = this.feedContentCache.get(r);
73
+ return n ? Math.floor(Date.now() / 1e3) > n.expiresAt ? (this.feedContentCache.delete(r), null) : n : null;
74
+ }
75
+ /**
76
+ * Set cached feed content for a schema and format
77
+ */
78
+ setFeedContent(e, t, r, n) {
79
+ const s = `${e}:${t}`, o = Math.floor(Date.now() / 1e3), l = o + this.config.ttl, h = O(e, t, o, r.length), f = {
80
+ content: r,
81
+ contentType: n,
82
+ etag: h,
83
+ lastModified: o,
84
+ expiresAt: l
85
+ };
86
+ this.feedContentCache.set(s, f);
87
+ }
88
+ /**
89
+ * Clear feed data cache for a schema
90
+ */
91
+ clearFeedData(e) {
92
+ this.feedDataCache.delete(e);
93
+ }
94
+ /**
95
+ * Clear content cache for a schema (all formats)
96
+ */
97
+ clearContentCache(e) {
98
+ const t = [];
99
+ for (const r of this.feedContentCache.keys())
100
+ r.startsWith(`${e}:`) && t.push(r);
101
+ t.forEach((r) => this.feedContentCache.delete(r));
102
+ }
103
+ /**
104
+ * Get cached image metadata for a transaction ID
105
+ */
106
+ getImageMetadata(e) {
107
+ const t = this.imageMetadataCache.get(e);
108
+ return t ? Math.floor(Date.now() / 1e3) > t.expiresAt ? (this.imageMetadataCache.delete(e), null) : t.metadata : null;
109
+ }
110
+ /**
111
+ * Set cached image metadata for a transaction ID
112
+ */
113
+ setImageMetadata(e, t) {
114
+ const r = Math.floor(Date.now() / 1e3), n = this.config.imageMetadata?.ttl || 604800, s = r + n, o = {
115
+ metadata: t,
116
+ cachedAt: r,
117
+ expiresAt: s
118
+ };
119
+ this.imageMetadataCache.set(e, o);
120
+ }
121
+ /**
122
+ * Clear all caches
123
+ */
124
+ clearAll() {
125
+ this.feedDataCache.clear(), this.feedContentCache.clear(), this.imageMetadataCache.clear();
126
+ }
127
+ /**
128
+ * Get or create a refresh lock for a schema
129
+ * Prevents concurrent fetches for the same schema
130
+ */
131
+ async withRefreshLock(e, t) {
132
+ const r = this.refreshLocks.get(e);
133
+ r && (await r, this.getFeedData(e));
134
+ const n = (async () => {
135
+ try {
136
+ return await t();
137
+ } finally {
138
+ this.refreshLocks.delete(e);
139
+ }
140
+ })();
141
+ return this.refreshLocks.set(e, n), n;
142
+ }
143
+ /**
144
+ * Update configuration
145
+ */
146
+ updateConfig(e) {
147
+ this.config = { ...this.config, ...e };
148
+ }
149
+ /**
150
+ * Get cache statistics
151
+ */
152
+ getStats() {
153
+ return {
154
+ feedDataCount: this.feedDataCache.size,
155
+ feedContentCount: this.feedContentCache.size,
156
+ imageMetadataCount: this.imageMetadataCache.size,
157
+ activeLocks: this.refreshLocks.size
158
+ };
159
+ }
160
+ }
161
+ class J {
162
+ cacheDir;
163
+ config;
164
+ constructor(e) {
165
+ this.cacheDir = e.cacheDir, this.config = e, this.ensureCacheDir().catch((t) => {
166
+ console.error("Failed to create cache directory:", t);
167
+ });
168
+ }
169
+ /**
170
+ * Ensure cache directory exists
171
+ */
172
+ async ensureCacheDir() {
173
+ try {
174
+ await p.mkdir(this.cacheDir, { recursive: !0 });
175
+ } catch (e) {
176
+ if (e.code !== "EEXIST")
177
+ throw e;
178
+ }
179
+ }
180
+ /**
181
+ * Get file path for feed data
182
+ */
183
+ getFeedDataPath(e) {
184
+ return D(this.cacheDir, `${e}.json`);
185
+ }
186
+ /**
187
+ * Get file path for feed content
188
+ */
189
+ getFeedContentPath(e, t) {
190
+ return D(this.cacheDir, `${e}-${t}.json`);
191
+ }
192
+ /**
193
+ * Get file path for image metadata
194
+ */
195
+ getImageMetadataPath(e) {
196
+ const t = D(this.cacheDir, "image-metadata");
197
+ return D(t, `${e}.json`);
198
+ }
199
+ /**
200
+ * Ensure image metadata directory exists
201
+ */
202
+ async ensureImageMetadataDir() {
203
+ try {
204
+ const e = D(this.cacheDir, "image-metadata");
205
+ await p.mkdir(e, { recursive: !0 });
206
+ } catch (e) {
207
+ if (e.code !== "EEXIST")
208
+ throw e;
209
+ }
210
+ }
211
+ /**
212
+ * Get cached feed data for a schema
213
+ */
214
+ async getFeedData(e) {
215
+ try {
216
+ const t = this.getFeedDataPath(e), r = await p.readFile(t, "utf-8"), n = JSON.parse(r);
217
+ return Math.floor(Date.now() / 1e3) - n.lastUpdated > this.config.ttl ? (await this.clearFeedData(e), null) : n;
218
+ } catch (t) {
219
+ return t.code === "ENOENT" || console.error(`Error reading feed data cache for ${e}:`, t), null;
220
+ }
221
+ }
222
+ /**
223
+ * Set cached feed data for a schema
224
+ */
225
+ async setFeedData(e, t) {
226
+ try {
227
+ await this.ensureCacheDir();
228
+ const r = this.getFeedDataPath(e);
229
+ await p.writeFile(r, JSON.stringify(t, null, 2), "utf-8");
230
+ } catch (r) {
231
+ console.error(`Error writing feed data cache for ${e}:`, r);
232
+ }
233
+ }
234
+ /**
235
+ * Get cached feed content for a schema and format
236
+ */
237
+ async getFeedContent(e, t) {
238
+ try {
239
+ const r = this.getFeedContentPath(e, t), n = await p.readFile(r, "utf-8"), s = JSON.parse(n);
240
+ return Math.floor(Date.now() / 1e3) > s.expiresAt ? (await this.clearFeedContent(e, t), null) : s;
241
+ } catch (r) {
242
+ return r.code === "ENOENT" || console.error(
243
+ `Error reading feed content cache for ${e}:${t}:`,
244
+ r
245
+ ), null;
246
+ }
247
+ }
248
+ /**
249
+ * Set cached feed content for a schema and format
250
+ */
251
+ async setFeedContent(e, t, r) {
252
+ try {
253
+ await this.ensureCacheDir();
254
+ const n = this.getFeedContentPath(e, t);
255
+ await p.writeFile(n, JSON.stringify(r, null, 2), "utf-8");
256
+ } catch (n) {
257
+ console.error(
258
+ `Error writing feed content cache for ${e}:${t}:`,
259
+ n
260
+ );
261
+ }
262
+ }
263
+ /**
264
+ * Clear feed data cache for a schema
265
+ */
266
+ async clearFeedData(e) {
267
+ try {
268
+ const t = this.getFeedDataPath(e);
269
+ await p.unlink(t);
270
+ } catch (t) {
271
+ t.code !== "ENOENT" && console.error(`Error clearing feed data cache for ${e}:`, t);
272
+ }
273
+ }
274
+ /**
275
+ * Clear content cache for a schema and format
276
+ */
277
+ async clearFeedContent(e, t) {
278
+ try {
279
+ const r = this.getFeedContentPath(e, t);
280
+ await p.unlink(r);
281
+ } catch (r) {
282
+ r.code !== "ENOENT" && console.error(
283
+ `Error clearing feed content cache for ${e}:${t}:`,
284
+ r
285
+ );
286
+ }
287
+ }
288
+ /**
289
+ * Clear all content cache for a schema (all formats)
290
+ */
291
+ async clearAllContentCache(e) {
292
+ try {
293
+ const t = await p.readdir(this.cacheDir), r = `${e}-`, n = ".json";
294
+ for (const s of t)
295
+ s.startsWith(r) && s.endsWith(n) && s !== `${e}.json` && await p.unlink(D(this.cacheDir, s));
296
+ } catch (t) {
297
+ console.error(`Error clearing all content cache for ${e}:`, t);
298
+ }
299
+ }
300
+ /**
301
+ * Get cached image metadata for a transaction ID
302
+ */
303
+ async getImageMetadata(e) {
304
+ try {
305
+ const t = this.getImageMetadataPath(e), r = await p.readFile(t, "utf-8"), n = JSON.parse(r);
306
+ return Math.floor(Date.now() / 1e3) > n.expiresAt ? (await this.clearImageMetadata(e), null) : n.metadata;
307
+ } catch (t) {
308
+ return t.code === "ENOENT" || console.error(`Error reading image metadata cache for ${e}:`, t), null;
309
+ }
310
+ }
311
+ /**
312
+ * Set cached image metadata for a transaction ID
313
+ */
314
+ async setImageMetadata(e, t) {
315
+ try {
316
+ await this.ensureImageMetadataDir();
317
+ const r = this.getImageMetadataPath(e), n = Math.floor(Date.now() / 1e3), s = this.config.imageMetadata?.ttl || 604800, o = n + s, l = {
318
+ metadata: t,
319
+ cachedAt: n,
320
+ expiresAt: o
321
+ };
322
+ await p.writeFile(r, JSON.stringify(l, null, 2), "utf-8");
323
+ } catch (r) {
324
+ console.error(`Error writing image metadata cache for ${e}:`, r);
325
+ }
326
+ }
327
+ /**
328
+ * Clear image metadata cache for a transaction ID
329
+ */
330
+ async clearImageMetadata(e) {
331
+ try {
332
+ const t = this.getImageMetadataPath(e);
333
+ await p.unlink(t);
334
+ } catch (t) {
335
+ t.code !== "ENOENT" && console.error(`Error clearing image metadata cache for ${e}:`, t);
336
+ }
337
+ }
338
+ /**
339
+ * Clear all caches
340
+ */
341
+ async clearAll() {
342
+ try {
343
+ const e = await p.readdir(this.cacheDir);
344
+ for (const r of e)
345
+ r.endsWith(".json") && await p.unlink(D(this.cacheDir, r));
346
+ const t = D(this.cacheDir, "image-metadata");
347
+ try {
348
+ const r = await p.readdir(t);
349
+ for (const n of r)
350
+ n.endsWith(".json") && await p.unlink(D(t, n));
351
+ } catch {
352
+ }
353
+ } catch (e) {
354
+ console.error("Error clearing all caches:", e);
355
+ }
356
+ }
357
+ }
358
+ class N {
359
+ memoryCache;
360
+ fileCache;
361
+ config;
362
+ stats = {
363
+ hits: 0,
364
+ misses: 0,
365
+ refreshes: 0,
366
+ errors: 0
367
+ };
368
+ constructor(e) {
369
+ this.config = e, this.memoryCache = new X(e), this.fileCache = new J(e);
370
+ }
371
+ /**
372
+ * Get cached feed data for a schema
373
+ * Checks memory cache first, then file cache
374
+ */
375
+ async getFeedData(e) {
376
+ if (!this.config.enabled)
377
+ return null;
378
+ try {
379
+ let t = this.memoryCache.getFeedData(e);
380
+ return t ? (this.stats.hits++, t) : (t = await this.fileCache.getFeedData(e), t ? (this.memoryCache.setFeedData(e, t.items), this.stats.hits++, t) : (this.stats.misses++, null));
381
+ } catch (t) {
382
+ return console.error(`Error getting feed data cache for ${e}:`, t), this.stats.errors++, null;
383
+ }
384
+ }
385
+ /**
386
+ * Set cached feed data for a schema
387
+ * Updates both memory and file cache
388
+ */
389
+ async setFeedData(e, t) {
390
+ if (this.config.enabled)
391
+ try {
392
+ this.memoryCache.setFeedData(e, t);
393
+ const r = this.memoryCache.getFeedData(e);
394
+ r && await this.fileCache.setFeedData(e, r);
395
+ } catch (r) {
396
+ console.error(`Error setting feed data cache for ${e}:`, r), this.stats.errors++;
397
+ }
398
+ }
399
+ /**
400
+ * Get cached feed content for a schema and format
401
+ * Checks memory cache first, then file cache
402
+ */
403
+ async getFeedContent(e, t) {
404
+ if (!this.config.enabled)
405
+ return null;
406
+ try {
407
+ let r = this.memoryCache.getFeedContent(e, t);
408
+ return r ? (this.stats.hits++, r) : (r = await this.fileCache.getFeedContent(e, t), r ? (this.memoryCache.setFeedContent(
409
+ e,
410
+ t,
411
+ r.content,
412
+ r.contentType
413
+ ), this.stats.hits++, r) : (this.stats.misses++, null));
414
+ } catch (r) {
415
+ return console.error(
416
+ `Error getting feed content cache for ${e}:${t}:`,
417
+ r
418
+ ), this.stats.errors++, null;
419
+ }
420
+ }
421
+ /**
422
+ * Set cached feed content for a schema and format
423
+ * Updates both memory and file cache
424
+ */
425
+ async setFeedContent(e, t, r, n) {
426
+ if (this.config.enabled)
427
+ try {
428
+ this.memoryCache.setFeedContent(e, t, r, n);
429
+ const s = this.memoryCache.getFeedContent(e, t);
430
+ s && await this.fileCache.setFeedContent(e, t, s);
431
+ } catch (s) {
432
+ console.error(
433
+ `Error setting feed content cache for ${e}:${t}:`,
434
+ s
435
+ ), this.stats.errors++;
436
+ }
437
+ }
438
+ /**
439
+ * Clear feed data cache for a schema
440
+ */
441
+ async clearFeedData(e) {
442
+ this.memoryCache.clearFeedData(e), await this.fileCache.clearFeedData(e), this.memoryCache.clearContentCache(e), await this.fileCache.clearAllContentCache(e);
443
+ }
444
+ /**
445
+ * Clear all caches
446
+ */
447
+ async clearAll() {
448
+ this.memoryCache.clearAll(), await this.fileCache.clearAll();
449
+ }
450
+ /**
451
+ * Get or create a refresh lock for a schema
452
+ * Prevents concurrent fetches for the same schema
453
+ */
454
+ async withRefreshLock(e, t) {
455
+ return this.memoryCache.withRefreshLock(e, t);
456
+ }
457
+ /**
458
+ * Merge new items with cached items
459
+ * Deduplicates by item ID and sorts by timeCreated (descending)
460
+ */
461
+ mergeItems(e, t) {
462
+ const r = /* @__PURE__ */ new Map();
463
+ for (const s of e) {
464
+ const o = s.id || s.seedUid || s.SeedUid || "";
465
+ o && r.set(o, s);
466
+ }
467
+ for (const s of t) {
468
+ const o = s.id || s.seedUid || s.SeedUid || "";
469
+ o ? r.set(o, s) : r.set(`temp-${Date.now()}-${Math.random()}`, s);
470
+ }
471
+ const n = Array.from(r.values());
472
+ return n.sort((s, o) => {
473
+ const l = s.timeCreated || 0;
474
+ return (o.timeCreated || 0) - l;
475
+ }), n;
476
+ }
477
+ /**
478
+ * Filter items to only include those newer than the given timestamp
479
+ */
480
+ filterNewItems(e, t) {
481
+ return e.filter((r) => {
482
+ const n = r.timeCreated;
483
+ return n && n > t;
484
+ });
485
+ }
486
+ /**
487
+ * Update configuration
488
+ */
489
+ updateConfig(e) {
490
+ this.config = { ...this.config, ...e }, this.memoryCache.updateConfig(this.config);
491
+ }
492
+ /**
493
+ * Get cache statistics
494
+ */
495
+ getStats() {
496
+ return {
497
+ ...this.stats,
498
+ memoryStats: this.memoryCache.getStats()
499
+ };
500
+ }
501
+ /**
502
+ * Get cached image metadata for a transaction ID
503
+ * Checks memory cache first, then file cache
504
+ */
505
+ async getImageMetadata(e) {
506
+ if (!this.config.enabled || !this.config.imageMetadata?.enabled)
507
+ return null;
508
+ try {
509
+ let t = this.memoryCache.getImageMetadata(e);
510
+ return t ? (this.stats.hits++, t) : (t = await this.fileCache.getImageMetadata(e), t ? (this.memoryCache.setImageMetadata(e, t), this.stats.hits++, t) : (this.stats.misses++, null));
511
+ } catch (t) {
512
+ return console.error(`Error getting image metadata cache for ${e}:`, t), this.stats.errors++, null;
513
+ }
514
+ }
515
+ /**
516
+ * Set cached image metadata for a transaction ID
517
+ * Updates both memory and file cache
518
+ */
519
+ async setImageMetadata(e, t) {
520
+ if (!(!this.config.enabled || !this.config.imageMetadata?.enabled))
521
+ try {
522
+ this.memoryCache.setImageMetadata(e, t), await this.fileCache.setImageMetadata(e, t);
523
+ } catch (r) {
524
+ console.error(`Error setting image metadata cache for ${e}:`, r), this.stats.errors++;
525
+ }
526
+ }
527
+ /**
528
+ * Reset statistics
529
+ */
530
+ resetStats() {
531
+ this.stats = {
532
+ hits: 0,
533
+ misses: 0,
534
+ refreshes: 0,
535
+ errors: 0
536
+ };
537
+ }
538
+ }
539
+ function P() {
540
+ const d = parseInt(process.env.CACHE_TTL || "3600", 10), e = process.env.CACHE_DIR || "./cache", t = !(process.env.CACHE_ENABLED === "false" || process.env.CACHE_ENABLED === "0" || process.env.CACHE_ENABLED === "no" || process.env.CACHE_ENABLED === "off"), r = process.env.CACHE_BACKGROUND_REFRESH === "true", n = parseInt(
541
+ process.env.CACHE_REFRESH_INTERVAL || "300",
542
+ 10
543
+ );
544
+ t || console.log("⚠️ Cache is DISABLED (CACHE_ENABLED=false)");
545
+ const s = process.env.IMAGE_METADATA_ENABLED !== "false", o = parseInt(process.env.IMAGE_METADATA_TTL || "604800", 10), l = process.env.IMAGE_METADATA_GATEWAYS ? process.env.IMAGE_METADATA_GATEWAYS.split(",").map((m) => m.trim()) : ["arweave.net", "ar-io.net"], h = parseInt(process.env.IMAGE_METADATA_TIMEOUT || "5000", 10);
546
+ return {
547
+ ttl: d,
548
+ cacheDir: e,
549
+ enabled: t,
550
+ backgroundRefresh: r,
551
+ refreshInterval: n,
552
+ imageMetadata: {
553
+ enabled: s,
554
+ ttl: o,
555
+ gateways: l,
556
+ timeout: h
557
+ }
558
+ };
559
+ }
560
+ function q() {
561
+ const d = process.env.FEED_ITEM_URL_BASE?.trim() || void 0, e = process.env.FEED_ITEM_URL_PATH?.trim() || "attestation/view";
562
+ return { itemUrlBase: d, itemUrlPath: e };
563
+ }
564
+ class b {
565
+ config;
566
+ constructor(e) {
567
+ this.config = e;
568
+ }
569
+ /**
570
+ * Detect if an Arweave transaction ID links to an image and extract metadata
571
+ */
572
+ async detectImage(e) {
573
+ const t = this.config.gateways || ["arweave.net"];
574
+ for (const r of t)
575
+ try {
576
+ const n = `https://${r}/${e}`, s = await this.getImageMetadata(n);
577
+ if (s.isImage)
578
+ return s;
579
+ } catch (n) {
580
+ console.warn(`Failed to fetch from gateway ${r} for transaction ${e}:`, n);
581
+ continue;
582
+ }
583
+ return {
584
+ isImage: !1,
585
+ url: `https://${t[0]}/${e}`
586
+ };
587
+ }
588
+ /**
589
+ * Get image metadata from a URL
590
+ */
591
+ async getImageMetadata(e) {
592
+ try {
593
+ const t = await this.fetchWithTimeout(e, { method: "HEAD" });
594
+ if (!t.ok)
595
+ return { isImage: !1, url: e };
596
+ const r = t.headers.get("content-type") || "", n = t.headers.get("content-length");
597
+ if (!this.isImageContentType(r))
598
+ return {
599
+ isImage: !1,
600
+ url: e,
601
+ mimeType: r || void 0,
602
+ size: n ? parseInt(n, 10) : void 0
603
+ };
604
+ const s = await this.fetchWithTimeout(e, {
605
+ headers: {
606
+ Range: "bytes=0-8192"
607
+ // First 8KB should be enough for most image headers
608
+ }
609
+ });
610
+ if (!s.ok)
611
+ return await this.getImageMetadataFromFullRequest(e, r, n);
612
+ const o = Buffer.from(await s.arrayBuffer()), l = this.extractImageDimensions(o, r), h = this.getImageFormat(r, o);
613
+ return {
614
+ isImage: !0,
615
+ url: e,
616
+ mimeType: r,
617
+ width: l.width,
618
+ height: l.height,
619
+ size: n ? parseInt(n, 10) : void 0,
620
+ format: h
621
+ };
622
+ } catch (t) {
623
+ return console.warn(`Error fetching image metadata from ${e}:`, t), { isImage: !1, url: e };
624
+ }
625
+ }
626
+ /**
627
+ * Fetch image metadata from full request (fallback when range requests fail)
628
+ */
629
+ async getImageMetadataFromFullRequest(e, t, r) {
630
+ try {
631
+ const n = await this.fetchWithTimeout(e);
632
+ if (!n.ok)
633
+ return { isImage: !1, url: e };
634
+ const s = Buffer.from(await n.arrayBuffer()), o = this.extractImageDimensions(s, t), l = this.getImageFormat(t, s);
635
+ return {
636
+ isImage: !0,
637
+ url: e,
638
+ mimeType: t,
639
+ width: o.width,
640
+ height: o.height,
641
+ size: s.length,
642
+ format: l
643
+ };
644
+ } catch (n) {
645
+ return console.warn(`Error in full request for ${e}:`, n), { isImage: !1, url: e };
646
+ }
647
+ }
648
+ /**
649
+ * Validate if Content-Type indicates an image
650
+ */
651
+ isImageContentType(e) {
652
+ return e ? (e.toLowerCase().split(";")[0] ?? "").trim().startsWith("image/") : !1;
653
+ }
654
+ /**
655
+ * Extract image dimensions from buffer
656
+ */
657
+ extractImageDimensions(e, t) {
658
+ try {
659
+ const r = S(e);
660
+ if (r.width && r.height)
661
+ return {
662
+ width: r.width,
663
+ height: r.height
664
+ };
665
+ } catch (r) {
666
+ console.warn("Failed to extract dimensions:", r);
667
+ }
668
+ return { width: 0, height: 0 };
669
+ }
670
+ /**
671
+ * Get image format from Content-Type or buffer analysis
672
+ */
673
+ getImageFormat(e, t) {
674
+ if (e) {
675
+ const r = e.toLowerCase().match(/image\/([^;]+)/);
676
+ if (r && r[1]) {
677
+ const n = r[1].toLowerCase();
678
+ return n === "jpeg" ? "jpeg" : n === "png" ? "png" : n === "gif" ? "gif" : n === "webp" ? "webp" : n === "svg+xml" ? "svg" : n;
679
+ }
680
+ }
681
+ try {
682
+ const r = S(t);
683
+ if (r.type)
684
+ return r.type.toLowerCase();
685
+ } catch {
686
+ }
687
+ }
688
+ /**
689
+ * Fetch with timeout
690
+ */
691
+ async fetchWithTimeout(e, t = {}) {
692
+ const r = new AbortController(), n = setTimeout(() => r.abort(), this.config.timeout);
693
+ try {
694
+ const s = await fetch(e, {
695
+ ...t,
696
+ signal: r.signal
697
+ });
698
+ return clearTimeout(n), s;
699
+ } catch (s) {
700
+ throw clearTimeout(n), s instanceof Error && s.name === "AbortError" ? new Error(`Request timeout after ${this.config.timeout}ms`) : s;
701
+ }
702
+ }
703
+ }
704
+ let I, M = null, E = null;
705
+ function V() {
706
+ if (!E) {
707
+ const d = P();
708
+ E = new N(d);
709
+ }
710
+ return E;
711
+ }
712
+ function de() {
713
+ E = null;
714
+ }
715
+ const Y = async () => {
716
+ if (M)
717
+ return M;
718
+ if (!I)
719
+ return M = (async () => {
720
+ try {
721
+ console.log("Initializing Seed Protocol client..."), await $.init({ config: {
722
+ endpoints: {
723
+ filePaths: "app-files",
724
+ files: "/app-files"
725
+ },
726
+ arweaveDomain: "arweave.net"
727
+ }, addresses: [] }), console.log("✅ Seed Protocol client initialized successfully"), I = $, M = null;
728
+ } catch (d) {
729
+ throw console.error("❌ Failed to initialize Seed Protocol client:", d), M = null, d;
730
+ }
731
+ })(), M;
732
+ }, U = async () => I || (M ? (await M, I) : (await Y(), I)), he = async () => {
733
+ try {
734
+ console.log("Tearing down Seed Protocol client..."), typeof $.stop == "function" && (await $.stop(), console.log("✅ Seed Protocol client stopped")), typeof $.unload == "function" && (await $.unload(), console.log("✅ Seed Protocol client unloaded")), console.log("✅ Seed Protocol client teardown complete");
735
+ } catch (d) {
736
+ console.error("❌ Failed to teardown Seed Protocol client:", d);
737
+ }
738
+ }, u = {
739
+ title: "Seed Protocol",
740
+ description: "Content published via Seed Protocol",
741
+ siteUrl: "https://seedprotocol.io",
742
+ language: "en",
743
+ copyright: `© ${(/* @__PURE__ */ new Date()).getFullYear()} All rights reserved`,
744
+ author: {
745
+ name: "Seed Protocol",
746
+ email: "info@seedprotocol.io",
747
+ link: "https://seedprotocol.io"
748
+ }
749
+ }, ge = x`
750
+ query GetSeeds($where: AttestationWhereInput!) {
751
+ itemSeeds: attestations(where: $where, orderBy: [{ timeCreated: desc }]) {
752
+ id
753
+ decodedDataJson
754
+ attester
755
+ schema {
756
+ schemaNames {
757
+ name
758
+ }
759
+ }
760
+ refUID
761
+ revoked
762
+ schemaId
763
+ timeCreated
764
+ isOffchain
765
+ }
766
+ }
767
+ `;
768
+ async function v(d, e, t) {
769
+ const r = d.filter(
770
+ (l) => l.storageTransactionId || l.storage_transaction_id
771
+ );
772
+ if (r.length === 0)
773
+ return d;
774
+ console.log(`Enriching ${r.length} items with image metadata`);
775
+ const n = r.map(async (l) => {
776
+ const h = l.storageTransactionId || l.storage_transaction_id;
777
+ if (!h) return l;
778
+ try {
779
+ let f = await t.getImageMetadata(h);
780
+ return f || (console.log(`Detecting image for transaction ${h}`), f = await e.detectImage(h), await t.setImageMetadata(h, f)), f.isImage && (l._imageMetadata = f, l._hasImage = !0), l;
781
+ } catch (f) {
782
+ return console.warn(`Error enriching item with transaction ${h}:`, f), l;
783
+ }
784
+ }), s = await Promise.all(n), o = /* @__PURE__ */ new Map();
785
+ return s.forEach((l) => {
786
+ const h = l.id || l.seedUid || l.SeedUid || "";
787
+ h && o.set(h, l);
788
+ }), d.map((l) => {
789
+ const h = l.id || l.seedUid || l.SeedUid || "";
790
+ return o.get(h) || l;
791
+ });
792
+ }
793
+ function K(d, e) {
794
+ const { schemaName: t, siteUrl: r, itemUrlBase: n, itemUrlPath: s } = e;
795
+ return d.map((o) => {
796
+ const l = o.id || o.seedUid || o.SeedUid || o.storageTransactionId || o.storage_transaction_id, h = n != null ? `${n.replace(/\/$/, "")}/${(s ?? "attestation/view").replace(/^\//, "")}/${l}` : `${r}/${F(t)}/${l}`, f = o.link || o.Link || o.import_url || o.importUrl || h;
797
+ let m;
798
+ if (o.pubDate || o.PubDate) {
799
+ const a = o.pubDate || o.PubDate;
800
+ m = new Date(a);
801
+ } else if (o.timeCreated)
802
+ m = new Date(o.timeCreated * 1e3);
803
+ else if (o.publishedAt || o.createdAt || o.updatedAt) {
804
+ const a = o.publishedAt || o.createdAt || o.updatedAt;
805
+ m = a && typeof a == "object" && a.constructor === Date ? a : new Date(a);
806
+ } else
807
+ m = /* @__PURE__ */ new Date();
808
+ const w = {
809
+ ...o,
810
+ // Preserve all dynamic properties first
811
+ // Map to standard feed fields
812
+ id: l,
813
+ title: o.title || o.Title || "Untitled",
814
+ link: f,
815
+ description: o.summary || o.description || "",
816
+ content: o.html || o.content || o.summary || "",
817
+ pubDate: m,
818
+ date: m,
819
+ // Map guid - use full URL when available for proper permalink
820
+ guid: o.guid || o.Guid || o.link || o.Link || f
821
+ };
822
+ if (o._imageMetadata && o._hasImage) {
823
+ const a = o._imageMetadata;
824
+ w._imageMetadata = a, w._hasImage = !0;
825
+ }
826
+ return Object.keys(w).forEach((a) => {
827
+ const i = w[a];
828
+ if (typeof i == "string" && /date|time|published|created|updated/i.test(a) && a !== "pubDate" && a !== "date") {
829
+ const c = new Date(i);
830
+ isNaN(c.getTime()) || (w[a] = c);
831
+ }
832
+ }), w;
833
+ });
834
+ }
835
+ const Q = (d, e, t, r) => {
836
+ const n = F(e), s = `${u.siteUrl}/${n}/${t}`, o = r ? `${s}?v=${r}` : s, l = `${u.title} - ${te(n)}`, h = /* @__PURE__ */ new Date(), f = q(), m = K(d, {
837
+ schemaName: e,
838
+ siteUrl: u.siteUrl,
839
+ itemUrlBase: f.itemUrlBase,
840
+ itemUrlPath: f.itemUrlPath
841
+ });
842
+ switch (t) {
843
+ case "atom": {
844
+ const w = {
845
+ id: o,
846
+ title: l,
847
+ updated: h,
848
+ links: [
849
+ { href: o, rel: "self" },
850
+ { href: u.siteUrl }
851
+ ],
852
+ subtitle: u.description,
853
+ rights: u.copyright,
854
+ author: u.author ? {
855
+ name: u.author.name,
856
+ email: u.author.email,
857
+ uri: u.author.link
858
+ } : void 0,
859
+ entries: m.map((a) => {
860
+ const i = {
861
+ id: a.id || a.link,
862
+ title: a.title || "Untitled",
863
+ updated: a.date || a.pubDate || h,
864
+ links: a.link ? [{ href: a.link }] : [],
865
+ ...a
866
+ // Preserve all dynamic properties
867
+ };
868
+ if (a.content && (i.content = a.content), a.description && (i.summary = a.description), a._imageMetadata && a._hasImage) {
869
+ const c = a._imageMetadata;
870
+ if (i.links || (i.links = []), i.links.push({
871
+ href: c.url,
872
+ rel: "enclosure",
873
+ type: c.mimeType || "image/jpeg",
874
+ length: c.size
875
+ }), c.url && i.content && !i.content.includes(c.url)) {
876
+ const g = `<img src="${c.url}" alt="${a.title || ""}"${c.width ? ` width="${c.width}"` : ""}${c.height ? ` height="${c.height}"` : ""} />`;
877
+ i.content = typeof i.content == "string" ? `${g}
878
+ ${i.content}` : { type: "html", value: `${g}
879
+ ${i.content.value || i.content}` };
880
+ }
881
+ }
882
+ return i;
883
+ })
884
+ };
885
+ return Promise.resolve(R(w));
886
+ }
887
+ case "json": {
888
+ const w = {
889
+ title: l,
890
+ home_page_url: u.siteUrl,
891
+ feed_url: o,
892
+ description: u.description,
893
+ author: u.author ? {
894
+ name: u.author.name,
895
+ url: u.author.link
896
+ } : void 0,
897
+ items: m.map((i) => {
898
+ const c = {
899
+ id: i.id || i.link,
900
+ ...i
901
+ // Preserve all dynamic properties
902
+ };
903
+ if (i.title && (c.title = i.title), i.link && (c.url = i.link), i.content && (c.content_html = i.content), i.description && (c.summary = i.description), (i.date || i.pubDate) && (c.date_published = i.date || i.pubDate), i._imageMetadata && i._hasImage) {
904
+ const g = i._imageMetadata;
905
+ if (c.image = g.url, c.attachments || (c.attachments = []), c.attachments.push({
906
+ url: g.url,
907
+ mime_type: g.mimeType || "image/jpeg",
908
+ size_in_bytes: g.size,
909
+ title: i.title || "Image"
910
+ }), g.url && c.content_html && !c.content_html.includes(g.url)) {
911
+ const C = `<img src="${g.url}" alt="${i.title || ""}"${g.width ? ` width="${g.width}"` : ""}${g.height ? ` height="${g.height}"` : ""} />`;
912
+ c.content_html = `${C}
913
+ ${c.content_html}`;
914
+ }
915
+ }
916
+ return c;
917
+ })
918
+ }, a = z(w);
919
+ return Promise.resolve(typeof a == "string" ? a : JSON.stringify(a));
920
+ }
921
+ case "rss":
922
+ default: {
923
+ const w = {
924
+ title: l,
925
+ link: u.siteUrl,
926
+ description: u.description,
927
+ language: u.language,
928
+ copyright: u.copyright,
929
+ webMaster: u.author?.email,
930
+ pubDate: h,
931
+ lastBuildDate: h,
932
+ items: m.map((a) => {
933
+ const i = {
934
+ ...a
935
+ // Preserve all dynamic properties
936
+ };
937
+ if (a.title && (i.title = a.title), a.link && (i.link = a.link), a.description && (i.description = a.description), (a.date || a.pubDate) && (i.pubDate = a.date || a.pubDate), a.guid ? i.guid = {
938
+ value: a.guid,
939
+ isPermaLink: typeof a.guid == "string" && (a.guid.startsWith("http://") || a.guid.startsWith("https://"))
940
+ } : a.id && (i.guid = {
941
+ value: a.id,
942
+ isPermaLink: typeof a.id == "string" && (a.id.startsWith("http://") || a.id.startsWith("https://"))
943
+ }), a._imageMetadata && a._hasImage) {
944
+ const c = a._imageMetadata;
945
+ i.enclosures = [{
946
+ url: c.url,
947
+ type: c.mimeType || "image/jpeg",
948
+ length: c.size
949
+ }];
950
+ const g = {
951
+ url: c.url,
952
+ type: c.mimeType || "image/jpeg",
953
+ medium: "image"
954
+ // image, video, audio, document
955
+ };
956
+ c.width && (g.width = c.width), c.height && (g.height = c.height), c.size && (g.fileSize = c.size), c.format && (g.format = c.format), i["media:content"] = [g];
957
+ const C = {
958
+ url: c.url
959
+ };
960
+ c.width && (C.width = c.width), c.height && (C.height = c.height), i["media:thumbnail"] = [C], a.title && (i["media:title"] = a.title);
961
+ const T = a.description || a.summary || a.title || "";
962
+ if (T && (i["media:description"] = T), a.title) {
963
+ const y = [];
964
+ a.title && y.push(...a.title.toLowerCase().split(/\s+/).filter((A) => A.length > 3)), a.description && y.push(...a.description.toLowerCase().split(/\s+/).filter((A) => A.length > 3)), y.length > 0 && (i["media:keywords"] = [...new Set(y)].slice(0, 10).join(", "));
965
+ }
966
+ if (a.authors && Array.isArray(a.authors) && a.authors.length > 0 ? i["media:credit"] = a.authors.map((y) => ({
967
+ value: y.name || y.displayName || "Unknown",
968
+ role: "author"
969
+ })) : u.author && (i["media:credit"] = [{
970
+ value: u.author.name,
971
+ role: "author"
972
+ }]), u.copyright && (i["media:copyright"] = u.copyright), e && (i["media:category"] = [{
973
+ value: e,
974
+ scheme: "http://www.schema.org/"
975
+ }]), i["media:group"] = [{
976
+ "media:content": [g],
977
+ "media:thumbnail": [C],
978
+ "media:title": a.title || "Untitled",
979
+ "media:description": T
980
+ }], c.url && a.content && !a.content.includes(c.url)) {
981
+ const y = `<img src="${c.url}" alt="${a.title || ""}"${c.width ? ` width="${c.width}"` : ""}${c.height ? ` height="${c.height}"` : ""} />`;
982
+ i["content:encoded"] = `${y}
983
+ ${a.content}`;
984
+ }
985
+ }
986
+ if (!i.enclosures && (a.feature_image || a.featureImage)) {
987
+ const c = a.feature_image || a.featureImage, g = typeof c == "string" && c.startsWith("http") ? c : `https://arweave.net/${c}`;
988
+ i.enclosures = [{
989
+ url: g,
990
+ type: "image/jpeg"
991
+ // Default, could be determined from URL or metadata
992
+ }];
993
+ }
994
+ if (i.dc = {}, (a.date || a.pubDate) && (i.dc.date = a.date || a.pubDate), a.timeCreated) {
995
+ const c = new Date(a.timeCreated * 1e3);
996
+ i.dc.dates || (i.dc.dates = []), i.dc.dates.push(c);
997
+ }
998
+ return (a.seedUid || a.SeedUid) && (i.dc.identifier || (i.dc.identifier = []), i.dc.identifier.push(a.seedUid || a.SeedUid)), (a.storageTransactionId || a.storage_transaction_id) && (i.dc.identifier || (i.dc.identifier = []), i.dc.identifier.push(a.storageTransactionId || a.storage_transaction_id)), (a.import_url || a.importUrl) && (i.dc.source = a.import_url || a.importUrl), (a.seedUid || a.SeedUid) && (i.seedUid = a.seedUid || a.SeedUid), (a.storageTransactionId || a.storage_transaction_id) && (i.storageTransactionId = a.storageTransactionId || a.storage_transaction_id), a.timeCreated && (i.timeCreated = a.timeCreated), i;
999
+ })
1000
+ };
1001
+ return Promise.resolve(L(w));
1002
+ }
1003
+ }
1004
+ };
1005
+ function Z(d) {
1006
+ switch (d) {
1007
+ case "atom":
1008
+ return "application/atom+xml; charset=utf-8";
1009
+ case "json":
1010
+ return "application/feed+json; charset=utf-8";
1011
+ case "rss":
1012
+ default:
1013
+ return "application/rss+xml; charset=utf-8";
1014
+ }
1015
+ }
1016
+ function ee(d) {
1017
+ const e = d.toLowerCase();
1018
+ return ["rss", "atom", "json"].includes(e) ? e : null;
1019
+ }
1020
+ function te(d) {
1021
+ return d.charAt(0).toUpperCase() + d.slice(1);
1022
+ }
1023
+ async function fe(d, e, t, r) {
1024
+ const n = ee(e);
1025
+ if (!n)
1026
+ return new Response(
1027
+ JSON.stringify({ error: `Invalid feed format: ${e}` }),
1028
+ {
1029
+ status: 400,
1030
+ headers: { "Content-Type": "application/json" }
1031
+ }
1032
+ );
1033
+ const s = F.singular(d.toLowerCase()), o = F(s);
1034
+ console.log(`Schema name: ${s}`), console.log(`Collection name: ${o}`);
1035
+ const l = V(), h = P();
1036
+ try {
1037
+ if (h.enabled) {
1038
+ const a = await l.getFeedContent(s, n);
1039
+ if (a)
1040
+ return t && G(t, a.etag) ? (console.log(`Cache hit with ETag match for ${s}:${n} - returning 304`), new Response(null, {
1041
+ status: 304,
1042
+ headers: {
1043
+ ETag: a.etag,
1044
+ "Last-Modified": new Date(a.lastModified * 1e3).toUTCString(),
1045
+ "Cache-Control": "public, max-age=3600, s-maxage=3600, must-revalidate"
1046
+ }
1047
+ })) : (console.log(`Cache hit for ${s}:${n}`), new Response(a.content, {
1048
+ status: 200,
1049
+ headers: {
1050
+ "Content-Type": a.contentType,
1051
+ ETag: a.etag,
1052
+ "Last-Modified": new Date(a.lastModified * 1e3).toUTCString(),
1053
+ "Cache-Control": "public, max-age=3600, s-maxage=3600, must-revalidate",
1054
+ "X-Feed-Schema": s,
1055
+ "X-Feed-Format": n,
1056
+ "X-Cache": "HIT"
1057
+ }
1058
+ }));
1059
+ }
1060
+ console.log(`Cache miss for ${s}:${n} - fetching items`);
1061
+ const f = h.enabled ? await l.withRefreshLock(s, async () => {
1062
+ const a = await l.getFeedData(s);
1063
+ let i;
1064
+ if (a) {
1065
+ console.log(`Incremental fetch: last processed timestamp: ${a.lastProcessedTimestamp}`);
1066
+ const g = await _(s);
1067
+ console.log(`First item: ${JSON.stringify(g[0])}`);
1068
+ const C = l.filterNewItems(g, a.lastProcessedTimestamp);
1069
+ C.length > 0 ? (console.log(`Found ${C.length} new items, merging with ${a.items.length} cached items`), i = l.mergeItems(a.items, C)) : (console.log("No new items found, using cached items"), i = a.items);
1070
+ } else {
1071
+ console.log("Cold cache - fetching all items");
1072
+ const g = await U();
1073
+ g && console.log(`Client initialized: ${g.isInitialized()}`), i = await _(s), console.log(`Found ${i.length} feed items for schema ${s}`);
1074
+ }
1075
+ let c = i;
1076
+ if (h.imageMetadata?.enabled) {
1077
+ const g = new b({
1078
+ gateways: h.imageMetadata.gateways,
1079
+ timeout: h.imageMetadata.timeout
1080
+ });
1081
+ c = await v(i, g, l);
1082
+ }
1083
+ return await l.setFeedData(s, i), c;
1084
+ }) : await (async () => {
1085
+ console.log("Cache disabled - fetching all items directly");
1086
+ const a = await U();
1087
+ a && console.log(`Client initialized: ${a.isInitialized()}`);
1088
+ const i = await _(s);
1089
+ console.log(`Found ${i.length} feed items for schema ${s}`);
1090
+ let c = i;
1091
+ if (h.imageMetadata?.enabled) {
1092
+ const g = new b({
1093
+ gateways: h.imageMetadata.gateways,
1094
+ timeout: h.imageMetadata.timeout
1095
+ });
1096
+ c = await v(i, g, l);
1097
+ }
1098
+ return c;
1099
+ })(), m = await Q(f, s, n, r), w = Z(n);
1100
+ if (h.enabled) {
1101
+ await l.setFeedContent(s, n, m, w);
1102
+ const a = await l.getFeedContent(s, n), i = a?.etag || "", c = a?.lastModified || Math.floor(Date.now() / 1e3);
1103
+ return new Response(m, {
1104
+ status: 200,
1105
+ headers: {
1106
+ "Content-Type": w,
1107
+ ETag: i,
1108
+ "Last-Modified": new Date(c * 1e3).toUTCString(),
1109
+ "Cache-Control": "public, max-age=3600, s-maxage=3600, must-revalidate",
1110
+ "X-Feed-Schema": s,
1111
+ "X-Feed-Format": n,
1112
+ "X-Cache": "MISS"
1113
+ }
1114
+ });
1115
+ }
1116
+ return new Response(m, {
1117
+ status: 200,
1118
+ headers: {
1119
+ "Content-Type": w,
1120
+ "Cache-Control": "public, max-age=3600, s-maxage=3600",
1121
+ "X-Feed-Schema": s,
1122
+ "X-Feed-Format": n
1123
+ }
1124
+ });
1125
+ } catch (f) {
1126
+ if (console.error("Feed generation error:", f), h.enabled) {
1127
+ const m = await l.getFeedContent(s, n);
1128
+ if (m)
1129
+ return console.log("Serving stale cache due to error"), new Response(m.content, {
1130
+ status: 200,
1131
+ headers: {
1132
+ "Content-Type": m.contentType,
1133
+ ETag: m.etag,
1134
+ "Cache-Control": "public, max-age=3600, s-maxage=3600, must-revalidate",
1135
+ "X-Feed-Schema": s,
1136
+ "X-Feed-Format": n,
1137
+ "X-Cache": "STALE",
1138
+ Warning: '299 - "Cache is stale"'
1139
+ }
1140
+ });
1141
+ }
1142
+ return new Response(
1143
+ JSON.stringify({
1144
+ error: "Failed to generate feed",
1145
+ message: f instanceof Error ? f.message : "Unknown error"
1146
+ }),
1147
+ {
1148
+ status: 500,
1149
+ headers: { "Content-Type": "application/json" }
1150
+ }
1151
+ );
1152
+ }
1153
+ }
1154
+ export {
1155
+ ge as GET_SEEDS,
1156
+ Q as createFeed,
1157
+ U as getClient,
1158
+ fe as handleFeedRequest,
1159
+ Y as initializeSeedClient,
1160
+ de as resetCacheManager,
1161
+ he as teardownSeedClient
1162
+ };
1163
+ //# sourceMappingURL=index.js.map