@parseapi/sdk 0.1.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/dist/index.js ADDED
@@ -0,0 +1,174 @@
1
+ // src/index.ts
2
+ var VERSION = "0.1.0";
3
+ var DEFAULT_BASE_URL = "https://api.parseapi.com";
4
+ var DEFAULT_TIMEOUT_MS = 1e4;
5
+ var DEFAULT_RETRIES = 2;
6
+ var RETRY_STATUS = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
7
+ var RETRY_AFTER_CAP_MS = 5e3;
8
+ var ParseAPIError = class extends Error {
9
+ /** HTTP status */
10
+ status;
11
+ /** Machine-readable error code, e.g. 'not_found', 'invalid_api_key', 'rate_limited' */
12
+ code;
13
+ /** Link to the docs section for this error */
14
+ docs;
15
+ /** Send this if you contact support */
16
+ requestId;
17
+ constructor(status, code, message, docs, requestId) {
18
+ super(message);
19
+ this.name = "ParseAPIError";
20
+ this.status = status;
21
+ this.code = code;
22
+ this.docs = docs;
23
+ this.requestId = requestId;
24
+ }
25
+ };
26
+ function env(name) {
27
+ return typeof process !== "undefined" ? process.env?.[name] : void 0;
28
+ }
29
+ function sleep(ms) {
30
+ return new Promise((resolve) => setTimeout(resolve, ms));
31
+ }
32
+ function retryDelayMs(attempt, retryAfter) {
33
+ if (retryAfter) {
34
+ const seconds = Number(retryAfter);
35
+ if (Number.isFinite(seconds) && seconds >= 0) {
36
+ return Math.min(seconds * 1e3, RETRY_AFTER_CAP_MS);
37
+ }
38
+ }
39
+ return Math.random() * 250 * 2 ** attempt;
40
+ }
41
+ function parseAPI(apiKey, options = {}) {
42
+ const key = apiKey ?? env("PARSEAPI_KEY");
43
+ if (!key) {
44
+ throw new Error("parseAPI: missing API key. Pass one or set PARSEAPI_KEY.");
45
+ }
46
+ const baseUrl = (options.baseUrl ?? env("PARSEAPI_BASE_URL") ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
47
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
48
+ const retries = options.retries ?? DEFAULT_RETRIES;
49
+ const doFetch = options.fetch ?? fetch;
50
+ async function request(path, query, headers) {
51
+ const url = new URL(baseUrl + path);
52
+ for (const [name, value] of Object.entries(query ?? {})) {
53
+ if (value !== void 0) url.searchParams.set(name, String(value));
54
+ }
55
+ for (let attempt = 0; ; attempt++) {
56
+ let res;
57
+ try {
58
+ res = await doFetch(url, {
59
+ headers: {
60
+ "X-API-Key": key,
61
+ "User-Agent": `parseapi-node/${VERSION}`,
62
+ ...headers
63
+ },
64
+ signal: AbortSignal.timeout(timeoutMs)
65
+ });
66
+ } catch (err) {
67
+ if (attempt < retries) {
68
+ await sleep(retryDelayMs(attempt, null));
69
+ continue;
70
+ }
71
+ throw err;
72
+ }
73
+ if (res.ok) {
74
+ return await res.json();
75
+ }
76
+ if (RETRY_STATUS.has(res.status) && attempt < retries) {
77
+ await sleep(retryDelayMs(attempt, res.headers.get("Retry-After")));
78
+ continue;
79
+ }
80
+ let body = {};
81
+ try {
82
+ body = await res.json();
83
+ } catch {
84
+ }
85
+ throw new ParseAPIError(
86
+ res.status,
87
+ typeof body.code === "string" ? body.code : "unknown_error",
88
+ typeof body.message === "string" ? body.message : `Request failed with status ${res.status}`,
89
+ typeof body.docs === "string" ? body.docs : null,
90
+ typeof body.request_id === "string" ? body.request_id : null
91
+ );
92
+ }
93
+ }
94
+ const enc = encodeURIComponent;
95
+ const deepQuery = (opts) => opts?.deep ? { deep: true } : {};
96
+ return {
97
+ ip: Object.assign(
98
+ (ip, opts) => request(`/ip/${enc(ip)}`, deepQuery(opts)),
99
+ {
100
+ self: (opts) => request("/ip", deepQuery(opts))
101
+ }
102
+ ),
103
+ continent: Object.assign(
104
+ (code) => request(`/continent/${enc(code)}`),
105
+ {
106
+ countries: (code) => request(`/continent/${enc(code)}/countries`)
107
+ }
108
+ ),
109
+ country: Object.assign(
110
+ (code) => request(`/country/${enc(code)}`),
111
+ {
112
+ states: (code) => request(`/country/${enc(code)}/states`)
113
+ }
114
+ ),
115
+ state: Object.assign(
116
+ (code, opts) => request(`/state/${enc(code)}`, { country: opts.country }),
117
+ {
118
+ districts: (code, opts) => request(`/state/${enc(code)}/districts`, { country: opts.country })
119
+ }
120
+ ),
121
+ district: (code, opts) => request(`/district/${enc(code)}`, { country: opts?.country }),
122
+ city: Object.assign(
123
+ (name, opts) => request(`/city/${enc(name)}`, { country: opts?.country, state: opts?.state }),
124
+ {
125
+ id: (id) => request(`/city/id/${enc(id)}`),
126
+ search: (q, opts) => request("/city", { q, country: opts?.country, state: opts?.state, limit: opts?.limit }),
127
+ nearest: (lat, lon) => request("/city", { lat, lon })
128
+ }
129
+ ),
130
+ postal: Object.assign(
131
+ (code, opts) => request(`/postal/${enc(code)}`, { country: opts.country }),
132
+ {
133
+ nearby: (code, opts) => request(`/postal/${enc(code)}/nearby`, {
134
+ country: opts.country,
135
+ radius: opts.radius,
136
+ unit: opts.unit
137
+ }),
138
+ distance: (from, to, opts) => request(`/postal/${enc(from)}/distance/${enc(to)}`, { country: opts.country })
139
+ }
140
+ ),
141
+ email: (email, opts) => request(`/email/${enc(email)}`, deepQuery(opts)),
142
+ phone: (number, opts) => request(`/phone/${enc(number)}`, { country: opts?.country, ...deepQuery(opts) }),
143
+ domain: (domain, opts) => request(`/domain/${enc(domain)}`, deepQuery(opts)),
144
+ mx: (domain) => request(`/mx/${enc(domain)}`),
145
+ useragent: (ua, opts) => request("/useragent", deepQuery(opts), { "User-Agent": ua }),
146
+ currency: Object.assign(
147
+ (code) => request(`/currency/${enc(code)}`),
148
+ {
149
+ rate: (base, quote) => request(`/currency/${enc(base)}/${enc(quote)}`)
150
+ }
151
+ ),
152
+ language: (code) => request(`/language/${enc(code)}`),
153
+ timezone: (id, opts) => request(`/timezone/${enc(id)}`, { at: opts?.at }),
154
+ holiday: Object.assign(
155
+ (country, opts) => request(`/holiday/${enc(country)}`, { year: opts?.year }),
156
+ {
157
+ date: (country, date) => request(`/holiday/${enc(country)}/${enc(date)}`)
158
+ }
159
+ ),
160
+ elevation: (lat, lon) => request("/elevation", { lat, lon }),
161
+ point: (lat, lon, opts) => request("/point", { lat, lon, ...deepQuery(opts) }),
162
+ weather: (lat, lon, opts) => request("/weather", { lat, lon, ...deepQuery(opts) }),
163
+ emoji: Object.assign(
164
+ (emoji) => request(`/emoji/${enc(emoji)}`),
165
+ {
166
+ search: (q, opts) => request("/emoji", { q, limit: opts?.limit })
167
+ }
168
+ )
169
+ };
170
+ }
171
+ export {
172
+ ParseAPIError,
173
+ parseAPI
174
+ };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@parseapi/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official parseAPI client for Node and TypeScript. One key, minimal JSON, fast.",
5
+ "keywords": [
6
+ "parseapi",
7
+ "ip",
8
+ "geolocation",
9
+ "email validation",
10
+ "phone validation",
11
+ "postal",
12
+ "timezone",
13
+ "currency",
14
+ "weather"
15
+ ],
16
+ "license": "MIT",
17
+ "author": "parseAPI <hello@parseapi.com> (https://parseapi.com)",
18
+ "homepage": "https://parseapi.com",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/parseapi/parseapi-node.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/parseapi/parseapi-node/issues"
25
+ },
26
+ "type": "module",
27
+ "main": "./dist/index.cjs",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js",
34
+ "require": "./dist/index.cjs"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "sideEffects": false,
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "scripts": {
45
+ "build": "tsup src/index.ts --format esm,cjs --dts --target node18 --clean",
46
+ "prepublishOnly": "npm run build && npm test",
47
+ "test": "vitest run",
48
+ "typecheck": "tsc --noEmit",
49
+ "smoke": "npm run build && node smoke/smoke.mjs"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^22.10.0",
53
+ "tsup": "^8.3.5",
54
+ "typescript": "^5.7.0",
55
+ "vitest": "^2.1.8"
56
+ }
57
+ }