webring 1.0.1 → 1.0.2
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/CHANGELOG.md +7 -0
- package/dist/cache.d.ts +3 -3
- package/dist/cache.js +39 -11
- package/dist/fetch.d.ts +11 -1
- package/dist/fetch.js +9 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +14 -24
- package/dist/index.test.js +91 -4
- package/dist/types.d.ts +87 -6
- package/dist/types.js +9 -3
- package/dist/util.d.ts +3 -0
- package/dist/util.js +13 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.2](https://github.com/shepherdjerred/webring/compare/v1.0.1...v1.0.2) (2024-06-21)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* disable style parsing ([0999f02](https://github.com/shepherdjerred/webring/commit/0999f025b4e9970a20bde8c1ffce1248ce38f3b7))
|
|
14
|
+
|
|
8
15
|
## [1.0.1](https://github.com/shepherdjerred/webring/compare/v1.0.0...v1.0.1) (2024-06-03)
|
|
9
16
|
|
|
10
17
|
|
package/dist/cache.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function fetchWithCache(source: Source, cache: Cache, config:
|
|
1
|
+
import { type Cache, type Result, type ResultEntry, type Source, type CachedConfiguration } from "./types.js";
|
|
2
|
+
export declare function fetchAllCached(config: CachedConfiguration): Promise<Result>;
|
|
3
|
+
export declare function fetchWithCache(source: Source, cache: Cache, config: CachedConfiguration): Promise<ResultEntry | undefined>;
|
package/dist/cache.js
CHANGED
|
@@ -1,25 +1,53 @@
|
|
|
1
1
|
import * as R from "remeda";
|
|
2
|
+
import { CacheSchema, } from "./types.js";
|
|
2
3
|
import { fetch } from "./fetch.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
import { asyncMapFilterUndefined } from "./util.js";
|
|
6
|
+
async function loadCache({ cache_file }) {
|
|
7
|
+
try {
|
|
8
|
+
await fs.access(cache_file);
|
|
9
|
+
const cacheFile = await fs.readFile(cache_file);
|
|
10
|
+
return CacheSchema.parse(JSON.parse(cacheFile.toString()));
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function saveCache({ cache_file }, cache) {
|
|
17
|
+
await fs.writeFile(cache_file, JSON.stringify(cache));
|
|
18
|
+
}
|
|
19
|
+
function toCacheEntry(result, now) {
|
|
20
|
+
return [result.source.url, { timestamp: now, data: result }];
|
|
21
|
+
}
|
|
22
|
+
function toCache(results, now) {
|
|
23
|
+
return R.pipe(results, R.map((result) => toCacheEntry(result, now)), R.fromEntries());
|
|
24
|
+
}
|
|
25
|
+
function updateCache(results, config) {
|
|
26
|
+
const now = new Date();
|
|
27
|
+
const updatedCache = toCache(results, now);
|
|
28
|
+
return saveCache(config.cache, updatedCache);
|
|
29
|
+
}
|
|
30
|
+
export async function fetchAllCached(config) {
|
|
31
|
+
const cache = await loadCache(config.cache);
|
|
32
|
+
const results = await asyncMapFilterUndefined(config.sources, (source) => fetchWithCache(source, cache, config));
|
|
33
|
+
await updateCache(results, config);
|
|
34
|
+
return results;
|
|
10
35
|
}
|
|
11
36
|
export async function fetchWithCache(source, cache, config) {
|
|
12
37
|
const cacheEntry = cache[source.url];
|
|
13
38
|
if (cacheEntry) {
|
|
14
39
|
const now = new Date();
|
|
15
|
-
if (now.getTime() - cacheEntry.timestamp.getTime() < config.cache_duration_minutes * 60 * 1000) {
|
|
16
|
-
console.log(`Cache entry found for ${source.url}
|
|
40
|
+
if (now.getTime() - cacheEntry.timestamp.getTime() < config.cache.cache_duration_minutes * 60 * 1000) {
|
|
41
|
+
console.log(`Cache entry found for ${source.url}.`);
|
|
17
42
|
return Promise.resolve(cacheEntry.data);
|
|
18
43
|
}
|
|
19
44
|
else {
|
|
20
|
-
console.log(`Cache entry for ${source.url} is too old
|
|
45
|
+
console.log(`Cache entry for ${source.url} is too old.`);
|
|
21
46
|
}
|
|
22
47
|
}
|
|
23
|
-
|
|
48
|
+
else {
|
|
49
|
+
console.log(`No cache entry for ${source.url}.`);
|
|
50
|
+
}
|
|
51
|
+
console.log(`Fetching ${source.url}`);
|
|
24
52
|
return fetch(source, config.truncate);
|
|
25
53
|
}
|
package/dist/fetch.d.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
import { type Source, type ResultEntry } from "./types.js";
|
|
1
|
+
import { type Source, type ResultEntry, type Configuration } from "./types.js";
|
|
2
|
+
export declare function fetchAll(config: Configuration): Promise<{
|
|
3
|
+
url: string;
|
|
4
|
+
title: string;
|
|
5
|
+
date: Date;
|
|
6
|
+
source: {
|
|
7
|
+
url: string;
|
|
8
|
+
title: string;
|
|
9
|
+
};
|
|
10
|
+
preview?: string | undefined;
|
|
11
|
+
}[]>;
|
|
2
12
|
export declare function fetch(source: Source, length: number): Promise<ResultEntry | undefined>;
|
package/dist/fetch.js
CHANGED
|
@@ -3,8 +3,12 @@ import sanitizeHtml from "sanitize-html";
|
|
|
3
3
|
import truncate from "truncate-html";
|
|
4
4
|
import { FeedEntrySchema } from "./types.js";
|
|
5
5
|
import * as R from "remeda";
|
|
6
|
+
import { asyncMapFilterUndefined } from "./util.js";
|
|
6
7
|
// for some reason, TypeScript does not infer the type of the default export correctly
|
|
7
8
|
const truncateFn = truncate;
|
|
9
|
+
export async function fetchAll(config) {
|
|
10
|
+
return await asyncMapFilterUndefined(config.sources, (source) => fetch(source, config.truncate));
|
|
11
|
+
}
|
|
8
12
|
export async function fetch(source, length) {
|
|
9
13
|
const parser = new Parser();
|
|
10
14
|
try {
|
|
@@ -19,7 +23,11 @@ export async function fetch(source, length) {
|
|
|
19
23
|
url: firstItem.link,
|
|
20
24
|
date: new Date(firstItem.date),
|
|
21
25
|
source,
|
|
22
|
-
preview: preview
|
|
26
|
+
preview: preview
|
|
27
|
+
? truncateFn(sanitizeHtml(preview, {
|
|
28
|
+
parseStyleAttributes: false,
|
|
29
|
+
}), length)
|
|
30
|
+
: undefined,
|
|
23
31
|
};
|
|
24
32
|
}
|
|
25
33
|
catch (e) {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import * as R from "remeda";
|
|
2
|
+
import { fetchAllCached as fetchAllCached } from "./cache.js";
|
|
3
|
+
import { fetchAll as fetchAllUncached } from "./fetch.js";
|
|
4
|
+
import { CachedConfigurationSchema } from "./types.js";
|
|
4
5
|
export async function run(config) {
|
|
5
|
-
const
|
|
6
|
-
let
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
await
|
|
6
|
+
const { success, data } = CachedConfigurationSchema.safeParse(config);
|
|
7
|
+
let result;
|
|
8
|
+
if (success) {
|
|
9
|
+
console.log(`Using cache at ${data.cache.cache_file}.`);
|
|
10
|
+
result = await fetchAllCached(data);
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
else {
|
|
13
|
+
console.log("Cache disabled.");
|
|
14
|
+
result = await fetchAllUncached(config);
|
|
13
15
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
cacheObject = CacheSchema.parse(JSON.parse(cacheFile.toString()));
|
|
17
|
-
}
|
|
18
|
-
catch (e) {
|
|
19
|
-
console.error("Error reading cache file:", e);
|
|
20
|
-
throw e;
|
|
21
|
-
}
|
|
22
|
-
const [result, updatedCache] = await runWithCache(config, cacheObject);
|
|
23
|
-
// write the updated cache to cache.json
|
|
24
|
-
await fs.writeFile(cacheFilename, JSON.stringify(updatedCache));
|
|
25
|
-
return result;
|
|
16
|
+
const topResults = R.pipe(result, R.sortBy((result) => result.date.getTime()), R.reverse(), R.take(config.number));
|
|
17
|
+
return topResults;
|
|
26
18
|
}
|
|
27
19
|
export * from "./types.js";
|
|
28
|
-
export * from "./cache.js";
|
|
29
|
-
export * from "./fetch.js";
|
package/dist/index.test.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
// sum.test.js
|
|
2
1
|
import { expect, test } from "vitest";
|
|
3
2
|
import { run } from "./index.js";
|
|
4
|
-
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { mkdtemp } from "fs/promises";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
// TODO: intercept network requests
|
|
7
|
+
test("it should fetch an RSS feed without caching", async () => {
|
|
5
8
|
const config = {
|
|
6
9
|
sources: [
|
|
7
10
|
{
|
|
@@ -10,10 +13,94 @@ test("it should work", async () => {
|
|
|
10
13
|
},
|
|
11
14
|
],
|
|
12
15
|
number: 1,
|
|
13
|
-
cache_duration_minutes: 0,
|
|
14
16
|
truncate: 300,
|
|
15
|
-
cache_file: "cache.json",
|
|
16
17
|
};
|
|
17
18
|
const result = await run(config);
|
|
18
19
|
expect(result).toMatchSnapshot();
|
|
19
20
|
});
|
|
21
|
+
test("it should fetch several RSS feeds", async () => {
|
|
22
|
+
const config = {
|
|
23
|
+
sources: [
|
|
24
|
+
{
|
|
25
|
+
url: "https://drewdevault.com/blog/index.xml",
|
|
26
|
+
title: "Drew DeVault",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
url: "https://danluu.com/atom.xml",
|
|
30
|
+
title: "Dan Luu",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
url: "https://jakelazaroff.com/rss.xml",
|
|
34
|
+
title: "Jake Lazaroff",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
url: "https://awesomekling.github.io/feed.xml",
|
|
38
|
+
title: "Andreas Kling",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
url: "https://xeiaso.net/blog.rss",
|
|
42
|
+
title: "Xe Iaso",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
url: "https://ciechanow.ski/atom.xml",
|
|
46
|
+
title: "Bartosz Ciechanowski",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
url: "https://explained-from-first-principles.com/feed.xml",
|
|
50
|
+
title: "Explained From First Principles",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
url: "http://www.aaronsw.com/2002/feeds/pgessays.rss",
|
|
54
|
+
title: "Paul Graham",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
url: "https://samwho.dev/rss.xml",
|
|
58
|
+
title: "Sam Rose",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
url: "https://rachelbythebay.com/w/atom.xml",
|
|
62
|
+
title: "Rachel Kroll",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
url: "https://brr.fyi/feed.xml",
|
|
66
|
+
title: "brr.fyi",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
url: "https://devblogs.microsoft.com/oldnewthing/feed",
|
|
70
|
+
title: "The Old New Thing",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
url: "https://ludic.mataroa.blog/rss/",
|
|
74
|
+
title: "Ludicity",
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
number: 20,
|
|
78
|
+
truncate: 300,
|
|
79
|
+
};
|
|
80
|
+
const result = await run(config);
|
|
81
|
+
expect(result).toMatchSnapshot();
|
|
82
|
+
});
|
|
83
|
+
test("it should fetch an RSS feed with caching", async () => {
|
|
84
|
+
const config = {
|
|
85
|
+
sources: [
|
|
86
|
+
{
|
|
87
|
+
title: "Jerred Shepherd",
|
|
88
|
+
url: "https://sjer.red/rss.xml",
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
number: 1,
|
|
92
|
+
truncate: 300,
|
|
93
|
+
cache: {
|
|
94
|
+
cache_file: `${await createTempDir()}/cache.json`,
|
|
95
|
+
cache_duration_minutes: 1,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
const result = await run(config);
|
|
99
|
+
expect(result).toMatchSnapshot();
|
|
100
|
+
});
|
|
101
|
+
// https://sdorra.dev/posts/2024-02-12-vitest-tmpdir
|
|
102
|
+
async function createTempDir() {
|
|
103
|
+
const ostmpdir = tmpdir();
|
|
104
|
+
const dir = join(ostmpdir, "unit-test-");
|
|
105
|
+
return await mkdtemp(dir);
|
|
106
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -10,6 +10,17 @@ declare const SourceSchema: z.ZodObject<{
|
|
|
10
10
|
url: string;
|
|
11
11
|
title: string;
|
|
12
12
|
}>;
|
|
13
|
+
export type CacheConfiguration = z.infer<typeof CacheConfigurationSchema>;
|
|
14
|
+
declare const CacheConfigurationSchema: z.ZodObject<{
|
|
15
|
+
cache_duration_minutes: z.ZodDefault<z.ZodNumber>;
|
|
16
|
+
cache_file: z.ZodDefault<z.ZodString>;
|
|
17
|
+
}, "strip", z.ZodTypeAny, {
|
|
18
|
+
cache_duration_minutes: number;
|
|
19
|
+
cache_file: string;
|
|
20
|
+
}, {
|
|
21
|
+
cache_duration_minutes?: number | undefined;
|
|
22
|
+
cache_file?: string | undefined;
|
|
23
|
+
}>;
|
|
13
24
|
export type Configuration = z.infer<typeof ConfigurationSchema>;
|
|
14
25
|
declare const ConfigurationSchema: z.ZodObject<{
|
|
15
26
|
sources: z.ZodArray<z.ZodObject<{
|
|
@@ -23,27 +34,97 @@ declare const ConfigurationSchema: z.ZodObject<{
|
|
|
23
34
|
title: string;
|
|
24
35
|
}>, "many">;
|
|
25
36
|
number: z.ZodDefault<z.ZodNumber>;
|
|
26
|
-
cache_duration_minutes: z.ZodDefault<z.ZodNumber>;
|
|
27
37
|
truncate: z.ZodDefault<z.ZodNumber>;
|
|
28
|
-
|
|
38
|
+
cache: z.ZodOptional<z.ZodObject<{
|
|
39
|
+
cache_duration_minutes: z.ZodDefault<z.ZodNumber>;
|
|
40
|
+
cache_file: z.ZodDefault<z.ZodString>;
|
|
41
|
+
}, "strip", z.ZodTypeAny, {
|
|
42
|
+
cache_duration_minutes: number;
|
|
43
|
+
cache_file: string;
|
|
44
|
+
}, {
|
|
45
|
+
cache_duration_minutes?: number | undefined;
|
|
46
|
+
cache_file?: string | undefined;
|
|
47
|
+
}>>;
|
|
29
48
|
}, "strip", z.ZodTypeAny, {
|
|
30
49
|
number: number;
|
|
31
50
|
sources: {
|
|
32
51
|
url: string;
|
|
33
52
|
title: string;
|
|
34
53
|
}[];
|
|
35
|
-
cache_duration_minutes: number;
|
|
36
54
|
truncate: number;
|
|
37
|
-
|
|
55
|
+
cache?: {
|
|
56
|
+
cache_duration_minutes: number;
|
|
57
|
+
cache_file: string;
|
|
58
|
+
} | undefined;
|
|
38
59
|
}, {
|
|
39
60
|
sources: {
|
|
40
61
|
url: string;
|
|
41
62
|
title: string;
|
|
42
63
|
}[];
|
|
43
64
|
number?: number | undefined;
|
|
44
|
-
cache_duration_minutes?: number | undefined;
|
|
45
65
|
truncate?: number | undefined;
|
|
46
|
-
|
|
66
|
+
cache?: {
|
|
67
|
+
cache_duration_minutes?: number | undefined;
|
|
68
|
+
cache_file?: string | undefined;
|
|
69
|
+
} | undefined;
|
|
70
|
+
}>;
|
|
71
|
+
export type CachedConfiguration = z.infer<typeof CachedConfigurationSchema>;
|
|
72
|
+
export declare const CachedConfigurationSchema: z.ZodObject<z.objectUtil.extendShape<{
|
|
73
|
+
sources: z.ZodArray<z.ZodObject<{
|
|
74
|
+
url: z.ZodString;
|
|
75
|
+
title: z.ZodString;
|
|
76
|
+
}, "strip", z.ZodTypeAny, {
|
|
77
|
+
url: string;
|
|
78
|
+
title: string;
|
|
79
|
+
}, {
|
|
80
|
+
url: string;
|
|
81
|
+
title: string;
|
|
82
|
+
}>, "many">;
|
|
83
|
+
number: z.ZodDefault<z.ZodNumber>;
|
|
84
|
+
truncate: z.ZodDefault<z.ZodNumber>;
|
|
85
|
+
cache: z.ZodOptional<z.ZodObject<{
|
|
86
|
+
cache_duration_minutes: z.ZodDefault<z.ZodNumber>;
|
|
87
|
+
cache_file: z.ZodDefault<z.ZodString>;
|
|
88
|
+
}, "strip", z.ZodTypeAny, {
|
|
89
|
+
cache_duration_minutes: number;
|
|
90
|
+
cache_file: string;
|
|
91
|
+
}, {
|
|
92
|
+
cache_duration_minutes?: number | undefined;
|
|
93
|
+
cache_file?: string | undefined;
|
|
94
|
+
}>>;
|
|
95
|
+
}, {
|
|
96
|
+
cache: z.ZodObject<{
|
|
97
|
+
cache_duration_minutes: z.ZodDefault<z.ZodNumber>;
|
|
98
|
+
cache_file: z.ZodDefault<z.ZodString>;
|
|
99
|
+
}, "strip", z.ZodTypeAny, {
|
|
100
|
+
cache_duration_minutes: number;
|
|
101
|
+
cache_file: string;
|
|
102
|
+
}, {
|
|
103
|
+
cache_duration_minutes?: number | undefined;
|
|
104
|
+
cache_file?: string | undefined;
|
|
105
|
+
}>;
|
|
106
|
+
}>, "strip", z.ZodTypeAny, {
|
|
107
|
+
number: number;
|
|
108
|
+
sources: {
|
|
109
|
+
url: string;
|
|
110
|
+
title: string;
|
|
111
|
+
}[];
|
|
112
|
+
truncate: number;
|
|
113
|
+
cache: {
|
|
114
|
+
cache_duration_minutes: number;
|
|
115
|
+
cache_file: string;
|
|
116
|
+
};
|
|
117
|
+
}, {
|
|
118
|
+
sources: {
|
|
119
|
+
url: string;
|
|
120
|
+
title: string;
|
|
121
|
+
}[];
|
|
122
|
+
cache: {
|
|
123
|
+
cache_duration_minutes?: number | undefined;
|
|
124
|
+
cache_file?: string | undefined;
|
|
125
|
+
};
|
|
126
|
+
number?: number | undefined;
|
|
127
|
+
truncate?: number | undefined;
|
|
47
128
|
}>;
|
|
48
129
|
export type ResultEntry = z.infer<typeof ResultEntrySchema>;
|
|
49
130
|
declare const ResultEntrySchema: z.ZodObject<{
|
package/dist/types.js
CHANGED
|
@@ -5,16 +5,22 @@ const SourceSchema = z.object({
|
|
|
5
5
|
// a title for the feed
|
|
6
6
|
title: z.string(),
|
|
7
7
|
});
|
|
8
|
+
const CacheConfigurationSchema = z.object({
|
|
9
|
+
// how long to cache a results for
|
|
10
|
+
cache_duration_minutes: z.number().default(60),
|
|
11
|
+
cache_file: z.string().default("cache.json"),
|
|
12
|
+
});
|
|
8
13
|
const ConfigurationSchema = z.object({
|
|
9
14
|
// list of sources to fetch
|
|
10
15
|
sources: SourceSchema.array(),
|
|
11
16
|
// how many entries to return
|
|
12
17
|
number: z.number().default(3),
|
|
13
|
-
// how long to cache a results for
|
|
14
|
-
cache_duration_minutes: z.number().default(60),
|
|
15
18
|
// how many words to truncate the preview to
|
|
16
19
|
truncate: z.number().default(300),
|
|
17
|
-
|
|
20
|
+
cache: CacheConfigurationSchema.optional(),
|
|
21
|
+
});
|
|
22
|
+
export const CachedConfigurationSchema = ConfigurationSchema.extend({
|
|
23
|
+
cache: CacheConfigurationSchema,
|
|
18
24
|
});
|
|
19
25
|
const ResultEntrySchema = z.object({
|
|
20
26
|
title: z.string(),
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function asyncMapFilterUndefined<T, U>(input: T[], fn: (x: T) => Promise<U | undefined>): Promise<U[]>;
|
|
2
|
+
export declare function asyncMap<T, U>(input: T[], fn: (x: T) => Promise<U>): Promise<U[]>;
|
|
3
|
+
export declare function filterUndefined<T>(input: (T | undefined)[]): T[];
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as R from "remeda";
|
|
2
|
+
// run an async map operation, filtering out undefined results
|
|
3
|
+
export async function asyncMapFilterUndefined(input, fn) {
|
|
4
|
+
const results = await asyncMap(input, fn);
|
|
5
|
+
return filterUndefined(results);
|
|
6
|
+
}
|
|
7
|
+
export async function asyncMap(input, fn) {
|
|
8
|
+
const promises = R.pipe(input, R.map((item) => fn(item)));
|
|
9
|
+
return Promise.all(promises);
|
|
10
|
+
}
|
|
11
|
+
export function filterUndefined(input) {
|
|
12
|
+
return R.pipe(input, R.filter((result) => result !== undefined));
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webring",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.2",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"prepare": "husky",
|
|
7
7
|
"lint": "eslint src",
|
|
8
8
|
"build": "tsc",
|
|
9
|
-
"test": "vitest"
|
|
9
|
+
"test": "vitest --disable-console-intercept"
|
|
10
10
|
},
|
|
11
11
|
"main": "dist/index.js",
|
|
12
12
|
"types": "dist/index.d.ts",
|