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/README.md +14 -14
- package/dist/extractia-sdk.browser.js +516 -82
- package/dist/extractia-sdk.cjs.js +499 -87
- package/dist/extractia-sdk.esm.js +499 -87
- package/dist/index.d.ts +197 -15
- package/package.json +22 -4
- package/src/apiClient.js +189 -23
- package/src/browser-entry.js +35 -0
- package/src/errors.js +278 -40
- package/src/index.d.ts +197 -15
- package/src/index.js +21 -0
- package/src/subusers.js +40 -16
- package/src/utils.js +219 -0
- package/dist/extractia-sdk.browser.js.map +0 -7
- package/dist/extractia-sdk.cjs.js.map +0 -7
- package/dist/extractia-sdk.esm.js.map +0 -7
- package/dist/extractia-sdk.js.map +0 -7
package/src/index.js
CHANGED
|
@@ -4,14 +4,28 @@ export * from "./documents.js";
|
|
|
4
4
|
export * from "./analytics.js";
|
|
5
5
|
export * from "./ocrTools.js";
|
|
6
6
|
export * from "./subusers.js";
|
|
7
|
+
export * from "./utils.js";
|
|
7
8
|
export {
|
|
8
9
|
ExtractiaError,
|
|
9
10
|
AuthError,
|
|
10
11
|
ForbiddenError,
|
|
11
12
|
TierError,
|
|
13
|
+
QuotaError,
|
|
12
14
|
RateLimitError,
|
|
13
15
|
NotFoundError,
|
|
16
|
+
ValidationError,
|
|
17
|
+
ConflictError,
|
|
18
|
+
ServerError,
|
|
19
|
+
NetworkError,
|
|
20
|
+
TimeoutError,
|
|
21
|
+
mapAxiosError,
|
|
14
22
|
} from "./errors.js";
|
|
23
|
+
export {
|
|
24
|
+
getToken,
|
|
25
|
+
hasToken,
|
|
26
|
+
clearToken,
|
|
27
|
+
getConfig,
|
|
28
|
+
} from "./apiClient.js";
|
|
15
29
|
|
|
16
30
|
import * as auth from "./auth.js";
|
|
17
31
|
import * as templates from "./templates.js";
|
|
@@ -19,6 +33,8 @@ import * as documents from "./documents.js";
|
|
|
19
33
|
import * as analytics from "./analytics.js";
|
|
20
34
|
import * as ocrTools from "./ocrTools.js";
|
|
21
35
|
import * as subusers from "./subusers.js";
|
|
36
|
+
import * as utils from "./utils.js";
|
|
37
|
+
import { getToken, hasToken, clearToken, getConfig } from "./apiClient.js";
|
|
22
38
|
|
|
23
39
|
const extractia = {
|
|
24
40
|
...auth,
|
|
@@ -27,6 +43,11 @@ const extractia = {
|
|
|
27
43
|
...analytics,
|
|
28
44
|
...ocrTools,
|
|
29
45
|
...subusers,
|
|
46
|
+
...utils,
|
|
47
|
+
getToken,
|
|
48
|
+
hasToken,
|
|
49
|
+
clearToken,
|
|
50
|
+
getConfig,
|
|
30
51
|
};
|
|
31
52
|
|
|
32
53
|
export default extractia;
|
package/src/subusers.js
CHANGED
|
@@ -11,6 +11,7 @@ import api from "./apiClient.js";
|
|
|
11
11
|
* @returns {Promise<Array<{
|
|
12
12
|
* username: string,
|
|
13
13
|
* permissions: string[],
|
|
14
|
+
* allowedFormIds: string[] | null,
|
|
14
15
|
* suspended: boolean,
|
|
15
16
|
* lastKnownLocation?: string,
|
|
16
17
|
* lastLocationAt?: string
|
|
@@ -25,24 +26,40 @@ export async function getSubUsers() {
|
|
|
25
26
|
* Creates a new sub-user for the authenticated account.
|
|
26
27
|
*
|
|
27
28
|
* Available permissions: `"upload"`, `"view"`, `"template"`, `"settings"`,
|
|
28
|
-
* `"export"`, `"
|
|
29
|
+
* `"export"`, `"ocr_tools"`, `"gallery"`, `"smart_scan"`, `"ia_agent"`.
|
|
29
30
|
*
|
|
30
|
-
* @param {Object}
|
|
31
|
-
* @param {string}
|
|
32
|
-
* @param {string}
|
|
33
|
-
* @param {string[]}
|
|
34
|
-
* @
|
|
31
|
+
* @param {Object} subUser - Sub-user definition.
|
|
32
|
+
* @param {string} subUser.username - Unique username (must not be an existing account email).
|
|
33
|
+
* @param {string} subUser.password - Initial password (must differ from the owner's password).
|
|
34
|
+
* @param {string[]} subUser.permissions - List of permission strings to grant.
|
|
35
|
+
* @param {string[]?} subUser.allowedFormIds - Optional list of form template IDs this sub-user may access.
|
|
36
|
+
* When omitted or null, the sub-user can access all forms.
|
|
37
|
+
* @returns {Promise<{ username: string, permissions: string[], allowedFormIds?: string[] }>} Confirmation object.
|
|
35
38
|
* @throws {ForbiddenError} 403 if the plan does not allow sub-users or the limit is reached.
|
|
36
39
|
* @throws {ExtractiaError} 409 if the username is already taken.
|
|
37
40
|
*/
|
|
38
|
-
export async function createSubUser({
|
|
39
|
-
|
|
41
|
+
export async function createSubUser({
|
|
42
|
+
username,
|
|
43
|
+
password,
|
|
44
|
+
permissions,
|
|
45
|
+
allowedFormIds,
|
|
46
|
+
}) {
|
|
47
|
+
const res = await api.post("/me/subusers", {
|
|
48
|
+
username,
|
|
49
|
+
password,
|
|
50
|
+
permissions,
|
|
51
|
+
...(allowedFormIds !== undefined ? { allowedFormIds } : {}),
|
|
52
|
+
});
|
|
40
53
|
return res.data;
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
/**
|
|
44
57
|
* Deletes a sub-user by username.
|
|
45
58
|
*
|
|
59
|
+
* @note The deleted sub-user's JWT token is immediately rejected by the server
|
|
60
|
+
* on every subsequent request — no grace period. Token revocation happens
|
|
61
|
+
* automatically via the authentication filter.
|
|
62
|
+
*
|
|
46
63
|
* @param {string} username - The sub-user's username.
|
|
47
64
|
* @returns {Promise<{ deleted: string }>} Confirmation with the deleted username.
|
|
48
65
|
* @throws {NotFoundError} 404 if the sub-user does not exist.
|
|
@@ -53,19 +70,24 @@ export async function deleteSubUser(username) {
|
|
|
53
70
|
}
|
|
54
71
|
|
|
55
72
|
/**
|
|
56
|
-
* Updates the permissions and/or password of a sub-user.
|
|
73
|
+
* Updates the permissions, allowed forms, and/or password of a sub-user.
|
|
57
74
|
*
|
|
58
|
-
* Pass only the fields you want to change. Omitting
|
|
75
|
+
* Pass only the fields you want to change. Omitting a field leaves it unchanged.
|
|
59
76
|
*
|
|
60
|
-
* @param {string} username
|
|
61
|
-
* @param {Object} updates
|
|
62
|
-
* @param {string[]} [updates.permissions]
|
|
63
|
-
* @param {string}
|
|
77
|
+
* @param {string} username - The sub-user's username.
|
|
78
|
+
* @param {Object} updates - Fields to update.
|
|
79
|
+
* @param {string[]} [updates.permissions] - New permission set (replaces existing).
|
|
80
|
+
* @param {string[]?} [updates.allowedFormIds] - New allowed form ID list (replaces existing).
|
|
81
|
+
* Pass an empty array to remove all restrictions.
|
|
82
|
+
* @param {string} [updates.password] - New password (must differ from owner's password).
|
|
64
83
|
* @returns {Promise<{ username: string, updated: boolean }>}
|
|
65
84
|
* @throws {NotFoundError} 404 if the sub-user does not exist.
|
|
66
85
|
*/
|
|
67
86
|
export async function updateSubUser(username, updates = {}) {
|
|
68
|
-
const res = await api.put(
|
|
87
|
+
const res = await api.put(
|
|
88
|
+
`/me/subusers/${encodeURIComponent(username)}`,
|
|
89
|
+
updates,
|
|
90
|
+
);
|
|
69
91
|
return res.data;
|
|
70
92
|
}
|
|
71
93
|
|
|
@@ -80,6 +102,8 @@ export async function updateSubUser(username, updates = {}) {
|
|
|
80
102
|
* @throws {NotFoundError} 404 if the sub-user does not exist.
|
|
81
103
|
*/
|
|
82
104
|
export async function toggleSuspendSubUser(username) {
|
|
83
|
-
const res = await api.put(
|
|
105
|
+
const res = await api.put(
|
|
106
|
+
`/me/subusers/${encodeURIComponent(username)}/suspend`,
|
|
107
|
+
);
|
|
84
108
|
return res.data;
|
|
85
109
|
}
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExtractIA SDK — utility helpers
|
|
3
|
+
*
|
|
4
|
+
* All helpers are pure / side-effect-free and safe to import in both
|
|
5
|
+
* browser and Node.js environments (FileReader is guarded).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ─── Base64 helpers ───────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Converts a browser `File` (or `Blob`) to a base64 data-URL string.
|
|
12
|
+
*
|
|
13
|
+
* The returned string includes the MIME prefix, e.g.:
|
|
14
|
+
* `data:image/jpeg;base64,/9j/4AAQ…`
|
|
15
|
+
*
|
|
16
|
+
* @param {File|Blob} file
|
|
17
|
+
* @returns {Promise<string>} Base64 data-URL.
|
|
18
|
+
* @throws {Error} In non-browser environments where `FileReader` is unavailable.
|
|
19
|
+
*/
|
|
20
|
+
export function fileToBase64(file) {
|
|
21
|
+
if (typeof FileReader === "undefined") {
|
|
22
|
+
return Promise.reject(
|
|
23
|
+
new Error(
|
|
24
|
+
"fileToBase64: FileReader is not available in this environment. " +
|
|
25
|
+
"In Node.js, read the file manually and encode it with Buffer.from(data).toString('base64').",
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const reader = new FileReader();
|
|
31
|
+
reader.onload = () => resolve(/** @type {string} */ (reader.result));
|
|
32
|
+
reader.onerror = () =>
|
|
33
|
+
reject(new Error(`fileToBase64: failed to read file "${file.name ?? "unknown"}".`));
|
|
34
|
+
reader.readAsDataURL(file);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Strips the data-URL prefix from a base64 string.
|
|
40
|
+
* If the string is already a raw base64 value, it is returned unchanged.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} base64OrDataUrl — `"data:image/jpeg;base64,ABC…"` or `"ABC…"`.
|
|
43
|
+
* @returns {string} Raw base64 characters without the `data:…;base64,` prefix.
|
|
44
|
+
* @throws {TypeError} If the argument is not a string.
|
|
45
|
+
*/
|
|
46
|
+
export function stripDataUrlPrefix(base64OrDataUrl) {
|
|
47
|
+
if (typeof base64OrDataUrl !== "string") {
|
|
48
|
+
throw new TypeError(
|
|
49
|
+
"stripDataUrlPrefix: argument must be a string.",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const idx = base64OrDataUrl.indexOf(";base64,");
|
|
53
|
+
return idx !== -1 ? base64OrDataUrl.slice(idx + 8) : base64OrDataUrl;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns the MIME type embedded in a data-URL prefix, or `null`.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} dataUrl — e.g. `"data:image/png;base64,…"`.
|
|
60
|
+
* @returns {string|null}
|
|
61
|
+
*/
|
|
62
|
+
export function getMimeType(dataUrl) {
|
|
63
|
+
if (typeof dataUrl !== "string") return null;
|
|
64
|
+
const match = dataUrl.match(/^data:([^;]+);base64,/);
|
|
65
|
+
return match ? match[1] : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Returns `true` if the string is a valid base64-encoded value
|
|
70
|
+
* (with or without a data-URL prefix).
|
|
71
|
+
*
|
|
72
|
+
* @param {string} str
|
|
73
|
+
* @returns {boolean}
|
|
74
|
+
*/
|
|
75
|
+
export function isBase64(str) {
|
|
76
|
+
if (typeof str !== "string" || !str.trim()) return false;
|
|
77
|
+
const raw = stripDataUrlPrefix(str);
|
|
78
|
+
// Must be divisible by 4 and only contain valid base64 characters
|
|
79
|
+
return raw.length > 0 && raw.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(raw);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Accepts either a browser `File` / `Blob` or a base64 string and
|
|
84
|
+
* always resolves to a base64 string (data-URL for files, unchanged for strings).
|
|
85
|
+
*
|
|
86
|
+
* Useful for SDK functions that accept both `File` inputs (browser) and
|
|
87
|
+
* pre-encoded base64 strings (Node.js / server-side).
|
|
88
|
+
*
|
|
89
|
+
* @param {File|Blob|string} fileOrBase64
|
|
90
|
+
* @returns {Promise<string>}
|
|
91
|
+
* @throws {TypeError} If the argument is neither a string nor a File/Blob.
|
|
92
|
+
*/
|
|
93
|
+
export async function ensureBase64(fileOrBase64) {
|
|
94
|
+
if (typeof fileOrBase64 === "string") return fileOrBase64;
|
|
95
|
+
if (
|
|
96
|
+
(typeof File !== "undefined" && fileOrBase64 instanceof File) ||
|
|
97
|
+
(typeof Blob !== "undefined" && fileOrBase64 instanceof Blob)
|
|
98
|
+
) {
|
|
99
|
+
return fileToBase64(fileOrBase64);
|
|
100
|
+
}
|
|
101
|
+
throw new TypeError(
|
|
102
|
+
"ensureBase64: argument must be a File, Blob, or a base64 string.",
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── Pagination helpers ───────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Async generator that wraps any paginated SDK function following the Spring
|
|
110
|
+
* `Page` contract (`{ content: T[], totalPages: number }`) and yields
|
|
111
|
+
* individual items across all pages.
|
|
112
|
+
*
|
|
113
|
+
* @template T
|
|
114
|
+
* @param {(opts: { page: number; size: number }) => Promise<{ content: T[]; totalPages: number }>} fn
|
|
115
|
+
* A paginated SDK function (e.g. `getCreditsHistory`, `getDocumentHistory`).
|
|
116
|
+
* @param {object} [opts]
|
|
117
|
+
* @param {number} [opts.size=50] — Items per request (max depends on endpoint).
|
|
118
|
+
* @param {number} [opts.startPage=0] — Zero-based page index to start from.
|
|
119
|
+
* @param {number} [opts.maxPages=1000] — Safety ceiling: stop after this many pages.
|
|
120
|
+
* @yields {T}
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* for await (const entry of paginate(getCreditsHistory, { size: 100 })) {
|
|
124
|
+
* console.log(entry.creditsConsumed);
|
|
125
|
+
* }
|
|
126
|
+
*/
|
|
127
|
+
export async function* paginate(
|
|
128
|
+
fn,
|
|
129
|
+
{ size = 50, startPage = 0, maxPages = 1_000 } = {},
|
|
130
|
+
) {
|
|
131
|
+
let page = startPage;
|
|
132
|
+
let pagesRead = 0;
|
|
133
|
+
|
|
134
|
+
while (pagesRead < maxPages) {
|
|
135
|
+
const result = await fn({ page, size });
|
|
136
|
+
const items = Array.isArray(result?.content) ? result.content : [];
|
|
137
|
+
|
|
138
|
+
for (const item of items) yield item;
|
|
139
|
+
|
|
140
|
+
const totalPages = result?.totalPages ?? 1;
|
|
141
|
+
if (items.length === 0 || page + 1 >= totalPages) break;
|
|
142
|
+
|
|
143
|
+
page++;
|
|
144
|
+
pagesRead++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Collects all pages of a paginated SDK function into a flat array.
|
|
150
|
+
*
|
|
151
|
+
* @template T
|
|
152
|
+
* @param {(opts: { page: number; size: number }) => Promise<{ content: T[]; totalPages: number }>} fn
|
|
153
|
+
* @param {Parameters<typeof paginate>[1]} [opts]
|
|
154
|
+
* @returns {Promise<T[]>}
|
|
155
|
+
*/
|
|
156
|
+
export async function paginateAll(fn, opts) {
|
|
157
|
+
const items = [];
|
|
158
|
+
for await (const item of paginate(fn, opts)) items.push(item);
|
|
159
|
+
return items;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ─── Async helpers ────────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Returns a Promise that resolves after `ms` milliseconds.
|
|
166
|
+
*
|
|
167
|
+
* @param {number} ms
|
|
168
|
+
* @returns {Promise<void>}
|
|
169
|
+
*/
|
|
170
|
+
export function delay(ms) {
|
|
171
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Calls an async function and retries it with exponential back-off when it
|
|
176
|
+
* throws a retryable `ExtractiaError` (i.e. `error.isRetryable()` returns `true`).
|
|
177
|
+
*
|
|
178
|
+
* This is useful when you have disabled the SDK's built-in automatic retry
|
|
179
|
+
* (`configure({ retries: 0 })`) and want finer control per call.
|
|
180
|
+
*
|
|
181
|
+
* @template T
|
|
182
|
+
* @param {() => Promise<T>} fn — Async function to call.
|
|
183
|
+
* @param {object} [opts]
|
|
184
|
+
* @param {number} [opts.retries=3] — Max additional attempts after the first failure.
|
|
185
|
+
* @param {number} [opts.initialDelay=500] — Initial back-off delay (ms); doubles each attempt.
|
|
186
|
+
* @param {(err: Error) => boolean} [opts.shouldRetry] — Custom predicate; defaults to `err.isRetryable()`.
|
|
187
|
+
* @returns {Promise<T>}
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* const doc = await withRetry(() => processImage(templateId, base64), { retries: 3 });
|
|
191
|
+
*/
|
|
192
|
+
export async function withRetry(
|
|
193
|
+
fn,
|
|
194
|
+
{ retries = 3, initialDelay = 500, shouldRetry } = {},
|
|
195
|
+
) {
|
|
196
|
+
const isRetryable =
|
|
197
|
+
shouldRetry ??
|
|
198
|
+
((err) => typeof err.isRetryable === "function" && err.isRetryable());
|
|
199
|
+
|
|
200
|
+
let lastErr;
|
|
201
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
202
|
+
try {
|
|
203
|
+
return await fn();
|
|
204
|
+
} catch (err) {
|
|
205
|
+
lastErr = err;
|
|
206
|
+
if (attempt < retries && isRetryable(err)) {
|
|
207
|
+
// Honour Retry-After for rate-limit errors
|
|
208
|
+
const wait =
|
|
209
|
+
err.retryAfter != null
|
|
210
|
+
? err.retryAfter * 1_000
|
|
211
|
+
: initialDelay * 2 ** attempt;
|
|
212
|
+
await delay(wait);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
throw err;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
throw lastErr;
|
|
219
|
+
}
|