@neurowire/core 0.3.0 → 0.5.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.d.ts CHANGED
@@ -296,6 +296,58 @@ type Mesh = z.infer<typeof MeshSchema>;
296
296
  /** Validate an unknown value into a Mesh. Throws (ZodError) on invalid input. */
297
297
  declare function parseMesh(data: unknown): Mesh;
298
298
 
299
+ /**
300
+ * Pure dedup helpers for watch-style polling. They compute a stable identity per
301
+ * entry and select the entries a caller has not seen yet. No I/O, no clock, no
302
+ * network: the seen-state lives entirely in the calling app layer.
303
+ */
304
+ /** Stable identity for dedup: the entry id when present, else its link. */
305
+ declare function entryKey(entry: NeurowireEntry): string;
306
+ /**
307
+ * The feed's entries whose {@link entryKey} is not in `seen`, in original order.
308
+ * `seen` is any iterable of keys (an array, a Set, ...); it is read once.
309
+ */
310
+ declare function newEntries(feed: NeurowireFeed, seen: Iterable<string>): NeurowireEntry[];
311
+
312
+ /**
313
+ * Pure, deterministic entry filtering by field and pattern. A rule matches a
314
+ * single field (title, summary, source, author, tag) against a substring or a
315
+ * regular expression. `filterEntries` keeps entries that satisfy an include set
316
+ * and avoid an exclude set. No I/O, no clock, no DOM.
317
+ */
318
+ type FilterField = 'title' | 'summary' | 'source' | 'author' | 'tag';
319
+ interface FilterRule {
320
+ field: FilterField;
321
+ pattern: string;
322
+ regex?: boolean;
323
+ }
324
+ interface FilterSpec {
325
+ include?: FilterRule[];
326
+ exclude?: FilterRule[];
327
+ }
328
+ /** Does any of the entry's values for the rule's field match the pattern? */
329
+ declare function matchRule(entry: NeurowireEntry, rule: FilterRule): boolean;
330
+ /**
331
+ * Keep an entry iff it matches at least one include rule (or there are none)
332
+ * and matches none of the exclude rules. An empty spec returns the feed
333
+ * entries unchanged.
334
+ */
335
+ declare function filterEntries(feed: NeurowireFeed, spec: FilterSpec): NeurowireFeed;
336
+
337
+ /**
338
+ * Stable, content-derived entry ids. Pure and dependency-free (no node:crypto)
339
+ * so core stays portable. Used to give entries that lack a real GUID a
340
+ * deterministic id, keeping dedup and round-trips stable across formats.
341
+ */
342
+ /** FNV-1a (64-bit) hash of `input`, returned as a fixed 16-char lowercase hex string. */
343
+ declare function hashHex(input: string): string;
344
+ /**
345
+ * A deterministic synthetic id derived from an entry's link and title.
346
+ * Same `(link, title)` always yields the same urn; different inputs (essentially)
347
+ * always differ. Shape: `urn:nwf:<16-char hex>`.
348
+ */
349
+ declare function stableId(link: string, title: string): string;
350
+
299
351
  /** One feed plus the source label its entries should carry in a merge. */
