@adriangalilea/utils 0.3.1 → 0.4.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/README.md CHANGED
@@ -186,17 +186,22 @@ dir.create(xdg.state('notify'))
186
186
 
187
187
  ### Unseen
188
188
 
189
- Persistent dedup filter makes any script idempotent. Run it once or a thousand times, you only process each item once. Any scheduling works: manual, cron, a loop, whatever.
189
+ Persistent dedup filter for arrays of objects. Remembers which IDs it has seen across runs, returns only the new ones.
190
190
 
191
191
  ```typescript
192
192
  import { unseen } from '@adriangalilea/utils'
193
193
 
194
- // only notifies on NEW orders safe to run on any schedule
195
- const fresh = await unseen('orders', allOrders, o => o.id)
196
- for (const o of fresh) await notify(o.summary)
197
- // 1st run: 5 orders notifies 5. 2nd run: same 5 → notifies 0. 3rd run: 7 → notifies 2.
194
+ type Message = { id: string, text: string }
195
+
196
+ const messages: Message[] = await fetchMessages()
197
+ const newMessages = await unseen('messages', messages, 'id')
198
+ // 1st run: 3 messages → returns 3. 2nd run: same 3 → returns []. 3rd run: 5 → returns 2 new.
199
+
200
+ for (const m of newMessages) console.log(m.text)
198
201
  ```
199
202
 
203
+ The third argument is the field name that uniquely identifies each item (e.g. `'id'`, `'messageId'`, `'bdnsCode'`). Type-safe — autocompletes valid fields, catches typos at compile time.
204
+
200
205
  State persists at `~/.local/state/unseen/{namespace}.json`.
201
206
 
202
207
  ## Release
@@ -1,26 +1,29 @@
1
1
  /**
2
- * Persistent dedup filter "what's new since last time?"
2
+ * Persistent dedup filter for arrays of objects.
3
3
  *
4
- * Makes any script idempotent. Run it once, run it a thousand times —
5
- * you only process each item once. This means any scheduling works:
6
- * manual `tsx check.ts`, a fish loop, a cron job, whatever.
7
- * Can't double-notify, can't miss items, can't corrupt state.
8
- *
9
- * 1st run: 5 orders exist → returns 5
10
- * 2nd run: same 5 orders → returns 0
11
- * 3rd run: 7 orders exist → returns 2
4
+ * You have objects with IDs. `unseen` remembers which IDs it has
5
+ * seen across runs and returns only the new ones.
12
6
  *
13
7
  * ```ts
14
- * const fresh = await unseen('orders', allOrders, o => o.id)
15
- * for (const o of fresh) await notify(o.summary)
8
+ * type Message = { id: string, text: string }
9
+ *
10
+ * const messages: Message[] = await fetchMessages()
11
+ * const newMessages = await unseen('messages', messages, 'id')
12
+ *
13
+ * // 1st run: 3 messages exist → returns all 3
14
+ * // 2nd run: same 3 messages → returns []
15
+ * // 3rd run: 5 messages exist → returns the 2 new ones
16
16
  * ```
17
17
  *
18
+ * Makes any script idempotent — run it once or a thousand times,
19
+ * you only process each item once. Any scheduling works.
20
+ *
18
21
  * State persists at `~/.local/state/unseen/{namespace}.json`
19
22
  *
20
- * @param namespace - Seen-set name (e.g. 'messages', 'github-issues', 'orders')
21
- * @param items - Items to filter
22
- * @param key - Extract a unique string key from each item
23
- * @returns Only items whose key hasn't been seen before
23
+ * @param namespace - Name for this seen-set (e.g. 'messages', 'orders')
24
+ * @param items - Array of objects to filter
25
+ * @param key - Which field is the unique ID (e.g. 'id', 'messageId', 'bdnsCode')
26
+ * @returns Only items not seen in previous runs
24
27
  */
25
- export declare function unseen<T>(namespace: string, items: T[], key: (item: T) => string): Promise<T[]>;
28
+ export declare function unseen<T>(namespace: string, items: T[], key: keyof T & string): Promise<T[]>;
26
29
  //# sourceMappingURL=unseen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"unseen.d.ts","sourceRoot":"","sources":["../../src/platform/unseen.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,MAAM,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAmBrG"}
