@bedolla/enrivision 0.1.5 → 0.1.6

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.
Files changed (64) hide show
  1. package/README.md +31 -6
  2. package/dist/client/EnriProxyClient.d.ts +289 -244
  3. package/dist/client/EnriProxyClient.d.ts.map +1 -1
  4. package/dist/client/EnriProxyClient.js +841 -115
  5. package/dist/client/EnriProxyClient.js.map +1 -1
  6. package/dist/client/EnriProxyClientContract.d.ts +425 -0
  7. package/dist/client/EnriProxyClientContract.d.ts.map +1 -0
  8. package/dist/client/EnriProxyClientContract.js +87 -0
  9. package/dist/client/EnriProxyClientContract.js.map +1 -0
  10. package/dist/index.js +23 -12
  11. package/dist/index.js.map +1 -1
  12. package/dist/package-info.d.ts +28 -0
  13. package/dist/package-info.d.ts.map +1 -1
  14. package/dist/package-info.js +28 -0
  15. package/dist/package-info.js.map +1 -1
  16. package/dist/server/EnriVisionServer.d.ts +186 -0
  17. package/dist/server/EnriVisionServer.d.ts.map +1 -1
  18. package/dist/server/EnriVisionServer.js +780 -93
  19. package/dist/server/EnriVisionServer.js.map +1 -1
  20. package/dist/shared/codepointTruncation.d.ts +61 -0
  21. package/dist/shared/codepointTruncation.d.ts.map +1 -0
  22. package/dist/shared/codepointTruncation.js +73 -0
  23. package/dist/shared/codepointTruncation.js.map +1 -0
  24. package/dist/shared/mediaUrlFetcher.d.ts +247 -9
  25. package/dist/shared/mediaUrlFetcher.d.ts.map +1 -1
  26. package/dist/shared/mediaUrlFetcher.js +712 -53
  27. package/dist/shared/mediaUrlFetcher.js.map +1 -1
  28. package/dist/shared/tar.d.ts +82 -2
  29. package/dist/shared/tar.d.ts.map +1 -1
  30. package/dist/shared/tar.js +106 -43
  31. package/dist/shared/tar.js.map +1 -1
  32. package/dist/shared/validation.d.ts +96 -2
  33. package/dist/shared/validation.d.ts.map +1 -1
  34. package/dist/shared/validation.js +169 -10
  35. package/dist/shared/validation.js.map +1 -1
  36. package/dist/tools/AnalyzeMediaContract.d.ts +457 -0
  37. package/dist/tools/AnalyzeMediaContract.d.ts.map +1 -0
  38. package/dist/tools/AnalyzeMediaContract.js +161 -0
  39. package/dist/tools/AnalyzeMediaContract.js.map +1 -0
  40. package/dist/tools/AnalyzeMediaExtractionSanitizer.d.ts +35 -0
  41. package/dist/tools/AnalyzeMediaExtractionSanitizer.d.ts.map +1 -0
  42. package/dist/tools/AnalyzeMediaExtractionSanitizer.js +214 -0
  43. package/dist/tools/AnalyzeMediaExtractionSanitizer.js.map +1 -0
  44. package/dist/tools/AnalyzeMediaInputResolver.d.ts +250 -0
  45. package/dist/tools/AnalyzeMediaInputResolver.d.ts.map +1 -0
  46. package/dist/tools/AnalyzeMediaInputResolver.js +430 -0
  47. package/dist/tools/AnalyzeMediaInputResolver.js.map +1 -0
  48. package/dist/tools/AnalyzeMediaParamParser.d.ts +299 -0
  49. package/dist/tools/AnalyzeMediaParamParser.d.ts.map +1 -0
  50. package/dist/tools/AnalyzeMediaParamParser.js +824 -0
  51. package/dist/tools/AnalyzeMediaParamParser.js.map +1 -0
  52. package/dist/tools/AnalyzeMediaResumableUploader.d.ts +244 -0
  53. package/dist/tools/AnalyzeMediaResumableUploader.d.ts.map +1 -0
  54. package/dist/tools/AnalyzeMediaResumableUploader.js +549 -0
  55. package/dist/tools/AnalyzeMediaResumableUploader.js.map +1 -0
  56. package/dist/tools/AnalyzeMediaTarPackager.d.ts +42 -0
  57. package/dist/tools/AnalyzeMediaTarPackager.d.ts.map +1 -0
  58. package/dist/tools/AnalyzeMediaTarPackager.js +245 -0
  59. package/dist/tools/AnalyzeMediaTarPackager.js.map +1 -0
  60. package/dist/tools/AnalyzeMediaTool.d.ts +155 -294
  61. package/dist/tools/AnalyzeMediaTool.d.ts.map +1 -1
  62. package/dist/tools/AnalyzeMediaTool.js +600 -457
  63. package/dist/tools/AnalyzeMediaTool.js.map +1 -1
  64. package/package.json +2 -1
