@adriangalilea/utils 0.2.0 → 0.3.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
@@ -163,6 +163,7 @@ expect(() => assert(false, 'boom')).toThrow(Panic)
163
163
  - **Directory Operations**: Create, list, walk directories
164
164
  - **KEV**: Redis-style environment variable management with monorepo support
165
165
  - **XDG**: XDG Base Directory paths — reads env vars set by [xdg-dirs](https://github.com/adriangalilea/xdg-dirs), falls back to spec defaults
166
+ - **Unseen**: Persistent dedup filter — "what's new since last time?" for cron/monitoring workflows
166
167
  - **Project Discovery**: Find project/monorepo roots, detect JS/TS projects
167
168
 
168
169
  ### XDG Base Directories
@@ -183,6 +184,21 @@ xdg.runtime('myapp') // $XDG_RUNTIME_DIR/myapp
183
184
  dir.create(xdg.state('notify'))
184
185
  ```
185
186
 
187
+ ### Unseen
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.
190
+
191
+ ```typescript
192
+ import { unseen } from '@adriangalilea/utils'
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.
198
+ ```
199
+
200
+ State persists at `~/.local/state/unseen/{namespace}.json`.
201
+
186
202
  ## Release
187
203
 
188
204
  Bump version in `package.json` (and `jsr.json`), push to `main`. CI handles everything:
package/dist/index.d.ts CHANGED
@@ -14,4 +14,5 @@ export * from './platform/path.js';
14
14
  export * from './platform/project.js';
15
15
  export * from './platform/kev.js';
16
16
  export * from './platform/xdg.js';
17
+ export * from './platform/unseen.js';
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAGvD,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,+BAA+B,CAAA;AAG7C,cAAc,gBAAgB,CAAA;AAI9B,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAGvD,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,+BAA+B,CAAA;AAG7C,cAAc,gBAAgB,CAAA;AAI9B,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA"}
package/dist/index.js CHANGED
@@ -18,4 +18,5 @@ export * from './platform/path.js';
18
18
  export * from './platform/project.js';
19
19
  export * from './platform/kev.js';
20
20
  export * from './platform/xdg.js';
21
+ export * from './platform/unseen.js';
21
22
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,8CAA8C;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAGtC,wCAAwC;AACxC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,+BAA+B,CAAA;AAE7C,yDAAyD;AACzD,cAAc,gBAAgB,CAAA;AAE9B,8BAA8B;AAC9B,mEAAmE;AACnE,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,8CAA8C;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAGtC,wCAAwC;AACxC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,+BAA+B,CAAA;AAE7C,yDAAyD;AACzD,cAAc,gBAAgB,CAAA;AAE9B,8BAA8B;AAC9B,mEAAmE;AACnE,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Persistent dedup filter — "what's new since last time?"
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
12
+ *
13
+ * ```ts
14
+ * const fresh = await unseen('orders', allOrders, o => o.id)
15
+ * for (const o of fresh) await notify(o.summary)
16
+ * ```
17
+ *
18
+ * State persists at `~/.local/state/unseen/{namespace}.json`
19
+ *
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
24
+ */
25
+ export declare function unseen<T>(namespace: string, items: T[], key: (item: T) => string): Promise<T[]>;
26
+ //# sourceMappingURL=unseen.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,45 @@
1
+ import { xdg } from './xdg.js';
2
+ import { dir } from './dir.js';
3
+ import { file } from './file.js';
4
+ import { join } from 'node:path';
5
+ const STORE_DIR = xdg.state('unseen');
6
+ /**
7
+ * Persistent dedup filter — "what's new since last time?"
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
17
+ *
18
+ * ```ts
19
+ * const fresh = await unseen('orders', allOrders, o => o.id)
20
+ * for (const o of fresh) await notify(o.summary)
21
+ * ```
22
+ *
23
+ * State persists at `~/.local/state/unseen/{namespace}.json`
24
+ *
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
29
+ */
30
+ export async function unseen(namespace, items, key) {
31
+ dir.create(STORE_DIR);
32
+ const storePath = join(STORE_DIR, `${namespace}.json`);
33
+ const seen = new Set(file.exists(storePath) ? JSON.parse(file.readText(storePath)) : []);
34
+ const result = [];
35
+ for (const item of items) {
36
+ const k = key(item);
37
+ if (!seen.has(k)) {
38
+ seen.add(k);
39
+ result.push(item);
40
+ }
41
+ }
42
+ file.write(storePath, JSON.stringify([...seen]));
43
+ return result;
44
+ }
45
+ //# sourceMappingURL=unseen.js.map
@@ -0,0 +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,MAAM,WAAW,CAAA;AAEhC,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,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACrB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,SAAS,OAAO,CAAC,CAAA;IAEtD,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adriangalilea/utils",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "TypeScript utilities - logger, currency, formatter, and more",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",