hantaviruswrapper 1.0.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 +159 -0
- package/dist/cache.js +20 -0
- package/dist/cache.js.map +1 -0
- package/dist/client.js +34 -0
- package/dist/client.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/rateLimit.js +45 -0
- package/dist/rateLimit.js.map +1 -0
- package/dist/service/filterCountries.js +17 -0
- package/dist/service/filterCountries.js.map +1 -0
- package/dist/service/filterOutbreaks.js +19 -0
- package/dist/service/filterOutbreaks.js.map +1 -0
- package/dist/service/getPublicJsonData.js +5 -0
- package/dist/service/getPublicJsonData.js.map +1 -0
- package/dist/types/BaseResponse.js +2 -0
- package/dist/types/BaseResponse.js.map +1 -0
- package/dist/types/GenericRequestError.js +7 -0
- package/dist/types/GenericRequestError.js.map +1 -0
- package/dist/types/publicJsonResponse.js +2 -0
- package/dist/types/publicJsonResponse.js.map +1 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# hantaviruswrapper
|
|
2
|
+
|
|
3
|
+
A TypeScript wrapper for the [hantaOSINT](https://hantaosint.com) API, an open-source intelligence feed that tracks hantavirus outbreaks worldwide.
|
|
4
|
+
|
|
5
|
+
This wrapper only covers the **free tier**. Right now, the only free route is:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
GET /api/v1/public.json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
> `public.json` is refreshed **once per day** by hantaOSINT. Fetching it more often just returns the same data, so you should cache it (see [Caching](#caching)).
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install hantaviruswrapper
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import {
|
|
23
|
+
getPublicResponseInfo,
|
|
24
|
+
getCountriesByStatus,
|
|
25
|
+
getOutbreaksByStrain,
|
|
26
|
+
} from "hantaviruswrapper";
|
|
27
|
+
|
|
28
|
+
const data = await getPublicResponseInfo();
|
|
29
|
+
|
|
30
|
+
const activeCountries = getCountriesByStatus(data, "active");
|
|
31
|
+
const hanSeoulOutbreaks = getOutbreaksByStrain(data, "Seoul");
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## API
|
|
35
|
+
|
|
36
|
+
### Fetching data
|
|
37
|
+
|
|
38
|
+
#### `getPublicResponseInfo()`
|
|
39
|
+
|
|
40
|
+
Fetches `GET /api/v1/public.json` and returns a typed `PublicJsonResponse`:
|
|
41
|
+
|
|
42
|
+
| Field | Type | Description |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| `meta` | `Meta` | Tier, license, generation time |
|
|
45
|
+
| `stats` | `Stats` | Global case/death counts |
|
|
46
|
+
| `countries` | `Country[]` | Per-country breakdown |
|
|
47
|
+
| `map_markers` | `MapMarker[]` | Geolocated case markers |
|
|
48
|
+
| `briefs` | `Brief[]` | Recent situation updates |
|
|
49
|
+
| `outbreaks` | `Outbreak[]` | Named outbreak summaries |
|
|
50
|
+
|
|
51
|
+
### Country filters
|
|
52
|
+
|
|
53
|
+
Every filter takes a `PublicJsonResponse` as its first argument. Fetch once, filter as many times as you want.
|
|
54
|
+
|
|
55
|
+
| Function | Signature | Description |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `getCountryByCode` | `(data, code: string) => Country \| undefined` | Exact match by country code (case-insensitive) |
|
|
58
|
+
| `getCountryByIso` | `(data, iso: string) => Country \| undefined` | Exact match by ISO alpha-3 (case-insensitive) |
|
|
59
|
+
| `getCountriesByStatus` | `(data, status: CountryStatus) => Country[]` | `"active"`, `"suspected"`, `"lockdown"`, `"historical"` |
|
|
60
|
+
| `getCountriesByTrend` | `(data, trend: Trend) => Country[]` | `"up"`, `"down"`, `"flat"` |
|
|
61
|
+
| `searchCountriesByName` | `(data, name: string) => Country[]` | Partial name search (case-insensitive) |
|
|
62
|
+
|
|
63
|
+
### Outbreak filters
|
|
64
|
+
|
|
65
|
+
| Function | Signature | Description |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `getOutbreakBySlug` | `(data, slug: string) => Outbreak \| undefined` | Exact match by slug |
|
|
68
|
+
| `getOutbreaksByStatus` | `(data, status: OutbreakStatus) => Outbreak[]` | `"active"`, `"resolved"` |
|
|
69
|
+
| `getOutbreaksByStrain` | `(data, strain: string) => Outbreak[]` | Partial strain search (case-insensitive) |
|
|
70
|
+
| `searchOutbreaksByName` | `(data, name: string) => Outbreak[]` | Partial name search (case-insensitive) |
|
|
71
|
+
| `getOutbreaksByOrigin` | `(data, origin: string) => Outbreak[]` | Partial origin search (case-insensitive) |
|
|
72
|
+
|
|
73
|
+
## Caching
|
|
74
|
+
|
|
75
|
+
Since `public.json` only updates daily, you really don't need to fetch it every time. `createCachedFetcher` gives you a simple in-memory cache with a TTL you set:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { createCachedFetcher, getCountriesByStatus } from "hantaviruswrapper";
|
|
79
|
+
|
|
80
|
+
// cache for 1 hour (the data only changes daily anyway)
|
|
81
|
+
const fetchData = createCachedFetcher(3_600_000);
|
|
82
|
+
|
|
83
|
+
const data = await fetchData(); // hits the API
|
|
84
|
+
const data2 = await fetchData(); // returns cached, no request
|
|
85
|
+
|
|
86
|
+
const active = getCountriesByStatus(data2, "active");
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Default TTL is 5 minutes. You can force a refresh whenever:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
fetchData.invalidate();
|
|
93
|
+
const fresh = await fetchData(); // hits the API again
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
You're also free to manage your own cache. Every filter function just takes a `PublicJsonResponse`, so pass in whatever you have.
|
|
97
|
+
|
|
98
|
+
## Rate limiting
|
|
99
|
+
|
|
100
|
+
The free tier has these limits:
|
|
101
|
+
|
|
102
|
+
| Tier | Limit | Burst | On exceed |
|
|
103
|
+
| --- | --- | --- | --- |
|
|
104
|
+
| Free | 60 / minute | 120 | HTTP 429 |
|
|
105
|
+
|
|
106
|
+
The wrapper includes a client-side token-bucket rate limiter that stops you from sending requests that would get rejected. It's built into the client, so you don't need to set anything up.
|
|
107
|
+
|
|
108
|
+
If a request would go over the limit, a `RateLimitError` is thrown before the request goes out:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { RateLimitError, rateLimiter } from "hantaviruswrapper";
|
|
112
|
+
|
|
113
|
+
// check first
|
|
114
|
+
if (rateLimiter.canRequest()) {
|
|
115
|
+
const data = await getPublicResponseInfo();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// or catch it
|
|
119
|
+
try {
|
|
120
|
+
const data = await getPublicResponseInfo();
|
|
121
|
+
} catch (e) {
|
|
122
|
+
if (e instanceof RateLimitError) {
|
|
123
|
+
console.log(`Retry after ${e.retryAfterMs}ms`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
If you're caching (and you should be), you'll probably never hit this.
|
|
129
|
+
|
|
130
|
+
## Types
|
|
131
|
+
|
|
132
|
+
All types are exported:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import type {
|
|
136
|
+
PublicJsonResponse,
|
|
137
|
+
Meta,
|
|
138
|
+
Stats,
|
|
139
|
+
Country,
|
|
140
|
+
CountryStatus,
|
|
141
|
+
Trend,
|
|
142
|
+
MapMarker,
|
|
143
|
+
MarkerCategory,
|
|
144
|
+
Brief,
|
|
145
|
+
BriefTag,
|
|
146
|
+
Outbreak,
|
|
147
|
+
OutbreakStatus,
|
|
148
|
+
} from "hantaviruswrapper";
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## About hantaOSINT
|
|
152
|
+
|
|
153
|
+
[hantaOSINT](https://hantaosint.com) tracks hantavirus activity globally. The API has free and paid tiers. This wrapper only covers the free routes.
|
|
154
|
+
|
|
155
|
+
Right now the only active free route is `GET /api/v1/public.json`. If more free endpoints get added, this wrapper will be updated to support them.
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
ISC
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getPublicResponseInfo } from "./service/getPublicJsonData.js";
|
|
2
|
+
export const createCachedFetcher = (ttlMs = 300_000) => {
|
|
3
|
+
let cached = null;
|
|
4
|
+
let cachedAt = 0;
|
|
5
|
+
const fetcher = async () => {
|
|
6
|
+
const now = Date.now();
|
|
7
|
+
if (cached && (now - cachedAt) < ttlMs) {
|
|
8
|
+
return cached;
|
|
9
|
+
}
|
|
10
|
+
cached = await getPublicResponseInfo();
|
|
11
|
+
cachedAt = Date.now();
|
|
12
|
+
return cached;
|
|
13
|
+
};
|
|
14
|
+
fetcher.invalidate = () => {
|
|
15
|
+
cached = null;
|
|
16
|
+
cachedAt = 0;
|
|
17
|
+
};
|
|
18
|
+
return fetcher;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAQvE,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,QAAgB,OAAO,EAAiB,EAAE;IAC7E,IAAI,MAAM,GAA8B,IAAI,CAAC;IAC7C,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,MAAM,OAAO,GAAG,KAAK,IAAiC,EAAE;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,MAAM,IAAI,CAAC,GAAG,GAAG,QAAQ,CAAC,GAAG,KAAK,EAAE,CAAC;YACxC,OAAO,MAAM,CAAC;QACf,CAAC;QACD,MAAM,GAAG,MAAM,qBAAqB,EAAE,CAAC;QACvC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,OAAO,MAAM,CAAC;IACf,CAAC,CAAC;IAEF,OAAO,CAAC,UAAU,GAAG,GAAG,EAAE;QACzB,MAAM,GAAG,IAAI,CAAC;QACd,QAAQ,GAAG,CAAC,CAAC;IACd,CAAC,CAAC;IAEF,OAAO,OAAO,CAAC;AAChB,CAAC,CAAC"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { GenericRequestError } from "./types/GenericRequestError.js";
|
|
2
|
+
import { rateLimiter } from "./rateLimit.js";
|
|
3
|
+
export const BASE_URL = "https://hantaosint.com/api/v1";
|
|
4
|
+
// the shape of this client will change in the future, i literally copied this from another project lol
|
|
5
|
+
export const hantaVirusClient = async (endpoint, path, requestType = 'GET') => {
|
|
6
|
+
rateLimiter.consume();
|
|
7
|
+
const requestUrl = new URL(`${BASE_URL}${endpoint}`);
|
|
8
|
+
// naming this request body since it is confusing occasionally
|
|
9
|
+
let reqBody;
|
|
10
|
+
if (path)
|
|
11
|
+
requestUrl.pathname += `/${path}`;
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch(requestUrl.toString(), {
|
|
14
|
+
method: requestType,
|
|
15
|
+
headers: {
|
|
16
|
+
'Accept': 'application/json'
|
|
17
|
+
},
|
|
18
|
+
body: reqBody
|
|
19
|
+
});
|
|
20
|
+
// the api does not return error result (or if it does, it isn't documented) and it honestly does not matter for now since we are not doing any searching
|
|
21
|
+
const data = await response.json();
|
|
22
|
+
if (!response.ok || (data && typeof data === "object" && "error" in data)) {
|
|
23
|
+
throw new Error(`hantaosint error: ${response.status}`);
|
|
24
|
+
}
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error instanceof Error) {
|
|
29
|
+
throw new GenericRequestError(endpoint, undefined, error.message);
|
|
30
|
+
}
|
|
31
|
+
throw new GenericRequestError(endpoint);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,CAAC,MAAM,QAAQ,GAAG,+BAA+B,CAAC;AAExD,uGAAuG;AACvG,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EAAK,QAAgB,EAAE,IAAa,EAAE,cAAsB,KAAK,EAAc,EAAE;IACrH,WAAW,CAAC,OAAO,EAAE,CAAC;IAEtB,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,QAAQ,GAAG,QAAQ,EAAE,CAAC,CAAC;IAErD,8DAA8D;IAC9D,IAAI,OAAoC,CAAC;IAEzC,IAAI,IAAI;QAAE,UAAU,CAAC,QAAQ,IAAI,IAAI,IAAI,EAAE,CAAC;IAE5C,IAAI,CAAC;QACJ,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE;YACnD,MAAM,EAAE,WAAW;YACnB,OAAO,EAAE;gBACR,QAAQ,EAAE,kBAAkB;aAC5B;YACD,IAAI,EAAE,OAAO;SACb,CAAC,CAAC;QAEH,yJAAyJ;QACzJ,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAO,CAAC;QAExC,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,IAAI,CAAC,EAAE,CAAC;YAC3E,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACzD,CAAC;QAED,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACzB,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC5B,MAAM,IAAI,mBAAmB,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;AACF,CAAC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { getPublicResponseInfo } from "./service/getPublicJsonData.js";
|
|
2
|
+
export { getCountryByCode, getCountryByIso, getCountriesByStatus, getCountriesByTrend, searchCountriesByName } from "./service/filterCountries.js";
|
|
3
|
+
export { getOutbreakBySlug, getOutbreaksByStatus, getOutbreaksByStrain, searchOutbreaksByName, getOutbreaksByOrigin } from "./service/filterOutbreaks.js";
|
|
4
|
+
export { createCachedFetcher } from "./cache.js";
|
|
5
|
+
export { RateLimiter, RateLimitError, rateLimiter } from "./rateLimit.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACnJ,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAC1J,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export class RateLimitError extends Error {
|
|
2
|
+
retryAfterMs;
|
|
3
|
+
constructor(retryAfterMs) {
|
|
4
|
+
super(`Rate limit exceeded. Retry after ${Math.ceil(retryAfterMs)}ms`);
|
|
5
|
+
this.name = "RateLimitError";
|
|
6
|
+
this.retryAfterMs = Math.ceil(retryAfterMs);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class RateLimiter {
|
|
10
|
+
tokens;
|
|
11
|
+
lastRefill;
|
|
12
|
+
maxTokens;
|
|
13
|
+
refillRatePerMs;
|
|
14
|
+
constructor(maxPerMinute = 60, burst = 120) {
|
|
15
|
+
this.maxTokens = burst;
|
|
16
|
+
this.tokens = burst;
|
|
17
|
+
this.refillRatePerMs = maxPerMinute / 60_000;
|
|
18
|
+
this.lastRefill = Date.now();
|
|
19
|
+
}
|
|
20
|
+
refill() {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const elapsed = now - this.lastRefill;
|
|
23
|
+
this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.refillRatePerMs);
|
|
24
|
+
this.lastRefill = now;
|
|
25
|
+
}
|
|
26
|
+
canRequest() {
|
|
27
|
+
this.refill();
|
|
28
|
+
return this.tokens >= 1;
|
|
29
|
+
}
|
|
30
|
+
get retryAfterMs() {
|
|
31
|
+
this.refill();
|
|
32
|
+
if (this.tokens >= 1)
|
|
33
|
+
return 0;
|
|
34
|
+
return Math.ceil((1 - this.tokens) / this.refillRatePerMs);
|
|
35
|
+
}
|
|
36
|
+
consume() {
|
|
37
|
+
this.refill();
|
|
38
|
+
if (this.tokens < 1) {
|
|
39
|
+
throw new RateLimitError(Math.ceil((1 - this.tokens) / this.refillRatePerMs));
|
|
40
|
+
}
|
|
41
|
+
this.tokens -= 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export const rateLimiter = new RateLimiter();
|
|
45
|
+
//# sourceMappingURL=rateLimit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../src/rateLimit.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACxB,YAAY,CAAS;IAErC,YAAY,YAAoB;QAC/B,KAAK,CAAC,oCAAoC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACvE,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC7C,CAAC;CACD;AAED,MAAM,OAAO,WAAW;IACf,MAAM,CAAS;IACf,UAAU,CAAS;IACV,SAAS,CAAS;IAClB,eAAe,CAAS;IAEzC,YAAY,eAAuB,EAAE,EAAE,QAAgB,GAAG;QACzD,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,eAAe,GAAG,YAAY,GAAG,MAAM,CAAC;QAC7C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9B,CAAC;IAEO,MAAM;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC;QACtC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC;QACrF,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;IACvB,CAAC;IAED,UAAU;QACT,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;IACzB,CAAC;IAED,IAAI,YAAY;QACf,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO;QACN,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;QAC/E,CAAC;QACD,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;IAClB,CAAC;CACD;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const getCountryByCode = (data, code) => {
|
|
2
|
+
return data.countries.find(c => c.code.toLowerCase() === code.toLowerCase());
|
|
3
|
+
};
|
|
4
|
+
export const getCountryByIso = (data, iso) => {
|
|
5
|
+
return data.countries.find(c => c.iso_a3.toLowerCase() === iso.toLowerCase());
|
|
6
|
+
};
|
|
7
|
+
export const getCountriesByStatus = (data, status) => {
|
|
8
|
+
return data.countries.filter(c => c.status === status);
|
|
9
|
+
};
|
|
10
|
+
export const getCountriesByTrend = (data, trend) => {
|
|
11
|
+
return data.countries.filter(c => c.trend === trend);
|
|
12
|
+
};
|
|
13
|
+
export const searchCountriesByName = (data, name) => {
|
|
14
|
+
const lower = name.toLowerCase();
|
|
15
|
+
return data.countries.filter(c => c.name.toLowerCase().includes(lower));
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=filterCountries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filterCountries.js","sourceRoot":"","sources":["../../src/service/filterCountries.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAwB,EAAE,IAAY,EAAuB,EAAE;IAC/F,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AAC9E,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAwB,EAAE,GAAW,EAAuB,EAAE;IAC7F,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;AAC/E,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,IAAwB,EAAE,MAAqB,EAAa,EAAE;IAClG,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;AACxD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,IAAwB,EAAE,KAAY,EAAa,EAAE;IACxF,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;AACtD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,IAAwB,EAAE,IAAY,EAAa,EAAE;IAC1F,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;AACzE,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const getOutbreakBySlug = (data, slug) => {
|
|
2
|
+
return data.outbreaks.find(o => o.slug === slug);
|
|
3
|
+
};
|
|
4
|
+
export const getOutbreaksByStatus = (data, status) => {
|
|
5
|
+
return data.outbreaks.filter(o => o.status === status);
|
|
6
|
+
};
|
|
7
|
+
export const getOutbreaksByStrain = (data, strain) => {
|
|
8
|
+
const lower = strain.toLowerCase();
|
|
9
|
+
return data.outbreaks.filter(o => o.strain.toLowerCase().includes(lower));
|
|
10
|
+
};
|
|
11
|
+
export const searchOutbreaksByName = (data, name) => {
|
|
12
|
+
const lower = name.toLowerCase();
|
|
13
|
+
return data.outbreaks.filter(o => o.name.toLowerCase().includes(lower));
|
|
14
|
+
};
|
|
15
|
+
export const getOutbreaksByOrigin = (data, origin) => {
|
|
16
|
+
const lower = origin.toLowerCase();
|
|
17
|
+
return data.outbreaks.filter(o => o.origin.toLowerCase().includes(lower));
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=filterOutbreaks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filterOutbreaks.js","sourceRoot":"","sources":["../../src/service/filterOutbreaks.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,IAAwB,EAAE,IAAY,EAAwB,EAAE;IACjG,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AAClD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,IAAwB,EAAE,MAAsB,EAAc,EAAE;IACpG,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;AACxD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,IAAwB,EAAE,MAAc,EAAc,EAAE;IAC5F,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IACnC,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3E,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,IAAwB,EAAE,IAAY,EAAc,EAAE;IAC3F,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;AACzE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,IAAwB,EAAE,MAAc,EAAc,EAAE;IAC5F,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IACnC,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3E,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getPublicJsonData.js","sourceRoot":"","sources":["../../src/service/getPublicJsonData.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,IAAI,EAAE;IAC/C,OAAO,MAAM,gBAAgB,CAAqB,cAAc,CAAC,CAAC;AACnE,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseResponse.js","sourceRoot":"","sources":["../../src/types/BaseResponse.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GenericRequestError.js","sourceRoot":"","sources":["../../src/types/GenericRequestError.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC7C,YAAY,QAAgB,EAAE,MAAe,EAAE,OAAgB;QAC9D,KAAK,CAAC,oBAAoB,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9E,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACxB,CAAC;CACD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publicJsonResponse.js","sourceRoot":"","sources":["../../src/types/publicJsonResponse.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hantaviruswrapper",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build",
|
|
15
|
+
"test": "NODE_OPTIONS='--experimental-vm-modules' jest --runInBand"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/grMLEqomlkkU5Eeinz4brIrOVCUCkJuN/hantaviruswrapper"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@tsconfig/node22": "^22.0.5",
|
|
27
|
+
"@types/jest": "^30.0.0",
|
|
28
|
+
"@types/node": "^25.6.0",
|
|
29
|
+
"@types/supertest": "^7.2.0",
|
|
30
|
+
"eslint": "^10.2.1",
|
|
31
|
+
"jest": "^30.2.0",
|
|
32
|
+
"nodemon": "^3.1.11",
|
|
33
|
+
"ts-jest": "^29.4.6",
|
|
34
|
+
"tsx": "^4.21.0",
|
|
35
|
+
"typescript": "^6.0.3",
|
|
36
|
+
"typescript-eslint": "^8.51.0"
|
|
37
|
+
},
|
|
38
|
+
"jest": {
|
|
39
|
+
"preset": "ts-jest/presets/default-esm",
|
|
40
|
+
"testEnvironment": "node",
|
|
41
|
+
"roots": [
|
|
42
|
+
"<rootDir>/tests"
|
|
43
|
+
],
|
|
44
|
+
"testMatch": [
|
|
45
|
+
"**/*.test.ts"
|
|
46
|
+
],
|
|
47
|
+
"collectCoverageFrom": [
|
|
48
|
+
"lib/**/*.{ts,js}",
|
|
49
|
+
"!lib/**/*.d.ts"
|
|
50
|
+
],
|
|
51
|
+
"transform": {
|
|
52
|
+
"^.+\\.ts$": [
|
|
53
|
+
"ts-jest",
|
|
54
|
+
{
|
|
55
|
+
"useESM": true
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"moduleNameMapper": {
|
|
60
|
+
"^(\\.{1,2}/.*)\\.js$": "$1"
|
|
61
|
+
},
|
|
62
|
+
"moduleFileExtensions": [
|
|
63
|
+
"ts",
|
|
64
|
+
"js",
|
|
65
|
+
"json",
|
|
66
|
+
"node"
|
|
67
|
+
],
|
|
68
|
+
"extensionsToTreatAsEsm": [
|
|
69
|
+
".ts"
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
}
|