@diffdelta/client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,370 @@
1
+ // src/cursor.ts
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
+ import { dirname, join } from "path";
4
+ import { homedir } from "os";
5
+ var DEFAULT_CURSOR_DIR = join(homedir(), ".diffdelta");
6
+ var DEFAULT_CURSOR_FILE = "cursors.json";
7
+ var CursorStore = class {
8
+ path;
9
+ cursors = {};
10
+ constructor(path) {
11
+ this.path = path || process.env.DD_CURSOR_PATH || join(DEFAULT_CURSOR_DIR, DEFAULT_CURSOR_FILE);
12
+ this.load();
13
+ }
14
+ /** Get the stored cursor for a feed key. */
15
+ get(key) {
16
+ return this.cursors[key];
17
+ }
18
+ /** Save a cursor and persist to disk. */
19
+ set(key, cursor) {
20
+ this.cursors[key] = cursor;
21
+ this.save();
22
+ }
23
+ /** Clear cursor(s). If key is undefined, clears all cursors. */
24
+ clear(key) {
25
+ if (key) {
26
+ delete this.cursors[key];
27
+ } else {
28
+ this.cursors = {};
29
+ }
30
+ this.save();
31
+ }
32
+ load() {
33
+ try {
34
+ if (existsSync(this.path)) {
35
+ const raw = readFileSync(this.path, "utf-8");
36
+ const data = JSON.parse(raw);
37
+ if (typeof data === "object" && data !== null) {
38
+ this.cursors = data;
39
+ }
40
+ }
41
+ } catch {
42
+ this.cursors = {};
43
+ }
44
+ }
45
+ save() {
46
+ try {
47
+ const dir = dirname(this.path);
48
+ if (!existsSync(dir)) {
49
+ mkdirSync(dir, { recursive: true });
50
+ }
51
+ writeFileSync(this.path, JSON.stringify(this.cursors, null, 2));
52
+ } catch {
53
+ }
54
+ }
55
+ };
56
+ var MemoryCursorStore = class {
57
+ cursors = {};
58
+ get(key) {
59
+ return this.cursors[key];
60
+ }
61
+ set(key, cursor) {
62
+ this.cursors[key] = cursor;
63
+ }
64
+ clear(key) {
65
+ if (key) {
66
+ delete this.cursors[key];
67
+ } else {
68
+ this.cursors = {};
69
+ }
70
+ }
71
+ };
72
+
73
+ // src/models.ts
74
+ function parseFeedItem(data, bucket = "new") {
75
+ const content = data.content;
76
+ let excerpt = "";
77
+ if (typeof content === "object" && content !== null) {
78
+ excerpt = content.excerpt_text || content.summary || "";
79
+ } else if (typeof content === "string") {
80
+ excerpt = content;
81
+ }
82
+ let riskScore = data.risk_score ?? null;
83
+ if (riskScore === null) {
84
+ const summary = data.summary;
85
+ if (typeof summary === "object" && summary !== null) {
86
+ riskScore = summary.risk_score ?? null;
87
+ }
88
+ }
89
+ return {
90
+ source: data.source || "",
91
+ id: data.id || "",
92
+ headline: data.headline || "",
93
+ url: data.url || "",
94
+ excerpt,
95
+ publishedAt: data.published_at || null,
96
+ updatedAt: data.updated_at || null,
97
+ bucket,
98
+ riskScore,
99
+ provenance: data.provenance || {},
100
+ raw: data
101
+ };
102
+ }
103
+ function parseHead(data) {
104
+ return {
105
+ cursor: data.cursor || "",
106
+ hash: data.hash || "",
107
+ changed: data.changed || false,
108
+ generatedAt: data.generated_at || "",
109
+ ttlSec: data.ttl_sec || 900
110
+ };
111
+ }
112
+ function parseFeed(data) {
113
+ const buckets = data.buckets || {};
114
+ const newItems = (buckets.new || []).map(
115
+ (i) => parseFeedItem(i, "new")
116
+ );
117
+ const updatedItems = (buckets.updated || []).map(
118
+ (i) => parseFeedItem(i, "updated")
119
+ );
120
+ const removedItems = (buckets.removed || []).map(
121
+ (i) => parseFeedItem(i, "removed")
122
+ );
123
+ return {
124
+ cursor: data.cursor || "",
125
+ prevCursor: data.prev_cursor || "",
126
+ sourceId: data.source_id || "",
127
+ generatedAt: data.generated_at || "",
128
+ items: [...newItems, ...updatedItems, ...removedItems],
129
+ new: newItems,
130
+ updated: updatedItems,
131
+ removed: removedItems,
132
+ narrative: data.batch_narrative || "",
133
+ raw: data
134
+ };
135
+ }
136
+ function parseSourceInfo(data) {
137
+ return {
138
+ sourceId: data.source_id || "",
139
+ name: data.name || "",
140
+ tags: data.tags || [],
141
+ description: data.description || "",
142
+ homepage: data.homepage || "",
143
+ enabled: data.enabled ?? true,
144
+ status: data.status || "ok",
145
+ headUrl: data.head_url || "",
146
+ latestUrl: data.latest_url || ""
147
+ };
148
+ }
149
+
150
+ // src/client.ts
151
+ var VERSION = "0.1.0";
152
+ var DEFAULT_BASE_URL = "https://diffdelta.io";
153
+ var DEFAULT_TIMEOUT = 15e3;
154
+ var DiffDelta = class {
155
+ baseUrl;
156
+ apiKey;
157
+ timeout;
158
+ cursors;
159
+ sourceTagsCache = null;
160
+ constructor(options = {}) {
161
+ this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
162
+ this.apiKey = options.apiKey;
163
+ this.timeout = options.timeout || DEFAULT_TIMEOUT;
164
+ if (options.cursorPath === null || options.cursorPath === "memory") {
165
+ this.cursors = new MemoryCursorStore();
166
+ } else {
167
+ try {
168
+ this.cursors = new CursorStore(options.cursorPath || void 0);
169
+ } catch {
170
+ this.cursors = new MemoryCursorStore();
171
+ }
172
+ }
173
+ }
174
+ // ── Core polling ──
175
+ /**
176
+ * Poll the global feed for new items since last poll.
177
+ *
178
+ * Checks head.json first (~400 bytes). Only fetches the full feed
179
+ * if the cursor has changed. Automatically saves the new cursor.
180
+ */
181
+ async poll(options = {}) {
182
+ const { tags, sources, buckets = ["new", "updated"] } = options;
183
+ return this.pollFeed({
184
+ headUrl: `${this.baseUrl}/diff/head.json`,
185
+ latestUrl: `${this.baseUrl}/diff/latest.json`,
186
+ cursorKey: "global",
187
+ tags,
188
+ sources,
189
+ buckets
190
+ });
191
+ }
192
+ /**
193
+ * Poll a specific source for new items since last poll.
194
+ *
195
+ * More efficient than `poll({ sources: [...] })` if you only
196
+ * care about one source — fetches a smaller payload.
197
+ */
198
+ async pollSource(sourceId, options = {}) {
199
+ const { tags, buckets = ["new", "updated"] } = options;
200
+ return this.pollFeed({
201
+ headUrl: `${this.baseUrl}/diff/source/${sourceId}/head.json`,
202
+ latestUrl: `${this.baseUrl}/diff/source/${sourceId}/latest.json`,
203
+ cursorKey: `source:${sourceId}`,
204
+ tags,
205
+ sources: void 0,
206
+ buckets
207
+ });
208
+ }
209
+ // ── Low-level fetch ──
210
+ /**
211
+ * Fetch a head.json pointer.
212
+ * @param url Full URL to head.json. Defaults to global head.
213
+ */
214
+ async head(url) {
215
+ const data = await this.fetchJson(url || `${this.baseUrl}/diff/head.json`);
216
+ return parseHead(data);
217
+ }
218
+ /**
219
+ * Fetch a full latest.json feed.
220
+ * @param url Full URL to latest.json. Defaults to global latest.
221
+ */
222
+ async fetchFeed(url) {
223
+ const data = await this.fetchJson(
224
+ url || `${this.baseUrl}/diff/latest.json`
225
+ );
226
+ return parseFeed(data);
227
+ }
228
+ /** List all available DiffDelta sources. */
229
+ async sources() {
230
+ const data = await this.fetchJson(`${this.baseUrl}/diff/sources.json`);
231
+ const raw = data.sources || [];
232
+ return raw.map(parseSourceInfo);
233
+ }
234
+ // ── Continuous monitoring ──
235
+ /**
236
+ * Continuously poll and call a function for each new item.
237
+ *
238
+ * @param callback - Async or sync function called for each new FeedItem.
239
+ * @param options - Watch options (tags, sources, interval, signal).
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * dd.watch(item => {
244
+ * console.log(`🚨 ${item.source}: ${item.headline}`);
245
+ * }, { tags: ["security"] });
246
+ * ```
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * // Stop with AbortController
251
+ * const ac = new AbortController();
252
+ * dd.watch(handler, { signal: ac.signal });
253
+ * setTimeout(() => ac.abort(), 60_000); // stop after 1 minute
254
+ * ```
255
+ */
256
+ async watch(callback, options = {}) {
257
+ const { tags, sources, buckets, signal } = options;
258
+ let interval = options.interval;
259
+ if (!interval) {
260
+ try {
261
+ const h = await this.head();
262
+ interval = Math.max(h.ttlSec, 60);
263
+ } catch {
264
+ interval = 900;
265
+ }
266
+ }
267
+ console.log(`[diffdelta] Watching for changes every ${interval}s...`);
268
+ while (!signal?.aborted) {
269
+ try {
270
+ const items = await this.poll({ tags, sources, buckets });
271
+ if (items.length > 0) {
272
+ console.log(`[diffdelta] ${items.length} new item(s) found.`);
273
+ for (const item of items) {
274
+ await callback(item);
275
+ }
276
+ } else {
277
+ console.log(`[diffdelta] No changes.`);
278
+ }
279
+ } catch (err) {
280
+ if (signal?.aborted) break;
281
+ console.error(`[diffdelta] Error: ${err}. Retrying in ${interval}s...`);
282
+ }
283
+ await new Promise((resolve) => {
284
+ const timer = setTimeout(resolve, interval * 1e3);
285
+ signal?.addEventListener("abort", () => {
286
+ clearTimeout(timer);
287
+ resolve();
288
+ }, { once: true });
289
+ });
290
+ }
291
+ console.log("[diffdelta] Stopped.");
292
+ }
293
+ // ── Cursor management ──
294
+ /** Reset stored cursors so the next poll returns all current items. */
295
+ resetCursors(sourceId) {
296
+ if (sourceId) {
297
+ this.cursors.clear(`source:${sourceId}`);
298
+ } else {
299
+ this.cursors.clear();
300
+ }
301
+ }
302
+ // ── Internal ──
303
+ async pollFeed(params) {
304
+ const { headUrl, latestUrl, cursorKey, tags, sources, buckets } = params;
305
+ const head = await this.head(headUrl);
306
+ const storedCursor = this.cursors.get(cursorKey);
307
+ if (storedCursor && storedCursor === head.cursor) {
308
+ return [];
309
+ }
310
+ const feed = await this.fetchFeed(latestUrl);
311
+ if (feed.cursor) {
312
+ this.cursors.set(cursorKey, feed.cursor);
313
+ }
314
+ let items = feed.items;
315
+ items = items.filter((i) => buckets.includes(i.bucket));
316
+ if (sources?.length) {
317
+ items = items.filter((i) => sources.includes(i.source));
318
+ }
319
+ if (tags?.length) {
320
+ const tagMap = await this.getSourceTags();
321
+ items = items.filter((i) => {
322
+ const itemTags = tagMap[i.source] || [];
323
+ return tags.some((t) => itemTags.includes(t));
324
+ });
325
+ }
326
+ return items;
327
+ }
328
+ async getSourceTags() {
329
+ if (this.sourceTagsCache) return this.sourceTagsCache;
330
+ try {
331
+ const allSources = await this.sources();
332
+ this.sourceTagsCache = Object.fromEntries(
333
+ allSources.map((s) => [s.sourceId, s.tags])
334
+ );
335
+ } catch {
336
+ this.sourceTagsCache = {};
337
+ }
338
+ return this.sourceTagsCache;
339
+ }
340
+ async fetchJson(url) {
341
+ const controller = new AbortController();
342
+ const timer = setTimeout(() => controller.abort(), this.timeout);
343
+ try {
344
+ const headers = {
345
+ "User-Agent": `diffdelta-js/${VERSION}`,
346
+ Accept: "application/json"
347
+ };
348
+ if (this.apiKey) {
349
+ headers["X-DiffDelta-Key"] = this.apiKey;
350
+ }
351
+ const res = await fetch(url, { headers, signal: controller.signal });
352
+ if (!res.ok) {
353
+ throw new Error(`HTTP ${res.status}: ${res.statusText} (${url})`);
354
+ }
355
+ return await res.json();
356
+ } finally {
357
+ clearTimeout(timer);
358
+ }
359
+ }
360
+ toString() {
361
+ const tier = this.apiKey ? "pro" : "free";
362
+ return `DiffDelta(baseUrl=${this.baseUrl}, tier=${tier})`;
363
+ }
364
+ };
365
+ export {
366
+ CursorStore,
367
+ DiffDelta,
368
+ MemoryCursorStore
369
+ };
370
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cursor.ts","../src/models.ts","../src/client.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nconst DEFAULT_CURSOR_DIR = join(homedir(), \".diffdelta\");\nconst DEFAULT_CURSOR_FILE = \"cursors.json\";\n\n/**\n * Persists cursors to a local JSON file so bots survive restarts.\n *\n * By default, cursors are saved to ~/.diffdelta/cursors.json.\n * Each feed key gets its own cursor entry.\n */\nexport class CursorStore {\n private path: string;\n private cursors: Record<string, string> = {};\n\n constructor(path?: string) {\n this.path =\n path ||\n process.env.DD_CURSOR_PATH ||\n join(DEFAULT_CURSOR_DIR, DEFAULT_CURSOR_FILE);\n this.load();\n }\n\n /** Get the stored cursor for a feed key. */\n get(key: string): string | undefined {\n return this.cursors[key];\n }\n\n /** Save a cursor and persist to disk. */\n set(key: string, cursor: string): void {\n this.cursors[key] = cursor;\n this.save();\n }\n\n /** Clear cursor(s). If key is undefined, clears all cursors. */\n clear(key?: string): void {\n if (key) {\n delete this.cursors[key];\n } else {\n this.cursors = {};\n }\n this.save();\n }\n\n private load(): void {\n try {\n if (existsSync(this.path)) {\n const raw = readFileSync(this.path, \"utf-8\");\n const data = JSON.parse(raw);\n if (typeof data === \"object\" && data !== null) {\n this.cursors = data;\n }\n }\n } catch {\n // Corrupted file — start fresh\n this.cursors = {};\n }\n }\n\n private save(): void {\n try {\n const dir = dirname(this.path);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(this.path, JSON.stringify(this.cursors, null, 2));\n } catch {\n // Can't write — silently continue (in-memory only)\n }\n }\n}\n\n/**\n * In-memory-only cursor store (no file I/O).\n * Useful for serverless, browser, or Deno environments.\n */\nexport class MemoryCursorStore {\n private cursors: Record<string, string> = {};\n\n get(key: string): string | undefined {\n return this.cursors[key];\n }\n\n set(key: string, cursor: string): void {\n this.cursors[key] = cursor;\n }\n\n clear(key?: string): void {\n if (key) {\n delete this.cursors[key];\n } else {\n this.cursors = {};\n }\n }\n}\n","/** A single item from a DiffDelta feed. */\nexport interface FeedItem {\n /** Source identifier (e.g. \"cisa_kev\", \"nist_nvd\"). */\n source: string;\n /** Unique item ID within the source. */\n id: string;\n /** Human/agent-readable headline. */\n headline: string;\n /** Link to the original source. */\n url: string;\n /** Summary text extracted from the source. */\n excerpt: string;\n /** When the item was originally published. */\n publishedAt: string | null;\n /** When the item was last updated. */\n updatedAt: string | null;\n /** Which change bucket: \"new\", \"updated\", or \"removed\". */\n bucket: \"new\" | \"updated\" | \"removed\";\n /** Risk score 0–10, or null if not scored. */\n riskScore: number | null;\n /** Raw provenance data (fetched_at, evidence_urls, content_hash). */\n provenance: Record<string, unknown>;\n /** The full raw item object from the feed. */\n raw: Record<string, unknown>;\n}\n\n/** The lightweight head pointer for change detection. */\nexport interface Head {\n /** Opaque cursor string for change detection. */\n cursor: string;\n /** Content hash of the latest feed. */\n hash: string;\n /** Whether content has changed since last generation. */\n changed: boolean;\n /** When this head was generated. */\n generatedAt: string;\n /** Recommended polling interval in seconds. */\n ttlSec: number;\n}\n\n/** A full DiffDelta feed response. */\nexport interface Feed {\n /** The new cursor (save this for next poll). */\n cursor: string;\n /** The previous cursor. */\n prevCursor: string;\n /** Source ID (if per-source feed) or \"global\". */\n sourceId: string;\n /** When this feed was generated. */\n generatedAt: string;\n /** All items across all buckets. */\n items: FeedItem[];\n /** Items in the \"new\" bucket. */\n new: FeedItem[];\n /** Items in the \"updated\" bucket. */\n updated: FeedItem[];\n /** Items in the \"removed\" bucket. */\n removed: FeedItem[];\n /** Human-readable summary of what changed. */\n narrative: string;\n /** The full raw feed object. */\n raw: Record<string, unknown>;\n}\n\n/** Metadata about an available DiffDelta source. */\nexport interface SourceInfo {\n /** Unique source identifier (e.g. \"cisa_kev\"). */\n sourceId: string;\n /** Human-readable display name. */\n name: string;\n /** List of tags (e.g. [\"security\"]). */\n tags: string[];\n /** Brief description of the source. */\n description: string;\n /** URL of the source's homepage. */\n homepage: string;\n /** Whether the source is currently active. */\n enabled: boolean;\n /** Health status (\"ok\", \"degraded\", \"error\"). */\n status: string;\n /** Path to the source's head.json. */\n headUrl: string;\n /** Path to the source's latest.json. */\n latestUrl: string;\n}\n\n// ── Parsing helpers ──\n\n/** Parse a raw feed item into a typed FeedItem. */\nexport function parseFeedItem(\n data: Record<string, unknown>,\n bucket: \"new\" | \"updated\" | \"removed\" = \"new\"\n): FeedItem {\n const content = data.content as Record<string, unknown> | string | undefined;\n let excerpt = \"\";\n if (typeof content === \"object\" && content !== null) {\n excerpt =\n (content.excerpt_text as string) ||\n (content.summary as string) ||\n \"\";\n } else if (typeof content === \"string\") {\n excerpt = content;\n }\n\n // Extract risk_score from top-level or nested summary\n let riskScore: number | null = (data.risk_score as number) ?? null;\n if (riskScore === null) {\n const summary = data.summary as Record<string, unknown> | undefined;\n if (typeof summary === \"object\" && summary !== null) {\n riskScore = (summary.risk_score as number) ?? null;\n }\n }\n\n return {\n source: (data.source as string) || \"\",\n id: (data.id as string) || \"\",\n headline: (data.headline as string) || \"\",\n url: (data.url as string) || \"\",\n excerpt,\n publishedAt: (data.published_at as string) || null,\n updatedAt: (data.updated_at as string) || null,\n bucket,\n riskScore,\n provenance: (data.provenance as Record<string, unknown>) || {},\n raw: data,\n };\n}\n\n/** Parse a raw head.json response into a typed Head. */\nexport function parseHead(data: Record<string, unknown>): Head {\n return {\n cursor: (data.cursor as string) || \"\",\n hash: (data.hash as string) || \"\",\n changed: (data.changed as boolean) || false,\n generatedAt: (data.generated_at as string) || \"\",\n ttlSec: (data.ttl_sec as number) || 900,\n };\n}\n\n/** Parse a raw latest.json response into a typed Feed. */\nexport function parseFeed(data: Record<string, unknown>): Feed {\n const buckets = (data.buckets as Record<string, unknown[]>) || {};\n const newItems = (buckets.new || []).map((i) =>\n parseFeedItem(i as Record<string, unknown>, \"new\")\n );\n const updatedItems = (buckets.updated || []).map((i) =>\n parseFeedItem(i as Record<string, unknown>, \"updated\")\n );\n const removedItems = (buckets.removed || []).map((i) =>\n parseFeedItem(i as Record<string, unknown>, \"removed\")\n );\n\n return {\n cursor: (data.cursor as string) || \"\",\n prevCursor: (data.prev_cursor as string) || \"\",\n sourceId: (data.source_id as string) || \"\",\n generatedAt: (data.generated_at as string) || \"\",\n items: [...newItems, ...updatedItems, ...removedItems],\n new: newItems,\n updated: updatedItems,\n removed: removedItems,\n narrative: (data.batch_narrative as string) || \"\",\n raw: data,\n };\n}\n\n/** Parse a raw source object into a typed SourceInfo. */\nexport function parseSourceInfo(data: Record<string, unknown>): SourceInfo {\n return {\n sourceId: (data.source_id as string) || \"\",\n name: (data.name as string) || \"\",\n tags: (data.tags as string[]) || [],\n description: (data.description as string) || \"\",\n homepage: (data.homepage as string) || \"\",\n enabled: (data.enabled as boolean) ?? true,\n status: (data.status as string) || \"ok\",\n headUrl: (data.head_url as string) || \"\",\n latestUrl: (data.latest_url as string) || \"\",\n };\n}\n","/**\n * DiffDelta TypeScript client — agent-ready intelligence feeds.\n *\n * @example\n * ```ts\n * import { DiffDelta } from \"diffdelta\";\n *\n * const dd = new DiffDelta();\n *\n * // Poll for new items across all sources\n * const items = await dd.poll();\n * items.forEach(i => console.log(`${i.source}: ${i.headline}`));\n *\n * // Poll only security sources\n * const sec = await dd.poll({ tags: [\"security\"] });\n *\n * // Continuous monitoring\n * dd.watch(item => console.log(\"🚨\", item.headline), { tags: [\"security\"] });\n * ```\n */\n\nimport { CursorStore, MemoryCursorStore } from \"./cursor.js\";\nimport type { FeedItem, Feed, Head, SourceInfo } from \"./models.js\";\nimport { parseFeedItem, parseFeed, parseHead, parseSourceInfo } from \"./models.js\";\n\nconst VERSION = \"0.1.0\";\nconst DEFAULT_BASE_URL = \"https://diffdelta.io\";\nconst DEFAULT_TIMEOUT = 15_000; // ms\n\nexport interface DiffDeltaOptions {\n /** DiffDelta API base URL. Defaults to https://diffdelta.io. */\n baseUrl?: string;\n /** Pro/Enterprise API key (dd_live_...). */\n apiKey?: string;\n /**\n * Path to cursor file. Defaults to ~/.diffdelta/cursors.json.\n * Set to `null` to disable file persistence (in-memory only).\n * Set to `\"memory\"` for explicit in-memory mode (serverless, edge).\n */\n cursorPath?: string | null;\n /** HTTP timeout in milliseconds. Defaults to 15000. */\n timeout?: number;\n}\n\nexport interface PollOptions {\n /** Filter items to these tags (e.g. [\"security\"]). */\n tags?: string[];\n /** Filter items to these source IDs (e.g. [\"cisa_kev\", \"nist_nvd\"]). */\n sources?: string[];\n /**\n * Which buckets to return.\n * Defaults to [\"new\", \"updated\"].\n * Use [\"new\", \"updated\", \"removed\"] to include removals.\n */\n buckets?: Array<\"new\" | \"updated\" | \"removed\">;\n}\n\nexport interface WatchOptions extends PollOptions {\n /** Seconds between polls. Defaults to feed TTL (usually 900s). */\n interval?: number;\n /** If provided, an AbortSignal to stop watching. */\n signal?: AbortSignal;\n}\n\ninterface CursorStoreInterface {\n get(key: string): string | undefined;\n set(key: string, cursor: string): void;\n clear(key?: string): void;\n}\n\nexport class DiffDelta {\n readonly baseUrl: string;\n readonly apiKey?: string;\n readonly timeout: number;\n\n private cursors: CursorStoreInterface;\n private sourceTagsCache: Record<string, string[]> | null = null;\n\n constructor(options: DiffDeltaOptions = {}) {\n this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.apiKey = options.apiKey;\n this.timeout = options.timeout || DEFAULT_TIMEOUT;\n\n // Cursor persistence\n if (options.cursorPath === null || options.cursorPath === \"memory\") {\n this.cursors = new MemoryCursorStore();\n } else {\n try {\n this.cursors = new CursorStore(options.cursorPath || undefined);\n } catch {\n // Fallback to memory if file system not available\n this.cursors = new MemoryCursorStore();\n }\n }\n }\n\n // ── Core polling ──\n\n /**\n * Poll the global feed for new items since last poll.\n *\n * Checks head.json first (~400 bytes). Only fetches the full feed\n * if the cursor has changed. Automatically saves the new cursor.\n */\n async poll(options: PollOptions = {}): Promise<FeedItem[]> {\n const { tags, sources, buckets = [\"new\", \"updated\"] } = options;\n\n return this.pollFeed({\n headUrl: `${this.baseUrl}/diff/head.json`,\n latestUrl: `${this.baseUrl}/diff/latest.json`,\n cursorKey: \"global\",\n tags,\n sources,\n buckets,\n });\n }\n\n /**\n * Poll a specific source for new items since last poll.\n *\n * More efficient than `poll({ sources: [...] })` if you only\n * care about one source — fetches a smaller payload.\n */\n async pollSource(\n sourceId: string,\n options: Omit<PollOptions, \"sources\"> = {}\n ): Promise<FeedItem[]> {\n const { tags, buckets = [\"new\", \"updated\"] } = options;\n\n return this.pollFeed({\n headUrl: `${this.baseUrl}/diff/source/${sourceId}/head.json`,\n latestUrl: `${this.baseUrl}/diff/source/${sourceId}/latest.json`,\n cursorKey: `source:${sourceId}`,\n tags,\n sources: undefined,\n buckets,\n });\n }\n\n // ── Low-level fetch ──\n\n /**\n * Fetch a head.json pointer.\n * @param url Full URL to head.json. Defaults to global head.\n */\n async head(url?: string): Promise<Head> {\n const data = await this.fetchJson(url || `${this.baseUrl}/diff/head.json`);\n return parseHead(data);\n }\n\n /**\n * Fetch a full latest.json feed.\n * @param url Full URL to latest.json. Defaults to global latest.\n */\n async fetchFeed(url?: string): Promise<Feed> {\n const data = await this.fetchJson(\n url || `${this.baseUrl}/diff/latest.json`\n );\n return parseFeed(data);\n }\n\n /** List all available DiffDelta sources. */\n async sources(): Promise<SourceInfo[]> {\n const data = await this.fetchJson(`${this.baseUrl}/diff/sources.json`);\n const raw = (data.sources || []) as Record<string, unknown>[];\n return raw.map(parseSourceInfo);\n }\n\n // ── Continuous monitoring ──\n\n /**\n * Continuously poll and call a function for each new item.\n *\n * @param callback - Async or sync function called for each new FeedItem.\n * @param options - Watch options (tags, sources, interval, signal).\n *\n * @example\n * ```ts\n * dd.watch(item => {\n * console.log(`🚨 ${item.source}: ${item.headline}`);\n * }, { tags: [\"security\"] });\n * ```\n *\n * @example\n * ```ts\n * // Stop with AbortController\n * const ac = new AbortController();\n * dd.watch(handler, { signal: ac.signal });\n * setTimeout(() => ac.abort(), 60_000); // stop after 1 minute\n * ```\n */\n async watch(\n callback: (item: FeedItem) => void | Promise<void>,\n options: WatchOptions = {}\n ): Promise<void> {\n const { tags, sources, buckets, signal } = options;\n let interval = options.interval;\n\n // Determine interval from feed TTL if not specified\n if (!interval) {\n try {\n const h = await this.head();\n interval = Math.max(h.ttlSec, 60);\n } catch {\n interval = 900; // default 15 minutes\n }\n }\n\n console.log(`[diffdelta] Watching for changes every ${interval}s...`);\n\n while (!signal?.aborted) {\n try {\n const items = await this.poll({ tags, sources, buckets });\n if (items.length > 0) {\n console.log(`[diffdelta] ${items.length} new item(s) found.`);\n for (const item of items) {\n await callback(item);\n }\n } else {\n console.log(`[diffdelta] No changes.`);\n }\n } catch (err) {\n if (signal?.aborted) break;\n console.error(`[diffdelta] Error: ${err}. Retrying in ${interval}s...`);\n }\n\n // Sleep with abort support\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, interval! * 1000);\n signal?.addEventListener(\"abort\", () => {\n clearTimeout(timer);\n resolve();\n }, { once: true });\n });\n }\n\n console.log(\"[diffdelta] Stopped.\");\n }\n\n // ── Cursor management ──\n\n /** Reset stored cursors so the next poll returns all current items. */\n resetCursors(sourceId?: string): void {\n if (sourceId) {\n this.cursors.clear(`source:${sourceId}`);\n } else {\n this.cursors.clear();\n }\n }\n\n // ── Internal ──\n\n private async pollFeed(params: {\n headUrl: string;\n latestUrl: string;\n cursorKey: string;\n tags?: string[];\n sources?: string[];\n buckets: string[];\n }): Promise<FeedItem[]> {\n const { headUrl, latestUrl, cursorKey, tags, sources, buckets } = params;\n\n // Step 1: Fetch head.json (~400 bytes)\n const head = await this.head(headUrl);\n\n // Step 2: Compare cursor\n const storedCursor = this.cursors.get(cursorKey);\n if (storedCursor && storedCursor === head.cursor) {\n return []; // Nothing changed\n }\n\n // Step 3: Fetch full feed\n const feed = await this.fetchFeed(latestUrl);\n\n // Step 4: Save new cursor\n if (feed.cursor) {\n this.cursors.set(cursorKey, feed.cursor);\n }\n\n // Step 5: Filter and return\n let items = feed.items;\n\n // Filter by bucket\n items = items.filter((i) => buckets.includes(i.bucket));\n\n // Filter by source\n if (sources?.length) {\n items = items.filter((i) => sources.includes(i.source));\n }\n\n // Filter by tags\n if (tags?.length) {\n const tagMap = await this.getSourceTags();\n items = items.filter((i) => {\n const itemTags = tagMap[i.source] || [];\n return tags.some((t) => itemTags.includes(t));\n });\n }\n\n return items;\n }\n\n private async getSourceTags(): Promise<Record<string, string[]>> {\n if (this.sourceTagsCache) return this.sourceTagsCache;\n try {\n const allSources = await this.sources();\n this.sourceTagsCache = Object.fromEntries(\n allSources.map((s) => [s.sourceId, s.tags])\n );\n } catch {\n this.sourceTagsCache = {};\n }\n return this.sourceTagsCache;\n }\n\n private async fetchJson(url: string): Promise<Record<string, unknown>> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const headers: Record<string, string> = {\n \"User-Agent\": `diffdelta-js/${VERSION}`,\n Accept: \"application/json\",\n };\n if (this.apiKey) {\n headers[\"X-DiffDelta-Key\"] = this.apiKey;\n }\n\n const res = await fetch(url, { headers, signal: controller.signal });\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}: ${res.statusText} (${url})`);\n }\n return (await res.json()) as Record<string, unknown>;\n } finally {\n clearTimeout(timer);\n }\n }\n\n toString(): string {\n const tier = this.apiKey ? \"pro\" : \"free\";\n return `DiffDelta(baseUrl=${this.baseUrl}, tier=${tier})`;\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@diffdelta/client",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript client for DiffDelta — agent-ready intelligence feeds",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "keywords": [
23
+ "diffdelta",
24
+ "ai-agents",
25
+ "security",
26
+ "changefeed",
27
+ "intelligence",
28
+ "mcp",
29
+ "vulnerability",
30
+ "cve"
31
+ ],
32
+ "author": "DiffDelta <human@diffdelta.io>",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/diffdelta/diffdelta-js.git"
37
+ },
38
+ "homepage": "https://diffdelta.io",
39
+ "bugs": {
40
+ "url": "https://github.com/diffdelta/diffdelta-js/issues"
41
+ },
42
+ "engines": {
43
+ "node": ">=18.0.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.0.0",
47
+ "tsup": "^8.0.0",
48
+ "typescript": "^5.3.0"
49
+ }
50
+ }