extractia-sdk 1.2.0 → 1.4.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.d.ts CHANGED
@@ -206,45 +206,94 @@ export interface OcrRunResult {
206
206
 
207
207
  /** Base error class for all Extractia SDK errors. */
208
208
  export class ExtractiaError extends Error {
209
- /** HTTP status code associated with the error. */
209
+ /** HTTP status code (0 = no response). */
210
210
  status: number;
211
- constructor(message: string, status: number);
211
+ /** Polished, user-facing English sentence always suitable for display. */
212
+ userMessage: string;
213
+ /** Machine-readable error code (e.g. "AUTH_ERROR", "QUOTA_EXCEEDED"). */
214
+ code: string;
215
+ /** X-Request-Id / X-Correlation-Id from the server response header, when present. */
216
+ requestId: string | null;
217
+ constructor(message: string, status?: number, userMessage?: string, code?: string);
218
+ /** Returns true if automatically retrying the same request may succeed. */
219
+ isRetryable(): boolean;
220
+ toJSON(): { name: string; code: string; status: number; message: string; userMessage: string; requestId: string | null };
212
221
  }
213
222
 
214
- /** Thrown when the API token is missing or invalid (HTTP 401). */
223
+ /** HTTP 401 token missing, expired, or malformed. */
215
224
  export class AuthError extends ExtractiaError {
216
225
  constructor(message?: string);
217
226
  }
218
227
 
219
- /** Thrown when the account lacks permission to perform the action (HTTP 403). */
228
+ /** HTTP 403 authenticated but lacking required permission. */
220
229
  export class ForbiddenError extends ExtractiaError {
221
230
  constructor(message?: string);
222
231
  }
223
232
 
224
- /** Thrown when the active plan does not allow the requested operation (HTTP 402). */
233
+ /** HTTP 402 plan tier does not include this feature. */
225
234
  export class TierError extends ExtractiaError {
226
235
  constructor(message?: string, status?: number);
227
236
  }
228
237
 
229
- /** Thrown when the API rate limit is exceeded (HTTP 429). */
230
- export class RateLimitError extends ExtractiaError {
231
- /** Seconds to wait before the next request. */
232
- retryAfter?: number;
238
+ /** HTTP 402 document processing quota exhausted for this billing period. */
239
+ export class QuotaError extends ExtractiaError {
233
240
  constructor(message?: string);
234
241
  }
235
242
 
236
- /** Thrown when a requested resource was not found (HTTP 404). */
243
+ /** HTTP 429 requests sent too fast. Check `retryAfter` for the suggested wait time. */
244
+ export class RateLimitError extends ExtractiaError {
245
+ /** Seconds to wait before retrying (from the Retry-After header), or null. */
246
+ retryAfter: number | null;
247
+ constructor(message?: string, retryAfter?: number | null);
248
+ isRetryable(): true;
249
+ }
250
+
251
+ /** HTTP 404 — the requested resource does not exist. */
237
252
  export class NotFoundError extends ExtractiaError {
238
253
  constructor(message?: string);
239
254
  }
240
255
 
256
+ /** HTTP 400 — request body / parameters failed server-side validation. */
257
+ export class ValidationError extends ExtractiaError {
258
+ /** Field-level errors, if the server provided them. */
259
+ fields: Record<string, string> | null;
260
+ constructor(message?: string, fields?: Record<string, string> | null);
261
+ }
262
+
263
+ /** HTTP 409 — a resource with the same unique identifier already exists. */
264
+ export class ConflictError extends ExtractiaError {
265
+ constructor(message?: string);
266
+ }
267
+
268
+ /** HTTP 5xx — unexpected server-side failure. Always retryable. */
269
+ export class ServerError extends ExtractiaError {
270
+ constructor(message?: string, status?: number);
271
+ isRetryable(): true;
272
+ }
273
+
274
+ /** No HTTP response — connection refused, DNS failure, etc. */
275
+ export class NetworkError extends ExtractiaError {
276
+ constructor(message?: string);
277
+ isRetryable(): true;
278
+ }
279
+
280
+ /** Request timed out before the server responded. */
281
+ export class TimeoutError extends ExtractiaError {
282
+ constructor(message?: string);
283
+ isRetryable(): true;
284
+ }
285
+
286
+ /** Maps an Axios error to the appropriate typed SDK error. */
287
+ export function mapAxiosError(err: unknown): ExtractiaError;
288
+
241
289
  // ─── Setup ────────────────────────────────────────────────────────────────────
242
290
 
243
291
  /**
244
292
  * Sets the Bearer API token used for all subsequent requests.
245
293
  * **Must be called before any SDK method.**
246
294
  *
247
- * @param token Your Extractia API token (from Account → API Tokens).
295
+ * @param token Your Extractia API key (from Account → API Tokens).
296
+ * @throws {Error} If the token is empty or not a string.
248
297
  *
249
298
  * @example
250
299
  * import { setToken } from 'extractia-sdk';
@@ -252,12 +301,59 @@ export class NotFoundError extends ExtractiaError {
252
301
  */
253
302
  export function setToken(token: string): void;
254
303
 
304
+ /** Returns the currently configured API token, or `null` if not set. */
305
+ export function getToken(): string | null;
306
+
307
+ /** Returns `true` if an API token has been set. */
308
+ export function hasToken(): boolean;
309
+
310
+ /** Clears the stored API token. Useful for logout flows or test teardown. */
311
+ export function clearToken(): void;
312
+
313
+ /** Returns a snapshot of the current SDK configuration (token excluded). */
314
+ export function getConfig(): {
315
+ baseURL: string;
316
+ timeout: number;
317
+ retries: number;
318
+ retryDelay: number;
319
+ debug: boolean;
320
+ defaultHeaders: Record<string, string>;
321
+ onBeforeRequest: Function | null;
322
+ onAfterResponse: Function | null;
323
+ onError: Function | null;
324
+ };
325
+
255
326
  /**
256
- * Configures SDK options.
257
- * @param opts
258
- * @param [opts.baseURL] Override the default API base URL (useful for self-hosted instances).
327
+ * Configures SDK behaviour. All properties are optional.
328
+ *
329
+ * @example
330
+ * configure({
331
+ * timeout: 30_000,
332
+ * retries: 2,
333
+ * debug: true,
334
+ * onError: (err) => sentryCapture(err),
335
+ * });
259
336
  */
260
- export function configure(opts: { baseURL?: string }): void;
337
+ export function configure(opts: {
338
+ /** Override the API base URL (useful for staging or proxies). */
339
+ baseURL?: string;
340
+ /** Request timeout in milliseconds. Default: 60 000. */
341
+ timeout?: number;
342
+ /** Automatic retries on retryable errors (429 / 5xx / network). Default: 1. */
343
+ retries?: number;
344
+ /** Base delay (ms) between retries; multiplied by attempt number. Default: 1 000. */
345
+ retryDelay?: number;
346
+ /** Log requests / responses / retries to console.debug. Default: false. */
347
+ debug?: boolean;
348
+ /** Extra headers merged into every request. */
349
+ defaultHeaders?: Record<string, string>;
350
+ /** Hook called before each request. Return a modified config or void. */
351
+ onBeforeRequest?: (config: unknown) => unknown | void;
352
+ /** Hook called after each successful response. */
353
+ onAfterResponse?: (response: unknown) => void;
354
+ /** Hook called with the mapped SDK error whenever a request fails (after retries). */
355
+ onError?: (error: ExtractiaError) => void;
356
+ }): void;
261
357
 
262
358
  // ─── Auth / Profile ───────────────────────────────────────────────────────────
263
359
 
@@ -694,11 +790,87 @@ export function toggleSuspendSubUser(username: string): Promise<{ username: stri
694
790
 
695
791
  // ─── Default export ───────────────────────────────────────────────────────────
696
792
 
793
+ // ─── Utilities ───────────────────────────────────────────────────────────────
794
+
795
+ /**
796
+ * Converts a browser `File` or `Blob` to a base64 data-URL string.
797
+ * @throws {Error} In Node.js environments where `FileReader` is unavailable.
798
+ */
799
+ export function fileToBase64(file: File | Blob): Promise<string>;
800
+
801
+ /**
802
+ * Strips the `data:…;base64,` prefix from a base64 string.
803
+ * Safe to call on a plain base64 string — returns it unchanged.
804
+ */
805
+ export function stripDataUrlPrefix(base64OrDataUrl: string): string;
806
+
807
+ /**
808
+ * Returns the MIME type embedded in a data-URL prefix, or `null`.
809
+ * @example getMimeType('data:image/png;base64,...') // → 'image/png'
810
+ */
811
+ export function getMimeType(dataUrl: string): string | null;
812
+
813
+ /**
814
+ * Returns `true` if the string is a valid base64-encoded value
815
+ * (with or without a data-URL prefix).
816
+ */
817
+ export function isBase64(str: string): boolean;
818
+
819
+ /**
820
+ * Accepts a `File`, `Blob`, or base64 string and always resolves to a
821
+ * base64 string — the data-URL for files and the string as-is otherwise.
822
+ */
823
+ export function ensureBase64(fileOrBase64: File | Blob | string): Promise<string>;
824
+
825
+ /**
826
+ * Async generator that yields individual items across all pages of a
827
+ * paginated SDK function.
828
+ *
829
+ * @example
830
+ * for await (const entry of paginate(getCreditsHistory, { size: 100 })) {
831
+ * console.log(entry);
832
+ * }
833
+ */
834
+ export function paginate<T>(
835
+ fn: (opts: { page: number; size: number }) => Promise<{ content: T[]; totalPages: number }>,
836
+ opts?: { size?: number; startPage?: number; maxPages?: number }
837
+ ): AsyncGenerator<T>;
838
+
839
+ /**
840
+ * Collects all pages of a paginated SDK function into a flat array.
841
+ */
842
+ export function paginateAll<T>(
843
+ fn: (opts: { page: number; size: number }) => Promise<{ content: T[]; totalPages: number }>,
844
+ opts?: { size?: number; startPage?: number; maxPages?: number }
845
+ ): Promise<T[]>;
846
+
847
+ /** Returns a Promise that resolves after `ms` milliseconds. */
848
+ export function delay(ms: number): Promise<void>;
849
+
850
+ /**
851
+ * Calls an async function and retries it with exponential back-off on
852
+ * retryable `ExtractiaError`s.
853
+ */
854
+ export function withRetry<T>(
855
+ fn: () => Promise<T>,
856
+ opts?: {
857
+ retries?: number;
858
+ initialDelay?: number;
859
+ shouldRetry?: (err: Error) => boolean;
860
+ }
861
+ ): Promise<T>;
862
+
863
+ // ─── Default export ───────────────────────────────────────────────────────────
864
+
697
865
  /** Namespace object bundling all SDK methods for UMD / CommonJS consumers. */
698
866
  declare const extractia: {
699
867
  // Setup
700
868
  setToken: typeof setToken;
869
+ getToken: typeof getToken;
870
+ hasToken: typeof hasToken;
871
+ clearToken: typeof clearToken;
701
872
  configure: typeof configure;
873
+ getConfig: typeof getConfig;
702
874
  // Auth
703
875
  getMyProfile: typeof getMyProfile;
704
876
  updateWebhook: typeof updateWebhook;
@@ -741,6 +913,16 @@ declare const extractia: {
741
913
  deleteSubUser: typeof deleteSubUser;
742
914
  updateSubUser: typeof updateSubUser;
743
915
  toggleSuspendSubUser: typeof toggleSuspendSubUser;
916
+ // Utilities
917
+ fileToBase64: typeof fileToBase64;
918
+ stripDataUrlPrefix: typeof stripDataUrlPrefix;
919
+ getMimeType: typeof getMimeType;
920
+ isBase64: typeof isBase64;
921
+ ensureBase64: typeof ensureBase64;
922
+ paginate: typeof paginate;
923
+ paginateAll: typeof paginateAll;
924
+ delay: typeof delay;
925
+ withRetry: typeof withRetry;
744
926
  };
745
927
 
746
928
  export default extractia;
package/package.json CHANGED
@@ -1,10 +1,19 @@
1
1
  {
2
2
  "name": "extractia-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "JavaScript SDK for the ExtractIA API — document extraction, OCR tools, AI summaries, templates & more",
5
5
  "type": "module",
6
6
  "author": "ExtractIA Team",
7
7
  "license": "MIT",
8
+ "homepage": "https://www.extractia.info/docs/sdk",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/extractia/extractia-sdk"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/extractia/extractia-sdk/issues",
15
+ "email": "support@extractia.info"
16
+ },
8
17
  "keywords": [
9
18
  "sdk",
10
19
  "extractia",
@@ -14,7 +23,10 @@
14
23
  "client",
15
24
  "document-extraction",
16
25
  "ai",
17
- "gemini"
26
+ "gemini",
27
+ "document-ai",
28
+ "typescript",
29
+ "data-extraction"
18
30
  ],
19
31
  "main": "dist/extractia-sdk.cjs.js",
20
32
  "module": "dist/extractia-sdk.esm.js",
@@ -34,12 +46,18 @@
34
46
  },
35
47
  "scripts": {
36
48
  "build": "node build.js",
37
- "test": "echo \"Error: no test specified\" && exit 1"
49
+ "test": "vitest run",
50
+ "test:watch": "vitest",
51
+ "test:coverage": "vitest run --coverage",
52
+ "test:ui": "vitest --ui"
38
53
  },
39
54
  "dependencies": {
40
55
  "axios": "^1.10.0"
41
56
  },
42
57
  "devDependencies": {
43
- "esbuild": "^0.25.6"
58
+ "@vitest/coverage-v8": "^2.1.9",
59
+ "axios-mock-adapter": "^2.1.0",
60
+ "esbuild": "^0.25.6",
61
+ "vitest": "^2.1.9"
44
62
  }
45
63
  }
package/src/apiClient.js CHANGED
@@ -1,46 +1,212 @@
1
1
  import axios from "axios";
2
- import { mapAxiosError } from "./errors.js";
2
+ import {
3
+ mapAxiosError,
4
+ ExtractiaError,
5
+ NetworkError,
6
+ TimeoutError,
7
+ } from "./errors.js";
3
8
 
4
- let token = null;
9
+ // ─── Internal state ───────────────────────────────────────────────────────────
5
10
 
6
- const DEFAULT_BASE_URL = "https://api.extractia.info/api/public";
11
+ let _token = null;
7
12
 
8
- const api = axios.create({
9
- baseURL: DEFAULT_BASE_URL,
10
- timeout: 60000, // 60s — AI processing can take 10–30s
13
+ /** @type {SDKConfig} */
14
+ const _config = {
15
+ baseURL: "https://api.extractia.info/api/public",
16
+ timeout: 60_000,
17
+ retries: 1,
18
+ retryDelay: 1_000,
19
+ debug: false,
20
+ defaultHeaders: {},
21
+ onBeforeRequest: null,
22
+ onAfterResponse: null,
23
+ onError: null,
24
+ };
25
+
26
+ // ─── Axios instance ───────────────────────────────────────────────────────────
27
+
28
+ export const _api = axios.create({
29
+ baseURL: _config.baseURL,
30
+ timeout: _config.timeout,
11
31
  });
12
32
 
13
- api.interceptors.request.use((config) => {
14
- if (!token) {
15
- throw new Error(
16
- "API token is required. Call setToken(yourApiToken) before making requests.",
33
+ // ── Request interceptor ──────────────────────────────────────────────────────
34
+ _api.interceptors.request.use((config) => {
35
+ if (!_token) {
36
+ const err = new ExtractiaError(
37
+ "API token not set. Call setToken(token) before making requests.",
38
+ 0,
39
+ "API token not set. Call setToken(token) before making requests.",
40
+ "TOKEN_MISSING",
41
+ );
42
+ return Promise.reject(err);
43
+ }
44
+
45
+ config.headers = config.headers ?? {};
46
+ config.headers["Authorization"] = `Bearer ${_token}`;
47
+
48
+ // Apply default headers
49
+ Object.assign(config.headers, _config.defaultHeaders);
50
+
51
+ if (_config.debug) {
52
+ console.debug(
53
+ `[ExtractIA SDK] → ${(config.method ?? "GET").toUpperCase()} ${config.baseURL ?? ""}${config.url ?? ""}`,
54
+ config.params ?? "",
17
55
  );
18
56
  }
19
- config.headers.Authorization = `Bearer ${token}`;
57
+
58
+ if (typeof _config.onBeforeRequest === "function") {
59
+ const modified = _config.onBeforeRequest(config);
60
+ return modified ?? config;
61
+ }
62
+
20
63
  return config;
21
64
  });
22
65
 
23
- api.interceptors.response.use(
24
- (response) => response,
25
- (err) => Promise.reject(mapAxiosError(err)),
66
+ // ── Response interceptor ─────────────────────────────────────────────────────
67
+ _api.interceptors.response.use(
68
+ (response) => {
69
+ if (_config.debug) {
70
+ console.debug(
71
+ `[ExtractIA SDK] ← ${response.status} ${response.config?.url ?? ""}`,
72
+ );
73
+ }
74
+ if (typeof _config.onAfterResponse === "function") {
75
+ _config.onAfterResponse(response);
76
+ }
77
+ return response;
78
+ },
79
+ async (err) => {
80
+ // If already a typed ExtractiaError (e.g. from request interceptor), pass through
81
+ if (err instanceof ExtractiaError) {
82
+ if (typeof _config.onError === "function") _config.onError(err);
83
+ return Promise.reject(err);
84
+ }
85
+
86
+ const mapped = mapAxiosError(err);
87
+ const cfg = err.config;
88
+
89
+ // Automatic retry for retryable errors (429 / 5xx / network / timeout)
90
+ if (
91
+ cfg &&
92
+ mapped.isRetryable() &&
93
+ (cfg._retryCount ?? 0) < _config.retries
94
+ ) {
95
+ cfg._retryCount = (cfg._retryCount ?? 0) + 1;
96
+
97
+ // Honour Retry-After header for rate limits, otherwise exponential back-off
98
+ const delayMs =
99
+ mapped.retryAfter != null
100
+ ? mapped.retryAfter * 1_000
101
+ : _config.retryDelay * cfg._retryCount;
102
+
103
+ if (_config.debug) {
104
+ console.debug(
105
+ `[ExtractIA SDK] retrying (${cfg._retryCount}/${_config.retries}) in ${delayMs}ms…`,
106
+ );
107
+ }
108
+
109
+ await new Promise((r) => setTimeout(r, delayMs));
110
+ return _api(cfg);
111
+ }
112
+
113
+ if (typeof _config.onError === "function") _config.onError(mapped);
114
+ return Promise.reject(mapped);
115
+ },
26
116
  );
27
117
 
118
+ // ─── Public API ───────────────────────────────────────────────────────────────
119
+
28
120
  /**
29
121
  * Sets the API token used for all subsequent requests.
30
122
  * Must be called before any SDK method.
31
- * @param {string} newToken - Your Extractia API token.
123
+ *
124
+ * @param {string} token - Your Extractia API key.
125
+ * @throws {Error} If the token is empty or not a string.
32
126
  */
33
- export function setToken(newToken) {
34
- token = newToken;
127
+ export function setToken(token) {
128
+ if (!token || typeof token !== "string" || !token.trim()) {
129
+ throw new Error("setToken: token must be a non-empty string.");
130
+ }
131
+ _token = token.trim();
132
+ }
133
+
134
+ /**
135
+ * Returns the currently configured API token, or `null` if not set.
136
+ * @returns {string|null}
137
+ */
138
+ export function getToken() {
139
+ return _token;
140
+ }
141
+
142
+ /**
143
+ * Returns `true` if an API token has been set.
144
+ * @returns {boolean}
145
+ */
146
+ export function hasToken() {
147
+ return Boolean(_token);
148
+ }
149
+
150
+ /**
151
+ * Clears the stored API token.
152
+ * Useful for logout flows or test teardown.
153
+ */
154
+ export function clearToken() {
155
+ _token = null;
156
+ }
157
+
158
+ /**
159
+ * Configures SDK behaviour. All properties are optional; unspecified properties
160
+ * retain their current value.
161
+ *
162
+ * @param {object} opts
163
+ * @param {string} [opts.baseURL]
164
+ * Override the API base URL (useful for staging environments or proxies).
165
+ * @param {number} [opts.timeout]
166
+ * Request timeout in milliseconds. Default: 60 000 (60 s).
167
+ * @param {number} [opts.retries]
168
+ * Number of automatic retries on retryable errors (429 / 5xx / network). Default: 1.
169
+ * @param {number} [opts.retryDelay]
170
+ * Base delay (ms) between retries; multiplied by the attempt number. Default: 1 000.
171
+ * @param {boolean} [opts.debug]
172
+ * Log request/response/retry info to `console.debug`. Default: false.
173
+ * @param {Record<string,string>} [opts.defaultHeaders]
174
+ * Extra HTTP headers merged into every request (e.g. `{ "X-App-Version": "2.0" }`).
175
+ * @param {(config: import('axios').InternalAxiosRequestConfig) => import('axios').InternalAxiosRequestConfig|void} [opts.onBeforeRequest]
176
+ * Hook called with the Axios request config before each request.
177
+ * Return a modified config or `void` to keep the original.
178
+ * @param {(response: import('axios').AxiosResponse) => void} [opts.onAfterResponse]
179
+ * Hook called after each successful response. Useful for logging or metrics.
180
+ * @param {(error: import('./errors.js').ExtractiaError) => void} [opts.onError]
181
+ * Hook called with the mapped SDK error whenever a request fails.
182
+ * Called after retries are exhausted.
183
+ */
184
+ export function configure(opts = {}) {
185
+ if (opts.baseURL) {
186
+ _config.baseURL = opts.baseURL;
187
+ _api.defaults.baseURL = opts.baseURL;
188
+ }
189
+ if (opts.timeout != null) {
190
+ _config.timeout = opts.timeout;
191
+ _api.defaults.timeout = opts.timeout;
192
+ }
193
+ if (opts.retries != null) _config.retries = opts.retries;
194
+ if (opts.retryDelay != null) _config.retryDelay = opts.retryDelay;
195
+ if (opts.debug != null) _config.debug = Boolean(opts.debug);
196
+ if (opts.defaultHeaders) {
197
+ _config.defaultHeaders = { ..._config.defaultHeaders, ...opts.defaultHeaders };
198
+ }
199
+ if (opts.onBeforeRequest) _config.onBeforeRequest = opts.onBeforeRequest;
200
+ if (opts.onAfterResponse) _config.onAfterResponse = opts.onAfterResponse;
201
+ if (opts.onError) _config.onError = opts.onError;
35
202
  }
36
203
 
37
204
  /**
38
- * Configures SDK options.
39
- * @param {object} opts
40
- * @param {string} [opts.baseURL] - Override the default API base URL.
205
+ * Returns a snapshot of the current SDK configuration (token excluded).
206
+ * @returns {Readonly<typeof _config>}
41
207
  */
42
- export function configure({ baseURL } = {}) {
43
- if (baseURL) api.defaults.baseURL = baseURL;
208
+ export function getConfig() {
209
+ return { ..._config };
44
210
  }
45
211
 
46
- export default api;
212
+ export default _api;
@@ -3,6 +3,23 @@ import * as templates from "./templates.js";
3
3
  import * as documents from "./documents.js";
4
4
  import * as analytics from "./analytics.js";
5
5
  import * as ocrTools from "./ocrTools.js";
6
+ import * as subusers from "./subusers.js";
7
+ import * as utils from "./utils.js";
8
+ import { getToken, hasToken, clearToken, getConfig } from "./apiClient.js";
9
+ import {
10
+ ExtractiaError,
11
+ AuthError,
12
+ ForbiddenError,
13
+ TierError,
14
+ QuotaError,
15
+ RateLimitError,
16
+ NotFoundError,
17
+ ValidationError,
18
+ ConflictError,
19
+ ServerError,
20
+ NetworkError,
21
+ TimeoutError,
22
+ } from "./errors.js";
6
23
 
7
24
  const extractia = {
8
25
  ...auth,
@@ -10,6 +27,24 @@ const extractia = {
10
27
  ...documents,
11
28
  ...analytics,
12
29
  ...ocrTools,
30
+ ...subusers,
31
+ ...utils,
32
+ getToken,
33
+ hasToken,
34
+ clearToken,
35
+ getConfig,
36
+ ExtractiaError,
37
+ AuthError,
38
+ ForbiddenError,
39
+ TierError,
40
+ QuotaError,
41
+ RateLimitError,
42
+ NotFoundError,
43
+ ValidationError,
44
+ ConflictError,
45
+ ServerError,
46
+ NetworkError,
47
+ TimeoutError,
13
48
  };
14
49
 
15
50
  export default extractia;