300
352
  interface MergePart {
301
353
  feed: NeurowireFeed;
@@ -434,4 +486,4 @@ declare function isFormat(value: string): value is Format;
434
486
  /** Serialize a feed to the requested format. */
435
487
  declare function serialize(feed: NeurowireFeed, format: Format): string;
436
488
 
437
- export { EXTENSIONS, EntrySchema, FORMATS, FeedSchema, type Format, GENERATOR, type JsonFeedDocument, MEDIA_TYPES, type MergeOptions, type MergePart, type Mesh, MeshSchema, type MeshSource, MeshSourceSchema, type NeurowireEntry, type NeurowireFeed, type NwfIssue, type NwfValidation, type Person, PersonSchema, type SelectOptions, type SortKey, type SortOrder, type TimeWindow, type WindowSpec, fromNwf, isFormat, mergeFeeds, parseDuration, parseMesh, parseNeurowireFeed, resolveWindow, selectEntries, serialize, toAtom, toJsonFeed, toJsonFeedObject, toMarkdown, toNwf, validateNwf };
489
+ export { EXTENSIONS, EntrySchema, FORMATS, FeedSchema, type FilterField, type FilterRule, type FilterSpec, type Format, GENERATOR, type JsonFeedDocument, MEDIA_TYPES, type MergeOptions, type MergePart, type Mesh, MeshSchema, type MeshSource, MeshSourceSchema, type NeurowireEntry, type NeurowireFeed, type NwfIssue, type NwfValidation, type Person, PersonSchema, type SelectOptions, type SortKey, type SortOrder, type TimeWindow, type WindowSpec, entryKey, filterEntries, fromNwf, hashHex, isFormat, matchRule, mergeFeeds, newEntries, parseDuration, parseMesh, parseNeurowireFeed, resolveWindow, selectEntries, serialize, stableId, toAtom, toJsonFeed, toJsonFeedObject, toMarkdown, toNwf, validateNwf };
package/dist/index.js CHANGED
@@ -1,3 +1,63 @@
1
+ // src/diff.ts
2
+ function entryKey(entry) {
3
+ return entry.id || entry.link;
4
+ }
5
+ function newEntries(feed, seen) {
6
+ const known = new Set(seen);
7
+ return feed.entries.filter((entry) => !known.has(entryKey(entry)));
8
+ }
9
+
10
+ // src/filter.ts
11
+ function fieldValues(entry, field) {
12
+ switch (field) {
13
+ case "title":
14
+ return [entry.title];
15
+ case "summary":
16
+ return [entry.summary ?? ""];
17
+ case "source":
18
+ return [entry.source?.name ?? ""];
19
+ case "author":
20
+ return [(entry.authors ?? []).map((a) => a.name).join(" ")];
21
+ case "tag":
22
+ return entry.tags ?? [];
23
+ }
24
+ }
25
+ function matchRule(entry, rule) {
26
+ const values = fieldValues(entry, rule.field);
27
+ if (rule.regex) {
28
+ const re = new RegExp(rule.pattern, "i");
29
+ return values.some((value) => re.test(value));
30
+ }
31
+ const needle = rule.pattern.toLowerCase();
32
+ return values.some((value) => value.toLowerCase().includes(needle));
33
+ }
34
+ function filterEntries(feed, spec) {
35
+ const include = spec.include ?? [];
36
+ const exclude = spec.exclude ?? [];
37
+ if (include.length === 0 && exclude.length === 0) return feed;
38
+ const kept = feed.entries.filter((entry) => {
39
+ if (include.length > 0 && !include.some((rule) => matchRule(entry, rule))) return false;
40
+ if (exclude.some((rule) => matchRule(entry, rule))) return false;
41
+ return true;
42
+ });
43
+ return { ...feed, entries: kept };
44
+ }
45
+
46
+ // src/id.ts
47
+ function hashHex(input) {
48
+ let hash = 0xcbf29ce484222325n;
49
+ const prime = 0x100000001b3n;
50
+ for (let i = 0; i < input.length; i++) {
51
+ hash ^= BigInt(input.charCodeAt(i));
52
+ hash = hash * prime & 0xffffffffffffffffn;
53
+ }
54
+ return hash.toString(16).padStart(16, "0");
55
+ }
56
+ function stableId(link, title) {
57
+ return `urn:nwf:${hashHex(`${link}
58
+ ${title}`)}`;
59
+ }
60
+
1
61
  // src/model.ts
2
62
  import { z } from "zod";
3
63
  var PersonSchema = z.object({
@@ -655,15 +715,21 @@ export {
655
715
  MeshSchema,
656
716
  MeshSourceSchema,
657
717
  PersonSchema,
718
+ entryKey,
719
+ filterEntries,
658
720
  fromNwf,
721
+ hashHex,
659
722
  isFormat,
723
+ matchRule,
660
724
  mergeFeeds,
725
+ newEntries,
661
726
  parseDuration,
662
727
  parseMesh,
663
728
  parseNeurowireFeed,
664
729
  resolveWindow,
665
730
  selectEntries,
666
731
  serialize,
732
+ stableId,
667
733
  toAtom,
668
734
  toJsonFeed,
669
735
  toJsonFeedObject,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neurowire/core",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Canonical feed model and serializers (Atom, JSON Feed 1.1, Markdown, nwf) for Neurowire.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",