@signaliz/sdk 1.0.60 → 1.0.62
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 +63 -15
- package/dist/{chunk-MFY4KSQ6.mjs → chunk-6G2FALRZ.mjs} +883 -107
- package/dist/index.d.mts +132 -11
- package/dist/index.d.ts +132 -11
- package/dist/index.js +883 -107
- package/dist/index.mjs +1 -1
- package/dist/mcp-config.js +883 -107
- package/dist/mcp-config.mjs +1 -1
- package/package.json +1 -1
|
@@ -9,17 +9,21 @@ var SignalizError = class extends Error {
|
|
|
9
9
|
this.details = data.details;
|
|
10
10
|
}
|
|
11
11
|
get isRetryable() {
|
|
12
|
+
if (typeof this.details?.retry_eligible === "boolean") {
|
|
13
|
+
return this.details.retry_eligible;
|
|
14
|
+
}
|
|
12
15
|
return this.errorType === "rate_limited" || this.errorType === "timeout" || this.errorType === "provider_error";
|
|
13
16
|
}
|
|
14
17
|
};
|
|
15
18
|
function parseError(status, body) {
|
|
16
19
|
if (body?.error && typeof body.error === "object") {
|
|
20
|
+
const nestedDetails = body.error.details && typeof body.error.details === "object" ? body.error.details : {};
|
|
17
21
|
return new SignalizError({
|
|
18
22
|
code: body.error.code || `HTTP_${status}`,
|
|
19
23
|
message: body.error.message || `Request failed with status ${status}`,
|
|
20
24
|
errorType: mapErrorType(body.error.code, status),
|
|
21
25
|
retryAfter: retryAfterSeconds(body.error),
|
|
22
|
-
details: body.error
|
|
26
|
+
details: mergePublicErrorDetails({ ...body, ...body.error }, nestedDetails)
|
|
23
27
|
});
|
|
24
28
|
}
|
|
25
29
|
const code = typeof body?.error_code === "string" && body.error_code.trim() ? body.error_code.trim() : `HTTP_${status}`;
|
|
@@ -28,7 +32,7 @@ function parseError(status, body) {
|
|
|
28
32
|
message: body?.message || body?.error || `Request failed with status ${status}`,
|
|
29
33
|
errorType: mapErrorType(code, status),
|
|
30
34
|
retryAfter: retryAfterSeconds(body),
|
|
31
|
-
details: body?.details
|
|
35
|
+
details: mergePublicErrorDetails(body, body?.details)
|
|
32
36
|
});
|
|
33
37
|
}
|
|
34
38
|
function retryAfterSeconds(body) {
|
|
@@ -40,17 +44,35 @@ function publicRetryDetails(body) {
|
|
|
40
44
|
...body?.retry_eligible !== void 0 ? { retry_eligible: body.retry_eligible } : {},
|
|
41
45
|
...body?.retry_after !== void 0 ? { retry_after: body.retry_after } : {},
|
|
42
46
|
...body?.retry_after_seconds !== void 0 ? { retry_after_seconds: body.retry_after_seconds } : {},
|
|
47
|
+
...body?.retry_strategy !== void 0 ? { retry_strategy: body.retry_strategy } : {},
|
|
48
|
+
...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
|
|
49
|
+
...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
|
|
50
|
+
...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
|
|
51
|
+
...body?.idempotency_key !== void 0 ? { idempotency_key: body.idempotency_key } : {},
|
|
52
|
+
...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
|
|
43
53
|
...body?.run_id !== void 0 ? { run_id: body.run_id } : {},
|
|
54
|
+
...body?.suggested_action !== void 0 ? { suggested_action: body.suggested_action } : {},
|
|
55
|
+
...body?.do_not_auto_resubmit !== void 0 ? { do_not_auto_resubmit: body.do_not_auto_resubmit } : {},
|
|
56
|
+
...body?.results_cleaned !== void 0 ? { results_cleaned: body.results_cleaned } : {},
|
|
57
|
+
...body?.results_expired !== void 0 ? { results_expired: body.results_expired } : {},
|
|
44
58
|
...body?.signal_run_id !== void 0 ? { signal_run_id: body.signal_run_id } : {},
|
|
45
59
|
...body?.company_signal_run_id !== void 0 ? { company_signal_run_id: body.company_signal_run_id } : {},
|
|
46
60
|
...body?.resume_context_persisted !== void 0 ? { resume_context_persisted: body.resume_context_persisted } : {}
|
|
47
61
|
};
|
|
48
62
|
return Object.keys(details).length > 0 ? details : void 0;
|
|
49
63
|
}
|
|
64
|
+
function mergePublicErrorDetails(body, explicitDetails) {
|
|
65
|
+
const merged = { ...publicRetryDetails(body) || {} };
|
|
66
|
+
if (explicitDetails && typeof explicitDetails === "object" && !Array.isArray(explicitDetails)) {
|
|
67
|
+
Object.assign(merged, explicitDetails);
|
|
68
|
+
}
|
|
69
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
70
|
+
}
|
|
50
71
|
function mapErrorType(code, status) {
|
|
51
72
|
if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
|
|
52
|
-
if (code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400) return "validation";
|
|
73
|
+
if (code === "PAYLOAD_TOO_LARGE" || code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400 || status === 413) return "validation";
|
|
53
74
|
if (code === "NOT_FOUND" || status === 404) return "not_found";
|
|
75
|
+
if (code === "BATCH_RESULTS_EXPIRED" || status === 410) return "not_found";
|
|
54
76
|
if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
|
|
55
77
|
if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
|
|
56
78
|
if (code === "INSUFFICIENT_CREDITS" || status === 402) return "insufficient_credits";
|
|
@@ -61,7 +83,7 @@ function mapErrorType(code, status) {
|
|
|
61
83
|
|
|
62
84
|
// src/client.ts
|
|
63
85
|
var DEFAULT_BASE_URL = "https://api.signaliz.com/functions/v1";
|
|
64
|
-
var DEFAULT_TIMEOUT =
|
|
86
|
+
var DEFAULT_TIMEOUT = 13e4;
|
|
65
87
|
var DEFAULT_MAX_RETRIES = 3;
|
|
66
88
|
var HttpClient = class {
|
|
67
89
|
constructor(config) {
|
|
@@ -75,9 +97,9 @@ var HttpClient = class {
|
|
|
75
97
|
throw new Error("Signaliz: provide either apiKey or clientId + clientSecret");
|
|
76
98
|
}
|
|
77
99
|
}
|
|
78
|
-
async request(functionName, body, method = "POST") {
|
|
100
|
+
async request(functionName, body, method = "POST", options = {}) {
|
|
79
101
|
let lastError;
|
|
80
|
-
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body) : void 0;
|
|
102
|
+
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body, options.automaticIdempotency !== false) : void 0;
|
|
81
103
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
82
104
|
let timer;
|
|
83
105
|
try {
|
|
@@ -108,7 +130,9 @@ var HttpClient = class {
|
|
|
108
130
|
const errBody = await res.json().catch(() => ({}));
|
|
109
131
|
const err = parseError(res.status, errBody);
|
|
110
132
|
if (err.isRetryable && attempt < this.maxRetries) {
|
|
111
|
-
const
|
|
133
|
+
const retryWithNewKey = err.details?.retry_strategy === "new_idempotency_key" && err.details?.credits_used === 0 && err.details?.credits_charged === 0;
|
|
134
|
+
if (retryWithNewKey) throw err;
|
|
135
|
+
const wait = err.retryAfter !== void 0 ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
|
|
112
136
|
await sleep(wait);
|
|
113
137
|
lastError = err;
|
|
114
138
|
continue;
|
|
@@ -143,8 +167,8 @@ var HttpClient = class {
|
|
|
143
167
|
throw lastError || new Error("Request failed after retries");
|
|
144
168
|
}
|
|
145
169
|
/** Convenience wrapper for POST-based edge function calls */
|
|
146
|
-
async post(functionName, body) {
|
|
147
|
-
return this.request(functionName, body, "POST");
|
|
170
|
+
async post(functionName, body, options = {}) {
|
|
171
|
+
return this.request(functionName, body, "POST", options);
|
|
148
172
|
}
|
|
149
173
|
/** Convenience wrapper for GET-based edge function calls with query params */
|
|
150
174
|
async get(functionName, query = {}) {
|
|
@@ -261,10 +285,12 @@ var HttpClient = class {
|
|
|
261
285
|
return this.accessTokenPromise;
|
|
262
286
|
}
|
|
263
287
|
};
|
|
264
|
-
function requestIdempotencyKey(body) {
|
|
288
|
+
function requestIdempotencyKey(body, automaticIdempotency) {
|
|
265
289
|
const requested = typeof body.idempotency_key === "string" ? body.idempotency_key.trim() : "";
|
|
290
|
+
if (requested) return requested;
|
|
291
|
+
if (!automaticIdempotency) return void 0;
|
|
266
292
|
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
267
|
-
return
|
|
293
|
+
return `sdk_${nonce}`;
|
|
268
294
|
}
|
|
269
295
|
function sleep(ms) {
|
|
270
296
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -366,7 +392,7 @@ function mapMcpErrorType(error) {
|
|
|
366
392
|
function mapErrorTypeFromCode(code) {
|
|
367
393
|
if (!code) return "unknown";
|
|
368
394
|
if (code === "RATE_LIMITED") return "rate_limited";
|
|
369
|
-
if (code === "INVALID_ARGUMENT" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
|
|
395
|
+
if (code === "INVALID_ARGUMENT" || code === "PAYLOAD_TOO_LARGE" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
|
|
370
396
|
if (code === "AUTH_REQUIRED" || code === "AUTH_001") return "auth_expired";
|
|
371
397
|
if (code === "QUOTA_EXCEEDED") return "insufficient_credits";
|
|
372
398
|
if (code === "BUILD_NOT_READY" || code === "ARTIFACT_NOT_READY") return "not_found";
|
|
@@ -374,6 +400,166 @@ function mapErrorTypeFromCode(code) {
|
|
|
374
400
|
return "unknown";
|
|
375
401
|
}
|
|
376
402
|
|
|
403
|
+
// src/email-validation.ts
|
|
404
|
+
function isValidEmailString(value) {
|
|
405
|
+
if (typeof value !== "string") return false;
|
|
406
|
+
const email = value.trim();
|
|
407
|
+
if (!email || email.length > 254) return false;
|
|
408
|
+
const separator = email.indexOf("@");
|
|
409
|
+
if (separator <= 0 || separator !== email.lastIndexOf("@")) return false;
|
|
410
|
+
const local = email.slice(0, separator);
|
|
411
|
+
const domain = email.slice(separator + 1).toLowerCase();
|
|
412
|
+
if (local.length > 64 || domain.length > 253 || local.startsWith(".") || local.endsWith(".") || local.includes("..") || domain.startsWith(".") || domain.endsWith(".") || domain.includes("..")) return false;
|
|
413
|
+
if (!/^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+$/.test(local)) return false;
|
|
414
|
+
const labels = domain.split(".");
|
|
415
|
+
if (labels.length < 2) return false;
|
|
416
|
+
return labels.every(
|
|
417
|
+
(label) => label.length > 0 && label.length <= 63 && /^[a-z0-9-]+$/.test(label) && !label.startsWith("-") && !label.endsWith("-")
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// src/core-product-input-bounds.ts
|
|
422
|
+
var LARGE_BATCH_MAX_ENCODED_BYTES = 8 * 1024 * 1024;
|
|
423
|
+
var DOMAIN_MAX_UTF8_BYTES = 253;
|
|
424
|
+
var NAME_MAX_UTF8_BYTES = 500;
|
|
425
|
+
var TITLE_MAX_UTF8_BYTES = 500;
|
|
426
|
+
var ID_MAX_UTF8_BYTES = 200;
|
|
427
|
+
var RESEARCH_PROMPT_MAX_UTF8_BYTES = 2e3;
|
|
428
|
+
var CAMPAIGN_OFFER_MAX_UTF8_BYTES = 4e3;
|
|
429
|
+
var SIGNAL_TYPE_MAX_UTF8_BYTES = 100;
|
|
430
|
+
var UTF8_ENCODER = new TextEncoder();
|
|
431
|
+
function byteLength(value) {
|
|
432
|
+
return UTF8_ENCODER.encode(value).byteLength;
|
|
433
|
+
}
|
|
434
|
+
function payloadTooLarge(message, details) {
|
|
435
|
+
throw new SignalizError({
|
|
436
|
+
code: "PAYLOAD_TOO_LARGE",
|
|
437
|
+
message,
|
|
438
|
+
errorType: "validation",
|
|
439
|
+
details: {
|
|
440
|
+
...details,
|
|
441
|
+
retry_eligible: false,
|
|
442
|
+
credits_used: 0,
|
|
443
|
+
credits_charged: 0
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
function assertStringBytes(value, field, maxBytes, index) {
|
|
448
|
+
if (typeof value !== "string") return;
|
|
449
|
+
const encodedBytes = byteLength(value);
|
|
450
|
+
if (encodedBytes <= maxBytes) return;
|
|
451
|
+
payloadTooLarge(
|
|
452
|
+
`${field} is ${encodedBytes} UTF-8 bytes; maximum is ${maxBytes} bytes`,
|
|
453
|
+
{
|
|
454
|
+
field,
|
|
455
|
+
...index !== void 0 ? { index } : {},
|
|
456
|
+
encoded_bytes: encodedBytes,
|
|
457
|
+
max_bytes: maxBytes
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
function assertCompanySignalRows(body) {
|
|
462
|
+
const requests = Array.isArray(body.requests) ? body.requests : [];
|
|
463
|
+
assertStringBytes(
|
|
464
|
+
body.research_prompt,
|
|
465
|
+
"research_prompt",
|
|
466
|
+
RESEARCH_PROMPT_MAX_UTF8_BYTES
|
|
467
|
+
);
|
|
468
|
+
if (Array.isArray(body.signal_types)) {
|
|
469
|
+
body.signal_types.forEach(
|
|
470
|
+
(signalType, index) => assertStringBytes(
|
|
471
|
+
signalType,
|
|
472
|
+
`signal_types[${index}]`,
|
|
473
|
+
SIGNAL_TYPE_MAX_UTF8_BYTES,
|
|
474
|
+
index
|
|
475
|
+
)
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
requests.forEach((value, index) => {
|
|
479
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
480
|
+
const request = value;
|
|
481
|
+
assertStringBytes(
|
|
482
|
+
request.company_domain,
|
|
483
|
+
`requests[${index}].company_domain`,
|
|
484
|
+
DOMAIN_MAX_UTF8_BYTES,
|
|
485
|
+
index
|
|
486
|
+
);
|
|
487
|
+
assertStringBytes(
|
|
488
|
+
request.company_name,
|
|
489
|
+
`requests[${index}].company_name`,
|
|
490
|
+
NAME_MAX_UTF8_BYTES,
|
|
491
|
+
index
|
|
492
|
+
);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
function assertSignalCopyRows(body) {
|
|
496
|
+
const requests = Array.isArray(body.requests) ? body.requests : [];
|
|
497
|
+
requests.forEach((value, index) => {
|
|
498
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
499
|
+
const request = value;
|
|
500
|
+
assertStringBytes(
|
|
501
|
+
request.company_domain,
|
|
502
|
+
`requests[${index}].company_domain`,
|
|
503
|
+
DOMAIN_MAX_UTF8_BYTES,
|
|
504
|
+
index
|
|
505
|
+
);
|
|
506
|
+
assertStringBytes(
|
|
507
|
+
request.person_name,
|
|
508
|
+
`requests[${index}].person_name`,
|
|
509
|
+
NAME_MAX_UTF8_BYTES,
|
|
510
|
+
index
|
|
511
|
+
);
|
|
512
|
+
assertStringBytes(
|
|
513
|
+
request.title,
|
|
514
|
+
`requests[${index}].title`,
|
|
515
|
+
TITLE_MAX_UTF8_BYTES,
|
|
516
|
+
index
|
|
517
|
+
);
|
|
518
|
+
assertStringBytes(
|
|
519
|
+
request.campaign_offer,
|
|
520
|
+
`requests[${index}].campaign_offer`,
|
|
521
|
+
CAMPAIGN_OFFER_MAX_UTF8_BYTES,
|
|
522
|
+
index
|
|
523
|
+
);
|
|
524
|
+
assertStringBytes(
|
|
525
|
+
request.research_prompt,
|
|
526
|
+
`requests[${index}].research_prompt`,
|
|
527
|
+
RESEARCH_PROMPT_MAX_UTF8_BYTES,
|
|
528
|
+
index
|
|
529
|
+
);
|
|
530
|
+
assertStringBytes(
|
|
531
|
+
request.signal_run_id,
|
|
532
|
+
`requests[${index}].signal_run_id`,
|
|
533
|
+
ID_MAX_UTF8_BYTES,
|
|
534
|
+
index
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
function assertLargeCoreProductInputBounds(path, body) {
|
|
539
|
+
const requests = Array.isArray(body.requests) ? body.requests : [];
|
|
540
|
+
if (requests.length <= 25) return;
|
|
541
|
+
assertStringBytes(body.idempotency_key, "idempotency_key", ID_MAX_UTF8_BYTES);
|
|
542
|
+
if (path === "api/v1/company-signals") {
|
|
543
|
+
assertCompanySignalRows(body);
|
|
544
|
+
} else {
|
|
545
|
+
assertSignalCopyRows(body);
|
|
546
|
+
}
|
|
547
|
+
const encoded = JSON.stringify(body);
|
|
548
|
+
if (typeof encoded !== "string") {
|
|
549
|
+
throw new TypeError("Core product batch input must be JSON serializable.");
|
|
550
|
+
}
|
|
551
|
+
const encodedBytes = byteLength(encoded);
|
|
552
|
+
if (encodedBytes <= LARGE_BATCH_MAX_ENCODED_BYTES) return;
|
|
553
|
+
payloadTooLarge(
|
|
554
|
+
`${path === "api/v1/company-signals" ? "Company Signal" : "Signal Copy"} batch request is ${encodedBytes} UTF-8 bytes after JSON encoding; maximum is ${LARGE_BATCH_MAX_ENCODED_BYTES} bytes`,
|
|
555
|
+
{
|
|
556
|
+
field: "requests",
|
|
557
|
+
encoded_bytes: encodedBytes,
|
|
558
|
+
max_bytes: LARGE_BATCH_MAX_ENCODED_BYTES
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
377
563
|
// src/index.ts
|
|
378
564
|
var MAX_ROW_RATE_LIMIT_RETRIES = 2;
|
|
379
565
|
var Signaliz = class {
|
|
@@ -407,9 +593,25 @@ var Signaliz = class {
|
|
|
407
593
|
options
|
|
408
594
|
);
|
|
409
595
|
}
|
|
596
|
+
async resumeFindEmailBatch(jobId, options) {
|
|
597
|
+
const startedAt = Date.now();
|
|
598
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
599
|
+
"api/v1/find-email",
|
|
600
|
+
jobId,
|
|
601
|
+
"Find Email",
|
|
602
|
+
options
|
|
603
|
+
);
|
|
604
|
+
return finalizeCoreBatch(
|
|
605
|
+
total,
|
|
606
|
+
startedAt,
|
|
607
|
+
recoverableRowsToRound(rows, "Find Email"),
|
|
608
|
+
(item) => normalizeFindEmailResult(item.raw),
|
|
609
|
+
options
|
|
610
|
+
);
|
|
611
|
+
}
|
|
410
612
|
async verifyEmail(email, options) {
|
|
411
613
|
const normalizedEmail = typeof email === "string" ? email.trim() : "";
|
|
412
|
-
if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail &&
|
|
614
|
+
if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail && !isValidEmailString(normalizedEmail)) {
|
|
413
615
|
const error = `Invalid email format: "${normalizedEmail}"`;
|
|
414
616
|
return normalizeVerifyEmailResult(normalizedEmail, {
|
|
415
617
|
success: false,
|
|
@@ -459,19 +661,20 @@ var Signaliz = class {
|
|
|
459
661
|
}
|
|
460
662
|
async verifyEmails(emails, options) {
|
|
461
663
|
validateBatchSize(emails);
|
|
664
|
+
const requests = emails.map((email) => verifyEmailBatchRequestBody(email, options));
|
|
462
665
|
if (options?.dryRun === true) {
|
|
463
666
|
return this.runCoreProductDryRun(
|
|
464
667
|
"api/v1/verify-email",
|
|
465
|
-
|
|
668
|
+
requests,
|
|
466
669
|
options.idempotencyKey
|
|
467
670
|
);
|
|
468
671
|
}
|
|
469
672
|
const startedAt = Date.now();
|
|
470
673
|
const round = await this.runCoreBatchRound(
|
|
471
674
|
"api/v1/verify-email",
|
|
472
|
-
|
|
675
|
+
requests.map((request, index) => ({
|
|
473
676
|
index,
|
|
474
|
-
request
|
|
677
|
+
request
|
|
475
678
|
})),
|
|
476
679
|
options
|
|
477
680
|
);
|
|
@@ -479,7 +682,23 @@ var Signaliz = class {
|
|
|
479
682
|
emails.length,
|
|
480
683
|
startedAt,
|
|
481
684
|
round,
|
|
482
|
-
(item) => normalizeVerifyEmailResult(
|
|
685
|
+
(item) => normalizeVerifyEmailResult(String(requests[item.index].email ?? ""), item.raw),
|
|
686
|
+
options
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
async resumeVerifyEmailBatch(jobId, options) {
|
|
690
|
+
const startedAt = Date.now();
|
|
691
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
692
|
+
"api/v1/verify-email",
|
|
693
|
+
jobId,
|
|
694
|
+
"Verify Email",
|
|
695
|
+
options
|
|
696
|
+
);
|
|
697
|
+
return finalizeCoreBatch(
|
|
698
|
+
total,
|
|
699
|
+
startedAt,
|
|
700
|
+
recoverableRowsToRound(rows, "Verify Email"),
|
|
701
|
+
(item) => normalizeVerifyEmailResult(String(item.raw?.email || ""), item.raw),
|
|
483
702
|
options
|
|
484
703
|
);
|
|
485
704
|
}
|
|
@@ -495,25 +714,92 @@ var Signaliz = class {
|
|
|
495
714
|
validateSignalDiscoveryParams(params);
|
|
496
715
|
const data = await this.client.post(
|
|
497
716
|
"api/v1/signals",
|
|
498
|
-
signalDiscoveryRequestBody(params)
|
|
717
|
+
signalDiscoveryRequestBody(params),
|
|
718
|
+
// Signals Everything owns a normalized, workspace-scoped automatic key
|
|
719
|
+
// at the API boundary so SDK, CLI, MCP, and direct REST calls coalesce.
|
|
720
|
+
{ automaticIdempotency: false }
|
|
499
721
|
);
|
|
500
722
|
return normalizeSignalDiscoveryResult(params, data);
|
|
501
723
|
}
|
|
502
724
|
async enrichCompanies(companies, options) {
|
|
503
725
|
validateBatchSize(companies);
|
|
504
726
|
companies.forEach((params) => validateCoreProductMaxWaitMs(params.maxWaitMs));
|
|
727
|
+
const requests = companies.map(companySignalRequestBody);
|
|
728
|
+
if (companies.length > 25 && requests.some((request) => request.include_candidate_evidence === true)) {
|
|
729
|
+
throw new RangeError(
|
|
730
|
+
"includeCandidateEvidence is not supported in Company Signals batches larger than 25 because rejected evidence diagnostics are intentionally excluded from durable scale storage; split diagnostic requests into batches of at most 25."
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
const largeBatch = companySignalLargeBatchSubmission(requests);
|
|
734
|
+
if (companies.length > 25 && !largeBatch) {
|
|
735
|
+
throw new RangeError(
|
|
736
|
+
"Company Signals batches larger than 25 require homogeneous new-enrichment rows with company identity only and uniform controls; split recovery or mixed-control rows into batches of at most 25."
|
|
737
|
+
);
|
|
738
|
+
}
|
|
505
739
|
if (options?.dryRun === true) {
|
|
506
740
|
return this.runCoreProductDryRun(
|
|
507
741
|
"api/v1/company-signals",
|
|
508
|
-
|
|
509
|
-
options.idempotencyKey
|
|
742
|
+
largeBatch?.requests || requests,
|
|
743
|
+
options.idempotencyKey,
|
|
744
|
+
largeBatch?.submissionFields
|
|
510
745
|
);
|
|
511
746
|
}
|
|
512
747
|
const startedAt = Date.now();
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
748
|
+
if (options?.waitForResult === false) {
|
|
749
|
+
if (!largeBatch) {
|
|
750
|
+
throw new RangeError(
|
|
751
|
+
"Company Signals --no-wait requires more than 25 homogeneous new-enrichment rows; recovery reads and mixed controls must use batches of at most 25."
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
return this.submitRecoverableCoreBatchJob(
|
|
755
|
+
"api/v1/company-signals",
|
|
756
|
+
largeBatch.requests,
|
|
757
|
+
"Company Signals",
|
|
758
|
+
options.idempotencyKey,
|
|
759
|
+
companies.map((_, index) => index),
|
|
760
|
+
largeBatch.submissionFields
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
if (largeBatch) {
|
|
764
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
765
|
+
"api/v1/company-signals",
|
|
766
|
+
largeBatch.requests,
|
|
767
|
+
"Company Signals",
|
|
768
|
+
options?.idempotencyKey,
|
|
769
|
+
companies.map((_, index) => index),
|
|
770
|
+
largeBatch.submissionFields
|
|
771
|
+
);
|
|
772
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
773
|
+
"api/v1/company-signals",
|
|
774
|
+
job.jobId,
|
|
775
|
+
"Company Signals",
|
|
776
|
+
{
|
|
777
|
+
pageSize: 25,
|
|
778
|
+
maxWaitMs: options?.maxWaitMs,
|
|
779
|
+
pollIntervalMs: options?.pollIntervalMs,
|
|
780
|
+
idempotencyKey: job.idempotencyKey,
|
|
781
|
+
compactDuplicates: options?.compactDuplicates === true
|
|
782
|
+
}
|
|
783
|
+
);
|
|
784
|
+
if (total !== companies.length) {
|
|
785
|
+
throw new Error(`Company Signals batch returned ${total} results for ${companies.length} requests`);
|
|
786
|
+
}
|
|
787
|
+
const batch = finalizeCoreBatch(
|
|
788
|
+
companies.length,
|
|
789
|
+
startedAt,
|
|
790
|
+
recoverableRowsToRound(rows, "Company Signals"),
|
|
791
|
+
(item) => normalizeCompanySignalResult(companies[item.index], item.raw)
|
|
792
|
+
);
|
|
793
|
+
return compactKnownDuplicateBatch(
|
|
794
|
+
batch,
|
|
795
|
+
dedupeExactBatchItems(
|
|
796
|
+
requests,
|
|
797
|
+
(request) => coreProductBatchRequestKey("api/v1/company-signals", request)
|
|
798
|
+
).sourceIndexByInput,
|
|
799
|
+
options?.compactDuplicates === true
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
const initialTasks = requests.map((request, index) => ({ index, request }));
|
|
517
803
|
const round = await this.runCoreBatchRound("api/v1/company-signals", initialTasks, options);
|
|
518
804
|
const results = round.map((item) => {
|
|
519
805
|
if (!item.success || !item.raw) {
|
|
@@ -536,6 +822,22 @@ var Signaliz = class {
|
|
|
536
822
|
results: compactedResults
|
|
537
823
|
};
|
|
538
824
|
}
|
|
825
|
+
async resumeCompanySignalBatch(jobId, options) {
|
|
826
|
+
const startedAt = Date.now();
|
|
827
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
828
|
+
"api/v1/company-signals",
|
|
829
|
+
jobId,
|
|
830
|
+
"Company Signals",
|
|
831
|
+
options
|
|
832
|
+
);
|
|
833
|
+
return finalizeRecoveredCoreBatch(
|
|
834
|
+
total,
|
|
835
|
+
startedAt,
|
|
836
|
+
recoverableRowsToRound(rows, "Company Signals"),
|
|
837
|
+
(item) => normalizeCompanySignalResult(companySignalParamsFromRecoveredRow(item.raw), item.raw),
|
|
838
|
+
options
|
|
839
|
+
);
|
|
840
|
+
}
|
|
539
841
|
async signalToCopy(params) {
|
|
540
842
|
validateSignalToCopyParams(params);
|
|
541
843
|
const request = signalToCopyRequestBody(params);
|
|
@@ -547,6 +849,7 @@ var Signaliz = class {
|
|
|
547
849
|
async createSignalCopyBatch(requests, options) {
|
|
548
850
|
validateBatchSize(requests);
|
|
549
851
|
requests.forEach(validateSignalToCopyParams);
|
|
852
|
+
validateLargeAvailableDataSignalCopyBatch(requests);
|
|
550
853
|
if (options?.dryRun === true) {
|
|
551
854
|
return this.runCoreProductDryRun(
|
|
552
855
|
"api/v1/signal-to-copy",
|
|
@@ -555,6 +858,59 @@ var Signaliz = class {
|
|
|
555
858
|
);
|
|
556
859
|
}
|
|
557
860
|
const startedAt = Date.now();
|
|
861
|
+
if (requests.length > 25) {
|
|
862
|
+
if (options?.waitForResult === false) {
|
|
863
|
+
return this.submitRecoverableCoreBatchJob(
|
|
864
|
+
"api/v1/signal-to-copy",
|
|
865
|
+
requests.map(availableDataSignalCopyRequestBody),
|
|
866
|
+
"Signal to Copy",
|
|
867
|
+
options.idempotencyKey,
|
|
868
|
+
requests.map((_, index) => index)
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
872
|
+
"api/v1/signal-to-copy",
|
|
873
|
+
requests.map(availableDataSignalCopyRequestBody),
|
|
874
|
+
"Signal to Copy",
|
|
875
|
+
options?.idempotencyKey,
|
|
876
|
+
requests.map((_, index) => index)
|
|
877
|
+
);
|
|
878
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
879
|
+
"api/v1/signal-to-copy",
|
|
880
|
+
job.jobId,
|
|
881
|
+
"Signal to Copy",
|
|
882
|
+
{
|
|
883
|
+
pageSize: 100,
|
|
884
|
+
maxWaitMs: options?.maxWaitMs,
|
|
885
|
+
pollIntervalMs: options?.pollIntervalMs,
|
|
886
|
+
idempotencyKey: job.idempotencyKey,
|
|
887
|
+
compactDuplicates: options?.compactDuplicates === true
|
|
888
|
+
}
|
|
889
|
+
);
|
|
890
|
+
if (total !== requests.length) {
|
|
891
|
+
throw new Error(`Signal to Copy batch returned ${total} results for ${requests.length} requests`);
|
|
892
|
+
}
|
|
893
|
+
const results2 = recoverableSignalCopyRows(rows);
|
|
894
|
+
const succeeded2 = results2.filter((item) => item.success).length;
|
|
895
|
+
const batch = {
|
|
896
|
+
total: requests.length,
|
|
897
|
+
succeeded: succeeded2,
|
|
898
|
+
failed: requests.length - succeeded2,
|
|
899
|
+
durationMs: Date.now() - startedAt,
|
|
900
|
+
results: results2
|
|
901
|
+
};
|
|
902
|
+
return compactKnownDuplicateBatch(
|
|
903
|
+
batch,
|
|
904
|
+
dedupeExactBatchItems(
|
|
905
|
+
requests,
|
|
906
|
+
(params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
|
|
907
|
+
).sourceIndexByInput,
|
|
908
|
+
options?.compactDuplicates === true
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
if (options?.waitForResult === false) {
|
|
912
|
+
throw new RangeError("Signal to Copy --no-wait requires a durable batch of more than 25 rows.");
|
|
913
|
+
}
|
|
558
914
|
const deduplicated = dedupeExactBatchItems(
|
|
559
915
|
requests,
|
|
560
916
|
(params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
|
|
@@ -579,12 +935,36 @@ var Signaliz = class {
|
|
|
579
935
|
results: compactedResults
|
|
580
936
|
};
|
|
581
937
|
}
|
|
582
|
-
async
|
|
583
|
-
const
|
|
938
|
+
async resumeSignalCopyBatch(jobId, options) {
|
|
939
|
+
const startedAt = Date.now();
|
|
940
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
941
|
+
"api/v1/signal-to-copy",
|
|
942
|
+
jobId,
|
|
943
|
+
"Signal to Copy",
|
|
944
|
+
options
|
|
945
|
+
);
|
|
946
|
+
const results = recoverableSignalCopyRows(rows);
|
|
947
|
+
const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
|
|
948
|
+
const succeeded = compactedResults.filter((item) => item.success).length;
|
|
949
|
+
return {
|
|
950
|
+
total,
|
|
951
|
+
succeeded,
|
|
952
|
+
failed: total - succeeded,
|
|
953
|
+
durationMs: Date.now() - startedAt,
|
|
954
|
+
results: compactedResults
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
async runCoreProductDryRun(path, requests, idempotencyKey, defaults = {}) {
|
|
958
|
+
const requestBody = compact({
|
|
584
959
|
requests,
|
|
960
|
+
...defaults,
|
|
585
961
|
dry_run: true,
|
|
586
962
|
idempotency_key: idempotencyKey
|
|
587
|
-
})
|
|
963
|
+
});
|
|
964
|
+
if (path === "api/v1/company-signals" || path === "api/v1/signal-to-copy") {
|
|
965
|
+
assertLargeCoreProductInputBounds(path, requestBody);
|
|
966
|
+
}
|
|
967
|
+
const data = await this.client.post(path, requestBody);
|
|
588
968
|
if (!isCoreProductDryRun(data)) {
|
|
589
969
|
throw new Error(`${path} dry run returned a live-result contract`);
|
|
590
970
|
}
|
|
@@ -630,76 +1010,209 @@ var Signaliz = class {
|
|
|
630
1010
|
}
|
|
631
1011
|
return uniqueResults;
|
|
632
1012
|
}
|
|
633
|
-
async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices) {
|
|
634
|
-
const
|
|
1013
|
+
async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
|
|
1014
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
1015
|
+
path,
|
|
1016
|
+
requests,
|
|
1017
|
+
label,
|
|
1018
|
+
idempotencyKey,
|
|
1019
|
+
idempotencyItemIndices,
|
|
1020
|
+
submissionFields
|
|
1021
|
+
);
|
|
1022
|
+
const recovered = await this.readRecoverableCoreBatchJob(path, job.jobId, label, {
|
|
1023
|
+
pageSize,
|
|
1024
|
+
maxWaitMs,
|
|
1025
|
+
idempotencyKey: job.idempotencyKey
|
|
1026
|
+
});
|
|
1027
|
+
if (recovered.total !== requests.length) {
|
|
1028
|
+
throw new Error(`${label} batch returned ${recovered.total} results for ${requests.length} requests`);
|
|
1029
|
+
}
|
|
1030
|
+
return recovered.rows;
|
|
1031
|
+
}
|
|
1032
|
+
async submitRecoverableCoreBatchJob(path, requests, label, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
|
|
635
1033
|
const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
|
|
1034
|
+
const requestBody = {
|
|
1035
|
+
...submissionFields,
|
|
1036
|
+
requests,
|
|
1037
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1038
|
+
batch_item_indices: idempotencyItemIndices
|
|
1039
|
+
};
|
|
1040
|
+
if (path === "api/v1/company-signals" || path === "api/v1/signal-to-copy") {
|
|
1041
|
+
assertLargeCoreProductInputBounds(path, requestBody);
|
|
1042
|
+
}
|
|
636
1043
|
let submission;
|
|
637
1044
|
try {
|
|
638
|
-
submission = await this.client.post(path,
|
|
639
|
-
requests,
|
|
640
|
-
idempotency_key: submissionIdempotencyKey,
|
|
641
|
-
batch_item_indices: idempotencyItemIndices
|
|
642
|
-
});
|
|
1045
|
+
submission = await this.client.post(path, requestBody);
|
|
643
1046
|
} catch (error) {
|
|
644
1047
|
if (error instanceof SignalizError && !error.isRetryable) throw error;
|
|
645
1048
|
const message = error instanceof Error ? error.message : String(error);
|
|
646
|
-
throw new
|
|
647
|
-
|
|
648
|
-
|
|
1049
|
+
throw new SignalizError({
|
|
1050
|
+
code: "BATCH_SUBMISSION_UNCONFIRMED",
|
|
1051
|
+
message: `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`,
|
|
1052
|
+
errorType: "provider_error",
|
|
1053
|
+
details: {
|
|
1054
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1055
|
+
suggested_action: "retry_with_idempotency_key",
|
|
1056
|
+
retry_eligible: true
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
649
1059
|
}
|
|
650
1060
|
const jobId = String(submission.job_id || "").trim();
|
|
651
|
-
if (!jobId)
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
1061
|
+
if (!jobId) {
|
|
1062
|
+
throw new SignalizError({
|
|
1063
|
+
code: "INVALID_JOB_RESPONSE",
|
|
1064
|
+
message: `${label} batch did not return a recoverable job_id`,
|
|
1065
|
+
errorType: "provider_error",
|
|
1066
|
+
details: {
|
|
1067
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1068
|
+
suggested_action: "retry_with_idempotency_key",
|
|
1069
|
+
retry_eligible: true
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
const submissionStatus = String(submission.status || "").toLowerCase();
|
|
1074
|
+
const status = ["queued", "processing", "completed", "partial", "failed"].includes(submissionStatus) ? submissionStatus : "queued";
|
|
1075
|
+
return {
|
|
1076
|
+
success: true,
|
|
1077
|
+
status,
|
|
1078
|
+
capability: path === "api/v1/company-signals" ? "company_signals" : "signal_to_copy",
|
|
1079
|
+
jobId,
|
|
1080
|
+
idempotencyKey: String(submission.idempotency_key || submissionIdempotencyKey),
|
|
1081
|
+
total: Math.max(0, Number(submission.total ?? submission.items_total ?? requests.length)),
|
|
1082
|
+
...Number.isFinite(Number(submission.next_poll_after_seconds)) ? {
|
|
1083
|
+
nextPollAfterSeconds: Number(submission.next_poll_after_seconds)
|
|
1084
|
+
} : {}
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
async readRecoverableCoreBatchJob(path, jobIdInput, label, options = {}) {
|
|
1088
|
+
const jobId = String(jobIdInput || "").trim();
|
|
1089
|
+
if (!jobId) throw new RangeError(`${label} jobId is required`);
|
|
1090
|
+
if (options.pageSize !== void 0 && (!Number.isFinite(options.pageSize) || options.pageSize <= 0)) {
|
|
1091
|
+
throw new RangeError("pageSize must be a positive finite number");
|
|
1092
|
+
}
|
|
1093
|
+
if (options.maxWaitMs !== void 0 && (!Number.isFinite(options.maxWaitMs) || options.maxWaitMs <= 0)) {
|
|
1094
|
+
throw new RangeError("maxWaitMs must be a positive finite number");
|
|
1095
|
+
}
|
|
1096
|
+
if (options.pollIntervalMs !== void 0 && (!Number.isFinite(options.pollIntervalMs) || options.pollIntervalMs <= 0)) {
|
|
1097
|
+
throw new RangeError("pollIntervalMs must be a positive finite number");
|
|
1098
|
+
}
|
|
1099
|
+
const maximumPageSize = path === "api/v1/signal-to-copy" ? 100 : 25;
|
|
1100
|
+
const pageSize = Math.min(maximumPageSize, Math.max(1, Math.trunc(options.pageSize ?? maximumPageSize)));
|
|
1101
|
+
const emailJob = path === "api/v1/find-email" || path === "api/v1/verify-email";
|
|
1102
|
+
const maxWaitMs = options.maxWaitMs ?? (emailJob ? 20 * 6e4 : void 0);
|
|
1103
|
+
const startedAt = Date.now();
|
|
1104
|
+
let observedIdempotencyKey = String(options.idempotencyKey || "").trim();
|
|
1105
|
+
try {
|
|
1106
|
+
let status = await this.client.post(path, {
|
|
664
1107
|
job_id: jobId,
|
|
665
1108
|
page: 1,
|
|
666
|
-
page_size: pageSize
|
|
1109
|
+
page_size: pageSize,
|
|
1110
|
+
compact_duplicates: options.compactDuplicates === true
|
|
667
1111
|
});
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
pageNumbers,
|
|
675
|
-
async (page) => await this.client.post(path, {
|
|
676
|
-
job_id: jobId,
|
|
677
|
-
page,
|
|
678
|
-
page_size: pageSize
|
|
679
|
-
}),
|
|
680
|
-
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
1112
|
+
observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
|
|
1113
|
+
assertRecoverableBatchResultsRetained(
|
|
1114
|
+
status,
|
|
1115
|
+
label,
|
|
1116
|
+
jobId,
|
|
1117
|
+
observedIdempotencyKey
|
|
681
1118
|
);
|
|
682
|
-
|
|
683
|
-
if (
|
|
684
|
-
throw new
|
|
1119
|
+
while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
|
|
1120
|
+
if (maxWaitMs !== void 0 && Date.now() - startedAt >= maxWaitMs) {
|
|
1121
|
+
throw new SignalizError({
|
|
1122
|
+
code: "TIMEOUT",
|
|
1123
|
+
message: `${label} batch ${jobId} did not complete within ${maxWaitMs}ms`,
|
|
1124
|
+
errorType: "timeout",
|
|
1125
|
+
retryAfter: Number(status.next_poll_after_seconds || 2),
|
|
1126
|
+
details: {
|
|
1127
|
+
job_id: jobId,
|
|
1128
|
+
...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
|
|
1129
|
+
suggested_action: "check_job_status",
|
|
1130
|
+
retry_eligible: true
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
685
1133
|
}
|
|
686
|
-
|
|
1134
|
+
const retryAfterMs = options.pollIntervalMs ?? Number(status.next_poll_after_seconds || 2) * 1e3;
|
|
1135
|
+
const boundedDelay = maxWaitMs === void 0 ? Math.max(250, retryAfterMs) : Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt));
|
|
1136
|
+
await sleep2(boundedDelay);
|
|
1137
|
+
status = await this.client.post(path, {
|
|
1138
|
+
job_id: jobId,
|
|
1139
|
+
page: 1,
|
|
1140
|
+
page_size: pageSize,
|
|
1141
|
+
compact_duplicates: options.compactDuplicates === true
|
|
1142
|
+
});
|
|
1143
|
+
observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
|
|
1144
|
+
assertRecoverableBatchResultsRetained(
|
|
1145
|
+
status,
|
|
1146
|
+
label,
|
|
1147
|
+
jobId,
|
|
1148
|
+
observedIdempotencyKey
|
|
1149
|
+
);
|
|
687
1150
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1151
|
+
const totalPages = Math.max(1, Number(status.total_pages || 1));
|
|
1152
|
+
const pages = [Array.isArray(status.results) ? status.results : []];
|
|
1153
|
+
if (totalPages > 1) {
|
|
1154
|
+
const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
|
|
1155
|
+
const remainingPages = await runBatch(
|
|
1156
|
+
pageNumbers,
|
|
1157
|
+
async (page) => await this.client.post(path, {
|
|
1158
|
+
job_id: jobId,
|
|
1159
|
+
page,
|
|
1160
|
+
page_size: pageSize,
|
|
1161
|
+
compact_duplicates: options.compactDuplicates === true
|
|
1162
|
+
}),
|
|
1163
|
+
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
1164
|
+
);
|
|
1165
|
+
for (const page of remainingPages.results) {
|
|
1166
|
+
if (!page.success || !page.data) {
|
|
1167
|
+
throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
|
|
1168
|
+
}
|
|
1169
|
+
observedIdempotencyKey = String(
|
|
1170
|
+
page.data.idempotency_key || observedIdempotencyKey
|
|
1171
|
+
).trim();
|
|
1172
|
+
assertRecoverableBatchResultsRetained(
|
|
1173
|
+
page.data,
|
|
1174
|
+
label,
|
|
1175
|
+
jobId,
|
|
1176
|
+
observedIdempotencyKey
|
|
1177
|
+
);
|
|
1178
|
+
pages.push(Array.isArray(page.data.results) ? page.data.results : []);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
const rows = pages.flat();
|
|
1182
|
+
const total = Math.max(0, Number(status.total_results ?? status.total ?? status.items_total ?? rows.length));
|
|
1183
|
+
if (rows.length !== total) {
|
|
1184
|
+
throw new Error(`${label} batch returned ${rows.length} results for ${total} job rows`);
|
|
1185
|
+
}
|
|
1186
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1187
|
+
for (const row of rows) {
|
|
1188
|
+
const index = Number(row.index);
|
|
1189
|
+
if (!Number.isInteger(index) || index < 0 || seen.has(index)) {
|
|
1190
|
+
throw new Error(`${label} batch returned an invalid result index`);
|
|
1191
|
+
}
|
|
1192
|
+
seen.add(index);
|
|
1193
|
+
}
|
|
1194
|
+
if (seen.size !== total) throw new Error(`${label} batch returned incomplete indexed results`);
|
|
1195
|
+
return { rows, total };
|
|
1196
|
+
} catch (error) {
|
|
1197
|
+
if (error instanceof SignalizError && String(error.details?.job_id || "") === jobId) {
|
|
1198
|
+
throw error;
|
|
698
1199
|
}
|
|
699
|
-
|
|
1200
|
+
const signalizError = error instanceof SignalizError ? error : null;
|
|
1201
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1202
|
+
throw new SignalizError({
|
|
1203
|
+
code: signalizError?.code || "BATCH_RECOVERY_FAILED",
|
|
1204
|
+
message: `${label} batch ${jobId} could not be fully retrieved. Resume the existing job instead of resubmitting it. ${message}`,
|
|
1205
|
+
errorType: signalizError?.errorType || "provider_error",
|
|
1206
|
+
retryAfter: signalizError?.retryAfter,
|
|
1207
|
+
details: {
|
|
1208
|
+
...signalizError?.details || {},
|
|
1209
|
+
job_id: jobId,
|
|
1210
|
+
...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
|
|
1211
|
+
suggested_action: "check_job_status",
|
|
1212
|
+
retry_eligible: true
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
700
1215
|
}
|
|
701
|
-
if (seen.size !== requests.length) throw new Error(`${label} batch returned incomplete indexed results`);
|
|
702
|
-
return rows;
|
|
703
1216
|
}
|
|
704
1217
|
async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0, idempotencyKey, idempotencyItemIndices = requests.map((_, index) => index)) {
|
|
705
1218
|
const results = new Array(requests.length);
|
|
@@ -764,8 +1277,11 @@ var Signaliz = class {
|
|
|
764
1277
|
(task) => coreProductBatchRequestKey(path, task.request)
|
|
765
1278
|
);
|
|
766
1279
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
767
|
-
const recoverableBatch = path === "api/v1/find-email" ? { label: "Find Email", pageSize:
|
|
768
|
-
|
|
1280
|
+
const recoverableBatch = path === "api/v1/find-email" ? { label: "Find Email", pageSize: 25, maxWaitMs: 20 * 6e4 } : path === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 25, maxWaitMs: 20 * 6e4 } : void 0;
|
|
1281
|
+
const containsFindRecoveryReads = path === "api/v1/find-email" && uniqueTasks.some(
|
|
1282
|
+
(task) => typeof task.request.run_id === "string" && task.request.run_id.trim()
|
|
1283
|
+
);
|
|
1284
|
+
if (recoverableBatch && uniqueTasks.length > 25 && !containsFindRecoveryReads) {
|
|
769
1285
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
770
1286
|
path,
|
|
771
1287
|
uniqueTasks.map((task) => task.request),
|
|
@@ -891,18 +1407,68 @@ function batchItemFailure(index, error, raw) {
|
|
|
891
1407
|
raw?.retry_after_seconds ?? raw?.retry_after ?? raw?.retryAfterSeconds ?? (Number.isFinite(Number(raw?.retry_after_ms)) ? Number(raw?.retry_after_ms) / 1e3 : void 0)
|
|
892
1408
|
);
|
|
893
1409
|
const retryEligible = raw?.retry_eligible ?? raw?.retryEligible;
|
|
1410
|
+
const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
|
|
1411
|
+
const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
|
|
1412
|
+
const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
|
|
1413
|
+
const runId = [raw?.run_id, raw?.runId, raw?.signal_run_id, raw?.signalRunId].find((value) => typeof value === "string" && value.trim());
|
|
1414
|
+
const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
|
|
1415
|
+
const jobId = raw?.job_id ?? raw?.jobId;
|
|
1416
|
+
const suggestedAction = raw?.suggested_action ?? raw?.suggestedAction;
|
|
894
1417
|
return {
|
|
895
1418
|
index,
|
|
896
1419
|
success: false,
|
|
897
1420
|
error,
|
|
898
1421
|
...errorCode ? { errorCode } : {},
|
|
899
1422
|
...typeof retryEligible === "boolean" ? { retryEligible } : {},
|
|
900
|
-
...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {}
|
|
1423
|
+
...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {},
|
|
1424
|
+
...retryStrategy === "new_idempotency_key" ? { retryStrategy } : {},
|
|
1425
|
+
...creditsUsed === 0 ? { creditsUsed: 0 } : {},
|
|
1426
|
+
...creditsCharged === 0 ? { creditsCharged: 0 } : {},
|
|
1427
|
+
...typeof runId === "string" && runId.trim() ? { runId: runId.trim() } : {},
|
|
1428
|
+
...typeof verificationRunId === "string" && verificationRunId.trim() ? { verificationRunId: verificationRunId.trim() } : {},
|
|
1429
|
+
...typeof jobId === "string" && jobId.trim() ? { jobId: jobId.trim() } : {},
|
|
1430
|
+
...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {}
|
|
901
1431
|
};
|
|
902
1432
|
}
|
|
903
1433
|
function recoverableJobRowFailed(row) {
|
|
904
1434
|
return coreProductResponseFailed(row) || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
|
|
905
1435
|
}
|
|
1436
|
+
function recoverableRowsToRound(rows, label) {
|
|
1437
|
+
return rows.map((raw) => {
|
|
1438
|
+
const index = Number(raw.index);
|
|
1439
|
+
const failed = recoverableJobRowFailed(raw);
|
|
1440
|
+
return {
|
|
1441
|
+
index,
|
|
1442
|
+
success: !failed,
|
|
1443
|
+
raw,
|
|
1444
|
+
error: failed ? raw.error || raw._error || raw.message || raw.failure_reason || `${label} item failed` : void 0
|
|
1445
|
+
};
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
function recoverableSignalCopyRows(rows) {
|
|
1449
|
+
return rows.map((raw) => {
|
|
1450
|
+
const index = Number(raw.index);
|
|
1451
|
+
const duplicateOf = recoveredDuplicateOf(raw);
|
|
1452
|
+
if (duplicateOf !== void 0) return { index, success: true, duplicateOf };
|
|
1453
|
+
if (recoverableJobRowFailed(raw)) {
|
|
1454
|
+
return batchItemFailure(
|
|
1455
|
+
index,
|
|
1456
|
+
raw.error || raw._error || raw.message || "Signal to Copy item failed",
|
|
1457
|
+
raw
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
try {
|
|
1461
|
+
assertTerminalSignalResponse(raw, "Signal to Copy");
|
|
1462
|
+
return { index, success: true, data: normalizeSignalToCopyResult(raw) };
|
|
1463
|
+
} catch (error) {
|
|
1464
|
+
return batchItemFailure(index, error instanceof Error ? error.message : String(error), raw);
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
function recoveredDuplicateOf(raw) {
|
|
1469
|
+
const value = Number(raw.duplicate_of ?? raw.duplicateOf ?? raw._duplicate_of);
|
|
1470
|
+
return Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
1471
|
+
}
|
|
906
1472
|
function newBatchIdempotencyKey(label) {
|
|
907
1473
|
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
908
1474
|
return `signaliz-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${nonce}`;
|
|
@@ -912,6 +1478,7 @@ function compact(input) {
|
|
|
912
1478
|
}
|
|
913
1479
|
function findEmailRequestBody(params) {
|
|
914
1480
|
return compact({
|
|
1481
|
+
run_id: params.runId,
|
|
915
1482
|
company_domain: params.companyDomain,
|
|
916
1483
|
full_name: params.fullName,
|
|
917
1484
|
first_name: params.firstName,
|
|
@@ -923,6 +1490,16 @@ function findEmailRequestBody(params) {
|
|
|
923
1490
|
idempotency_key: params.idempotencyKey
|
|
924
1491
|
});
|
|
925
1492
|
}
|
|
1493
|
+
function verifyEmailBatchRequestBody(input, options) {
|
|
1494
|
+
if (typeof input === "string") {
|
|
1495
|
+
return compact({ email: input, skip_cache: options?.skipCache });
|
|
1496
|
+
}
|
|
1497
|
+
return compact({
|
|
1498
|
+
email: input.email,
|
|
1499
|
+
skip_cache: input.skipCache ?? options?.skipCache,
|
|
1500
|
+
idempotency_key: input.idempotencyKey
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
926
1503
|
function isCoreProductDryRun(data) {
|
|
927
1504
|
return data.dry_run === true && data.status === "planned";
|
|
928
1505
|
}
|
|
@@ -951,76 +1528,111 @@ function normalizeCoreProductDryRunResult(data) {
|
|
|
951
1528
|
};
|
|
952
1529
|
}
|
|
953
1530
|
function normalizeFindEmailResult(data) {
|
|
1531
|
+
const processing = String(data.status || "").toLowerCase() === "processing";
|
|
954
1532
|
const failed = coreProductResponseFailed(data);
|
|
955
1533
|
const rawEmail = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
|
|
956
1534
|
const email = failed ? null : rawEmail;
|
|
957
1535
|
const found = Boolean(email) && data.found !== false;
|
|
958
1536
|
const verificationStatus = data.verification_status ?? (data.is_valid === true ? "valid" : data.status ?? "unknown");
|
|
1537
|
+
const deliverabilityStatus = data.deliverability_status ?? verificationStatus;
|
|
1538
|
+
const verificationVerdict = data.verification_verdict ?? verificationStatus;
|
|
959
1539
|
const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
|
|
960
1540
|
const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
|
|
961
1541
|
const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
|
|
962
1542
|
const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
|
|
963
1543
|
const verified = !explicitlyNotSendSafe && (data.is_valid === true || data.isValid === true || data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
|
|
964
1544
|
const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
|
|
1545
|
+
const providerUsed = data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown";
|
|
1546
|
+
const verificationSource = data.verification_source ?? providerUsed;
|
|
1547
|
+
const billingMetadata = data.billing_metadata && typeof data.billing_metadata === "object" && !Array.isArray(data.billing_metadata) ? data.billing_metadata : void 0;
|
|
965
1548
|
return {
|
|
966
|
-
success: !failed && found,
|
|
1549
|
+
success: processing || !failed && found,
|
|
967
1550
|
found,
|
|
968
1551
|
email,
|
|
969
1552
|
firstName: data.first_name,
|
|
970
1553
|
lastName: data.last_name,
|
|
971
1554
|
confidence: failed ? 0 : data.confidence ?? data.confidence_score ?? (verified ? 1 : 0),
|
|
972
1555
|
verificationStatus: failed ? "error" : verificationStatus,
|
|
1556
|
+
deliverabilityStatus: failed ? "error" : deliverabilityStatus,
|
|
1557
|
+
verificationVerdict: failed ? "error" : verificationVerdict,
|
|
973
1558
|
isVerified: !failed && verified,
|
|
974
1559
|
isVerifiedForSending: !failed && !needsReverification && data.verified_for_sending === true,
|
|
1560
|
+
isDeliverable: !failed && !needsReverification && data.verified_for_sending === true,
|
|
975
1561
|
isCatchAll: data.email_is_catch_all === true || data.is_catch_all === true,
|
|
976
1562
|
verificationFreshness,
|
|
977
1563
|
verificationAgeDays,
|
|
978
1564
|
verifiedAt: typeof data.verified_at === "string" ? data.verified_at : null,
|
|
979
1565
|
needsReverification: failed || needsReverification,
|
|
980
|
-
providerUsed
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1566
|
+
providerUsed,
|
|
1567
|
+
verificationSource,
|
|
1568
|
+
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1569
|
+
failureReason: data.failure_reason ?? (failed || !found ? "no_verified_email_found" : "none"),
|
|
1570
|
+
creditsUsed: data.credits_used ?? billingMetadata?.credits_used,
|
|
1571
|
+
estimatedExternalCostUsd: data.estimated_external_cost_usd ?? billingMetadata?.estimated_external_cost_usd,
|
|
1572
|
+
billingMetadata,
|
|
1573
|
+
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1574
|
+
resultReturnedFrom: data.result_returned_from ?? billingMetadata?.result_returned_from,
|
|
1575
|
+
cacheHit: typeof data.cache_hit === "boolean" ? data.cache_hit : billingMetadata?.cache_hit,
|
|
986
1576
|
negativeCacheHit: data.negative_cache_hit,
|
|
987
1577
|
negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
|
|
988
1578
|
cacheTier: data.cache_tier,
|
|
989
|
-
freshEnrichmentUsed: data.fresh_enrichment_used,
|
|
990
|
-
liveProviderCalled: data.live_provider_called,
|
|
991
|
-
openrouterCalled: data.openrouter_called,
|
|
992
|
-
byoOpenrouterUsed: data.byo_openrouter_used,
|
|
993
|
-
error: found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
|
|
994
|
-
errorCode: found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
|
|
1579
|
+
freshEnrichmentUsed: typeof data.fresh_enrichment_used === "boolean" ? data.fresh_enrichment_used : billingMetadata?.fresh_enrichment_used,
|
|
1580
|
+
liveProviderCalled: typeof data.live_provider_called === "boolean" ? data.live_provider_called : billingMetadata?.live_provider_called,
|
|
1581
|
+
openrouterCalled: typeof data.openrouter_called === "boolean" ? data.openrouter_called : billingMetadata?.openrouter_called,
|
|
1582
|
+
byoOpenrouterUsed: typeof data.byo_openrouter_used === "boolean" ? data.byo_openrouter_used : billingMetadata?.byo_openrouter_used,
|
|
1583
|
+
error: processing || found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
|
|
1584
|
+
errorCode: processing || found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
|
|
1585
|
+
status: processing ? "processing" : "completed",
|
|
1586
|
+
runId: data.run_id,
|
|
1587
|
+
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1588
|
+
suggestedAction: data.suggested_action,
|
|
1589
|
+
cachePublicationStatus: data.cache_publication_status,
|
|
1590
|
+
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
995
1591
|
raw: data
|
|
996
1592
|
};
|
|
997
1593
|
}
|
|
998
1594
|
function normalizeVerifyEmailResult(email, data) {
|
|
999
1595
|
const failed = coreProductResponseFailed(data);
|
|
1000
|
-
const verificationStatus = String(data.verification_status ?? "").toLowerCase();
|
|
1001
|
-
const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
|
|
1002
1596
|
const malformed = data.is_malformed === true || data.verification_verdict === "malformed" || data.verification_status === "malformed" || data.deliverability_status === "malformed";
|
|
1597
|
+
const verificationStatus = String(
|
|
1598
|
+
failed ? malformed ? "malformed" : "error" : data.verification_status ?? data.verification_verdict ?? data.deliverability_status ?? "unknown"
|
|
1599
|
+
).toLowerCase();
|
|
1600
|
+
const deliverabilityStatus = String(data.deliverability_status ?? verificationStatus).toLowerCase();
|
|
1601
|
+
const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
|
|
1602
|
+
const isCatchAll = data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false;
|
|
1603
|
+
const verifiedForSending = !failed && data.verified_for_sending === true;
|
|
1604
|
+
const verificationRunId = data.verification_run_id ?? data.run_id;
|
|
1003
1605
|
return {
|
|
1004
1606
|
success: !failed,
|
|
1005
1607
|
error: failed ? coreProductErrorMessage(data) : void 0,
|
|
1006
1608
|
errorCode: failed ? coreProductErrorCode(data) : void 0,
|
|
1007
1609
|
email: data.email ?? email,
|
|
1008
1610
|
status: !failed && String(data.status || "").toLowerCase() === "processing" ? "processing" : "completed",
|
|
1009
|
-
verificationRunId
|
|
1611
|
+
verificationRunId,
|
|
1010
1612
|
retryAfterMs: data.retry_after_ms,
|
|
1011
1613
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1012
1614
|
isValid: failed ? false : data.is_valid ?? data.valid ?? verified,
|
|
1013
1615
|
isDeliverable: failed ? false : data.is_deliverable ?? data.deliverable ?? verified,
|
|
1014
1616
|
isMalformed: malformed,
|
|
1015
|
-
isCatchAll
|
|
1016
|
-
verifiedForSending
|
|
1617
|
+
isCatchAll,
|
|
1618
|
+
verifiedForSending,
|
|
1619
|
+
verificationStatus,
|
|
1620
|
+
deliverabilityStatus,
|
|
1017
1621
|
verificationVerdict: failed ? malformed ? "malformed" : "error" : data.verification_verdict ?? data.verification_status ?? "unknown",
|
|
1622
|
+
isRoleAccount: data.is_role_account === true || data.email_is_role_account === true || data.role === true,
|
|
1623
|
+
quality: typeof data.quality === "string" ? data.quality : null,
|
|
1624
|
+
recommendation: typeof data.recommendation === "string" ? data.recommendation : verifiedForSending ? "send" : ["unknown", "verifier_error"].includes(verificationStatus) ? "manual_review" : "do_not_send",
|
|
1625
|
+
smtpStatus: data.smtp_status ?? (isCatchAll ? "accept_all" : verifiedForSending ? "accepted" : "not_checked"),
|
|
1626
|
+
providerStatus: data.provider_status ?? verificationStatus,
|
|
1627
|
+
failureReason: data.failure_reason ?? (verifiedForSending ? "none" : isCatchAll ? "catch_all_domain_not_send_safe" : "verification_not_send_safe"),
|
|
1018
1628
|
confidenceScore: failed ? 0 : data.confidence_score ?? data.confidence ?? data.diagnostics?.confidence_score ?? (verified ? 1 : 0),
|
|
1019
1629
|
provider: data.provider,
|
|
1020
1630
|
verificationSource: data.verification_source,
|
|
1631
|
+
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1021
1632
|
billingReplayed: data.billing_replayed,
|
|
1022
1633
|
creditsUsed: data.credits_used,
|
|
1023
1634
|
billingMetadata: data.billing_metadata,
|
|
1635
|
+
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1024
1636
|
resultReturnedFrom: data.result_returned_from,
|
|
1025
1637
|
cacheHit: data.cache_hit,
|
|
1026
1638
|
negativeCacheHit: data.negative_cache_hit,
|
|
@@ -1030,6 +1642,11 @@ function normalizeVerifyEmailResult(email, data) {
|
|
|
1030
1642
|
liveProviderCalled: data.live_provider_called,
|
|
1031
1643
|
openrouterCalled: data.openrouter_called,
|
|
1032
1644
|
byoOpenrouterUsed: data.byo_openrouter_used,
|
|
1645
|
+
estimatedExternalCostUsd: data.estimated_external_cost_usd,
|
|
1646
|
+
runId: data.run_id ?? verificationRunId,
|
|
1647
|
+
cachePublicationStatus: data.cache_publication_status,
|
|
1648
|
+
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1649
|
+
suggestedAction: data.suggested_action,
|
|
1033
1650
|
raw: data
|
|
1034
1651
|
};
|
|
1035
1652
|
}
|
|
@@ -1057,6 +1674,54 @@ function companySignalRequestBody(params) {
|
|
|
1057
1674
|
idempotency_key: params.idempotencyKey
|
|
1058
1675
|
});
|
|
1059
1676
|
}
|
|
1677
|
+
function companySignalParamsFromRecoveredRow(row) {
|
|
1678
|
+
return {
|
|
1679
|
+
companyDomain: row.company?.domain ?? row.company_domain,
|
|
1680
|
+
companyName: row.company?.name ?? row.company_name,
|
|
1681
|
+
signalRunId: row.signal_run_id ?? row.run_id
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
var COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS = [
|
|
1685
|
+
"research_prompt",
|
|
1686
|
+
"signal_types",
|
|
1687
|
+
"target_signal_count",
|
|
1688
|
+
"lookback_days",
|
|
1689
|
+
"online",
|
|
1690
|
+
"enable_deep_search",
|
|
1691
|
+
"search_mode",
|
|
1692
|
+
"include_summary",
|
|
1693
|
+
"enable_outreach_intelligence",
|
|
1694
|
+
"enable_predictive_intelligence",
|
|
1695
|
+
"skip_cache",
|
|
1696
|
+
"include_candidate_evidence"
|
|
1697
|
+
];
|
|
1698
|
+
function companySignalLargeBatchSubmission(requests) {
|
|
1699
|
+
if (requests.length <= 25) return null;
|
|
1700
|
+
if (requests.some(
|
|
1701
|
+
(request) => typeof request.signal_run_id === "string" && request.signal_run_id.trim() || typeof request.idempotency_key === "string" && request.idempotency_key.trim()
|
|
1702
|
+
)) {
|
|
1703
|
+
return null;
|
|
1704
|
+
}
|
|
1705
|
+
const submissionFields = Object.fromEntries(
|
|
1706
|
+
COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => requests[0]?.[field] !== void 0).map((field) => [field, requests[0][field]])
|
|
1707
|
+
);
|
|
1708
|
+
const controlKey = JSON.stringify(submissionFields);
|
|
1709
|
+
for (const request of requests) {
|
|
1710
|
+
const requestControls = Object.fromEntries(
|
|
1711
|
+
COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => request[field] !== void 0).map((field) => [field, request[field]])
|
|
1712
|
+
);
|
|
1713
|
+
if (JSON.stringify(requestControls) !== controlKey) return null;
|
|
1714
|
+
const hasIdentity = typeof request.company_domain === "string" && request.company_domain.trim() || typeof request.company_name === "string" && request.company_name.trim();
|
|
1715
|
+
if (!hasIdentity) return null;
|
|
1716
|
+
}
|
|
1717
|
+
return {
|
|
1718
|
+
requests: requests.map((request) => compact({
|
|
1719
|
+
company_domain: request.company_domain,
|
|
1720
|
+
company_name: request.company_name
|
|
1721
|
+
})),
|
|
1722
|
+
submissionFields
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1060
1725
|
function normalizeCompanySignalResult(params, data) {
|
|
1061
1726
|
const failed = coreProductResponseFailed(data);
|
|
1062
1727
|
const rawSignals = failed ? [] : data.signals ?? data.signal_feed ?? [];
|
|
@@ -1127,6 +1792,12 @@ function validateSignalDiscoveryParams(params) {
|
|
|
1127
1792
|
if (query && (query.length < 2 || query.length > 2e3)) {
|
|
1128
1793
|
throw new RangeError("query must contain between 2 and 2000 characters");
|
|
1129
1794
|
}
|
|
1795
|
+
if (params.icpContext !== void 0) {
|
|
1796
|
+
const icpContext = typeof params.icpContext === "string" ? params.icpContext.trim() : "";
|
|
1797
|
+
if (icpContext.length < 2 || icpContext.length > 4e3) {
|
|
1798
|
+
throw new RangeError("icpContext must contain between 2 and 4000 characters");
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1130
1801
|
if (params.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit < 1 || params.limit > 1e3)) {
|
|
1131
1802
|
throw new RangeError("limit must be an integer between 1 and 1000");
|
|
1132
1803
|
}
|
|
@@ -1151,6 +1822,7 @@ function signalDiscoveryRequestBody(params) {
|
|
|
1151
1822
|
}
|
|
1152
1823
|
return compact({
|
|
1153
1824
|
query: params.query?.trim(),
|
|
1825
|
+
icp_context: params.icpContext?.trim(),
|
|
1154
1826
|
limit: params.limit ?? 100,
|
|
1155
1827
|
sources: params.sources,
|
|
1156
1828
|
lookback_days: params.lookbackDays,
|
|
@@ -1176,6 +1848,8 @@ function normalizeSignalDiscoveryResult(params, data) {
|
|
|
1176
1848
|
availableCompanySignalCount: Number.isFinite(Number(data.available_company_signal_count)) ? Number(data.available_company_signal_count) : void 0,
|
|
1177
1849
|
sourceLanes: Array.isArray(data.source_lanes) ? data.source_lanes.filter((lane) => typeof lane === "string") : void 0,
|
|
1178
1850
|
evidenceValidation: data.evidence_validation && typeof data.evidence_validation === "object" ? data.evidence_validation : void 0,
|
|
1851
|
+
discoveryDiagnostics: data.discovery_diagnostics && typeof data.discovery_diagnostics === "object" ? data.discovery_diagnostics : void 0,
|
|
1852
|
+
identityResolution: data.identity_resolution && typeof data.identity_resolution === "object" ? data.identity_resolution : void 0,
|
|
1179
1853
|
costGuardrail: normalizeSignalDiscoveryCostGuardrail(data.cost_guardrail),
|
|
1180
1854
|
warnings: Array.isArray(data.warnings) ? data.warnings.filter((warning) => typeof warning === "string") : void 0,
|
|
1181
1855
|
raw: data
|
|
@@ -1204,7 +1878,15 @@ function normalizeSignalDiscoveryCostGuardrail(value) {
|
|
|
1204
1878
|
guardrail.projected_cost_per_requested_company_signal_usd
|
|
1205
1879
|
),
|
|
1206
1880
|
sourceCostUsd: numberOrNullable(guardrail.source_cost_usd),
|
|
1207
|
-
costSource: ["observed", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
|
|
1881
|
+
costSource: ["observed", "catalog_preflight", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
|
|
1882
|
+
identityResolutionCostUsd: numberOrNullable(guardrail.identity_resolution_cost_usd),
|
|
1883
|
+
identityCostSource: [
|
|
1884
|
+
"observed_zero_credit_receipts",
|
|
1885
|
+
"contractual_zero_cost",
|
|
1886
|
+
"not_used",
|
|
1887
|
+
"unavailable"
|
|
1888
|
+
].includes(String(guardrail.identity_cost_source)) ? String(guardrail.identity_cost_source) : void 0,
|
|
1889
|
+
identityProviderRequests: numberOrUndefined(guardrail.identity_provider_requests),
|
|
1208
1890
|
qualifiedCompanySignalCount: numberOrUndefined(guardrail.qualified_company_signal_count),
|
|
1209
1891
|
returnedCompanySignalCount: numberOrUndefined(guardrail.returned_company_signal_count),
|
|
1210
1892
|
availableCompanySignalCount: numberOrUndefined(guardrail.available_company_signal_count),
|
|
@@ -1290,6 +1972,9 @@ function dedupeExactBatchItems(items, keyForItem) {
|
|
|
1290
1972
|
function normalizedBatchText(value) {
|
|
1291
1973
|
return typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, " ") : "";
|
|
1292
1974
|
}
|
|
1975
|
+
function normalizedBatchIdempotencyKey(value) {
|
|
1976
|
+
return typeof value === "string" ? value.trim() : "";
|
|
1977
|
+
}
|
|
1293
1978
|
function normalizedBatchDomain(value) {
|
|
1294
1979
|
return normalizedBatchText(value).replace(/^https?:\/\//, "").replace(/^www\./, "").split(/[/?#]/, 1)[0].replace(/\.+$/, "");
|
|
1295
1980
|
}
|
|
@@ -1308,11 +1993,13 @@ function normalizedFindEmailBatchName(request) {
|
|
|
1308
1993
|
function coreProductBatchRequestKey(path, request) {
|
|
1309
1994
|
if (path === "api/v1/find-email") {
|
|
1310
1995
|
return JSON.stringify([
|
|
1996
|
+
normalizedBatchText(request.run_id),
|
|
1311
1997
|
normalizedFindEmailBatchName(request),
|
|
1312
1998
|
normalizedBatchDomain(request.company_domain),
|
|
1313
1999
|
normalizedBatchLinkedIn(request.linkedin_url),
|
|
1314
2000
|
normalizedBatchText(request.company_name),
|
|
1315
|
-
request.skip_cache === true || request.bypass_cache === true
|
|
2001
|
+
request.skip_cache === true || request.bypass_cache === true,
|
|
2002
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1316
2003
|
]);
|
|
1317
2004
|
}
|
|
1318
2005
|
if (path === "api/v1/verify-email") {
|
|
@@ -1320,7 +2007,8 @@ function coreProductBatchRequestKey(path, request) {
|
|
|
1320
2007
|
normalizedBatchText(request.email),
|
|
1321
2008
|
request.skip_cache === true || request.bypass_cache === true,
|
|
1322
2009
|
request.skip_catch_all_verification === true,
|
|
1323
|
-
request.skip_esp_check === true
|
|
2010
|
+
request.skip_esp_check === true,
|
|
2011
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1324
2012
|
]);
|
|
1325
2013
|
}
|
|
1326
2014
|
if (path === "api/v1/company-signals") {
|
|
@@ -1342,7 +2030,8 @@ function coreProductBatchRequestKey(path, request) {
|
|
|
1342
2030
|
normalizedBatchText(request.search_mode || "balanced"),
|
|
1343
2031
|
request.enable_outreach_intelligence === true,
|
|
1344
2032
|
request.enable_predictive_intelligence === true,
|
|
1345
|
-
request.skip_cache === true || request.bypass_cache === true
|
|
2033
|
+
request.skip_cache === true || request.bypass_cache === true,
|
|
2034
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1346
2035
|
]);
|
|
1347
2036
|
}
|
|
1348
2037
|
return JSON.stringify([
|
|
@@ -1351,9 +2040,11 @@ function coreProductBatchRequestKey(path, request) {
|
|
|
1351
2040
|
typeof request.title === "string" ? request.title.trim() : "",
|
|
1352
2041
|
typeof request.campaign_offer === "string" ? request.campaign_offer.trim() : "",
|
|
1353
2042
|
typeof request.research_prompt === "string" ? request.research_prompt.trim() : "",
|
|
2043
|
+
Number(request.lookback_days) || null,
|
|
1354
2044
|
typeof request.signal_run_id === "string" ? request.signal_run_id.trim() : "",
|
|
1355
2045
|
request.skip_cache === true,
|
|
1356
|
-
request.enable_deep_search === true
|
|
2046
|
+
request.enable_deep_search === true,
|
|
2047
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1357
2048
|
]);
|
|
1358
2049
|
}
|
|
1359
2050
|
function finalizeCoreBatch(total, startedAt, round, normalize, options) {
|
|
@@ -1391,6 +2082,37 @@ function finalizeCoreBatch(total, startedAt, round, normalize, options) {
|
|
|
1391
2082
|
results: compactedResults
|
|
1392
2083
|
};
|
|
1393
2084
|
}
|
|
2085
|
+
function finalizeRecoveredCoreBatch(total, startedAt, round, normalize, options) {
|
|
2086
|
+
const results = round.map((item) => {
|
|
2087
|
+
const duplicateOf = item.raw ? recoveredDuplicateOf(item.raw) : void 0;
|
|
2088
|
+
if (duplicateOf !== void 0) return { index: item.index, success: true, duplicateOf };
|
|
2089
|
+
if (!item.success || !item.raw) {
|
|
2090
|
+
return batchItemFailure(
|
|
2091
|
+
item.index,
|
|
2092
|
+
item.error || "Signaliz batch item failed",
|
|
2093
|
+
item.raw ?? item
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
try {
|
|
2097
|
+
return { index: item.index, success: true, data: normalize(item) };
|
|
2098
|
+
} catch (error) {
|
|
2099
|
+
return {
|
|
2100
|
+
index: item.index,
|
|
2101
|
+
success: false,
|
|
2102
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
});
|
|
2106
|
+
const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
|
|
2107
|
+
const succeeded = compactedResults.filter((item) => item.success).length;
|
|
2108
|
+
return {
|
|
2109
|
+
total,
|
|
2110
|
+
succeeded,
|
|
2111
|
+
failed: total - succeeded,
|
|
2112
|
+
durationMs: Date.now() - startedAt,
|
|
2113
|
+
results: compactedResults
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
1394
2116
|
function compactExactDuplicateResults(results, enabled) {
|
|
1395
2117
|
if (!enabled) return results;
|
|
1396
2118
|
const sourceIndexByRaw = /* @__PURE__ */ new WeakMap();
|
|
@@ -1406,6 +2128,19 @@ function compactExactDuplicateResults(results, enabled) {
|
|
|
1406
2128
|
return item;
|
|
1407
2129
|
});
|
|
1408
2130
|
}
|
|
2131
|
+
function compactKnownDuplicateBatch(batch, sourceIndexByInput, enabled) {
|
|
2132
|
+
if (!enabled) return batch;
|
|
2133
|
+
const canonicalInputByUniqueIndex = /* @__PURE__ */ new Map();
|
|
2134
|
+
sourceIndexByInput.forEach((uniqueIndex, inputIndex) => {
|
|
2135
|
+
if (!canonicalInputByUniqueIndex.has(uniqueIndex)) canonicalInputByUniqueIndex.set(uniqueIndex, inputIndex);
|
|
2136
|
+
});
|
|
2137
|
+
const results = batch.results.map((item, index) => {
|
|
2138
|
+
const canonicalIndex = canonicalInputByUniqueIndex.get(sourceIndexByInput[index]) ?? index;
|
|
2139
|
+
if (canonicalIndex === index || !item.success || !batch.results[canonicalIndex]?.success) return item;
|
|
2140
|
+
return { index, success: true, duplicateOf: canonicalIndex };
|
|
2141
|
+
});
|
|
2142
|
+
return { ...batch, results };
|
|
2143
|
+
}
|
|
1409
2144
|
function signalToCopyRequestBody(params) {
|
|
1410
2145
|
return compact({
|
|
1411
2146
|
company_domain: params.companyDomain,
|
|
@@ -1413,6 +2148,7 @@ function signalToCopyRequestBody(params) {
|
|
|
1413
2148
|
title: params.title,
|
|
1414
2149
|
campaign_offer: params.campaignOffer,
|
|
1415
2150
|
research_prompt: params.researchPrompt,
|
|
2151
|
+
lookback_days: params.lookbackDays,
|
|
1416
2152
|
signal_run_id: params.signalRunId,
|
|
1417
2153
|
skip_cache: params.skipCache,
|
|
1418
2154
|
enable_deep_search: params.enableDeepSearch,
|
|
@@ -1421,8 +2157,33 @@ function signalToCopyRequestBody(params) {
|
|
|
1421
2157
|
idempotency_key: params.idempotencyKey
|
|
1422
2158
|
});
|
|
1423
2159
|
}
|
|
2160
|
+
function validateLargeAvailableDataSignalCopyBatch(requests) {
|
|
2161
|
+
if (requests.length <= 25) return;
|
|
2162
|
+
const invalidIndex = requests.findIndex(
|
|
2163
|
+
(request) => request.skipCache === true || request.enableDeepSearch === true || Boolean(request.idempotencyKey?.trim()) || request.maxWaitMs !== void 0 || request.pollIntervalMs !== void 0 || request.waitForResult === false || !request.companyDomain?.trim() || !request.personName?.trim() || !request.title?.trim() || !request.campaignOffer?.trim()
|
|
2164
|
+
);
|
|
2165
|
+
if (invalidIndex >= 0) {
|
|
2166
|
+
throw new RangeError(
|
|
2167
|
+
`Signal to Copy batches larger than 25 are available-data-only; row ${invalidIndex} requests fresh/deep/synchronous controls or lacks required recipient fields. Split fresh requests into batches of at most 25.`
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
function availableDataSignalCopyRequestBody(params) {
|
|
2172
|
+
return compact({
|
|
2173
|
+
company_domain: params.companyDomain,
|
|
2174
|
+
person_name: params.personName,
|
|
2175
|
+
title: params.title,
|
|
2176
|
+
campaign_offer: params.campaignOffer,
|
|
2177
|
+
research_prompt: params.researchPrompt,
|
|
2178
|
+
lookback_days: params.lookbackDays,
|
|
2179
|
+
signal_run_id: params.signalRunId
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
1424
2182
|
function validateSignalToCopyParams(params) {
|
|
1425
2183
|
validateCoreProductMaxWaitMs(params.maxWaitMs);
|
|
2184
|
+
if (params.lookbackDays !== void 0 && (!Number.isInteger(params.lookbackDays) || params.lookbackDays < 1 || params.lookbackDays > 365)) {
|
|
2185
|
+
throw new RangeError("lookbackDays must be an integer between 1 and 365");
|
|
2186
|
+
}
|
|
1426
2187
|
if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
|
|
1427
2188
|
const missing = [
|
|
1428
2189
|
["companyDomain", params.companyDomain],
|
|
@@ -1551,6 +2312,21 @@ async function waitForNextSignalPoll(data, override, startedAt, maxWaitMs) {
|
|
|
1551
2312
|
function sleep2(ms) {
|
|
1552
2313
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1553
2314
|
}
|
|
2315
|
+
function assertRecoverableBatchResultsRetained(status, label, jobId, idempotencyKey) {
|
|
2316
|
+
if (status.results_expired !== true && status.results_cleaned !== true) return;
|
|
2317
|
+
throw new SignalizError({
|
|
2318
|
+
code: "BATCH_RESULTS_EXPIRED",
|
|
2319
|
+
message: `${label} batch ${jobId} completed, but its retained result pages have expired and cannot be recovered. Do not automatically resubmit provider work.`,
|
|
2320
|
+
errorType: "not_found",
|
|
2321
|
+
details: {
|
|
2322
|
+
job_id: jobId,
|
|
2323
|
+
...idempotencyKey ? { idempotency_key: idempotencyKey } : {},
|
|
2324
|
+
suggested_action: "review_before_resubmitting",
|
|
2325
|
+
retry_eligible: false,
|
|
2326
|
+
results_expired: true
|
|
2327
|
+
}
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
1554
2330
|
async function runBatch(items, execute, options) {
|
|
1555
2331
|
validateBatchSize(items);
|
|
1556
2332
|
const concurrency = validatedBatchConcurrency(options);
|