@@ -0,0 +1,549 @@
1
+ /**
2
+ * ANALYZE MEDIA RESUMABLE UPLOADER
3
+ *
4
+ * Streams local bytes to EnriProxy through resumable upload sessions with
5
+ * offset resync and bounded retries. Upload progress is reported to stderr
6
+ * at most once per 10% step (plus completion) so multi-hundred-chunk
7
+ * uploads do not flood the operator log.
8
+ *
9
+ * @module tools/AnalyzeMediaResumableUploader
10
+ */
11
+ import { open } from "node:fs/promises";
12
+ import { EnriProxyHttpError, } from "../client/EnriProxyClient.js";
13
+ import { ANALYZE_MEDIA_LIMITS } from "./AnalyzeMediaContract.js";
14
+ /**
15
+ * Hard cap for the overall upload deadline (mirrors EnriCode 20 min).
16
+ */
17
+ const MAX_UPLOAD_DEADLINE_MS = 20 * 60 * 1000;
18
+ /**
19
+ * Headroom added to the size-derived overall upload deadline.
20
+ */
21
+ const UPLOAD_DEADLINE_HEADROOM_MS = 60_000;
22
+ /**
23
+ * Worst-case upload throughput for deadline sizing (~1 Mbps, mirrors EnriCode).
24
+ */
25
+ const UPLOAD_DEADLINE_BYTES_PER_SECOND = 125_000;
26
+ /**
27
+ * File-identity re-check cadence in chunks (mirrors EnriCode 16).
28
+ */
29
+ export const IDENTITY_CHECK_EVERY_CHUNKS = 16;
30
+ /**
31
+ * File-identity re-check cadence in milliseconds (mirrors EnriCode 5 s).
32
+ */
33
+ export const IDENTITY_CHECK_EVERY_MS = 5_000;
34
+ /**
35
+ * Consecutive upload iterations without offset advance before the upload is
36
+ * declared stuck (mirrors the single-file same-offset 409 guard).
37
+ *
38
+ * @remarks
39
+ * A 2xx chunk response that reports the pre-chunk offset advances nothing:
40
+ * retrying the same bytes forever would spin to the 20 min deadline, so the
41
+ * third consecutive no-advance iteration fails fast with the inconsistent-
42
+ * protocol error instead.
43
+ */
44
+ export const MAX_CONSECUTIVE_NO_ADVANCE = 3;
45
+ /**
46
+ * Uploads local bytes through resumable EnriProxy sessions.
47
+ */
48
+ export class AnalyzeMediaResumableUploader {
49
+ /**
50
+ * Uploads a file to EnriProxy in resumable chunks.
51
+ *
52
+ * @param client - EnriProxy client.
53
+ * @param filePath - Local file path.
54
+ * @param fileSize - Total file size in bytes.
55
+ * @param session - Server-created session.
56
+ * @param timeoutMs - Request timeout in milliseconds.
57
+ * @param signal - Optional cancellation signal.
58
+ * @param expectedIdentity - Resolve-time file identity compared at open (`fstat`); same-size swaps fail loudly.
59
+ * @returns Final offset.
60
+ * @throws Error with an Spanish-first bilingual message when the upload stalls, is cancelled, or stays incomplete.
61
+ */
62
+ async uploadFileResumable(client, filePath, fileSize, session, timeoutMs, signal, expectedIdentity) {
63
+ const chunkSize = effectiveChunkSizeBytes(session.chunk_size_bytes);
64
+ // One scratch for the whole upload, bounded by the actual file size so a
65
+ // 1-byte file never pins the full 16 MiB ceiling (mirrors EnriCode
66
+ // `max(1, min(effectiveChunkSize, totalBytes))`). Every iteration reads
67
+ // into a subarray view instead of allocating per chunk. Safe because each
68
+ // chunk is fully consumed (retries included) before the next read, and an
69
+ // offset resync discards the chunk before re-reading at the new offset.
70
+ const scratch = Buffer.allocUnsafe(resolveScratchSize(chunkSize, fileSize));
71
+ const progress = new UploadProgressLogger(fileSize);
72
+ const uploadStartedAt = Date.now();
73
+ const uploadDeadlineMs = resolveUploadDeadlineMs(fileSize);
74
+ const handle = await open(filePath, "r");
75
+ try {
76
+ // Staged resolve-time identity first (same-size replacement
77
+ // between resolve and upload), then the size gate: either mismatch
78
+ // fails loudly instead of shipping the wrong content.
79
+ const openedStat = await handle.stat();
80
+ const initialIdentity = describeFileIdentity(openedStat);
81
+ if (typeof expectedIdentity === "string" && initialIdentity !== expectedIdentity) {
82
+ throw new Error(`El archivo cambió entre la resolución y la subida (${filePath}); reintente la llamada con el archivo estable. / File changed between resolve and upload (${filePath}); retry the call with a stable file.`);
83
+ }
84
+ if (openedStat.size !== fileSize) {
85
+ throw new Error(`El archivo cambió antes de la subida (${filePath}); reintente la llamada. / File changed before upload (${filePath}); retry the call.`);
86
+ }
87
+ let offset = await withResumableRetry(() => client.getUploadOffset(session.upload_id, signal), signal);
88
+ if (!Number.isFinite(offset) || offset < 0 || offset > fileSize) {
89
+ throw new Error(`Offset del servidor inválido para la subida: ${String(offset)} (tamaño del archivo: ${String(fileSize)}). / Invalid server offset for the upload: ${String(offset)} (file size: ${String(fileSize)}).`);
90
+ }
91
+ let chunksSinceIdentityCheck = 0;
92
+ let lastIdentityCheckAt = 0;
93
+ let consecutiveNoAdvance = 0;
94
+ while (offset < fileSize) {
95
+ this.throwIfCancelled(signal);
96
+ if (Date.now() - uploadStartedAt > uploadDeadlineMs) {
97
+ await deleteUploadSessionBestEffort(client, session.upload_id);
98
+ throw new Error(`La subida excedió el tiempo máximo (${String(Math.round(uploadDeadlineMs / 1000))} s para ${String(fileSize)} bytes); reintente con un archivo más pequeño o una conexión más rápida. / Upload exceeded the maximum time (${String(Math.round(uploadDeadlineMs / 1000))} s for ${String(fileSize)} bytes); retry with a smaller file or a faster connection.`);
99
+ }
100
+ if (shouldRecheckIdentity(chunksSinceIdentityCheck, Date.now(), lastIdentityCheckAt)) {
101
+ // fstat on the OPEN handle (never a path re-stat): a rename-swap
102
+ // keeps this fd on the original bytes, so path-stat would judge a
103
+ // file we are no longer reading. Comparing the handle identity
104
+ // catches in-place same-size writes; truncation is caught by size.
105
+ const current = await handle.stat();
106
+ if (describeFileIdentity(current) !== initialIdentity) {
107
+ await deleteUploadSessionBestEffort(client, session.upload_id);
108
+ throw new Error(`El archivo cambió mientras se subía (${filePath}); reintente la llamada con el archivo estable. / File changed while uploading (${filePath}); retry the call with a stable file.`);
109
+ }
110
+ if (current.size < fileSize) {
111
+ await deleteUploadSessionBestEffort(client, session.upload_id);
112
+ throw new Error(`El archivo se truncó mientras se subía (${filePath}); reintente la llamada con el archivo estable. / File was truncated while uploading (${filePath}); retry the call with a stable file.`);
113
+ }
114
+ chunksSinceIdentityCheck = 0;
115
+ lastIdentityCheckAt = Date.now();
116
+ }
117
+ const remaining = fileSize - offset;
118
+ const nextSize = Math.min(chunkSize, remaining);
119
+ const view = scratch.subarray(0, nextSize);
120
+ const read = await handle.read(view, 0, nextSize, offset);
121
+ if (read.bytesRead <= 0) {
122
+ break;
123
+ }
124
+ const chunk = read.bytesRead === view.length ? view : view.subarray(0, read.bytesRead);
125
+ const expectedOffset = offset;
126
+ const nextOffset = await this.uploadChunkWithRetry(client, session.upload_id, expectedOffset, chunk, resolveChunkTimeoutMs(chunk.length, timeoutMs), signal);
127
+ assertNoForwardGap(nextOffset, expectedOffset, chunk.length);
128
+ if (nextOffset === expectedOffset) {
129
+ consecutiveNoAdvance += 1;
130
+ if (consecutiveNoAdvance >= MAX_CONSECUTIVE_NO_ADVANCE) {
131
+ await deleteUploadSessionBestEffort(client, session.upload_id);
132
+ throw new Error(`El servidor rechazó el fragmento sin avanzar el offset (protocolo de subida inconsistente); reintente la llamada. / The server rejected the chunk without advancing the offset (inconsistent upload protocol); retry the call.`);
133
+ }
134
+ }
135
+ else {
136
+ consecutiveNoAdvance = 0;
137
+ }
138
+ offset = nextOffset;
139
+ chunksSinceIdentityCheck += 1;
140
+ progress.report(offset);
141
+ }
142
+ return offset;
143
+ }
144
+ finally {
145
+ await handle.close();
146
+ }
147
+ }
148
+ /**
149
+ * Uploads a single chunk with retry and offset resync.
150
+ *
151
+ * @param client - EnriProxy client.
152
+ * @param uploadId - Upload id.
153
+ * @param offset - Expected offset.
154
+ * @param chunk - Chunk bytes.
155
+ * @param timeoutMs - Timeout in milliseconds.
156
+ * @param signal - Optional cancellation signal.
157
+ * @returns New offset (possibly resynced by the server on 409).
158
+ * @throws Error with an Spanish-first bilingual message when the chunk is rejected or cancelled.
159
+ */
160
+ async uploadChunkWithRetry(client, uploadId, offset, chunk, timeoutMs, signal) {
161
+ // 3 attempts per chunk (initial try plus 2 retries, mirrors EnriCode
162
+ // `MAX_CHUNK_ATTEMPTS`): terminal statuses fail fast instead of burning
163
+ // attempts and masking auth/quota as a generic failure.
164
+ const maxAttempts = 3;
165
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
166
+ this.throwIfCancelled(signal);
167
+ try {
168
+ return await client.appendUploadChunk({ uploadId, offset, chunk, timeoutMs, signal });
169
+ }
170
+ catch (error) {
171
+ this.throwIfCancelled(signal);
172
+ const message = error instanceof Error ? error.message : String(error);
173
+ if (error instanceof EnriProxyHttpError) {
174
+ // Offset mismatch: resync once and let the caller re-read at the
175
+ // correct offset. A same-offset 409 means the server rejected the
176
+ // bytes without advancing: fail fast instead of probing again
177
+ // (a second immediate probe returns the same offset) or burning
178
+ // all retry attempts.
179
+ if (error.status === 409) {
180
+ const actual = await withResumableRetry(() => client.getUploadOffset(uploadId, signal), signal);
181
+ if (actual !== offset) {
182
+ if (!isProgressQuiet()) {
183
+ console.error(`enrivision: offset resync (${offset} -> ${actual})`);
184
+ }
185
+ return actual;
186
+ }
187
+ throw new Error(`El servidor rechazó el fragmento sin avanzar el offset (protocolo de subida inconsistente); reintente la llamada. / The server rejected the chunk without advancing the offset (inconsistent upload protocol); retry the call.`);
188
+ }
189
+ // Do not retry on client errors (except 409 which is handled above).
190
+ if (error.status === 400 ||
191
+ error.status === 401 ||
192
+ error.status === 403 ||
193
+ error.status === 404 ||
194
+ error.status === 410 ||
195
+ error.status === 413) {
196
+ throw error;
197
+ }
198
+ }
199
+ if (attempt === maxAttempts) {
200
+ throw error;
201
+ }
202
+ const retryAfterMs = retryAfterDelayMs(error);
203
+ const backoffMs = retryAfterMs ?? Math.min(1000 * Math.pow(2, attempt - 1), 10000);
204
+ if (!isProgressQuiet()) {
205
+ console.error(`enrivision: retry ${attempt}/${maxAttempts} after ${backoffMs}ms (${message})`);
206
+ }
207
+ await sleepAbortably(backoffMs, signal);
208
+ }
209
+ }
210
+ throw new Error("La subida falló tras los reintentos. / Upload failed after retries.");
211
+ }
212
+ /**
213
+ * Throws a Spanish cancellation error when the signal fired.
214
+ *
215
+ * @param signal - Optional cancellation signal.
216
+ * @throws Error with an Spanish-first bilingual message when cancelled.
217
+ */
218
+ throwIfCancelled(signal) {
219
+ if (signal?.aborted) {
220
+ throw new Error("La solicitud fue cancelada por el cliente. / Request cancelled by the client.");
221
+ }
222
+ }
223
+ }
224
+ /**
225
+ * Reports upload progress to stderr at most once per 10% step.
226
+ *
227
+ * @remarks
228
+ * Shared by single-file and tar-set uploads so both planes log the same
229
+ * `enrivision: upload NN%` lines (suppressible with `ENRIVISION_QUIET=1`).
230
+ */
231
+ export class UploadProgressLogger {
232
+ /**
233
+ * Total bytes to send.
234
+ */
235
+ totalBytes;
236
+ /**
237
+ * Last reported 10% step (-1 before the first report).
238
+ */
239
+ lastReportedStep = -1;
240
+ /**
241
+ * Creates one throttled progress logger.
242
+ *
243
+ * @param totalBytes - Total bytes to send.
244
+ */
245
+ constructor(totalBytes) {
246
+ this.totalBytes = totalBytes;
247
+ }
248
+ /**
249
+ * Reports progress when a new 10% step (or completion) is reached.
250
+ *
251
+ * @param sentBytes - Bytes sent so far.
252
+ */
253
+ report(sentBytes) {
254
+ if (this.totalBytes <= 0 || isProgressQuiet()) {
255
+ return;
256
+ }
257
+ const step = sentBytes >= this.totalBytes ? 10 : Math.floor((sentBytes / this.totalBytes) * 10);
258
+ if (step <= this.lastReportedStep) {
259
+ return;
260
+ }
261
+ this.lastReportedStep = step;
262
+ const progress = Math.floor((sentBytes / this.totalBytes) * 100);
263
+ console.error(`enrivision: upload ${progress}% (${sentBytes}/${this.totalBytes} bytes)`);
264
+ }
265
+ }
266
+ /**
267
+ * Reports whether upload progress/retry logs are suppressed.
268
+ *
269
+ * @remarks
270
+ * Scripted stdio hosts set `ENRIVISION_QUIET=1` to silence stderr progress
271
+ * lines; transport framing always stays on stdout, so this only mutes noise.
272
+ *
273
+ * @returns True when `ENRIVISION_QUIET` is exactly `"1"`.
274
+ */
275
+ export function isProgressQuiet() {
276
+ return process.env["ENRIVISION_QUIET"] === "1";
277
+ }
278
+ /**
279
+ * Reads a server-requested retry delay from an HTTP error.
280
+ *
281
+ * @remarks
282
+ * Only 408 (timeout) and 429 (rate limit) honor `Retry-After`: other
283
+ * statuses either fail fast (4xx) or use exponential backoff (5xx). The
284
+ * delay accepts delta-seconds or an HTTP date, clamped to 0..30 s so a
285
+ * hostile header can never stall the upload plane for hours.
286
+ *
287
+ * @param error - Error thrown by the failed attempt.
288
+ * @returns Retry delay in milliseconds, or null when no retry delay applies.
289
+ */
290
+ export function retryAfterDelayMs(error) {
291
+ if (!(error instanceof EnriProxyHttpError)) {
292
+ return null;
293
+ }
294
+ if (error.status !== 408 && error.status !== 429) {
295
+ return null;
296
+ }
297
+ const raw = findHeaderValue(error.headers, "retry-after");
298
+ const first = Array.isArray(raw) ? raw[0] : raw;
299
+ if (typeof first !== "string" || !first.trim()) {
300
+ return null;
301
+ }
302
+ const trimmed = first.trim();
303
+ if (/^\d+$/u.test(trimmed)) {
304
+ return Math.min(30_000, Math.max(0, Number.parseInt(trimmed, 10) * 1000));
305
+ }
306
+ const at = Date.parse(trimmed);
307
+ if (!Number.isFinite(at)) {
308
+ return null;
309
+ }
310
+ return Math.min(30_000, Math.max(0, at - Date.now()));
311
+ }
312
+ /**
313
+ * Runs one resumable-plane operation with bounded backoff.
314
+ *
315
+ * @remarks
316
+ * Covers session creation and offset probes (single-shot before): up to 3
317
+ * attempts with exponential backoff plus jitter. `408`/`429` are retried
318
+ * honoring the server `Retry-After` header; other 4xx fail fast.
319
+ * Cancellation always throws immediately; the last error is rethrown in
320
+ * Spanish as received.
321
+ *
322
+ * @param operation - Thunk performing the HTTP operation.
323
+ * @param signal - Optional cancellation signal.
324
+ * @returns Operation result.
325
+ * @throws Error from the last attempt when all attempts fail.
326
+ */
327
+ export async function withResumableRetry(operation, signal) {
328
+ const maxAttempts = 3;
329
+ let lastError = null;
330
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
331
+ if (signal?.aborted) {
332
+ throw new Error("La solicitud fue cancelada por el cliente. / Request cancelled by the client.");
333
+ }
334
+ try {
335
+ return await operation();
336
+ }
337
+ catch (error) {
338
+ lastError = error;
339
+ if (signal?.aborted) {
340
+ throw new Error("La solicitud fue cancelada por el cliente. / Request cancelled by the client.");
341
+ }
342
+ if (error instanceof EnriProxyHttpError &&
343
+ error.status >= 400 &&
344
+ error.status < 500 &&
345
+ error.status !== 408 &&
346
+ error.status !== 429) {
347
+ throw error;
348
+ }
349
+ if (attempt === maxAttempts) {
350
+ break;
351
+ }
352
+ const retryAfterMs = retryAfterDelayMs(error);
353
+ const backoffMs = retryAfterMs ??
354
+ (Math.min(1000 * Math.pow(2, attempt - 1), 8000) + Math.floor(Math.random() * 250));
355
+ if (!isProgressQuiet()) {
356
+ const message = error instanceof Error ? error.message : String(error);
357
+ console.error(`enrivision: retry ${attempt}/${maxAttempts} after ${backoffMs}ms (${message})`);
358
+ }
359
+ await sleepAbortably(backoffMs, signal);
360
+ }
361
+ }
362
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
363
+ }
364
+ /**
365
+ * Finds one HTTP error header value without regard to capitalization.
366
+ *
367
+ * @remarks
368
+ * Servers send `Retry-After`, `retry-after`, or any other casing; checking
369
+ * only two spellings drops the rest. Mirrors the case-insensitive lookup
370
+ * used by the client (`getHeaderValue`).
371
+ *
372
+ * @param headers - Response headers.
373
+ * @param name - Header name (any casing).
374
+ * @returns Header value when present, otherwise undefined.
375
+ */
376
+ function findHeaderValue(headers, name) {
377
+ const target = name.toLowerCase();
378
+ for (const [key, value] of Object.entries(headers)) {
379
+ if (key.toLowerCase() === target) {
380
+ return value;
381
+ }
382
+ }
383
+ return undefined;
384
+ }
385
+ /**
386
+ * Sleeps abortably for the given delay.
387
+ *
388
+ * @param delayMs - Delay in milliseconds.
389
+ * @param signal - Optional cancellation signal.
390
+ * @throws Error with an Spanish-first bilingual message when cancelled while sleeping.
391
+ */
392
+ function sleepAbortably(delayMs, signal) {
393
+ if (signal?.aborted) {
394
+ return Promise.reject(new Error("La solicitud fue cancelada por el cliente. / Request cancelled by the client."));
395
+ }
396
+ return new Promise((resolve, reject) => {
397
+ const timer = setTimeout(() => {
398
+ signal?.removeEventListener("abort", onAbort);
399
+ resolve();
400
+ }, Math.max(0, delayMs));
401
+ const onAbort = () => {
402
+ clearTimeout(timer);
403
+ reject(new Error("La solicitud fue cancelada por el cliente. / Request cancelled by the client."));
404
+ };
405
+ signal?.addEventListener("abort", onAbort, { once: true });
406
+ });
407
+ }
408
+ /**
409
+ * Default chunk size when the server advertisement is missing or invalid.
410
+ *
411
+ * @remarks
412
+ * Mirrors EnriCode `VisionAnalyzeMediaUploadCoordinator.DEFAULT_CHUNK_SIZE_BYTES`.
413
+ */
414
+ export const DEFAULT_CHUNK_BYTES = 256 * 1024;
415
+ /**
416
+ * Caps a server-advertised chunk size to the local 16 MiB ceiling.
417
+ *
418
+ * @remarks
419
+ * A compromised proxy must never trick the client into a 1 GiB
420
+ * `Buffer.allocUnsafe` (OOM); the cap keeps every allocation bounded.
421
+ * Missing or invalid advertisements fall back to the 256 KiB default
422
+ * (mirrors EnriCode): falling back to the 16 MiB ceiling instead would
423
+ * force every misbehaving-proxy upload through maximum-size allocations.
424
+ *
425
+ * @param serverChunkSizeBytes - Chunk size advertised by the server.
426
+ * @returns Effective chunk size in bytes (4 KiB..16 MiB, default 256 KiB).
427
+ */
428
+ export function effectiveChunkSizeBytes(serverChunkSizeBytes) {
429
+ if (!Number.isFinite(serverChunkSizeBytes) || serverChunkSizeBytes <= 0) {
430
+ return DEFAULT_CHUNK_BYTES;
431
+ }
432
+ return Math.min(ANALYZE_MEDIA_LIMITS.maxChunkBytes, Math.max(4096, Math.floor(serverChunkSizeBytes)));
433
+ }
434
+ /**
435
+ * Derives the global upload deadline for a payload size.
436
+ *
437
+ * @remarks
438
+ * Mirrors EnriCode `VisionAnalyzeMediaUploadCoordinator`: size-derived at
439
+ * ~1 Mbps plus 60 s headroom, capped at 20 min, so a trickling connection
440
+ * can never run for hours past every analysis budget.
441
+ *
442
+ * @param totalBytes - Total payload bytes.
443
+ * @returns Deadline in milliseconds.
444
+ */
445
+ export function resolveUploadDeadlineMs(totalBytes) {
446
+ const sized = Math.ceil(Math.max(0, totalBytes) / UPLOAD_DEADLINE_BYTES_PER_SECOND) * 1000
447
+ + UPLOAD_DEADLINE_HEADROOM_MS;
448
+ return Math.min(MAX_UPLOAD_DEADLINE_MS, sized);
449
+ }
450
+ /**
451
+ * Describes a stable file identity for TOCTOU detection.
452
+ *
453
+ * @remarks
454
+ * Size alone misses same-size replacements: inode, mtime, birthtime, and
455
+ * link count join the identity, mirroring EnriCode `describeFileIdentity`.
456
+ *
457
+ * @param stats - File stats.
458
+ * @returns Stable identity string (ino:size:mtime:birthtime:nlink).
459
+ */
460
+ export function describeFileIdentity(stats) {
461
+ const birthtimeMs = typeof stats.birthtimeMs === "number" ? stats.birthtimeMs : 0;
462
+ const nlink = typeof stats.nlink === "number" ? stats.nlink : 0;
463
+ return `${String(stats.ino)}:${String(stats.size)}:${String(stats.mtimeMs)}:${String(birthtimeMs)}:${String(nlink)}`;
464
+ }
465
+ /**
466
+ * Deletes an upload session without ever throwing.
467
+ *
468
+ * @param client - EnriProxy client.
469
+ * @param uploadId - Upload id to release.
470
+ */
471
+ async function deleteUploadSessionBestEffort(client, uploadId) {
472
+ try {
473
+ await client.deleteUploadSession(uploadId, AbortSignal.timeout(15_000));
474
+ }
475
+ catch {
476
+ // Best-effort: the original deadline/identity error always wins.
477
+ }
478
+ }
479
+ /**
480
+ * Rejects a server offset that jumped past the bytes just sent.
481
+ *
482
+ * @remarks
483
+ * A forward jump (`nextOffset > expectedOffset + sentBytes`) means the
484
+ * server skipped bytes that were never sent: resuming there would ship a
485
+ * file with a hole. Backward jumps are legitimate 409 resyncs handled by
486
+ * the caller; only forward jumps fail here.
487
+ *
488
+ * @param nextOffset - Server-reported offset after the chunk.
489
+ * @param expectedOffset - Offset the chunk was sent at.
490
+ * @param sentBytes - Chunk length in bytes.
491
+ * @throws Error with an Spanish-first bilingual message when the server skipped unsent bytes.
492
+ */
493
+ export function assertNoForwardGap(nextOffset, expectedOffset, sentBytes) {
494
+ if (nextOffset > expectedOffset + sentBytes) {
495
+ throw new Error(`El servidor reportó un offset adelantado (${String(nextOffset)} > ${String(expectedOffset + sentBytes)}); hay bytes sin enviar y la subida no puede continuar sin huecos. Reintente la llamada. / The server reported a forward-skipped offset (${String(nextOffset)} > ${String(expectedOffset + sentBytes)}); some bytes were never sent and the upload cannot continue without gaps. Retry the call.`);
496
+ }
497
+ }
498
+ /**
499
+ * Derives the per-upload scratch size, bounded by the actual file size.
500
+ *
501
+ * @remarks
502
+ * Mirrors EnriCode `max(1, min(effectiveChunkSize, totalBytes))`: tiny files
503
+ * must not pin the full 16 MiB ceiling per concurrent upload session.
504
+ *
505
+ * @param chunkSize - Negotiated chunk size in bytes.
506
+ * @param fileSize - Total file size in bytes.
507
+ * @returns Scratch size in bytes (always at least 1).
508
+ */
509
+ export function resolveScratchSize(chunkSize, fileSize) {
510
+ return Math.max(1, Math.min(chunkSize, fileSize));
511
+ }
512
+ /**
513
+ * Reports whether the periodic file-identity re-check is due.
514
+ *
515
+ * @remarks
516
+ * Shared by the single-file and tar upload loops so both planes re-check on
517
+ * the same cadence: every 16 chunks or every 5 s (mirrors EnriCode).
518
+ *
519
+ * @param chunksSinceCheck - Chunks uploaded since the last check.
520
+ * @param nowMs - Current time in milliseconds.
521
+ * @param lastCheckMs - Time of the last check in milliseconds.
522
+ * @returns True when the identity re-check must run now.
523
+ */
524
+ export function shouldRecheckIdentity(chunksSinceCheck, nowMs, lastCheckMs) {
525
+ return chunksSinceCheck >= IDENTITY_CHECK_EVERY_CHUNKS || nowMs - lastCheckMs >= IDENTITY_CHECK_EVERY_MS;
526
+ }
527
+ /**
528
+ * Derives a per-chunk timeout from the chunk size and the call timeout.
529
+ *
530
+ * @remarks
531
+ * Assumes ~125 KB/s worst case (mirrors EnriCode `CHUNK_TIMEOUT_BYTES_PER_SECOND`), clamped to
532
+ * 30 s..300 s so one chunk can never hang the upload for hours (the old
533
+ * fixed 30 min per chunk did). The operator budget caps the derived value
534
+ * via `Math.min`, but the 30 s floor always wins: a tighter operator budget
535
+ * must not force single-chunk budgets that fail on slow links where EnriCode
536
+ * still uses the 30 s floor.
537
+ *
538
+ * @param chunkBytes - Chunk size in bytes.
539
+ * @param callTimeoutMs - Configured call timeout in milliseconds.
540
+ * @returns Effective chunk timeout in milliseconds (never below 30 s).
541
+ */
542
+ export function resolveChunkTimeoutMs(chunkBytes, callTimeoutMs) {
543
+ const derived = Math.min(300000, Math.max(30000, Math.ceil(Math.max(1, chunkBytes) / 125000) * 1000));
544
+ if (Number.isFinite(callTimeoutMs) && callTimeoutMs > 0) {
545
+ return Math.max(30000, Math.min(callTimeoutMs, derived));
546
+ }
547
+ return derived;
548
+ }
549
+ //# sourceMappingURL=AnalyzeMediaResumableUploader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnalyzeMediaResumableUploader.js","sourceRoot":"","sources":["../../src/tools/AnalyzeMediaResumableUploader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * ANALYZE MEDIA TAR PACKAGER
3
+ *
4
+ * Packages multiple local images into a single EnriVision media-set tar
5
+ * archive and uploads it through a resumable EnriProxy session. A single
6
+ * archive avoids creating many concurrent upload sessions (capped per API
7
+ * key) and enables server-side batching plus reduce for large screenshot
8
+ * sets.
9
+ *
10
+ * @module tools/AnalyzeMediaTarPackager
11
+ */
12
+ import type { EnriProxyClient } from "../client/EnriProxyClient.js";
13
+ import type { ResolvedMediaInput } from "./AnalyzeMediaInputResolver.js";
14
+ import { type AnalyzeMediaResumableUploader } from "./AnalyzeMediaResumableUploader.js";
15
+ /**
16
+ * Packages and uploads multi-image sets as one media-set tar archive.
17
+ */
18
+ export declare class AnalyzeMediaTarPackager {
19
+ /**
20
+ * Chunk uploader used for the tar byte stream.
21
+ */
22
+ private readonly uploader;
23
+ /**
24
+ * Creates a new {@link AnalyzeMediaTarPackager}.
25
+ *
26
+ * @param uploader - Resumable chunk uploader.
27
+ */
28
+ constructor(uploader: AnalyzeMediaResumableUploader);
29
+ /**
30
+ * Uploads multiple local images as a single media-set tar archive.
31
+ *
32
+ * @param client - EnriProxy client.
33
+ * @param inputs - Resolved media inputs (URLs already materialized).
34
+ * @param timeoutMs - Request timeout per HTTP request.
35
+ * @param clientTraceId - Client trace id for correlation.
36
+ * @param signal - Optional cancellation signal.
37
+ * @returns Upload id for the created tar session.
38
+ * @throws Error with an Spanish-first bilingual message when inputs are not images or the upload stalls.
39
+ */
40
+ uploadImageSetAsMediaSetTar(client: EnriProxyClient, inputs: readonly ResolvedMediaInput[], timeoutMs: number, clientTraceId: string, signal?: AbortSignal): Promise<string>;
41
+ }
42
+ //# sourceMappingURL=AnalyzeMediaTarPackager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnalyzeMediaTarPackager.d.ts","sourceRoot":"","sources":["../../src/tools/AnalyzeMediaTarPackager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAGpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAUL,KAAK,6BAA6B,EACnC,MAAM,oCAAoC,CAAC;AAoD5C;;GAEG;AACH,qBAAa,uBAAuB;IAClC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;IAEzD;;;;OAIG;gBACgB,QAAQ,EAAE,6BAA6B;IAI1D;;;;;;;;;;OAUG;IACU,2BAA2B,CACtC,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC;CA6OnB"}