1
+ {"version":3,"file":"unseen.d.ts","sourceRoot":"","sources":["../../src/platform/unseen.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,MAAM,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAmBlG"}
@@ -4,28 +4,31 @@ import { file } from './file.js';
4
4
  import { join, dirname } from 'node:path';
5
5
  const STORE_DIR = xdg.state('unseen');
6
6
  /**
7
- * Persistent dedup filter "what's new since last time?"
7
+ * Persistent dedup filter for arrays of objects.
8
8
  *
9
- * Makes any script idempotent. Run it once, run it a thousand times —
10
- * you only process each item once. This means any scheduling works:
11
- * manual `tsx check.ts`, a fish loop, a cron job, whatever.
12
- * Can't double-notify, can't miss items, can't corrupt state.
13
- *
14
- * 1st run: 5 orders exist → returns 5
15
- * 2nd run: same 5 orders → returns 0
16
- * 3rd run: 7 orders exist → returns 2
9
+ * You have objects with IDs. `unseen` remembers which IDs it has
10
+ * seen across runs and returns only the new ones.
17
11
  *
18
12
  * ```ts
19
- * const fresh = await unseen('orders', allOrders, o => o.id)
20
- * for (const o of fresh) await notify(o.summary)
13
+ * type Message = { id: string, text: string }
14
+ *
15
+ * const messages: Message[] = await fetchMessages()
16
+ * const newMessages = await unseen('messages', messages, 'id')
17
+ *
18
+ * // 1st run: 3 messages exist → returns all 3
19
+ * // 2nd run: same 3 messages → returns []
20
+ * // 3rd run: 5 messages exist → returns the 2 new ones
21
21
  * ```
22
22
  *
23
+ * Makes any script idempotent — run it once or a thousand times,
24
+ * you only process each item once. Any scheduling works.
25
+ *
23
26
  * State persists at `~/.local/state/unseen/{namespace}.json`
24
27
  *
25
- * @param namespace - Seen-set name (e.g. 'messages', 'github-issues', 'orders')
26
- * @param items - Items to filter
27
- * @param key - Extract a unique string key from each item
28
- * @returns Only items whose key hasn't been seen before
28
+ * @param namespace - Name for this seen-set (e.g. 'messages', 'orders')
29
+ * @param items - Array of objects to filter
30
+ * @param key - Which field is the unique ID (e.g. 'id', 'messageId', 'bdnsCode')
31
+ * @returns Only items not seen in previous runs
29
32
  */
30
33
  export async function unseen(namespace, items, key) {
31
34
  const storePath = join(STORE_DIR, `${namespace}.json`);
@@ -33,9 +36,9 @@ export async function unseen(namespace, items, key) {
33
36
  const seen = new Set(file.exists(storePath) ? JSON.parse(file.readText(storePath)) : []);
34
37
  const result = [];
35
38
  for (const item of items) {
36
- const k = key(item);
37
- if (!seen.has(k)) {
38
- seen.add(k);
39
+ const id = String(item[key]);
40
+ if (!seen.has(id)) {
41
+ seen.add(id);
39
42
  result.push(item);
40
43
  }
41
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"unseen.js","sourceRoot":"","sources":["../../src/platform/unseen.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEzC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;AAErC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAI,SAAiB,EAAE,KAAU,EAAE,GAAwB;IACrF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,SAAS,OAAO,CAAC,CAAA;IACtD,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAA;IAE9B,MAAM,IAAI,GAAgB,IAAI,GAAG,CAC/B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAa,CAAC,CAAC,CAAC,EAAE,CAC/E,CAAA;IAED,MAAM,MAAM,GAAQ,EAAE,CAAA;IACtB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAA;QACnB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACjB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YACX,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACnB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;IAChD,OAAO,MAAM,CAAA;AACf,CAAC"}
1
+ {"version":3,"file":"unseen.js","sourceRoot":"","sources":["../../src/platform/unseen.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEzC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAI,SAAiB,EAAE,KAAU,EAAE,GAAqB;IAClF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,SAAS,OAAO,CAAC,CAAA;IACtD,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAA;IAE9B,MAAM,IAAI,GAAgB,IAAI,GAAG,CAC/B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAa,CAAC,CAAC,CAAC,EAAE,CAC/E,CAAA;IAED,MAAM,MAAM,GAAQ,EAAE,CAAA;IACtB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;QAC5B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACZ,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACnB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;IAChD,OAAO,MAAM,CAAA;AACf,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adriangalilea/utils",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "TypeScript utilities - logger, currency, formatter, and more",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",