dymo-api 1.2.29 → 1.2.31
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/cjs/branches/private/functions/isValidPhone/index.cjs +8 -0
- package/dist/cjs/dymo-api.cjs +51 -21
- package/dist/cjs/lib/resilience/fallback.cjs +427 -0
- package/dist/cjs/lib/resilience/index.cjs +135 -0
- package/dist/esm/branches/private/functions/isValidPhone/index.js +8 -0
- package/dist/esm/dymo-api.js +51 -21
- package/dist/esm/lib/resilience/fallback.js +423 -0
- package/dist/esm/lib/resilience/index.js +131 -0
- package/dist/types/dymo-api.d.ts +22 -17
- package/dist/types/lib/resilience/fallback.d.ts +26 -0
- package/dist/types/lib/resilience/index.d.ts +13 -0
- package/dist/types/lib/types/data-verifier.d.ts +4 -4
- package/dist/types/lib/types/interfaces.d.ts +15 -0
- package/dist/types/lib/types/primitives.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
class RateLimitManager {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.tracker = {};
|
|
4
|
+
}
|
|
5
|
+
static getInstance() {
|
|
6
|
+
if (!RateLimitManager.instance)
|
|
7
|
+
RateLimitManager.instance = new RateLimitManager();
|
|
8
|
+
return RateLimitManager.instance;
|
|
9
|
+
}
|
|
10
|
+
;
|
|
11
|
+
updateRateLimit(clientId, headers) {
|
|
12
|
+
if (!this.tracker[clientId])
|
|
13
|
+
this.tracker[clientId] = {};
|
|
14
|
+
const limitInfo = this.tracker[clientId];
|
|
15
|
+
if (headers["x-ratelimit-limit-requests"])
|
|
16
|
+
limitInfo.limit = parseInt(headers["x-ratelimit-limit-requests"]);
|
|
17
|
+
if (headers["x-ratelimit-remaining-requests"])
|
|
18
|
+
limitInfo.remaining = parseInt(headers["x-ratelimit-remaining-requests"]);
|
|
19
|
+
if (headers["x-ratelimit-reset-requests"])
|
|
20
|
+
limitInfo.resetTime = headers["x-ratelimit-reset-requests"];
|
|
21
|
+
if (headers["retry-after"])
|
|
22
|
+
limitInfo.retryAfter = parseInt(headers["retry-after"]);
|
|
23
|
+
limitInfo.lastUpdated = Date.now();
|
|
24
|
+
}
|
|
25
|
+
;
|
|
26
|
+
isRateLimited(clientId) {
|
|
27
|
+
const limitInfo = this.tracker[clientId];
|
|
28
|
+
return limitInfo?.remaining !== undefined && limitInfo.remaining <= 0;
|
|
29
|
+
}
|
|
30
|
+
;
|
|
31
|
+
getRetryAfter(clientId) {
|
|
32
|
+
return this.tracker[clientId]?.retryAfter;
|
|
33
|
+
}
|
|
34
|
+
;
|
|
35
|
+
clearExpiredLimits() {
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
Object.keys(this.tracker).forEach(clientId => {
|
|
38
|
+
const limitInfo = this.tracker[clientId];
|
|
39
|
+
if (limitInfo.lastUpdated && (now - limitInfo.lastUpdated) > 300000)
|
|
40
|
+
delete this.tracker[clientId];
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
;
|
|
44
|
+
}
|
|
45
|
+
;
|
|
46
|
+
export class ResilienceManager {
|
|
47
|
+
constructor(config = {}, clientId = "default") {
|
|
48
|
+
this.config = {
|
|
49
|
+
fallbackEnabled: config.fallbackEnabled ?? false,
|
|
50
|
+
retryAttempts: Math.max(0, config.retryAttempts ?? 2), // Number of additional retries
|
|
51
|
+
retryDelay: Math.max(0, config.retryDelay ?? 1000)
|
|
52
|
+
};
|
|
53
|
+
this.clientId = clientId;
|
|
54
|
+
this.rateLimitManager = RateLimitManager.getInstance();
|
|
55
|
+
}
|
|
56
|
+
;
|
|
57
|
+
async executeWithResilience(axiosClient, requestConfig, fallbackData) {
|
|
58
|
+
let lastError;
|
|
59
|
+
const totalAttempts = 1 + this.config.retryAttempts; // 1 normal + N retries
|
|
60
|
+
// Clean up expired rate limits periodically
|
|
61
|
+
this.rateLimitManager.clearExpiredLimits();
|
|
62
|
+
// Check if client is currently rate limited
|
|
63
|
+
if (this.rateLimitManager.isRateLimited(this.clientId)) {
|
|
64
|
+
const retryAfter = this.rateLimitManager.getRetryAfter(this.clientId);
|
|
65
|
+
if (retryAfter) {
|
|
66
|
+
console.warn(`[Dymo API] Client ${this.clientId} is rate limited. Waiting ${retryAfter} seconds...`);
|
|
67
|
+
await this.sleep(retryAfter * 1000);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (let attempt = 1; attempt <= totalAttempts; attempt++) {
|
|
71
|
+
try {
|
|
72
|
+
const response = await axiosClient.request(requestConfig);
|
|
73
|
+
// Update rate limit tracking
|
|
74
|
+
this.rateLimitManager.updateRateLimit(this.clientId, response.headers);
|
|
75
|
+
// Check for rate limiting
|
|
76
|
+
if (response.status === 429) {
|
|
77
|
+
const retryAfter = this.rateLimitManager.getRetryAfter(this.clientId);
|
|
78
|
+
if (retryAfter) {
|
|
79
|
+
console.warn(`[Dymo API] Rate limited. Waiting ${retryAfter} seconds (no retries)`);
|
|
80
|
+
await this.sleep(retryAfter * 1000);
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Rate limited (429) - not retrying`);
|
|
83
|
+
}
|
|
84
|
+
return response.data;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
lastError = error;
|
|
88
|
+
let shouldRetry = this.shouldRetry(error);
|
|
89
|
+
const isLastAttempt = attempt === totalAttempts;
|
|
90
|
+
// Don't retry on rate limiting (429)
|
|
91
|
+
if (error.response?.status === 429)
|
|
92
|
+
shouldRetry = false;
|
|
93
|
+
if (!shouldRetry || isLastAttempt) {
|
|
94
|
+
if (this.config.fallbackEnabled && fallbackData) {
|
|
95
|
+
console.warn(`[Dymo API] Request failed after ${attempt} attempts. Using fallback data.`);
|
|
96
|
+
return fallbackData;
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
const delay = this.config.retryDelay * Math.pow(2, attempt - 1);
|
|
101
|
+
console.warn(`[Dymo API] Attempt ${attempt} failed. Retrying in ${delay}ms...`);
|
|
102
|
+
await this.sleep(delay);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw lastError;
|
|
106
|
+
}
|
|
107
|
+
;
|
|
108
|
+
shouldRetry(error) {
|
|
109
|
+
const statusCode = error.response?.status;
|
|
110
|
+
const isNetworkError = !error.response && error.code !== "ECONNABORTED";
|
|
111
|
+
const isServerError = statusCode && statusCode >= 500;
|
|
112
|
+
// Retry on: network errors, server errors (5xx)
|
|
113
|
+
// DON'T retry on: rate limiting (429) - handled separately
|
|
114
|
+
// DON'T retry on: client errors (4xx except 429)
|
|
115
|
+
return isNetworkError || isServerError;
|
|
116
|
+
}
|
|
117
|
+
;
|
|
118
|
+
sleep(ms) {
|
|
119
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
120
|
+
}
|
|
121
|
+
;
|
|
122
|
+
getConfig() {
|
|
123
|
+
return { ...this.config };
|
|
124
|
+
}
|
|
125
|
+
;
|
|
126
|
+
getClientId() {
|
|
127
|
+
return this.clientId;
|
|
128
|
+
}
|
|
129
|
+
;
|
|
130
|
+
}
|
|
131
|
+
;
|
package/dist/types/dymo-api.d.ts
CHANGED
|
@@ -6,30 +6,35 @@ declare class DymoAPI {
|
|
|
6
6
|
private rules;
|
|
7
7
|
private baseUrl;
|
|
8
8
|
private axiosClient;
|
|
9
|
+
private resilienceManager;
|
|
9
10
|
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
11
|
+
* @param {Object} options - Options to create the DymoAPI instance.
|
|
12
|
+
* @param {string} [options.rootApiKey] - The root API key.
|
|
13
|
+
* @param {string} [options.apiKey] - The API key.
|
|
14
|
+
* @param {string} [options.baseUrl] - Whether to use a local server instead of the cloud server.
|
|
15
|
+
* @param {Object} [options.serverEmailConfig] - The server email config.
|
|
16
|
+
* @param {Object} [options.rules] - The rules.
|
|
17
|
+
* @param {Object} [options.resilience] - The resilience config.
|
|
18
|
+
* @description
|
|
19
|
+
* This is the main class to interact with the Dymo API. It should be
|
|
20
|
+
* instantiated with the root API key and the API key. The root API key is
|
|
21
|
+
* used to fetch the tokens and the API key is used to authenticate the
|
|
22
|
+
* requests. Requests are retried once by default with exponential backoff.
|
|
23
|
+
* @example
|
|
24
|
+
* const dymoApi = new DymoAPI({
|
|
25
|
+
* rootApiKey: "6bfb7675-6b69-4f8d-9f43-5a6f7f02c6c5",
|
|
26
|
+
* apiKey: "dm_4c8b7675-6b69-4f8d-9f43-5a6f7f02c6c5"
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
constructor({ rootApiKey, apiKey, baseUrl, serverEmailConfig, rules, resilience }?: {
|
|
27
30
|
rootApiKey?: string | null;
|
|
28
31
|
apiKey?: string | null;
|
|
29
32
|
baseUrl?: string;
|
|
30
33
|
serverEmailConfig?: Interfaces.ServerEmailConfig;
|
|
31
34
|
rules?: Interfaces.Rules;
|
|
35
|
+
resilience?: Interfaces.ResilienceConfig;
|
|
32
36
|
});
|
|
37
|
+
private getEmailPlugins;
|
|
33
38
|
/**
|
|
34
39
|
* Validates the given data against the configured validation settings.
|
|
35
40
|
*
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare class FallbackDataGenerator {
|
|
2
|
+
static generateFallbackData<T>(method: string, inputData?: any): T;
|
|
3
|
+
private static validateURL;
|
|
4
|
+
private static validateEmail;
|
|
5
|
+
private static validateDomain;
|
|
6
|
+
private static validateCreditCard;
|
|
7
|
+
private static validateIP;
|
|
8
|
+
private static validatePhone;
|
|
9
|
+
private static validateWallet;
|
|
10
|
+
private static validateIBAN;
|
|
11
|
+
private static extractDomain;
|
|
12
|
+
private static generateDataValidationAnalysis;
|
|
13
|
+
private static generateEmailValidatorResponse;
|
|
14
|
+
private static generateEmailDataAnalysis;
|
|
15
|
+
private static generateIPValidatorResponse;
|
|
16
|
+
private static generateIPDataAnalysis;
|
|
17
|
+
private static generatePhoneValidatorResponse;
|
|
18
|
+
private static generatePhoneDataAnalysis;
|
|
19
|
+
private static generateHTTPRequest;
|
|
20
|
+
private static generateEmailStatus;
|
|
21
|
+
private static generateSRNSummary;
|
|
22
|
+
private static generateExtractWithTextly;
|
|
23
|
+
private static generatePrayerTimes;
|
|
24
|
+
private static generateSatinizedInputAnalysis;
|
|
25
|
+
private static generatePasswordValidationResult;
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ResilienceConfig } from "../types/interfaces";
|
|
2
|
+
import { AxiosInstance, AxiosRequestConfig } from "axios";
|
|
3
|
+
export declare class ResilienceManager {
|
|
4
|
+
private config;
|
|
5
|
+
private clientId;
|
|
6
|
+
private rateLimitManager;
|
|
7
|
+
constructor(config?: ResilienceConfig, clientId?: string);
|
|
8
|
+
executeWithResilience<T>(axiosClient: AxiosInstance, requestConfig: AxiosRequestConfig, fallbackData?: T): Promise<T>;
|
|
9
|
+
private shouldRetry;
|
|
10
|
+
private sleep;
|
|
11
|
+
getConfig(): Required<ResilienceConfig>;
|
|
12
|
+
getClientId(): string;
|
|
13
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MxRecord } from "dns";
|
|
2
|
-
import { Email, Phone, CreditCard } from "./primitives";
|
|
2
|
+
import { Email, Phone, CreditCard, Char } from "./primitives";
|
|
3
3
|
export type VerifyPlugins = "blocklist" | "gravatar" | "compromiseDetector" | "mxRecords" | "nsfw" | "reputation" | "riskScore" | "torNetwork" | "typosquatting" | "urlShortener";
|
|
4
4
|
export type ReputationPlugin = "low" | "medium" | "high" | "very-high" | "education" | "governmental" | "unknown";
|
|
5
5
|
export type TyposquattingPlugin = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
|
|
@@ -69,10 +69,10 @@ export type IPValidator = string;
|
|
|
69
69
|
* @typedef {"FRAUD"|"INVALID"|"HIGH_RISK_SCORE"} NegativeIPRules
|
|
70
70
|
*
|
|
71
71
|
* @description
|
|
72
|
-
* Values indicating why an
|
|
72
|
+
* Values indicating why an IP is considered negative.
|
|
73
73
|
* ⚠️ TOR_NETWORK and HIGH_RISK_SCORE are premium features.
|
|
74
74
|
*/
|
|
75
|
-
export type NegativeIPRules = "FRAUD" | "INVALID" | "TOR_NETWORK" | "HIGH_RISK_SCORE" | `COUNTRY:${
|
|
75
|
+
export type NegativeIPRules = "FRAUD" | "INVALID" | "TOR_NETWORK" | "HIGH_RISK_SCORE" | `COUNTRY:${Char}${Char}`;
|
|
76
76
|
export type PhoneValidator = Phone;
|
|
77
77
|
/**
|
|
78
78
|
* @typedef {"FRAUD"|"INVALID"|"HIGH_RISK_SCORE"} NegativePhoneRules
|
|
@@ -80,7 +80,7 @@ export type PhoneValidator = Phone;
|
|
|
80
80
|
* @description
|
|
81
81
|
* Values indicating why an phone is considered negative.
|
|
82
82
|
*/
|
|
83
|
-
export type NegativePhoneRules = "FRAUD" | "INVALID" | "HIGH_RISK_SCORE"
|
|
83
|
+
export type NegativePhoneRules = "FRAUD" | "INVALID" | "HIGH_RISK_SCORE" | `COUNTRY:${Char}${Char}`;
|
|
84
84
|
export type NegativeSensitiveInfoRules = "EMAIL" | "PHONE" | "CREDIT_CARD" | "URL" | "DOMAIN" | "IP" | "WALLET" | "USER_AGENT";
|
|
85
85
|
/**
|
|
86
86
|
* Response returned by the email validator.
|
|
@@ -162,6 +162,21 @@ export interface ExtractWithTextly {
|
|
|
162
162
|
[key: string]: JsonSchemaProperty;
|
|
163
163
|
};
|
|
164
164
|
}
|
|
165
|
+
export interface RateLimitInfo {
|
|
166
|
+
limit?: number;
|
|
167
|
+
remaining?: number;
|
|
168
|
+
resetTime?: string;
|
|
169
|
+
retryAfter?: number;
|
|
170
|
+
lastUpdated?: number;
|
|
171
|
+
}
|
|
172
|
+
export interface RateLimitTracker {
|
|
173
|
+
[clientId: string]: RateLimitInfo;
|
|
174
|
+
}
|
|
175
|
+
export interface ResilienceConfig {
|
|
176
|
+
fallbackEnabled?: boolean;
|
|
177
|
+
retryAttempts?: number;
|
|
178
|
+
retryDelay?: number;
|
|
179
|
+
}
|
|
165
180
|
export * from "./rules";
|
|
166
181
|
export * from "./primitives";
|
|
167
182
|
export * from "./data-verifier";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type Email = `${string}@${string}.${string}` | string;
|
|
2
2
|
type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
|
|
3
|
+
export type Char = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z";
|
|
3
4
|
export type Phone = {
|
|
4
5
|
/** The country code of the phone number. */
|
|
5
6
|
iso: any;
|