@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/README.md +41 -0
- package/dist/cache/CacheManager.d.ts +81 -0
- package/dist/cache/CacheManager.d.ts.map +1 -0
- package/dist/cache/FileCache.d.ts +75 -0
- package/dist/cache/FileCache.d.ts.map +1 -0
- package/dist/cache/MemoryCache.d.ts +68 -0
- package/dist/cache/MemoryCache.d.ts.map +1 -0
- package/dist/cache/config.d.ts +13 -0
- package/dist/cache/config.d.ts.map +1 -0
- package/dist/cache/types.d.ts +59 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1163 -0
- package/dist/index.js.map +1 -0
- package/dist/services/arweaveImageService.d.ts +41 -0
- package/dist/services/arweaveImageService.d.ts.map +1 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/etag.d.ts +28 -0
- package/dist/utils/etag.d.ts.map +1 -0
- package/package.json +48 -0
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
|