@abyss-project/tools 2.11.0 → 2.12.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/api/extraction.api.d.ts +1 -2
- package/dist/api/extraction.api.js +82 -43
- package/dist/api/extraction.api.js.map +1 -1
- package/dist/api/index.d.ts +0 -1
- package/dist/api/index.js +0 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/probe-scan.api.d.ts +2 -9
- package/dist/api/probe-scan.api.js +16 -26
- package/dist/api/probe-scan.api.js.map +1 -1
- package/dist/api/probe-scan.api.test.js +10 -0
- package/dist/api/probe-scan.api.test.js.map +1 -1
- package/dist/api/probe-scan.application.api.js +2 -2
- package/dist/api/probe-scan.application.api.js.map +1 -1
- package/dist/api/probe-scan.application.api.test.js +4 -0
- package/dist/api/probe-scan.application.api.test.js.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -4
- package/dist/index.js.map +1 -1
- package/dist/types/enum/document-type.enum.d.ts +0 -1
- package/dist/types/enum/document-type.enum.js +0 -1
- package/dist/types/enum/document-type.enum.js.map +1 -1
- package/dist/types/interface/models/extraction.dto.d.ts +0 -10
- package/dist/types/interface/models/extraction.dto.js.map +1 -1
- package/package.json +4 -4
- package/src/api/extraction.api.ts +84 -64
- package/src/api/index.ts +0 -1
- package/src/api/probe-scan.api.test.ts +26 -0
- package/src/api/probe-scan.api.ts +66 -54
- package/src/api/probe-scan.application.api.test.ts +14 -0
- package/src/api/probe-scan.application.api.ts +5 -5
- package/src/index.ts +0 -17
- package/src/types/enum/document-type.enum.ts +0 -1
- package/src/types/interface/api/requests/probe-scan.request.ts +5 -1
- package/src/types/interface/models/extraction.dto.ts +0 -17
- package/dist/api/extraction.application.api.d.ts +0 -4
- package/dist/api/extraction.application.api.js +0 -30
- package/dist/api/extraction.application.api.js.map +0 -1
- package/src/api/extraction.application.api.ts +0 -79
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extraction.dto.js","sourceRoot":"","sources":["../../../../src/types/interface/models/extraction.dto.ts"],"names":[],"mappings":";;;AAKA,yFAAoF;
|
|
1
|
+
{"version":3,"file":"extraction.dto.js","sourceRoot":"","sources":["../../../../src/types/interface/models/extraction.dto.ts"],"names":[],"mappings":";;;AAKA,yFAAoF;AA6F3E,sGA7FA,+CAAqB,OA6FA;AA5F9B,iHAAsG;AAW7F,4GAXA,iEAA2B,OAWA;AAoRpC,IAAY,sBAYX;AAZD,WAAY,sBAAsB;IAChC,qEAA2C,CAAA;IAC3C,+DAAqC,CAAA;IACrC,+EAAqD,CAAA;IACrD,uEAA6C,CAAA;IAC7C,6DAAmC,CAAA;IACnC,2DAAiC,CAAA;IACjC,6DAAmC,CAAA;IACnC,uEAA6C,CAAA;IAC7C,uDAA6B,CAAA;IAC7B,mEAAyC,CAAA;IACzC,6DAAmC,CAAA;AACrC,CAAC,EAZW,sBAAsB,sCAAtB,sBAAsB,QAYjC;AAEY,QAAA,yBAAyB,GACpC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;AAuCxC,IAAY,2BAGX;AAHD,WAAY,2BAA2B;IACrC,4CAAa,CAAA;IACb,0CAAW,CAAA;AACb,CAAC,EAHW,2BAA2B,2CAA3B,2BAA2B,QAGtC"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abyss-project/tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"description": "Core package to interact with Abyss-Project Tools",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
-
"engines": {
|
|
8
|
-
"node": ">=18"
|
|
9
|
-
},
|
|
10
7
|
"repository": {
|
|
11
8
|
"type": "git",
|
|
12
9
|
"url": "https://gitlab.com/abyss-group"
|
|
@@ -14,6 +11,9 @@
|
|
|
14
11
|
"keywords": [],
|
|
15
12
|
"author": "scylla",
|
|
16
13
|
"license": "ISC",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=19.8"
|
|
16
|
+
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"axios": "^1.6.0",
|
|
19
19
|
"axios-retry": "^4.5.0",
|
|
@@ -3,11 +3,6 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Sync helper accepts an inline PDF buffer (multipart) or a storage_file_id.
|
|
5
5
|
// Queue helper enqueues for async processing + webhook delivery.
|
|
6
|
-
//
|
|
7
|
-
// Multipart bodies are built with the global `FormData` + `Blob` — the same
|
|
8
|
-
// code in a browser and on Node (>= 18, see package.json `engines`). axios
|
|
9
|
-
// serializes a spec-compliant FormData on both and sets the multipart
|
|
10
|
-
// boundary itself, so no content-type header is ever set by hand.
|
|
11
6
|
// ──────────────────────────────────────────────
|
|
12
7
|
|
|
13
8
|
import { AbyssToolsCore } from '..';
|
|
@@ -97,9 +92,16 @@ function authHeader(apiKey?: string): Record<string, string> {
|
|
|
97
92
|
return apiKey ? { [DEFAULT_API_KEY_HEADER]: apiKey } : {};
|
|
98
93
|
}
|
|
99
94
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
async function loadFormData(): Promise<any> {
|
|
96
|
+
if (typeof FormData !== 'undefined' && typeof window !== 'undefined') {
|
|
97
|
+
return FormData;
|
|
98
|
+
}
|
|
99
|
+
// Use indirect eval so bundlers (esbuild/vite/webpack) do not try to
|
|
100
|
+
// resolve the Node-only "form-data" package when this file is included
|
|
101
|
+
// in a browser bundle. The require only runs at runtime on Node.
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
|
|
103
|
+
const nodeRequire: NodeRequire = new Function('return require')();
|
|
104
|
+
return nodeRequire('form-data');
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
function serializeParams<T extends object>(o: T): Record<string, unknown> {
|
|
@@ -122,22 +124,18 @@ export interface ExtractCallOptions {
|
|
|
122
124
|
idempotencyKey?: string;
|
|
123
125
|
}
|
|
124
126
|
|
|
125
|
-
/**
|
|
126
|
-
* `POST /extract/extract` with `processing: 'sync'`. The route still moves an
|
|
127
|
-
* async plan (an OCR pass, or more than one pass — inline or reached through
|
|
128
|
-
* `plan_preset_id`) onto the queue and answers 202 with the queue body, so the
|
|
129
|
-
* call resolves either the extraction (`extraction_id`) or the queued job
|
|
130
|
-
* (`job_id`); narrow on `'job_id' in response.data`.
|
|
131
|
-
*/
|
|
132
127
|
export const extractSync = async (
|
|
133
128
|
body: IExtractBody,
|
|
134
129
|
scope: IExtractionContentScopeQuery = {},
|
|
135
130
|
apiKey?: string,
|
|
136
131
|
opts: ExtractCallOptions = {},
|
|
137
|
-
): Promise<IExtractSyncResponse
|
|
138
|
-
return postExtract(
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
): Promise<IExtractSyncResponse> => {
|
|
133
|
+
return postExtract(
|
|
134
|
+
{ ...body, processing: 'sync' },
|
|
135
|
+
scope,
|
|
136
|
+
apiKey,
|
|
137
|
+
opts,
|
|
138
|
+
) as Promise<IExtractSyncResponse>;
|
|
141
139
|
};
|
|
142
140
|
|
|
143
141
|
export const extractQueue = async (
|
|
@@ -166,11 +164,54 @@ const postExtract = async (
|
|
|
166
164
|
}
|
|
167
165
|
const params = serializeParams(scope);
|
|
168
166
|
if (body.pdf) {
|
|
169
|
-
const
|
|
167
|
+
const isBrowser = typeof window !== 'undefined' && typeof FormData !== 'undefined';
|
|
168
|
+
const filename = body.pdf_filename ?? 'document.pdf';
|
|
169
|
+
if (isBrowser) {
|
|
170
|
+
const form = new FormData();
|
|
171
|
+
const blob = new Blob([body.pdf as Uint8Array], { type: 'application/pdf' });
|
|
172
|
+
form.append('file', blob, filename);
|
|
173
|
+
appendIf(form, 'mode', body.mode);
|
|
174
|
+
if (body.hint) form.append('hint', JSON.stringify(body.hint));
|
|
175
|
+
appendIf(form, 'template_id', body.template_id);
|
|
176
|
+
if (body.template) form.append('template', JSON.stringify(body.template));
|
|
177
|
+
appendIf(form, 'user_external_id', body.user_external_id);
|
|
178
|
+
if (body.user_metadata) form.append('user_metadata', JSON.stringify(body.user_metadata));
|
|
179
|
+
if (typeof body.archive === 'boolean') form.append('archive', String(body.archive));
|
|
180
|
+
if (typeof body.force === 'boolean') form.append('force', String(body.force));
|
|
181
|
+
if (body.processing) form.append('processing', body.processing);
|
|
182
|
+
return (
|
|
183
|
+
await AbyssToolsCore.axios.post(`extract/extract`, form, {
|
|
184
|
+
params,
|
|
185
|
+
headers: { ...authHeader(apiKey), ...extraHeaders },
|
|
186
|
+
maxBodyLength: Infinity,
|
|
187
|
+
maxContentLength: Infinity,
|
|
188
|
+
})
|
|
189
|
+
).data;
|
|
190
|
+
}
|
|
191
|
+
const FormDataCtor = await loadFormData();
|
|
192
|
+
const form = new FormDataCtor();
|
|
193
|
+
const buf = Buffer.isBuffer(body.pdf) ? body.pdf : Buffer.from(body.pdf);
|
|
194
|
+
form.append('file', buf, {
|
|
195
|
+
filename,
|
|
196
|
+
contentType: 'application/pdf',
|
|
197
|
+
});
|
|
198
|
+
appendIf(form, 'mode', body.mode);
|
|
199
|
+
if (body.hint) form.append('hint', JSON.stringify(body.hint));
|
|
200
|
+
appendIf(form, 'template_id', body.template_id);
|
|
201
|
+
if (body.template) form.append('template', JSON.stringify(body.template));
|
|
202
|
+
appendIf(form, 'user_external_id', body.user_external_id);
|
|
203
|
+
if (body.user_metadata) form.append('user_metadata', JSON.stringify(body.user_metadata));
|
|
204
|
+
if (typeof body.archive === 'boolean') form.append('archive', String(body.archive));
|
|
205
|
+
if (typeof body.force === 'boolean') form.append('force', String(body.force));
|
|
206
|
+
if (body.processing) form.append('processing', body.processing);
|
|
170
207
|
return (
|
|
171
208
|
await AbyssToolsCore.axios.post(`extract/extract`, form, {
|
|
172
209
|
params,
|
|
173
|
-
headers: {
|
|
210
|
+
headers: {
|
|
211
|
+
...(typeof form.getHeaders === 'function' ? form.getHeaders() : {}),
|
|
212
|
+
...authHeader(apiKey),
|
|
213
|
+
...extraHeaders,
|
|
214
|
+
},
|
|
174
215
|
maxBodyLength: Infinity,
|
|
175
216
|
maxContentLength: Infinity,
|
|
176
217
|
})
|
|
@@ -187,50 +228,11 @@ const postExtract = async (
|
|
|
187
228
|
).data;
|
|
188
229
|
};
|
|
189
230
|
|
|
190
|
-
function appendIf(form:
|
|
231
|
+
function appendIf(form: any, key: string, value: unknown): void {
|
|
191
232
|
if (value === undefined || value === null) return;
|
|
192
233
|
form.append(key, String(value));
|
|
193
234
|
}
|
|
194
235
|
|
|
195
|
-
/** Append the shared extract text fields (all optional) onto a multipart form. */
|
|
196
|
-
function appendExtractFields(form: FormData, body: IExtractBody): void {
|
|
197
|
-
appendIf(form, 'mode', body.mode);
|
|
198
|
-
if (body.hint) form.append('hint', JSON.stringify(body.hint));
|
|
199
|
-
appendIf(form, 'template_id', body.template_id);
|
|
200
|
-
if (body.template) form.append('template', JSON.stringify(body.template));
|
|
201
|
-
appendIf(form, 'user_external_id', body.user_external_id);
|
|
202
|
-
if (body.user_metadata) form.append('user_metadata', JSON.stringify(body.user_metadata));
|
|
203
|
-
if (typeof body.archive === 'boolean') form.append('archive', String(body.archive));
|
|
204
|
-
if (typeof body.force === 'boolean') form.append('force', String(body.force));
|
|
205
|
-
if (body.processing) form.append('processing', body.processing);
|
|
206
|
-
if (body.plan) form.append('plan', JSON.stringify(body.plan));
|
|
207
|
-
appendIf(form, 'plan_preset_id', body.plan_preset_id);
|
|
208
|
-
// Wire name read by the API's parseExtractRequest ('true' / 'false').
|
|
209
|
-
if (typeof body.improvementConsent === 'boolean') {
|
|
210
|
-
form.append('improvement_consent', String(body.improvementConsent));
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Build the multipart form for an extract upload, shared by the personal and
|
|
216
|
-
* application extract clients: the PDF as the `file` part (filename from
|
|
217
|
-
* `body.pdf_filename`, default `document.pdf`) plus the extract text fields.
|
|
218
|
-
* Scope params + auth headers are added by the caller; axios sets the
|
|
219
|
-
* multipart content type. Requires `body.pdf` to be set.
|
|
220
|
-
*/
|
|
221
|
-
export function buildExtractMultipart(body: IExtractBody): FormData {
|
|
222
|
-
const pdf = body.pdf;
|
|
223
|
-
if (!pdf) {
|
|
224
|
-
// Precondition: only the multipart-upload path calls this (both callers
|
|
225
|
-
// guard on `body.pdf` first). Enforced here so the helper is self-contained.
|
|
226
|
-
throw new Error('buildExtractMultipart requires body.pdf');
|
|
227
|
-
}
|
|
228
|
-
const form = new FormData();
|
|
229
|
-
appendPdfPart(form, pdf, body.pdf_filename ?? 'document.pdf');
|
|
230
|
-
appendExtractFields(form, body);
|
|
231
|
-
return form;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
236
|
export const extractBulk = async (
|
|
235
237
|
body: IExtractBulkBody,
|
|
236
238
|
scope: IExtractionContentScopeQuery = {},
|
|
@@ -251,11 +253,29 @@ export const detect = async (
|
|
|
251
253
|
pdfFilename = 'document.pdf',
|
|
252
254
|
apiKey?: string,
|
|
253
255
|
): Promise<IDetectResponse> => {
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
+
const isBrowser = typeof window !== 'undefined' && typeof FormData !== 'undefined';
|
|
257
|
+
if (isBrowser) {
|
|
258
|
+
const form = new FormData();
|
|
259
|
+
const blob = new Blob([pdf as Uint8Array], { type: 'application/pdf' });
|
|
260
|
+
form.append('file', blob, pdfFilename);
|
|
261
|
+
return (
|
|
262
|
+
await AbyssToolsCore.axios.post<IDetectResponse>(`extract/detect`, form, {
|
|
263
|
+
headers: authHeader(apiKey),
|
|
264
|
+
maxBodyLength: Infinity,
|
|
265
|
+
maxContentLength: Infinity,
|
|
266
|
+
})
|
|
267
|
+
).data;
|
|
268
|
+
}
|
|
269
|
+
const FormDataCtor = await loadFormData();
|
|
270
|
+
const form = new FormDataCtor();
|
|
271
|
+
const buf = Buffer.isBuffer(pdf) ? pdf : Buffer.from(pdf);
|
|
272
|
+
form.append('file', buf, { filename: pdfFilename, contentType: 'application/pdf' });
|
|
256
273
|
return (
|
|
257
274
|
await AbyssToolsCore.axios.post<IDetectResponse>(`extract/detect`, form, {
|
|
258
|
-
headers:
|
|
275
|
+
headers: {
|
|
276
|
+
...(typeof form.getHeaders === 'function' ? form.getHeaders() : {}),
|
|
277
|
+
...authHeader(apiKey),
|
|
278
|
+
},
|
|
259
279
|
maxBodyLength: Infinity,
|
|
260
280
|
maxContentLength: Infinity,
|
|
261
281
|
})
|
package/src/api/index.ts
CHANGED
|
@@ -18,4 +18,30 @@ void _scanAsync;
|
|
|
18
18
|
void _getScan;
|
|
19
19
|
void _listScans;
|
|
20
20
|
|
|
21
|
+
// The accepted file inputs (#519). A Blob covers the browser's File and Node's
|
|
22
|
+
// file-backed fs.openAsBlob(path); raw bytes are accepted as a Uint8Array (and
|
|
23
|
+
// therefore a Buffer). A Node stream is refused AT COMPILE TIME: the body is a
|
|
24
|
+
// spec FormData, whose parts need a known length, so honouring a stream would
|
|
25
|
+
// mean buffering up to 2 GiB in memory.
|
|
26
|
+
declare const blob: Blob;
|
|
27
|
+
declare const bytes: Uint8Array;
|
|
28
|
+
declare const buffer: Buffer;
|
|
29
|
+
declare const stream: NodeJS.ReadableStream;
|
|
30
|
+
|
|
31
|
+
const _fromBlob: api.ProbeScanFileInput = blob;
|
|
32
|
+
const _fromBytes: api.ProbeScanFileInput = bytes;
|
|
33
|
+
const _fromBuffer: api.ProbeScanFileInput = buffer;
|
|
34
|
+
// @ts-expect-error a Readable stream is not an accepted scan input since 2.12.0
|
|
35
|
+
const _fromStream: api.ProbeScanFileInput = stream;
|
|
36
|
+
|
|
37
|
+
void _fromBlob;
|
|
38
|
+
void _fromBytes;
|
|
39
|
+
void _fromBuffer;
|
|
40
|
+
void _fromStream;
|
|
41
|
+
|
|
42
|
+
// buildScanForm returns the FormData alone — axios sets the multipart
|
|
43
|
+
// content type and boundary, so there are no headers to merge in.
|
|
44
|
+
const _form: FormData = api.buildScanForm(blob, { filename: 'f.bin' });
|
|
45
|
+
void _form;
|
|
46
|
+
|
|
21
47
|
export {};
|
|
@@ -6,13 +6,20 @@
|
|
|
6
6
|
// add no extra path segment (`@Controller({ prefixPath: '/scan' })`), so
|
|
7
7
|
// these calls hit `scan`/`scan/:id` directly — no `probe/` prefix.
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// extraction.api.ts
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
9
|
+
// Multipart bodies are built with the global `FormData` + `Blob` — the same
|
|
10
|
+
// code in a browser and on Node, mirroring this package's own
|
|
11
|
+
// extraction.api.ts. axios serializes a spec-compliant FormData on both and
|
|
12
|
+
// sets the multipart boundary itself, so no content-type header is ever set by
|
|
13
|
+
// hand. AbyssProbe is called from the abyss-main UI (a browser) as well as
|
|
14
|
+
// from Node services (abyss-storage, external integrators).
|
|
15
|
+
//
|
|
16
|
+
// A Node caller holding a file ON DISK passes `await fs.openAsBlob(path)`,
|
|
17
|
+
// which requires Node >= 19.8 — hence `engines.node` in package.json.
|
|
18
|
+
// `FormData` and `Blob` alone would only need Node 18, but openAsBlob is this
|
|
19
|
+
// client's documented way to send a file, and promising 18.x only to fail at
|
|
20
|
+
// runtime is worse than refusing the install. A caller that already holds its
|
|
21
|
+
// bytes passes a `Buffer`/`Uint8Array` and has no such constraint (see
|
|
22
|
+
// {@link ProbeScanFileInput}).
|
|
16
23
|
//
|
|
17
24
|
// `authHeader`/`serializeProbeQuery` are kept LOCAL and unexported (not a
|
|
18
25
|
// shared utils file) — same convention as every other extract/*.api.ts file
|
|
@@ -61,60 +68,65 @@ function serializeProbeQuery<T extends object>(query: T): Record<string, unknown
|
|
|
61
68
|
return out;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Anything `sdk.probe.scan` accepts as the file to submit.
|
|
73
|
+
*
|
|
74
|
+
* `Blob` covers the browser's `File` (a `Blob` subclass) and, on Node, a
|
|
75
|
+
* FILE-BACKED blob from `await fs.openAsBlob(path)` (Node >= 19.8): its bytes
|
|
76
|
+
* are read from disk as the request body is written, so scanning a file at the
|
|
77
|
+
* 2 GiB ceiling never holds the file in memory. `Uint8Array` (and therefore
|
|
78
|
+
* `Buffer`) is for a caller that already holds the bytes.
|
|
79
|
+
*
|
|
80
|
+
* A Node `Readable` is NOT accepted. The body is a spec `FormData`, whose
|
|
81
|
+
* parts must have a known length, so a stream could only be honoured by
|
|
82
|
+
* buffering it whole — precisely what a 2 GiB scan must not do. A caller
|
|
83
|
+
* holding a path passes `await fs.openAsBlob(path)` instead.
|
|
84
|
+
*/
|
|
85
|
+
export type ProbeScanFileInput = Blob | Uint8Array;
|
|
79
86
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
/**
|
|
88
|
+
* The file as one multipart part.
|
|
89
|
+
*
|
|
90
|
+
* A `Blob` is used as-is, keeping whatever streaming its implementation gives
|
|
91
|
+
* (a file-backed `openAsBlob` reads from disk lazily); raw bytes are wrapped
|
|
92
|
+
* in a `Blob` carrying `mimeType`. `Buffer` is never named here — this code
|
|
93
|
+
* also runs in a browser bundle, where the global does not exist — and
|
|
94
|
+
* `Buffer` is a `Uint8Array` subclass, so the check below already covers it.
|
|
95
|
+
*
|
|
96
|
+
* Anything else is refused with an actionable message. Until 2.12.0 the same
|
|
97
|
+
* call died on Node with `ReferenceError: require is not defined`, thrown by a
|
|
98
|
+
* `form-data` loader before any request was made (#519): it named neither the
|
|
99
|
+
* cause nor the fix.
|
|
100
|
+
*/
|
|
101
|
+
function toScanFilePart(file: ProbeScanFileInput, mimeType?: string): Blob {
|
|
102
|
+
if (file instanceof Blob) return file;
|
|
103
|
+
if (file instanceof Uint8Array) return new Blob([file], { type: mimeType ?? '' });
|
|
104
|
+
|
|
105
|
+
const isStream = typeof (file as { pipe?: unknown } | null | undefined)?.pipe === 'function';
|
|
106
|
+
throw new TypeError(
|
|
107
|
+
`A probe scan file must be a Blob (a browser File, or fs.openAsBlob(path) on Node), ` +
|
|
108
|
+
`a Buffer or a Uint8Array — received ${isStream ? 'a stream' : typeof file}. ` +
|
|
109
|
+
`On Node, pass \`await fs.openAsBlob(path)\`: a file-backed Blob is streamed from ` +
|
|
110
|
+
`disk, so the file is never buffered in memory.`,
|
|
111
|
+
);
|
|
83
112
|
}
|
|
84
113
|
|
|
85
114
|
/**
|
|
86
115
|
* Exported (not just this file's own `scan` internals) so
|
|
87
116
|
* probe-scan.application.api.ts reuses this EXACT multipart-building logic
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
117
|
+
* rather than duplicating it — the file-upload contract is identical for a
|
|
118
|
+
* personal/org caller and an application caller; only the URL/auth differ
|
|
119
|
+
* (see that file's header comment).
|
|
120
|
+
*
|
|
121
|
+
* Returns the form alone: axios sets the multipart content type, boundary
|
|
122
|
+
* included, so there is no header for the caller to merge in.
|
|
92
123
|
*/
|
|
93
|
-
export
|
|
94
|
-
|
|
95
|
-
options
|
|
96
|
-
): Promise<BuiltScanForm> {
|
|
97
|
-
const filename = options.filename ?? 'file';
|
|
98
|
-
|
|
99
|
-
if (isBrowserEnv()) {
|
|
100
|
-
const form = new FormData();
|
|
101
|
-
const blob =
|
|
102
|
-
file instanceof Blob ? file : new Blob([file as BlobPart], { type: options.mimeType });
|
|
103
|
-
form.append('file', blob, filename);
|
|
104
|
-
if (options.context) form.append('context', JSON.stringify(options.context));
|
|
105
|
-
if (options.callbackUrl) form.append('callbackUrl', options.callbackUrl);
|
|
106
|
-
return { form, headers: {} };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const FormDataCtor = await loadNodeFormData();
|
|
110
|
-
const form = new FormDataCtor();
|
|
111
|
-
const filePart =
|
|
112
|
-
Buffer.isBuffer(file) || file instanceof Uint8Array ? Buffer.from(file as Uint8Array) : file;
|
|
113
|
-
form.append('file', filePart, { filename, contentType: options.mimeType });
|
|
124
|
+
export function buildScanForm(file: ProbeScanFileInput, options: IScanOptions): FormData {
|
|
125
|
+
const form = new FormData();
|
|
126
|
+
form.append('file', toScanFilePart(file, options.mimeType), options.filename ?? 'file');
|
|
114
127
|
if (options.context) form.append('context', JSON.stringify(options.context));
|
|
115
128
|
if (options.callbackUrl) form.append('callbackUrl', options.callbackUrl);
|
|
116
|
-
|
|
117
|
-
return { form, headers };
|
|
129
|
+
return form;
|
|
118
130
|
}
|
|
119
131
|
|
|
120
132
|
/**
|
|
@@ -134,11 +146,11 @@ export const scan = async (
|
|
|
134
146
|
options: IScanOptions = {},
|
|
135
147
|
apiKey?: string,
|
|
136
148
|
): Promise<IScanResponse> => {
|
|
137
|
-
const
|
|
149
|
+
const form = buildScanForm(file, options);
|
|
138
150
|
return (
|
|
139
151
|
await AbyssToolsCore.axios.post<IScanResponse>('scan', form, {
|
|
140
152
|
params: serializeProbeQuery({ mode: options.mode, organizationId: options.organizationId }),
|
|
141
|
-
headers:
|
|
153
|
+
headers: authHeader(apiKey),
|
|
142
154
|
maxBodyLength: Infinity,
|
|
143
155
|
maxContentLength: Infinity,
|
|
144
156
|
})
|
|
@@ -18,4 +18,18 @@ void _scanAsync;
|
|
|
18
18
|
void _getScan;
|
|
19
19
|
void _listScans;
|
|
20
20
|
|
|
21
|
+
// The application client takes the SAME file input as the personal one (it
|
|
22
|
+
// reuses buildScanForm verbatim) — abyss-storage, its main caller, hands over
|
|
23
|
+
// `await fs.openAsBlob(path)`. A stream is refused at compile time (#519); see
|
|
24
|
+
// probe-scan.api.test.ts for the full input matrix.
|
|
25
|
+
declare const blob: Blob;
|
|
26
|
+
declare const stream: NodeJS.ReadableStream;
|
|
27
|
+
|
|
28
|
+
const _fromBlob: Parameters<typeof api.scanSyncApplication>[1] = blob;
|
|
29
|
+
// @ts-expect-error a Readable stream is not an accepted scan input since 2.12.0
|
|
30
|
+
const _fromStream: Parameters<typeof api.scanSyncApplication>[1] = stream;
|
|
31
|
+
|
|
32
|
+
void _fromBlob;
|
|
33
|
+
void _fromStream;
|
|
34
|
+
|
|
21
35
|
export {};
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
// (abyss-storage, external integrations) authenticating with a project API
|
|
9
9
|
// key instead of a personal/org session.
|
|
10
10
|
//
|
|
11
|
-
// Reuses probe-scan.api.ts's `buildScanForm` (
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// Reuses probe-scan.api.ts's `buildScanForm` (the global `FormData` + `Blob`,
|
|
12
|
+
// one code path for the browser and for Node — see that file's header for what
|
|
13
|
+
// a Node caller passes) and every request/response TYPE verbatim
|
|
14
14
|
// (`IScanOptions`, `IListScansQuery`, `IScanResponse`, `IScanSyncResponse`,
|
|
15
15
|
// `IScanAsyncResponse`, `IGetScanResponse`, `IPaginateScanResponse`) — never
|
|
16
16
|
// redefined here. `organizationId` is `Omit`'d from every option/query type
|
|
@@ -79,11 +79,11 @@ export const scanApplication = async (
|
|
|
79
79
|
options: Omit<IScanOptions, 'organizationId'> = {},
|
|
80
80
|
applicationApiKey?: string,
|
|
81
81
|
): Promise<IScanResponse> => {
|
|
82
|
-
const
|
|
82
|
+
const form = buildScanForm(file, options);
|
|
83
83
|
return (
|
|
84
84
|
await AbyssToolsCore.axios.post<IScanResponse>(`application/${applicationId}/scan`, form, {
|
|
85
85
|
params: serializeProbeQuery({ mode: options.mode }),
|
|
86
|
-
headers:
|
|
86
|
+
headers: authHeaderApplication(applicationApiKey),
|
|
87
87
|
maxBodyLength: Infinity,
|
|
88
88
|
maxContentLength: Infinity,
|
|
89
89
|
})
|
package/src/index.ts
CHANGED
|
@@ -28,7 +28,6 @@ import * as metricsApi from './api/metrics.api';
|
|
|
28
28
|
import * as organizationApi from './api/organization.api';
|
|
29
29
|
import * as tagApi from './api/tag.api';
|
|
30
30
|
import * as extractionApi from './api/extraction.api';
|
|
31
|
-
import * as extractionApplicationApi from './api/extraction.application.api';
|
|
32
31
|
import * as extractionSettingsApi from './api/extraction-settings.api';
|
|
33
32
|
import * as extractionPlanPresetApi from './api/extraction-plan-preset.api';
|
|
34
33
|
import * as monitorApi from './api/monitor.api';
|
|
@@ -407,19 +406,6 @@ type AbyssToolsCoreSDK = {
|
|
|
407
406
|
run: typeof extractionApi.extractSync;
|
|
408
407
|
runQueue: typeof extractionApi.extractQueue;
|
|
409
408
|
runBulk: typeof extractionApi.extractBulk;
|
|
410
|
-
/**
|
|
411
|
-
* Application/API-key-auth counterpart — for a backend satellite (e.g.
|
|
412
|
-
* AbyssBanking) triggering a SYNCHRONOUS extraction server-side with its
|
|
413
|
-
* APPLICATION api key: the run is OWNED by the application and BILLED to
|
|
414
|
-
* its resolved owning organization (never an end user). Hits
|
|
415
|
-
* `/extract/application/:applicationId/extract`, authenticated with the
|
|
416
|
-
* `abyss-api-key-application` header ({@link API_KEY_APPLICATION_HEADER})
|
|
417
|
-
* instead of the personal `abyss-api-key`. Does NOT change the personal `run`.
|
|
418
|
-
*/
|
|
419
|
-
application: {
|
|
420
|
-
/** `POST /extract/application/:applicationId/extract` — synchronous; owned + billed to the app's org. */
|
|
421
|
-
run: typeof extractionApplicationApi.runExtractionApplication;
|
|
422
|
-
};
|
|
423
409
|
files: {
|
|
424
410
|
list: typeof extractionApi.listFiles;
|
|
425
411
|
listShared: typeof extractionApi.listSharedFiles;
|
|
@@ -839,9 +825,6 @@ export class AbyssToolsCore {
|
|
|
839
825
|
run: extractionApi.extractSync,
|
|
840
826
|
runQueue: extractionApi.extractQueue,
|
|
841
827
|
runBulk: extractionApi.extractBulk,
|
|
842
|
-
application: {
|
|
843
|
-
run: extractionApplicationApi.runExtractionApplication,
|
|
844
|
-
},
|
|
845
828
|
files: {
|
|
846
829
|
list: extractionApi.listFiles,
|
|
847
830
|
listShared: extractionApi.listSharedFiles,
|
|
@@ -21,7 +21,11 @@ export interface IScanOptions {
|
|
|
21
21
|
callbackUrl?: string;
|
|
22
22
|
/** Filename reported to the server (defaults to `'file'`). Cosmetic only. */
|
|
23
23
|
filename?: string;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Content-Type stamped on the file part when the caller passes raw bytes
|
|
26
|
+
* (`Buffer`/`Uint8Array`). Ignored when the file is already a `Blob` — a
|
|
27
|
+
* browser `File` and `fs.openAsBlob(path)` carry their own type.
|
|
28
|
+
*/
|
|
25
29
|
mimeType?: string;
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -63,23 +63,6 @@ export interface IExtractionMetadata {
|
|
|
63
63
|
[k: string]: unknown;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/**
|
|
67
|
-
* Payslip (fiche de paie) metadata, extracted generically by FR labels — no
|
|
68
|
-
* per-employer template. Amounts are integer cents; `pasRatePct` is a percent
|
|
69
|
-
* (e.g. 3.6 for 3,6 %). `period` is a stable label (ISO month or a "du … au …"
|
|
70
|
-
* span). Every field is optional: a field is present only when confidently found.
|
|
71
|
-
*/
|
|
72
|
-
export interface IPayslipMetadata {
|
|
73
|
-
netCents?: number;
|
|
74
|
-
grossCents?: number;
|
|
75
|
-
netTaxableCents?: number;
|
|
76
|
-
pasRatePct?: number;
|
|
77
|
-
pasAmountCents?: number;
|
|
78
|
-
period?: string;
|
|
79
|
-
employer?: string;
|
|
80
|
-
currency?: string;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
66
|
export interface IExtractionFile {
|
|
84
67
|
id: string;
|
|
85
68
|
userId: string;
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { IExtractBody } from '../types';
|
|
2
|
-
import type { IExtractSyncResponse } from '../types';
|
|
3
|
-
import { ExtractCallOptions } from './extraction.api';
|
|
4
|
-
export declare const runExtractionApplication: (applicationId: string, body: IExtractBody, options?: ExtractCallOptions, applicationApiKey?: string) => Promise<IExtractSyncResponse>;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runExtractionApplication = void 0;
|
|
4
|
-
const __1 = require("..");
|
|
5
|
-
const extraction_api_1 = require("./extraction.api");
|
|
6
|
-
function authHeaderApplication(applicationApiKey) {
|
|
7
|
-
return applicationApiKey ? { [__1.API_KEY_APPLICATION_HEADER]: applicationApiKey } : {};
|
|
8
|
-
}
|
|
9
|
-
const runExtractionApplication = async (applicationId, body, options = {}, applicationApiKey) => {
|
|
10
|
-
const extraHeaders = {};
|
|
11
|
-
if (options.idempotencyKey) {
|
|
12
|
-
extraHeaders['Idempotency-Key'] = options.idempotencyKey;
|
|
13
|
-
}
|
|
14
|
-
const url = `extract/application/${applicationId}/extract`;
|
|
15
|
-
if (body.pdf) {
|
|
16
|
-
const form = (0, extraction_api_1.buildExtractMultipart)(body);
|
|
17
|
-
return (await __1.AbyssToolsCore.axios.post(url, form, {
|
|
18
|
-
headers: { ...authHeaderApplication(applicationApiKey), ...extraHeaders },
|
|
19
|
-
maxBodyLength: Infinity,
|
|
20
|
-
maxContentLength: Infinity,
|
|
21
|
-
})).data;
|
|
22
|
-
}
|
|
23
|
-
const jsonBody = { ...body };
|
|
24
|
-
delete jsonBody.pdf;
|
|
25
|
-
return (await __1.AbyssToolsCore.axios.post(url, jsonBody, {
|
|
26
|
-
headers: { ...authHeaderApplication(applicationApiKey), ...extraHeaders },
|
|
27
|
-
})).data;
|
|
28
|
-
};
|
|
29
|
-
exports.runExtractionApplication = runExtractionApplication;
|
|
30
|
-
//# sourceMappingURL=extraction.application.api.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extraction.application.api.js","sourceRoot":"","sources":["../../src/api/extraction.application.api.ts"],"names":[],"mappings":";;;AAwBA,0BAAgE;AAGhE,qDAA6E;AAE7E,SAAS,qBAAqB,CAAC,iBAA0B;IACvD,OAAO,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,8BAA0B,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACtF,CAAC;AAgBM,MAAM,wBAAwB,GAAG,KAAK,EAC3C,aAAqB,EACrB,IAAkB,EAClB,UAA8B,EAAE,EAChC,iBAA0B,EACK,EAAE;IACjC,MAAM,YAAY,GAA2B,EAAE,CAAC;IAChD,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;QAC3B,YAAY,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;IAC3D,CAAC;IACD,MAAM,GAAG,GAAG,uBAAuB,aAAa,UAAU,CAAC;IAE3D,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,IAAA,sCAAqB,EAAC,IAAI,CAAC,CAAC;QACzC,OAAO,CACL,MAAM,kBAAc,CAAC,KAAK,CAAC,IAAI,CAAuB,GAAG,EAAE,IAAI,EAAE;YAC/D,OAAO,EAAE,EAAE,GAAG,qBAAqB,CAAC,iBAAiB,CAAC,EAAE,GAAG,YAAY,EAAE;YACzE,aAAa,EAAE,QAAQ;YACvB,gBAAgB,EAAE,QAAQ;SAC3B,CAAC,CACH,CAAC,IAAI,CAAC;IACT,CAAC;IAGD,MAAM,QAAQ,GAA8B,EAAE,GAAG,IAAI,EAAE,CAAC;IACxD,OAAQ,QAA8B,CAAC,GAAG,CAAC;IAC3C,OAAO,CACL,MAAM,kBAAc,CAAC,KAAK,CAAC,IAAI,CAAuB,GAAG,EAAE,QAAQ,EAAE;QACnE,OAAO,EAAE,EAAE,GAAG,qBAAqB,CAAC,iBAAiB,CAAC,EAAE,GAAG,YAAY,EAAE;KAC1E,CAAC,CACH,CAAC,IAAI,CAAC;AACT,CAAC,CAAC;AA/BW,QAAA,wBAAwB,4BA+BnC"}
|