@fjell/client-api 4.4.16 → 4.4.18
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/ClientApiOptions.d.ts +7 -0
- package/dist/errors/index.d.ts +150 -0
- package/dist/http/HttpWrapper.d.ts +61 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +717 -13
- package/dist/index.js.map +4 -4
- package/dist/ops/action.d.ts +2 -2
- package/dist/ops/all.d.ts +2 -2
- package/dist/ops/allAction.d.ts +2 -2
- package/dist/ops/allFacet.d.ts +2 -2
- package/dist/ops/create.d.ts +2 -2
- package/dist/ops/errorHandling.d.ts +27 -0
- package/dist/ops/facet.d.ts +2 -2
- package/dist/ops/find.d.ts +2 -2
- package/dist/ops/findOne.d.ts +2 -2
- package/dist/ops/get.d.ts +2 -2
- package/dist/ops/index.d.ts +3 -3
- package/dist/ops/one.d.ts +2 -2
- package/dist/ops/remove.d.ts +2 -2
- package/dist/ops/update.d.ts +2 -2
- package/package.json +2 -3
- package/vitest.config.ts +0 -6
package/dist/index.js
CHANGED
|
@@ -96,18 +96,119 @@ var getOneOperation = (api, apiOptions, utilities) => {
|
|
|
96
96
|
|
|
97
97
|
// src/ops/create.ts
|
|
98
98
|
var logger5 = logger_default.get("client-api", "ops", "create");
|
|
99
|
+
function shouldRetryError(error) {
|
|
100
|
+
if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND" || error.code === "ENETUNREACH" || error.message?.includes("timeout") || error.message?.includes("network")) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (error.status >= 500 || error.status === 429) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (error.status >= 400 && error.status < 500 && error.status !== 429) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
function calculateRetryDelay(attempt, config) {
|
|
112
|
+
const exponentialDelay = (config.initialDelayMs || 1e3) * Math.pow(config.backoffMultiplier || 2, attempt);
|
|
113
|
+
const cappedDelay = Math.min(exponentialDelay, config.maxDelayMs || 3e4);
|
|
114
|
+
const jitter = 0.5 + Math.random() * 0.5;
|
|
115
|
+
return Math.floor(cappedDelay * jitter);
|
|
116
|
+
}
|
|
117
|
+
function enhanceError(error, context) {
|
|
118
|
+
if (!error) return new Error("Unknown error occurred");
|
|
119
|
+
if (error.context) return error;
|
|
120
|
+
const enhancedError = new Error(error.message || "HTTP operation failed");
|
|
121
|
+
Object.assign(enhancedError, {
|
|
122
|
+
code: error.code || error.status || "UNKNOWN_ERROR",
|
|
123
|
+
status: error.status,
|
|
124
|
+
context,
|
|
125
|
+
originalError: error
|
|
126
|
+
});
|
|
127
|
+
return enhancedError;
|
|
128
|
+
}
|
|
99
129
|
var getCreateOperation = (api, apiOptions, utilities) => {
|
|
100
130
|
const create = async (item, locations = []) => {
|
|
101
131
|
const requestOptions = Object.assign({}, apiOptions.postOptions, { isAuthenticated: apiOptions.writeAuthenticated });
|
|
102
132
|
logger5.default("create", { item, locations, requestOptions });
|
|
103
133
|
utilities.verifyLocations(locations);
|
|
104
134
|
const loc = locations;
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
135
|
+
const operationContext = {
|
|
136
|
+
operation: "create",
|
|
137
|
+
path: utilities.getPath(loc),
|
|
138
|
+
itemType: typeof item,
|
|
139
|
+
hasLocations: locations.length > 0
|
|
140
|
+
};
|
|
141
|
+
const retryConfig = {
|
|
142
|
+
maxRetries: 3,
|
|
143
|
+
initialDelayMs: 1e3,
|
|
144
|
+
maxDelayMs: 3e4,
|
|
145
|
+
backoffMultiplier: 2,
|
|
146
|
+
...apiOptions.retryConfig
|
|
147
|
+
};
|
|
148
|
+
let lastError = null;
|
|
149
|
+
const startTime = Date.now();
|
|
150
|
+
for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
|
|
151
|
+
try {
|
|
152
|
+
logger5.debug(`Creating item (attempt ${attempt + 1})`, operationContext);
|
|
153
|
+
const result = await utilities.processOne(api.httpPost(
|
|
154
|
+
utilities.getPath(loc),
|
|
155
|
+
item,
|
|
156
|
+
requestOptions
|
|
157
|
+
));
|
|
158
|
+
const created = utilities.validatePK(result);
|
|
159
|
+
if (attempt > 0) {
|
|
160
|
+
logger5.info(`Create operation succeeded after ${attempt} retries`, {
|
|
161
|
+
...operationContext,
|
|
162
|
+
totalAttempts: attempt + 1,
|
|
163
|
+
duration: Date.now() - startTime
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return created;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
lastError = error;
|
|
169
|
+
if (attempt === retryConfig.maxRetries) {
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
const isRetryable = shouldRetryError(error);
|
|
173
|
+
if (!isRetryable) {
|
|
174
|
+
logger5.debug("Not retrying create operation due to non-retryable error", {
|
|
175
|
+
...operationContext,
|
|
176
|
+
errorMessage: error.message,
|
|
177
|
+
errorCode: error.code || error.status,
|
|
178
|
+
attempt: attempt + 1
|
|
179
|
+
});
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
const delay = calculateRetryDelay(attempt, retryConfig);
|
|
183
|
+
logger5.warning(`Retrying create operation (attempt ${attempt + 2}) after ${delay}ms`, {
|
|
184
|
+
...operationContext,
|
|
185
|
+
errorMessage: error.message,
|
|
186
|
+
errorCode: error.code || error.status,
|
|
187
|
+
delay,
|
|
188
|
+
attemptNumber: attempt + 1
|
|
189
|
+
});
|
|
190
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const finalError = enhanceError(lastError, operationContext);
|
|
194
|
+
if (apiOptions.errorHandler) {
|
|
195
|
+
try {
|
|
196
|
+
apiOptions.errorHandler(finalError, operationContext);
|
|
197
|
+
} catch (handlerError) {
|
|
198
|
+
logger5.error("Custom error handler failed", {
|
|
199
|
+
originalError: finalError.message,
|
|
200
|
+
handlerError: handlerError?.message || String(handlerError)
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
logger5.error(`Create operation failed after ${retryConfig.maxRetries + 1} attempts`, {
|
|
205
|
+
...operationContext,
|
|
206
|
+
errorMessage: finalError.message,
|
|
207
|
+
errorCode: finalError.code || finalError.status,
|
|
208
|
+
duration: Date.now() - startTime,
|
|
209
|
+
totalAttempts: retryConfig.maxRetries + 1
|
|
210
|
+
});
|
|
211
|
+
throw finalError;
|
|
111
212
|
};
|
|
112
213
|
return create;
|
|
113
214
|
};
|
|
@@ -131,16 +232,121 @@ var getUpdateOperation = (api, apiOptions, utilities) => {
|
|
|
131
232
|
|
|
132
233
|
// src/ops/get.ts
|
|
133
234
|
var logger7 = logger_default.get("client-api", "ops", "get");
|
|
235
|
+
function shouldRetryError2(error) {
|
|
236
|
+
if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND" || error.code === "ENETUNREACH" || error.message?.includes("timeout") || error.message?.includes("network")) {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
if (error.status >= 500 || error.status === 429) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
if (error.status >= 400 && error.status < 500 && error.status !== 429) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
function calculateRetryDelay2(attempt, config) {
|
|
248
|
+
const exponentialDelay = (config.initialDelayMs || 1e3) * Math.pow(config.backoffMultiplier || 2, attempt);
|
|
249
|
+
const cappedDelay = Math.min(exponentialDelay, config.maxDelayMs || 3e4);
|
|
250
|
+
const jitter = 0.5 + Math.random() * 0.5;
|
|
251
|
+
return Math.floor(cappedDelay * jitter);
|
|
252
|
+
}
|
|
253
|
+
function enhanceError2(error, context) {
|
|
254
|
+
if (!error) return new Error("Unknown error occurred");
|
|
255
|
+
if (error.context) return error;
|
|
256
|
+
const enhancedError = new Error(error.message || "HTTP operation failed");
|
|
257
|
+
Object.assign(enhancedError, {
|
|
258
|
+
code: error.code || error.status || "UNKNOWN_ERROR",
|
|
259
|
+
status: error.status,
|
|
260
|
+
context,
|
|
261
|
+
originalError: error
|
|
262
|
+
});
|
|
263
|
+
return enhancedError;
|
|
264
|
+
}
|
|
134
265
|
var getGetOperation = (api, apiOptions, utilities) => {
|
|
135
266
|
const get = async (ik) => {
|
|
136
267
|
const requestOptions = Object.assign({}, apiOptions.getOptions, { isAuthenticated: apiOptions.readAuthenticated });
|
|
137
268
|
logger7.default("get", { ik, requestOptions });
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
269
|
+
const operationContext = {
|
|
270
|
+
operation: "get",
|
|
271
|
+
path: utilities.getPath(ik),
|
|
272
|
+
keyType: typeof ik
|
|
273
|
+
};
|
|
274
|
+
const retryConfig = {
|
|
275
|
+
maxRetries: 3,
|
|
276
|
+
initialDelayMs: 1e3,
|
|
277
|
+
maxDelayMs: 3e4,
|
|
278
|
+
backoffMultiplier: 2,
|
|
279
|
+
...apiOptions.retryConfig
|
|
280
|
+
};
|
|
281
|
+
let lastError = null;
|
|
282
|
+
const startTime = Date.now();
|
|
283
|
+
for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
|
|
284
|
+
try {
|
|
285
|
+
logger7.debug(`Getting item (attempt ${attempt + 1})`, operationContext);
|
|
286
|
+
const result = await utilities.processOne(
|
|
287
|
+
api.httpGet(
|
|
288
|
+
utilities.getPath(ik),
|
|
289
|
+
requestOptions
|
|
290
|
+
)
|
|
291
|
+
);
|
|
292
|
+
const item = utilities.validatePK(result);
|
|
293
|
+
if (attempt > 0) {
|
|
294
|
+
logger7.info(`Get operation succeeded after ${attempt} retries`, {
|
|
295
|
+
...operationContext,
|
|
296
|
+
totalAttempts: attempt + 1,
|
|
297
|
+
duration: Date.now() - startTime
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
return item;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
lastError = error;
|
|
303
|
+
if (error.status === 404) {
|
|
304
|
+
logger7.debug("Item not found (404)", operationContext);
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
if (attempt === retryConfig.maxRetries) {
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
const isRetryable = shouldRetryError2(error);
|
|
311
|
+
if (!isRetryable) {
|
|
312
|
+
logger7.debug("Not retrying get operation due to non-retryable error", {
|
|
313
|
+
...operationContext,
|
|
314
|
+
errorMessage: error.message,
|
|
315
|
+
errorCode: error.code || error.status,
|
|
316
|
+
attempt: attempt + 1
|
|
317
|
+
});
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
const delay = calculateRetryDelay2(attempt, retryConfig);
|
|
321
|
+
logger7.warning(`Retrying get operation (attempt ${attempt + 2}) after ${delay}ms`, {
|
|
322
|
+
...operationContext,
|
|
323
|
+
errorMessage: error.message,
|
|
324
|
+
errorCode: error.code || error.status,
|
|
325
|
+
delay,
|
|
326
|
+
attemptNumber: attempt + 1
|
|
327
|
+
});
|
|
328
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const finalError = enhanceError2(lastError, operationContext);
|
|
332
|
+
if (apiOptions.errorHandler) {
|
|
333
|
+
try {
|
|
334
|
+
apiOptions.errorHandler(finalError, operationContext);
|
|
335
|
+
} catch (handlerError) {
|
|
336
|
+
logger7.error("Custom error handler failed", {
|
|
337
|
+
originalError: finalError.message,
|
|
338
|
+
handlerError: handlerError?.message || String(handlerError)
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
logger7.error(`Get operation failed after ${retryConfig.maxRetries + 1} attempts`, {
|
|
343
|
+
...operationContext,
|
|
344
|
+
errorMessage: finalError.message,
|
|
345
|
+
errorCode: finalError.code || finalError.status,
|
|
346
|
+
duration: Date.now() - startTime,
|
|
347
|
+
totalAttempts: retryConfig.maxRetries + 1
|
|
348
|
+
});
|
|
349
|
+
throw finalError;
|
|
144
350
|
};
|
|
145
351
|
return get;
|
|
146
352
|
};
|
|
@@ -534,12 +740,510 @@ var createRegistry = (registryHub) => {
|
|
|
534
740
|
...baseRegistry
|
|
535
741
|
};
|
|
536
742
|
};
|
|
743
|
+
|
|
744
|
+
// src/errors/index.ts
|
|
745
|
+
var ClientApiError = class extends Error {
|
|
746
|
+
timestamp;
|
|
747
|
+
context;
|
|
748
|
+
constructor(message, context) {
|
|
749
|
+
super(message);
|
|
750
|
+
this.name = this.constructor.name;
|
|
751
|
+
this.timestamp = /* @__PURE__ */ new Date();
|
|
752
|
+
this.context = context;
|
|
753
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
754
|
+
}
|
|
755
|
+
toJSON() {
|
|
756
|
+
return {
|
|
757
|
+
name: this.name,
|
|
758
|
+
code: this.code,
|
|
759
|
+
message: this.message,
|
|
760
|
+
isRetryable: this.isRetryable,
|
|
761
|
+
timestamp: this.timestamp,
|
|
762
|
+
context: this.context,
|
|
763
|
+
stack: this.stack
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
var NetworkError = class extends ClientApiError {
|
|
768
|
+
code = "NETWORK_ERROR";
|
|
769
|
+
isRetryable = true;
|
|
770
|
+
constructor(message, context) {
|
|
771
|
+
super(`Network error: ${message}`, context);
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
var TimeoutError = class extends ClientApiError {
|
|
775
|
+
code = "TIMEOUT_ERROR";
|
|
776
|
+
isRetryable = true;
|
|
777
|
+
constructor(timeout, context) {
|
|
778
|
+
super(`Request timed out after ${timeout}ms`, context);
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
var AuthenticationError = class extends ClientApiError {
|
|
782
|
+
code = "AUTHENTICATION_ERROR";
|
|
783
|
+
isRetryable = false;
|
|
784
|
+
constructor(message, context) {
|
|
785
|
+
super(message || "Authentication failed - invalid or expired credentials", context);
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
var AuthorizationError = class extends ClientApiError {
|
|
789
|
+
code = "AUTHORIZATION_ERROR";
|
|
790
|
+
isRetryable = false;
|
|
791
|
+
constructor(message, context) {
|
|
792
|
+
super(message || "Access forbidden - insufficient permissions", context);
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
var NotFoundError = class extends ClientApiError {
|
|
796
|
+
code = "NOT_FOUND_ERROR";
|
|
797
|
+
isRetryable = false;
|
|
798
|
+
constructor(resource, identifier, context) {
|
|
799
|
+
const message = identifier ? `${resource} with identifier '${identifier}' not found` : `${resource} not found`;
|
|
800
|
+
super(message, context);
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
var ValidationError = class extends ClientApiError {
|
|
804
|
+
code = "VALIDATION_ERROR";
|
|
805
|
+
isRetryable = false;
|
|
806
|
+
validationErrors;
|
|
807
|
+
constructor(message, validationErrors, context) {
|
|
808
|
+
super(`Validation error: ${message}`, context);
|
|
809
|
+
this.validationErrors = validationErrors;
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
var ConflictError = class extends ClientApiError {
|
|
813
|
+
code = "CONFLICT_ERROR";
|
|
814
|
+
isRetryable = false;
|
|
815
|
+
constructor(message, context) {
|
|
816
|
+
super(`Conflict: ${message}`, context);
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
var RateLimitError = class extends ClientApiError {
|
|
820
|
+
code = "RATE_LIMIT_ERROR";
|
|
821
|
+
isRetryable = true;
|
|
822
|
+
retryAfter;
|
|
823
|
+
constructor(retryAfter, context) {
|
|
824
|
+
const message = retryAfter ? `Rate limit exceeded - retry after ${retryAfter} seconds` : "Rate limit exceeded";
|
|
825
|
+
super(message, context);
|
|
826
|
+
this.retryAfter = retryAfter;
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
var ServerError = class extends ClientApiError {
|
|
830
|
+
code = "SERVER_ERROR";
|
|
831
|
+
isRetryable = true;
|
|
832
|
+
statusCode;
|
|
833
|
+
constructor(statusCode, message, context) {
|
|
834
|
+
super(message || `Server error (${statusCode})`, context);
|
|
835
|
+
this.statusCode = statusCode;
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
var PayloadTooLargeError = class extends ClientApiError {
|
|
839
|
+
code = "PAYLOAD_TOO_LARGE_ERROR";
|
|
840
|
+
isRetryable = false;
|
|
841
|
+
constructor(maxSize, context) {
|
|
842
|
+
const message = maxSize ? `Request payload too large - maximum size is ${maxSize}` : "Request payload too large";
|
|
843
|
+
super(message, context);
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
var HttpError = class extends ClientApiError {
|
|
847
|
+
code = "HTTP_ERROR";
|
|
848
|
+
isRetryable;
|
|
849
|
+
statusCode;
|
|
850
|
+
statusText;
|
|
851
|
+
constructor(statusCode, statusText, message, context) {
|
|
852
|
+
super(message || `HTTP error ${statusCode}: ${statusText}`, context);
|
|
853
|
+
this.statusCode = statusCode;
|
|
854
|
+
this.statusText = statusText;
|
|
855
|
+
this.isRetryable = statusCode >= 500;
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
var ConfigurationError = class extends ClientApiError {
|
|
859
|
+
code = "CONFIGURATION_ERROR";
|
|
860
|
+
isRetryable = false;
|
|
861
|
+
constructor(message, context) {
|
|
862
|
+
super(`Configuration error: ${message}`, context);
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
var ParseError = class extends ClientApiError {
|
|
866
|
+
code = "PARSE_ERROR";
|
|
867
|
+
isRetryable = false;
|
|
868
|
+
constructor(message, context) {
|
|
869
|
+
super(`Parse error: ${message}`, context);
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
function createHttpError(statusCode, statusText, responseBody, context) {
|
|
873
|
+
const errorContext = { statusCode, statusText, responseBody, ...context };
|
|
874
|
+
switch (statusCode) {
|
|
875
|
+
case 400:
|
|
876
|
+
if (responseBody?.validationErrors) {
|
|
877
|
+
return new ValidationError(
|
|
878
|
+
responseBody.message || "Request validation failed",
|
|
879
|
+
responseBody.validationErrors,
|
|
880
|
+
errorContext
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
return new ValidationError(responseBody?.message || statusText, [], errorContext);
|
|
884
|
+
case 401:
|
|
885
|
+
return new AuthenticationError(responseBody?.message, errorContext);
|
|
886
|
+
case 403:
|
|
887
|
+
return new AuthorizationError(responseBody?.message, errorContext);
|
|
888
|
+
case 404:
|
|
889
|
+
return new NotFoundError(
|
|
890
|
+
responseBody?.resource || "Resource",
|
|
891
|
+
responseBody?.identifier,
|
|
892
|
+
errorContext
|
|
893
|
+
);
|
|
894
|
+
case 409:
|
|
895
|
+
return new ConflictError(responseBody?.message || statusText, errorContext);
|
|
896
|
+
case 413:
|
|
897
|
+
return new PayloadTooLargeError(responseBody?.maxSize, errorContext);
|
|
898
|
+
case 429: {
|
|
899
|
+
let retryAfter;
|
|
900
|
+
if (responseBody?.retryAfter) {
|
|
901
|
+
retryAfter = responseBody.retryAfter;
|
|
902
|
+
} else if (context?.headers?.["retry-after"]) {
|
|
903
|
+
retryAfter = parseInt(context.headers["retry-after"]);
|
|
904
|
+
}
|
|
905
|
+
return new RateLimitError(retryAfter, errorContext);
|
|
906
|
+
}
|
|
907
|
+
default:
|
|
908
|
+
if (statusCode >= 500) {
|
|
909
|
+
return new ServerError(statusCode, responseBody?.message || statusText, errorContext);
|
|
910
|
+
}
|
|
911
|
+
return new HttpError(statusCode, statusText, responseBody?.message, errorContext);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function createNetworkError(error, context) {
|
|
915
|
+
const errorContext = { originalError: error, ...context };
|
|
916
|
+
if (error.code === "ECONNABORTED" || error.message?.includes("timeout")) {
|
|
917
|
+
return new TimeoutError(error.timeout || 5e3, errorContext);
|
|
918
|
+
}
|
|
919
|
+
if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND" || error.code === "ENETUNREACH" || error.message?.includes("network")) {
|
|
920
|
+
return new NetworkError(error.message || "Network connection failed", errorContext);
|
|
921
|
+
}
|
|
922
|
+
return new NetworkError(error.message || "Unknown network error", errorContext);
|
|
923
|
+
}
|
|
924
|
+
function isRetryableError(error) {
|
|
925
|
+
return error instanceof ClientApiError && error.isRetryable;
|
|
926
|
+
}
|
|
927
|
+
function isClientApiError(error) {
|
|
928
|
+
return error instanceof ClientApiError;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/ops/errorHandling.ts
|
|
932
|
+
function shouldRetryError3(error) {
|
|
933
|
+
if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND" || error.code === "ENETUNREACH" || error.message?.includes("timeout") || error.message?.includes("network")) {
|
|
934
|
+
return true;
|
|
935
|
+
}
|
|
936
|
+
if (error.status >= 500 || error.status === 429) {
|
|
937
|
+
return true;
|
|
938
|
+
}
|
|
939
|
+
if (error.status >= 400 && error.status < 500 && error.status !== 429) {
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
944
|
+
function calculateRetryDelay3(attempt, config) {
|
|
945
|
+
const exponentialDelay = (config.initialDelayMs || 1e3) * Math.pow(config.backoffMultiplier || 2, attempt);
|
|
946
|
+
const cappedDelay = Math.min(exponentialDelay, config.maxDelayMs || 3e4);
|
|
947
|
+
const jitter = 0.5 + Math.random() * 0.5;
|
|
948
|
+
return Math.floor(cappedDelay * jitter);
|
|
949
|
+
}
|
|
950
|
+
function enhanceError3(error, context) {
|
|
951
|
+
if (!error) return new Error("Unknown error occurred");
|
|
952
|
+
if (error.context) return error;
|
|
953
|
+
const enhancedError = new Error(error.message || "HTTP operation failed");
|
|
954
|
+
Object.assign(enhancedError, {
|
|
955
|
+
code: error.code || error.status || "UNKNOWN_ERROR",
|
|
956
|
+
status: error.status,
|
|
957
|
+
context,
|
|
958
|
+
originalError: error
|
|
959
|
+
});
|
|
960
|
+
return enhancedError;
|
|
961
|
+
}
|
|
962
|
+
function getRetryConfig(apiOptions) {
|
|
963
|
+
return {
|
|
964
|
+
maxRetries: 3,
|
|
965
|
+
initialDelayMs: 1e3,
|
|
966
|
+
maxDelayMs: 3e4,
|
|
967
|
+
backoffMultiplier: 2,
|
|
968
|
+
...apiOptions.retryConfig
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
function executeErrorHandler(errorHandler, error, context, logger21) {
|
|
972
|
+
if (!errorHandler) return;
|
|
973
|
+
try {
|
|
974
|
+
errorHandler(error, context);
|
|
975
|
+
} catch (handlerError) {
|
|
976
|
+
logger21.error("Custom error handler failed", {
|
|
977
|
+
originalError: error.message,
|
|
978
|
+
handlerError: handlerError?.message || String(handlerError)
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
async function executeWithRetry(operation, operationName, operationContext, apiOptions, logger21, specialErrorHandling) {
|
|
983
|
+
const retryConfig = getRetryConfig(apiOptions);
|
|
984
|
+
let lastError = null;
|
|
985
|
+
const startTime = Date.now();
|
|
986
|
+
for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
|
|
987
|
+
try {
|
|
988
|
+
logger21.debug(`Executing ${operationName} (attempt ${attempt + 1})`, operationContext);
|
|
989
|
+
const result = await operation();
|
|
990
|
+
if (attempt > 0) {
|
|
991
|
+
logger21.info(`${operationName} operation succeeded after ${attempt} retries`, {
|
|
992
|
+
...operationContext,
|
|
993
|
+
totalAttempts: attempt + 1,
|
|
994
|
+
duration: Date.now() - startTime
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
return result;
|
|
998
|
+
} catch (error) {
|
|
999
|
+
lastError = error;
|
|
1000
|
+
if (specialErrorHandling) {
|
|
1001
|
+
const specialResult = specialErrorHandling(error);
|
|
1002
|
+
if (specialResult !== void 0) {
|
|
1003
|
+
return specialResult;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
if (attempt === retryConfig.maxRetries) {
|
|
1007
|
+
break;
|
|
1008
|
+
}
|
|
1009
|
+
const isRetryable = shouldRetryError3(error);
|
|
1010
|
+
if (!isRetryable) {
|
|
1011
|
+
logger21.debug(`Not retrying ${operationName} operation due to non-retryable error`, {
|
|
1012
|
+
...operationContext,
|
|
1013
|
+
errorMessage: error.message,
|
|
1014
|
+
errorCode: error.code || error.status,
|
|
1015
|
+
attempt: attempt + 1
|
|
1016
|
+
});
|
|
1017
|
+
break;
|
|
1018
|
+
}
|
|
1019
|
+
const delay = calculateRetryDelay3(attempt, retryConfig);
|
|
1020
|
+
logger21.warning(`Retrying ${operationName} operation (attempt ${attempt + 2}) after ${delay}ms`, {
|
|
1021
|
+
...operationContext,
|
|
1022
|
+
errorMessage: error.message,
|
|
1023
|
+
errorCode: error.code || error.status,
|
|
1024
|
+
delay,
|
|
1025
|
+
attemptNumber: attempt + 1
|
|
1026
|
+
});
|
|
1027
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
const finalError = enhanceError3(lastError, operationContext);
|
|
1031
|
+
executeErrorHandler(apiOptions.errorHandler, finalError, operationContext, logger21);
|
|
1032
|
+
logger21.error(`${operationName} operation failed after ${retryConfig.maxRetries + 1} attempts`, {
|
|
1033
|
+
...operationContext,
|
|
1034
|
+
errorMessage: finalError.message,
|
|
1035
|
+
errorCode: finalError.code || finalError.status,
|
|
1036
|
+
duration: Date.now() - startTime,
|
|
1037
|
+
totalAttempts: retryConfig.maxRetries + 1
|
|
1038
|
+
});
|
|
1039
|
+
throw finalError;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// src/http/HttpWrapper.ts
|
|
1043
|
+
var logger20 = {
|
|
1044
|
+
debug: (message, context) => console.debug(message, context),
|
|
1045
|
+
info: (message, context) => console.info(message, context),
|
|
1046
|
+
warning: (message, context) => console.warn(message, context),
|
|
1047
|
+
error: (message, context) => console.error(message, context)
|
|
1048
|
+
};
|
|
1049
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
1050
|
+
maxRetries: 3,
|
|
1051
|
+
initialDelayMs: 1e3,
|
|
1052
|
+
maxDelayMs: 3e4,
|
|
1053
|
+
backoffMultiplier: 2,
|
|
1054
|
+
enableJitter: true,
|
|
1055
|
+
shouldRetry: (error, attemptNumber) => {
|
|
1056
|
+
if (attemptNumber >= 3) return false;
|
|
1057
|
+
if (error.isRetryable) return true;
|
|
1058
|
+
return false;
|
|
1059
|
+
},
|
|
1060
|
+
onRetry: (error, attemptNumber, delay) => {
|
|
1061
|
+
logger20.warning(`Retrying HTTP request (attempt ${attemptNumber + 1}) after ${delay}ms`, {
|
|
1062
|
+
errorCode: error.code,
|
|
1063
|
+
errorMessage: error.message,
|
|
1064
|
+
delay,
|
|
1065
|
+
attemptNumber
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1070
|
+
function calculateDelay(attemptNumber, config) {
|
|
1071
|
+
const exponentialDelay = config.initialDelayMs * Math.pow(config.backoffMultiplier, attemptNumber);
|
|
1072
|
+
const cappedDelay = Math.min(exponentialDelay, config.maxDelayMs);
|
|
1073
|
+
if (!config.enableJitter) {
|
|
1074
|
+
return cappedDelay;
|
|
1075
|
+
}
|
|
1076
|
+
const jitter = 0.5 + Math.random() * 0.5;
|
|
1077
|
+
return Math.floor(cappedDelay * jitter);
|
|
1078
|
+
}
|
|
1079
|
+
var HttpWrapper = class {
|
|
1080
|
+
api;
|
|
1081
|
+
retryConfig;
|
|
1082
|
+
constructor(api, retryConfig = {}) {
|
|
1083
|
+
this.api = api;
|
|
1084
|
+
this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...retryConfig };
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Execute HTTP operation with retry logic and error handling
|
|
1088
|
+
*/
|
|
1089
|
+
async executeWithRetry(operation, operationName, context) {
|
|
1090
|
+
let lastError = null;
|
|
1091
|
+
const startTime = Date.now();
|
|
1092
|
+
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
|
|
1093
|
+
try {
|
|
1094
|
+
logger20.debug(`Executing ${operationName}`, {
|
|
1095
|
+
attempt: attempt + 1,
|
|
1096
|
+
maxRetries: this.retryConfig.maxRetries + 1,
|
|
1097
|
+
...context
|
|
1098
|
+
});
|
|
1099
|
+
const result = await operation();
|
|
1100
|
+
if (attempt > 0) {
|
|
1101
|
+
logger20.info(`${operationName} succeeded after ${attempt} retries`, {
|
|
1102
|
+
totalAttempts: attempt + 1,
|
|
1103
|
+
duration: Date.now() - startTime,
|
|
1104
|
+
...context
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
return result;
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
lastError = this.convertToClientApiError(error, operationName, context);
|
|
1110
|
+
if (attempt === this.retryConfig.maxRetries) {
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
if (!this.retryConfig.shouldRetry(lastError, attempt)) {
|
|
1114
|
+
logger20.debug(`Not retrying ${operationName} due to non-retryable error`, {
|
|
1115
|
+
errorCode: lastError.code,
|
|
1116
|
+
errorMessage: lastError.message,
|
|
1117
|
+
attempt: attempt + 1,
|
|
1118
|
+
...context
|
|
1119
|
+
});
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
let delay = calculateDelay(attempt, this.retryConfig);
|
|
1123
|
+
if (lastError instanceof RateLimitError && lastError.retryAfter) {
|
|
1124
|
+
delay = Math.max(delay, lastError.retryAfter * 1e3);
|
|
1125
|
+
}
|
|
1126
|
+
this.retryConfig.onRetry(lastError, attempt, delay);
|
|
1127
|
+
await sleep(delay);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
logger20.error(`${operationName} failed after ${this.retryConfig.maxRetries + 1} attempts`, {
|
|
1131
|
+
errorCode: lastError?.code,
|
|
1132
|
+
errorMessage: lastError?.message,
|
|
1133
|
+
duration: Date.now() - startTime,
|
|
1134
|
+
...context
|
|
1135
|
+
});
|
|
1136
|
+
throw lastError;
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Convert any error to a ClientApiError
|
|
1140
|
+
*/
|
|
1141
|
+
convertToClientApiError(error, operationName, context) {
|
|
1142
|
+
const errorContext = { operation: operationName, ...context };
|
|
1143
|
+
if (error instanceof ClientApiError) {
|
|
1144
|
+
return error;
|
|
1145
|
+
}
|
|
1146
|
+
if (error.response) {
|
|
1147
|
+
const { status, statusText, data, headers } = error.response;
|
|
1148
|
+
return createHttpError(status, statusText, data, {
|
|
1149
|
+
...errorContext,
|
|
1150
|
+
headers,
|
|
1151
|
+
url: error.config?.url
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
if (error.request) {
|
|
1155
|
+
return createNetworkError(error, {
|
|
1156
|
+
...errorContext,
|
|
1157
|
+
url: error.config?.url,
|
|
1158
|
+
method: error.config?.method
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
return createNetworkError(error, errorContext);
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Wrapper for HTTP GET operations
|
|
1165
|
+
*/
|
|
1166
|
+
async get(url, options, context) {
|
|
1167
|
+
return this.executeWithRetry(
|
|
1168
|
+
() => this.api.httpGet(url, options),
|
|
1169
|
+
"GET",
|
|
1170
|
+
{ url, ...context }
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Wrapper for HTTP POST operations
|
|
1175
|
+
*/
|
|
1176
|
+
async post(url, data, options, context) {
|
|
1177
|
+
return this.executeWithRetry(
|
|
1178
|
+
() => this.api.httpPost(url, data, options),
|
|
1179
|
+
"POST",
|
|
1180
|
+
{ url, hasData: !!data, ...context }
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Wrapper for HTTP PUT operations
|
|
1185
|
+
*/
|
|
1186
|
+
async put(url, data, options, context) {
|
|
1187
|
+
return this.executeWithRetry(
|
|
1188
|
+
() => this.api.httpPut(url, data, options),
|
|
1189
|
+
"PUT",
|
|
1190
|
+
{ url, hasData: !!data, ...context }
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Wrapper for HTTP DELETE operations
|
|
1195
|
+
*/
|
|
1196
|
+
async delete(url, options, context) {
|
|
1197
|
+
return this.executeWithRetry(
|
|
1198
|
+
() => this.api.httpDelete(url, options),
|
|
1199
|
+
"DELETE",
|
|
1200
|
+
{ url, ...context }
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Update retry configuration
|
|
1205
|
+
*/
|
|
1206
|
+
updateRetryConfig(newConfig) {
|
|
1207
|
+
Object.assign(this.retryConfig, newConfig);
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Get current retry configuration
|
|
1211
|
+
*/
|
|
1212
|
+
getRetryConfig() {
|
|
1213
|
+
return { ...this.retryConfig };
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
537
1216
|
export {
|
|
1217
|
+
AuthenticationError,
|
|
1218
|
+
AuthorizationError,
|
|
1219
|
+
ClientApiError,
|
|
1220
|
+
ConfigurationError,
|
|
1221
|
+
ConflictError,
|
|
1222
|
+
HttpError,
|
|
1223
|
+
HttpWrapper,
|
|
1224
|
+
NetworkError,
|
|
1225
|
+
NotFoundError,
|
|
1226
|
+
ParseError,
|
|
1227
|
+
PayloadTooLargeError,
|
|
1228
|
+
RateLimitError,
|
|
1229
|
+
ServerError,
|
|
1230
|
+
TimeoutError,
|
|
1231
|
+
ValidationError,
|
|
1232
|
+
calculateRetryDelay3 as calculateRetryDelay,
|
|
538
1233
|
createCItemApi,
|
|
1234
|
+
createHttpError,
|
|
539
1235
|
createInstance,
|
|
540
1236
|
createInstanceFactory,
|
|
1237
|
+
createNetworkError,
|
|
541
1238
|
createPItemApi,
|
|
542
1239
|
createRegistry,
|
|
543
|
-
createRegistryFactory
|
|
1240
|
+
createRegistryFactory,
|
|
1241
|
+
enhanceError3 as enhanceError,
|
|
1242
|
+
executeErrorHandler,
|
|
1243
|
+
executeWithRetry,
|
|
1244
|
+
getRetryConfig,
|
|
1245
|
+
isClientApiError,
|
|
1246
|
+
isRetryableError,
|
|
1247
|
+
shouldRetryError3 as shouldRetryError
|
|
544
1248
|
};
|
|
545
1249
|
//# sourceMappingURL=index.js.map
|