@adriangalilea/utils 0.2.0 → 0.3.1
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 +16 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/platform/unseen.d.ts +26 -0
- package/dist/platform/unseen.d.ts.map +1 -0
- package/dist/platform/unseen.js +45 -0
- package/dist/platform/unseen.js.map +1 -0
- package/package.json +1 -1
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
package/dist/index.d.ts.map
CHANGED
|
@@ -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
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, dirname } 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
|
+
const storePath = join(STORE_DIR, `${namespace}.json`);
|
|
32
|
+
dir.create(dirname(storePath));
|
|
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,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"}
|