@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
package/dist/mcp-config.js
CHANGED
|
@@ -40,17 +40,21 @@ var SignalizError = class extends Error {
|
|
|
40
40
|
this.details = data.details;
|
|
41
41
|
}
|
|
42
42
|
get isRetryable() {
|
|
43
|
+
if (typeof this.details?.retry_eligible === "boolean") {
|
|
44
|
+
return this.details.retry_eligible;
|
|
45
|
+
}
|
|
43
46
|
return this.errorType === "rate_limited" || this.errorType === "timeout" || this.errorType === "provider_error";
|
|
44
47
|
}
|
|
45
48
|
};
|
|
46
49
|
function parseError(status, body) {
|
|
47
50
|
if (body?.error && typeof body.error === "object") {
|
|
51
|
+
const nestedDetails = body.error.details && typeof body.error.details === "object" ? body.error.details : {};
|
|
48
52
|
return new SignalizError({
|
|
49
53
|
code: body.error.code || `HTTP_${status}`,
|
|
50
54
|
message: body.error.message || `Request failed with status ${status}`,
|
|
51
55
|
errorType: mapErrorType(body.error.code, status),
|
|
52
56
|
retryAfter: retryAfterSeconds(body.error),
|
|
53
|
-
details: body.error
|
|
57
|
+
details: mergePublicErrorDetails({ ...body, ...body.error }, nestedDetails)
|
|
54
58
|
});
|
|
55
59
|
}
|
|
56
60
|
const code = typeof body?.error_code === "string" && body.error_code.trim() ? body.error_code.trim() : `HTTP_${status}`;
|
|
@@ -59,7 +63,7 @@ function parseError(status, body) {
|
|
|
59
63
|
message: body?.message || body?.error || `Request failed with status ${status}`,
|
|
60
64
|
errorType: mapErrorType(code, status),
|
|
61
65
|
retryAfter: retryAfterSeconds(body),
|
|
62
|
-
details: body?.details
|
|
66
|
+
details: mergePublicErrorDetails(body, body?.details)
|
|
63
67
|
});
|
|
64
68
|
}
|
|
65
69
|
function retryAfterSeconds(body) {
|
|
@@ -71,17 +75,35 @@ function publicRetryDetails(body) {
|
|
|
71
75
|
...body?.retry_eligible !== void 0 ? { retry_eligible: body.retry_eligible } : {},
|
|
72
76
|
...body?.retry_after !== void 0 ? { retry_after: body.retry_after } : {},
|
|
73
77
|
...body?.retry_after_seconds !== void 0 ? { retry_after_seconds: body.retry_after_seconds } : {},
|
|
78
|
+
...body?.retry_strategy !== void 0 ? { retry_strategy: body.retry_strategy } : {},
|
|
79
|
+
...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
|
|
80
|
+
...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
|
|
81
|
+
...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
|
|
82
|
+
...body?.idempotency_key !== void 0 ? { idempotency_key: body.idempotency_key } : {},
|
|
83
|
+
...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
|
|
74
84
|
...body?.run_id !== void 0 ? { run_id: body.run_id } : {},
|
|
85
|
+
...body?.suggested_action !== void 0 ? { suggested_action: body.suggested_action } : {},
|
|
86
|
+
...body?.do_not_auto_resubmit !== void 0 ? { do_not_auto_resubmit: body.do_not_auto_resubmit } : {},
|
|
87
|
+
...body?.results_cleaned !== void 0 ? { results_cleaned: body.results_cleaned } : {},
|
|
88
|
+
...body?.results_expired !== void 0 ? { results_expired: body.results_expired } : {},
|
|
75
89
|
...body?.signal_run_id !== void 0 ? { signal_run_id: body.signal_run_id } : {},
|
|
76
90
|
...body?.company_signal_run_id !== void 0 ? { company_signal_run_id: body.company_signal_run_id } : {},
|
|
77
91
|
...body?.resume_context_persisted !== void 0 ? { resume_context_persisted: body.resume_context_persisted } : {}
|
|
78
92
|
};
|
|
79
93
|
return Object.keys(details).length > 0 ? details : void 0;
|
|
80
94
|
}
|
|
95
|
+
function mergePublicErrorDetails(body, explicitDetails) {
|
|
96
|
+
const merged = { ...publicRetryDetails(body) || {} };
|
|
97
|
+
if (explicitDetails && typeof explicitDetails === "object" && !Array.isArray(explicitDetails)) {
|
|
98
|
+
Object.assign(merged, explicitDetails);
|
|
99
|
+
}
|
|
100
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
101
|
+
}
|
|
81
102
|
function mapErrorType(code, status) {
|
|
82
103
|
if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
|
|
83
|
-
if (code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400) return "validation";
|
|
104
|
+
if (code === "PAYLOAD_TOO_LARGE" || code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400 || status === 413) return "validation";
|
|
84
105
|
if (code === "NOT_FOUND" || status === 404) return "not_found";
|
|
106
|
+
if (code === "BATCH_RESULTS_EXPIRED" || status === 410) return "not_found";
|
|
85
107
|
if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
|
|
86
108
|
if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
|
|
87
109
|
if (code === "INSUFFICIENT_CREDITS" || status === 402) return "insufficient_credits";
|
|
@@ -92,7 +114,7 @@ function mapErrorType(code, status) {
|
|
|
92
114
|
|
|
93
115
|
// src/client.ts
|
|
94
116
|
var DEFAULT_BASE_URL = "https://api.signaliz.com/functions/v1";
|
|
95
|
-
var DEFAULT_TIMEOUT =
|
|
117
|
+
var DEFAULT_TIMEOUT = 13e4;
|
|
96
118
|
var DEFAULT_MAX_RETRIES = 3;
|
|
97
119
|
var HttpClient = class {
|
|
98
120
|
constructor(config) {
|
|
@@ -106,9 +128,9 @@ var HttpClient = class {
|
|
|
106
128
|
throw new Error("Signaliz: provide either apiKey or clientId + clientSecret");
|
|
107
129
|
}
|
|
108
130
|
}
|
|
109
|
-
async request(functionName, body, method = "POST") {
|
|
131
|
+
async request(functionName, body, method = "POST", options = {}) {
|
|
110
132
|
let lastError;
|
|
111
|
-
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body) : void 0;
|
|
133
|
+
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body, options.automaticIdempotency !== false) : void 0;
|
|
112
134
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
113
135
|
let timer;
|
|
114
136
|
try {
|
|
@@ -139,7 +161,9 @@ var HttpClient = class {
|
|
|
139
161
|
const errBody = await res.json().catch(() => ({}));
|
|
140
162
|
const err = parseError(res.status, errBody);
|
|
141
163
|
if (err.isRetryable && attempt < this.maxRetries) {
|
|
142
|
-
const
|
|
164
|
+
const retryWithNewKey = err.details?.retry_strategy === "new_idempotency_key" && err.details?.credits_used === 0 && err.details?.credits_charged === 0;
|
|
165
|
+
if (retryWithNewKey) throw err;
|
|
166
|
+
const wait = err.retryAfter !== void 0 ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
|
|
143
167
|
await sleep(wait);
|
|
144
168
|
lastError = err;
|
|
145
169
|
continue;
|
|
@@ -174,8 +198,8 @@ var HttpClient = class {
|
|
|
174
198
|
throw lastError || new Error("Request failed after retries");
|
|
175
199
|
}
|
|
176
200
|
/** Convenience wrapper for POST-based edge function calls */
|
|
177
|
-
async post(functionName, body) {
|
|
178
|
-
return this.request(functionName, body, "POST");
|
|
201
|
+
async post(functionName, body, options = {}) {
|
|
202
|
+
return this.request(functionName, body, "POST", options);
|
|
179
203
|
}
|
|
180
204
|
/** Convenience wrapper for GET-based edge function calls with query params */
|
|
181
205
|
async get(functionName, query = {}) {
|
|
@@ -292,10 +316,12 @@ var HttpClient = class {
|
|
|
292
316
|
return this.accessTokenPromise;
|
|
293
317
|
}
|
|
294
318
|
};
|
|
295
|
-
function requestIdempotencyKey(body) {
|
|
319
|
+
function requestIdempotencyKey(body, automaticIdempotency) {
|
|
296
320
|
const requested = typeof body.idempotency_key === "string" ? body.idempotency_key.trim() : "";
|
|
321
|
+
if (requested) return requested;
|
|
322
|
+
if (!automaticIdempotency) return void 0;
|
|
297
323
|
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
298
|
-
return
|
|
324
|
+
return `sdk_${nonce}`;
|
|
299
325
|
}
|
|
300
326
|
function sleep(ms) {
|
|
301
327
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -397,7 +423,7 @@ function mapMcpErrorType(error) {
|
|
|
397
423
|
function mapErrorTypeFromCode(code) {
|
|
398
424
|
if (!code) return "unknown";
|
|
399
425
|
if (code === "RATE_LIMITED") return "rate_limited";
|
|
400
|
-
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";
|
|
426
|
+
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";
|
|
401
427
|
if (code === "AUTH_REQUIRED" || code === "AUTH_001") return "auth_expired";
|
|
402
428
|
if (code === "QUOTA_EXCEEDED") return "insufficient_credits";
|
|
403
429
|
if (code === "BUILD_NOT_READY" || code === "ARTIFACT_NOT_READY") return "not_found";
|
|
@@ -405,6 +431,166 @@ function mapErrorTypeFromCode(code) {
|
|
|
405
431
|
return "unknown";
|
|
406
432
|
}
|
|
407
433
|
|
|
434
|
+
// src/email-validation.ts
|
|
435
|
+
function isValidEmailString(value) {
|
|
436
|
+
if (typeof value !== "string") return false;
|
|
437
|
+
const email = value.trim();
|
|
438
|
+
if (!email || email.length > 254) return false;
|
|
439
|
+
const separator = email.indexOf("@");
|
|
440
|
+
if (separator <= 0 || separator !== email.lastIndexOf("@")) return false;
|
|
441
|
+
const local = email.slice(0, separator);
|
|
442
|
+
const domain = email.slice(separator + 1).toLowerCase();
|
|
443
|
+
if (local.length > 64 || domain.length > 253 || local.startsWith(".") || local.endsWith(".") || local.includes("..") || domain.startsWith(".") || domain.endsWith(".") || domain.includes("..")) return false;
|
|
444
|
+
if (!/^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+$/.test(local)) return false;
|
|
445
|
+
const labels = domain.split(".");
|
|
446
|
+
if (labels.length < 2) return false;
|
|
447
|
+
return labels.every(
|
|
448
|
+
(label) => label.length > 0 && label.length <= 63 && /^[a-z0-9-]+$/.test(label) && !label.startsWith("-") && !label.endsWith("-")
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/core-product-input-bounds.ts
|
|
453
|
+
var LARGE_BATCH_MAX_ENCODED_BYTES = 8 * 1024 * 1024;
|
|
454
|
+
var DOMAIN_MAX_UTF8_BYTES = 253;
|
|
455
|
+
var NAME_MAX_UTF8_BYTES = 500;
|
|
456
|
+
var TITLE_MAX_UTF8_BYTES = 500;
|
|
457
|
+
var ID_MAX_UTF8_BYTES = 200;
|
|
458
|
+
var RESEARCH_PROMPT_MAX_UTF8_BYTES = 2e3;
|
|
459
|
+
var CAMPAIGN_OFFER_MAX_UTF8_BYTES = 4e3;
|
|
460
|
+
var SIGNAL_TYPE_MAX_UTF8_BYTES = 100;
|
|
461
|
+
var UTF8_ENCODER = new TextEncoder();
|
|
462
|
+
function byteLength(value) {
|
|
463
|
+
return UTF8_ENCODER.encode(value).byteLength;
|
|
464
|
+
}
|
|
465
|
+
function payloadTooLarge(message, details) {
|
|
466
|
+
throw new SignalizError({
|
|
467
|
+
code: "PAYLOAD_TOO_LARGE",
|
|
468
|
+
message,
|
|
469
|
+
errorType: "validation",
|
|
470
|
+
details: {
|
|
471
|
+
...details,
|
|
472
|
+
retry_eligible: false,
|
|
473
|
+
credits_used: 0,
|
|
474
|
+
credits_charged: 0
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
function assertStringBytes(value, field, maxBytes, index) {
|
|
479
|
+
if (typeof value !== "string") return;
|
|
480
|
+
const encodedBytes = byteLength(value);
|
|
481
|
+
if (encodedBytes <= maxBytes) return;
|
|
482
|
+
payloadTooLarge(
|
|
483
|
+
`${field} is ${encodedBytes} UTF-8 bytes; maximum is ${maxBytes} bytes`,
|
|
484
|
+
{
|
|
485
|
+
field,
|
|
486
|
+
...index !== void 0 ? { index } : {},
|
|
487
|
+
encoded_bytes: encodedBytes,
|
|
488
|
+
max_bytes: maxBytes
|
|
489
|
+
}
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
function assertCompanySignalRows(body) {
|
|
493
|
+
const requests = Array.isArray(body.requests) ? body.requests : [];
|
|
494
|
+
assertStringBytes(
|
|
495
|
+
body.research_prompt,
|
|
496
|
+
"research_prompt",
|
|
497
|
+
RESEARCH_PROMPT_MAX_UTF8_BYTES
|
|
498
|
+
);
|
|
499
|
+
if (Array.isArray(body.signal_types)) {
|
|
500
|
+
body.signal_types.forEach(
|
|
501
|
+
(signalType, index) => assertStringBytes(
|
|
502
|
+
signalType,
|
|
503
|
+
`signal_types[${index}]`,
|
|
504
|
+
SIGNAL_TYPE_MAX_UTF8_BYTES,
|
|
505
|
+
index
|
|
506
|
+
)
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
requests.forEach((value, index) => {
|
|
510
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
511
|
+
const request = value;
|
|
512
|
+
assertStringBytes(
|
|
513
|
+
request.company_domain,
|
|
514
|
+
`requests[${index}].company_domain`,
|
|
515
|
+
DOMAIN_MAX_UTF8_BYTES,
|
|
516
|
+
index
|
|
517
|
+
);
|
|
518
|
+
assertStringBytes(
|
|
519
|
+
request.company_name,
|
|
520
|
+
`requests[${index}].company_name`,
|
|
521
|
+
NAME_MAX_UTF8_BYTES,
|
|
522
|
+
index
|
|
523
|
+
);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
function assertSignalCopyRows(body) {
|
|
527
|
+
const requests = Array.isArray(body.requests) ? body.requests : [];
|
|
528
|
+
requests.forEach((value, index) => {
|
|
529
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
530
|
+
const request = value;
|
|
531
|
+
assertStringBytes(
|
|
532
|
+
request.company_domain,
|
|
533
|
+
`requests[${index}].company_domain`,
|
|
534
|
+
DOMAIN_MAX_UTF8_BYTES,
|
|
535
|
+
index
|
|
536
|
+
);
|
|
537
|
+
assertStringBytes(
|
|
538
|
+
request.person_name,
|
|
539
|
+
`requests[${index}].person_name`,
|
|
540
|
+
NAME_MAX_UTF8_BYTES,
|
|
541
|
+
index
|
|
542
|
+
);
|
|
543
|
+
assertStringBytes(
|
|
544
|
+
request.title,
|
|
545
|
+
`requests[${index}].title`,
|
|
546
|
+
TITLE_MAX_UTF8_BYTES,
|
|
547
|
+
index
|
|
548
|
+
);
|
|
549
|
+
assertStringBytes(
|
|
550
|
+
request.campaign_offer,
|
|
551
|
+
`requests[${index}].campaign_offer`,
|
|
552
|
+
CAMPAIGN_OFFER_MAX_UTF8_BYTES,
|
|
553
|
+
index
|
|
554
|
+
);
|
|
555
|
+
assertStringBytes(
|
|
556
|
+
request.research_prompt,
|
|
557
|
+
`requests[${index}].research_prompt`,
|
|
558
|
+
RESEARCH_PROMPT_MAX_UTF8_BYTES,
|
|
559
|
+
index
|
|
560
|
+
);
|
|
561
|
+
assertStringBytes(
|
|
562
|
+
request.signal_run_id,
|
|
563
|
+
`requests[${index}].signal_run_id`,
|
|
564
|
+
ID_MAX_UTF8_BYTES,
|
|
565
|
+
index
|
|
566
|
+
);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
function assertLargeCoreProductInputBounds(path2, body) {
|
|
570
|
+
const requests = Array.isArray(body.requests) ? body.requests : [];
|
|
571
|
+
if (requests.length <= 25) return;
|
|
572
|
+
assertStringBytes(body.idempotency_key, "idempotency_key", ID_MAX_UTF8_BYTES);
|
|
573
|
+
if (path2 === "api/v1/company-signals") {
|
|
574
|
+
assertCompanySignalRows(body);
|
|
575
|
+
} else {
|
|
576
|
+
assertSignalCopyRows(body);
|
|
577
|
+
}
|
|
578
|
+
const encoded = JSON.stringify(body);
|
|
579
|
+
if (typeof encoded !== "string") {
|
|
580
|
+
throw new TypeError("Core product batch input must be JSON serializable.");
|
|
581
|
+
}
|
|
582
|
+
const encodedBytes = byteLength(encoded);
|
|
583
|
+
if (encodedBytes <= LARGE_BATCH_MAX_ENCODED_BYTES) return;
|
|
584
|
+
payloadTooLarge(
|
|
585
|
+
`${path2 === "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`,
|
|
586
|
+
{
|
|
587
|
+
field: "requests",
|
|
588
|
+
encoded_bytes: encodedBytes,
|
|
589
|
+
max_bytes: LARGE_BATCH_MAX_ENCODED_BYTES
|
|
590
|
+
}
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
408
594
|
// src/index.ts
|
|
409
595
|
var MAX_ROW_RATE_LIMIT_RETRIES = 2;
|
|
410
596
|
var Signaliz = class {
|
|
@@ -438,9 +624,25 @@ var Signaliz = class {
|
|
|
438
624
|
options
|
|
439
625
|
);
|
|
440
626
|
}
|
|
627
|
+
async resumeFindEmailBatch(jobId, options) {
|
|
628
|
+
const startedAt = Date.now();
|
|
629
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
630
|
+
"api/v1/find-email",
|
|
631
|
+
jobId,
|
|
632
|
+
"Find Email",
|
|
633
|
+
options
|
|
634
|
+
);
|
|
635
|
+
return finalizeCoreBatch(
|
|
636
|
+
total,
|
|
637
|
+
startedAt,
|
|
638
|
+
recoverableRowsToRound(rows, "Find Email"),
|
|
639
|
+
(item) => normalizeFindEmailResult(item.raw),
|
|
640
|
+
options
|
|
641
|
+
);
|
|
642
|
+
}
|
|
441
643
|
async verifyEmail(email, options) {
|
|
442
644
|
const normalizedEmail = typeof email === "string" ? email.trim() : "";
|
|
443
|
-
if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail &&
|
|
645
|
+
if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail && !isValidEmailString(normalizedEmail)) {
|
|
444
646
|
const error = `Invalid email format: "${normalizedEmail}"`;
|
|
445
647
|
return normalizeVerifyEmailResult(normalizedEmail, {
|
|
446
648
|
success: false,
|
|
@@ -490,19 +692,20 @@ var Signaliz = class {
|
|
|
490
692
|
}
|
|
491
693
|
async verifyEmails(emails, options) {
|
|
492
694
|
validateBatchSize(emails);
|
|
695
|
+
const requests = emails.map((email) => verifyEmailBatchRequestBody(email, options));
|
|
493
696
|
if (options?.dryRun === true) {
|
|
494
697
|
return this.runCoreProductDryRun(
|
|
495
698
|
"api/v1/verify-email",
|
|
496
|
-
|
|
699
|
+
requests,
|
|
497
700
|
options.idempotencyKey
|
|
498
701
|
);
|
|
499
702
|
}
|
|
500
703
|
const startedAt = Date.now();
|
|
501
704
|
const round = await this.runCoreBatchRound(
|
|
502
705
|
"api/v1/verify-email",
|
|
503
|
-
|
|
706
|
+
requests.map((request, index) => ({
|
|
504
707
|
index,
|
|
505
|
-
request
|
|
708
|
+
request
|
|
506
709
|
})),
|
|
507
710
|
options
|
|
508
711
|
);
|
|
@@ -510,7 +713,23 @@ var Signaliz = class {
|
|
|
510
713
|
emails.length,
|
|
511
714
|
startedAt,
|
|
512
715
|
round,
|
|
513
|
-
(item) => normalizeVerifyEmailResult(
|
|
716
|
+
(item) => normalizeVerifyEmailResult(String(requests[item.index].email ?? ""), item.raw),
|
|
717
|
+
options
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
async resumeVerifyEmailBatch(jobId, options) {
|
|
721
|
+
const startedAt = Date.now();
|
|
722
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
723
|
+
"api/v1/verify-email",
|
|
724
|
+
jobId,
|
|
725
|
+
"Verify Email",
|
|
726
|
+
options
|
|
727
|
+
);
|
|
728
|
+
return finalizeCoreBatch(
|
|
729
|
+
total,
|
|
730
|
+
startedAt,
|
|
731
|
+
recoverableRowsToRound(rows, "Verify Email"),
|
|
732
|
+
(item) => normalizeVerifyEmailResult(String(item.raw?.email || ""), item.raw),
|
|
514
733
|
options
|
|
515
734
|
);
|
|
516
735
|
}
|
|
@@ -526,25 +745,92 @@ var Signaliz = class {
|
|
|
526
745
|
validateSignalDiscoveryParams(params);
|
|
527
746
|
const data = await this.client.post(
|
|
528
747
|
"api/v1/signals",
|
|
529
|
-
signalDiscoveryRequestBody(params)
|
|
748
|
+
signalDiscoveryRequestBody(params),
|
|
749
|
+
// Signals Everything owns a normalized, workspace-scoped automatic key
|
|
750
|
+
// at the API boundary so SDK, CLI, MCP, and direct REST calls coalesce.
|
|
751
|
+
{ automaticIdempotency: false }
|
|
530
752
|
);
|
|
531
753
|
return normalizeSignalDiscoveryResult(params, data);
|
|
532
754
|
}
|
|
533
755
|
async enrichCompanies(companies, options) {
|
|
534
756
|
validateBatchSize(companies);
|
|
535
757
|
companies.forEach((params) => validateCoreProductMaxWaitMs(params.maxWaitMs));
|
|
758
|
+
const requests = companies.map(companySignalRequestBody);
|
|
759
|
+
if (companies.length > 25 && requests.some((request) => request.include_candidate_evidence === true)) {
|
|
760
|
+
throw new RangeError(
|
|
761
|
+
"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."
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
const largeBatch = companySignalLargeBatchSubmission(requests);
|
|
765
|
+
if (companies.length > 25 && !largeBatch) {
|
|
766
|
+
throw new RangeError(
|
|
767
|
+
"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."
|
|
768
|
+
);
|
|
769
|
+
}
|
|
536
770
|
if (options?.dryRun === true) {
|
|
537
771
|
return this.runCoreProductDryRun(
|
|
538
772
|
"api/v1/company-signals",
|
|
539
|
-
|
|
540
|
-
options.idempotencyKey
|
|
773
|
+
largeBatch?.requests || requests,
|
|
774
|
+
options.idempotencyKey,
|
|
775
|
+
largeBatch?.submissionFields
|
|
541
776
|
);
|
|
542
777
|
}
|
|
543
778
|
const startedAt = Date.now();
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
779
|
+
if (options?.waitForResult === false) {
|
|
780
|
+
if (!largeBatch) {
|
|
781
|
+
throw new RangeError(
|
|
782
|
+
"Company Signals --no-wait requires more than 25 homogeneous new-enrichment rows; recovery reads and mixed controls must use batches of at most 25."
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
return this.submitRecoverableCoreBatchJob(
|
|
786
|
+
"api/v1/company-signals",
|
|
787
|
+
largeBatch.requests,
|
|
788
|
+
"Company Signals",
|
|
789
|
+
options.idempotencyKey,
|
|
790
|
+
companies.map((_, index) => index),
|
|
791
|
+
largeBatch.submissionFields
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
if (largeBatch) {
|
|
795
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
796
|
+
"api/v1/company-signals",
|
|
797
|
+
largeBatch.requests,
|
|
798
|
+
"Company Signals",
|
|
799
|
+
options?.idempotencyKey,
|
|
800
|
+
companies.map((_, index) => index),
|
|
801
|
+
largeBatch.submissionFields
|
|
802
|
+
);
|
|
803
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
804
|
+
"api/v1/company-signals",
|
|
805
|
+
job.jobId,
|
|
806
|
+
"Company Signals",
|
|
807
|
+
{
|
|
808
|
+
pageSize: 25,
|
|
809
|
+
maxWaitMs: options?.maxWaitMs,
|
|
810
|
+
pollIntervalMs: options?.pollIntervalMs,
|
|
811
|
+
idempotencyKey: job.idempotencyKey,
|
|
812
|
+
compactDuplicates: options?.compactDuplicates === true
|
|
813
|
+
}
|
|
814
|
+
);
|
|
815
|
+
if (total !== companies.length) {
|
|
816
|
+
throw new Error(`Company Signals batch returned ${total} results for ${companies.length} requests`);
|
|
817
|
+
}
|
|
818
|
+
const batch = finalizeCoreBatch(
|
|
819
|
+
companies.length,
|
|
820
|
+
startedAt,
|
|
821
|
+
recoverableRowsToRound(rows, "Company Signals"),
|
|
822
|
+
(item) => normalizeCompanySignalResult(companies[item.index], item.raw)
|
|
823
|
+
);
|
|
824
|
+
return compactKnownDuplicateBatch(
|
|
825
|
+
batch,
|
|
826
|
+
dedupeExactBatchItems(
|
|
827
|
+
requests,
|
|
828
|
+
(request) => coreProductBatchRequestKey("api/v1/company-signals", request)
|
|
829
|
+
).sourceIndexByInput,
|
|
830
|
+
options?.compactDuplicates === true
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
const initialTasks = requests.map((request, index) => ({ index, request }));
|
|
548
834
|
const round = await this.runCoreBatchRound("api/v1/company-signals", initialTasks, options);
|
|
549
835
|
const results = round.map((item) => {
|
|
550
836
|
if (!item.success || !item.raw) {
|
|
@@ -567,6 +853,22 @@ var Signaliz = class {
|
|
|
567
853
|
results: compactedResults
|
|
568
854
|
};
|
|
569
855
|
}
|
|
856
|
+
async resumeCompanySignalBatch(jobId, options) {
|
|
857
|
+
const startedAt = Date.now();
|
|
858
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
859
|
+
"api/v1/company-signals",
|
|
860
|
+
jobId,
|
|
861
|
+
"Company Signals",
|
|
862
|
+
options
|
|
863
|
+
);
|
|
864
|
+
return finalizeRecoveredCoreBatch(
|
|
865
|
+
total,
|
|
866
|
+
startedAt,
|
|
867
|
+
recoverableRowsToRound(rows, "Company Signals"),
|
|
868
|
+
(item) => normalizeCompanySignalResult(companySignalParamsFromRecoveredRow(item.raw), item.raw),
|
|
869
|
+
options
|
|
870
|
+
);
|
|
871
|
+
}
|
|
570
872
|
async signalToCopy(params) {
|
|
571
873
|
validateSignalToCopyParams(params);
|
|
572
874
|
const request = signalToCopyRequestBody(params);
|
|
@@ -578,6 +880,7 @@ var Signaliz = class {
|
|
|
578
880
|
async createSignalCopyBatch(requests, options) {
|
|
579
881
|
validateBatchSize(requests);
|
|
580
882
|
requests.forEach(validateSignalToCopyParams);
|
|
883
|
+
validateLargeAvailableDataSignalCopyBatch(requests);
|
|
581
884
|
if (options?.dryRun === true) {
|
|
582
885
|
return this.runCoreProductDryRun(
|
|
583
886
|
"api/v1/signal-to-copy",
|
|
@@ -586,6 +889,59 @@ var Signaliz = class {
|
|
|
586
889
|
);
|
|
587
890
|
}
|
|
588
891
|
const startedAt = Date.now();
|
|
892
|
+
if (requests.length > 25) {
|
|
893
|
+
if (options?.waitForResult === false) {
|
|
894
|
+
return this.submitRecoverableCoreBatchJob(
|
|
895
|
+
"api/v1/signal-to-copy",
|
|
896
|
+
requests.map(availableDataSignalCopyRequestBody),
|
|
897
|
+
"Signal to Copy",
|
|
898
|
+
options.idempotencyKey,
|
|
899
|
+
requests.map((_, index) => index)
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
903
|
+
"api/v1/signal-to-copy",
|
|
904
|
+
requests.map(availableDataSignalCopyRequestBody),
|
|
905
|
+
"Signal to Copy",
|
|
906
|
+
options?.idempotencyKey,
|
|
907
|
+
requests.map((_, index) => index)
|
|
908
|
+
);
|
|
909
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
910
|
+
"api/v1/signal-to-copy",
|
|
911
|
+
job.jobId,
|
|
912
|
+
"Signal to Copy",
|
|
913
|
+
{
|
|
914
|
+
pageSize: 100,
|
|
915
|
+
maxWaitMs: options?.maxWaitMs,
|
|
916
|
+
pollIntervalMs: options?.pollIntervalMs,
|
|
917
|
+
idempotencyKey: job.idempotencyKey,
|
|
918
|
+
compactDuplicates: options?.compactDuplicates === true
|
|
919
|
+
}
|
|
920
|
+
);
|
|
921
|
+
if (total !== requests.length) {
|
|
922
|
+
throw new Error(`Signal to Copy batch returned ${total} results for ${requests.length} requests`);
|
|
923
|
+
}
|
|
924
|
+
const results2 = recoverableSignalCopyRows(rows);
|
|
925
|
+
const succeeded2 = results2.filter((item) => item.success).length;
|
|
926
|
+
const batch = {
|
|
927
|
+
total: requests.length,
|
|
928
|
+
succeeded: succeeded2,
|
|
929
|
+
failed: requests.length - succeeded2,
|
|
930
|
+
durationMs: Date.now() - startedAt,
|
|
931
|
+
results: results2
|
|
932
|
+
};
|
|
933
|
+
return compactKnownDuplicateBatch(
|
|
934
|
+
batch,
|
|
935
|
+
dedupeExactBatchItems(
|
|
936
|
+
requests,
|
|
937
|
+
(params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
|
|
938
|
+
).sourceIndexByInput,
|
|
939
|
+
options?.compactDuplicates === true
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
if (options?.waitForResult === false) {
|
|
943
|
+
throw new RangeError("Signal to Copy --no-wait requires a durable batch of more than 25 rows.");
|
|
944
|
+
}
|
|
589
945
|
const deduplicated = dedupeExactBatchItems(
|
|
590
946
|
requests,
|
|
591
947
|
(params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
|
|
@@ -610,12 +966,36 @@ var Signaliz = class {
|
|
|
610
966
|
results: compactedResults
|
|
611
967
|
};
|
|
612
968
|
}
|
|
613
|
-
async
|
|
614
|
-
const
|
|
969
|
+
async resumeSignalCopyBatch(jobId, options) {
|
|
970
|
+
const startedAt = Date.now();
|
|
971
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
972
|
+
"api/v1/signal-to-copy",
|
|
973
|
+
jobId,
|
|
974
|
+
"Signal to Copy",
|
|
975
|
+
options
|
|
976
|
+
);
|
|
977
|
+
const results = recoverableSignalCopyRows(rows);
|
|
978
|
+
const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
|
|
979
|
+
const succeeded = compactedResults.filter((item) => item.success).length;
|
|
980
|
+
return {
|
|
981
|
+
total,
|
|
982
|
+
succeeded,
|
|
983
|
+
failed: total - succeeded,
|
|
984
|
+
durationMs: Date.now() - startedAt,
|
|
985
|
+
results: compactedResults
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
async runCoreProductDryRun(path2, requests, idempotencyKey, defaults = {}) {
|
|
989
|
+
const requestBody = compact({
|
|
615
990
|
requests,
|
|
991
|
+
...defaults,
|
|
616
992
|
dry_run: true,
|
|
617
993
|
idempotency_key: idempotencyKey
|
|
618
|
-
})
|
|
994
|
+
});
|
|
995
|
+
if (path2 === "api/v1/company-signals" || path2 === "api/v1/signal-to-copy") {
|
|
996
|
+
assertLargeCoreProductInputBounds(path2, requestBody);
|
|
997
|
+
}
|
|
998
|
+
const data = await this.client.post(path2, requestBody);
|
|
619
999
|
if (!isCoreProductDryRun(data)) {
|
|
620
1000
|
throw new Error(`${path2} dry run returned a live-result contract`);
|
|
621
1001
|
}
|
|
@@ -661,76 +1041,209 @@ var Signaliz = class {
|
|
|
661
1041
|
}
|
|
662
1042
|
return uniqueResults;
|
|
663
1043
|
}
|
|
664
|
-
async runRecoverableCoreBatchJob(path2, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices) {
|
|
665
|
-
const
|
|
1044
|
+
async runRecoverableCoreBatchJob(path2, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
|
|
1045
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
1046
|
+
path2,
|
|
1047
|
+
requests,
|
|
1048
|
+
label,
|
|
1049
|
+
idempotencyKey,
|
|
1050
|
+
idempotencyItemIndices,
|
|
1051
|
+
submissionFields
|
|
1052
|
+
);
|
|
1053
|
+
const recovered = await this.readRecoverableCoreBatchJob(path2, job.jobId, label, {
|
|
1054
|
+
pageSize,
|
|
1055
|
+
maxWaitMs,
|
|
1056
|
+
idempotencyKey: job.idempotencyKey
|
|
1057
|
+
});
|
|
1058
|
+
if (recovered.total !== requests.length) {
|
|
1059
|
+
throw new Error(`${label} batch returned ${recovered.total} results for ${requests.length} requests`);
|
|
1060
|
+
}
|
|
1061
|
+
return recovered.rows;
|
|
1062
|
+
}
|
|
1063
|
+
async submitRecoverableCoreBatchJob(path2, requests, label, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
|
|
666
1064
|
const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
|
|
1065
|
+
const requestBody = {
|
|
1066
|
+
...submissionFields,
|
|
1067
|
+
requests,
|
|
1068
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1069
|
+
batch_item_indices: idempotencyItemIndices
|
|
1070
|
+
};
|
|
1071
|
+
if (path2 === "api/v1/company-signals" || path2 === "api/v1/signal-to-copy") {
|
|
1072
|
+
assertLargeCoreProductInputBounds(path2, requestBody);
|
|
1073
|
+
}
|
|
667
1074
|
let submission;
|
|
668
1075
|
try {
|
|
669
|
-
submission = await this.client.post(path2,
|
|
670
|
-
requests,
|
|
671
|
-
idempotency_key: submissionIdempotencyKey,
|
|
672
|
-
batch_item_indices: idempotencyItemIndices
|
|
673
|
-
});
|
|
1076
|
+
submission = await this.client.post(path2, requestBody);
|
|
674
1077
|
} catch (error) {
|
|
675
1078
|
if (error instanceof SignalizError && !error.isRetryable) throw error;
|
|
676
1079
|
const message = error instanceof Error ? error.message : String(error);
|
|
677
|
-
throw new
|
|
678
|
-
|
|
679
|
-
|
|
1080
|
+
throw new SignalizError({
|
|
1081
|
+
code: "BATCH_SUBMISSION_UNCONFIRMED",
|
|
1082
|
+
message: `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`,
|
|
1083
|
+
errorType: "provider_error",
|
|
1084
|
+
details: {
|
|
1085
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1086
|
+
suggested_action: "retry_with_idempotency_key",
|
|
1087
|
+
retry_eligible: true
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
680
1090
|
}
|
|
681
1091
|
const jobId = String(submission.job_id || "").trim();
|
|
682
|
-
if (!jobId)
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1092
|
+
if (!jobId) {
|
|
1093
|
+
throw new SignalizError({
|
|
1094
|
+
code: "INVALID_JOB_RESPONSE",
|
|
1095
|
+
message: `${label} batch did not return a recoverable job_id`,
|
|
1096
|
+
errorType: "provider_error",
|
|
1097
|
+
details: {
|
|
1098
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1099
|
+
suggested_action: "retry_with_idempotency_key",
|
|
1100
|
+
retry_eligible: true
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
const submissionStatus = String(submission.status || "").toLowerCase();
|
|
1105
|
+
const status = ["queued", "processing", "completed", "partial", "failed"].includes(submissionStatus) ? submissionStatus : "queued";
|
|
1106
|
+
return {
|
|
1107
|
+
success: true,
|
|
1108
|
+
status,
|
|
1109
|
+
capability: path2 === "api/v1/company-signals" ? "company_signals" : "signal_to_copy",
|
|
1110
|
+
jobId,
|
|
1111
|
+
idempotencyKey: String(submission.idempotency_key || submissionIdempotencyKey),
|
|
1112
|
+
total: Math.max(0, Number(submission.total ?? submission.items_total ?? requests.length)),
|
|
1113
|
+
...Number.isFinite(Number(submission.next_poll_after_seconds)) ? {
|
|
1114
|
+
nextPollAfterSeconds: Number(submission.next_poll_after_seconds)
|
|
1115
|
+
} : {}
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
async readRecoverableCoreBatchJob(path2, jobIdInput, label, options = {}) {
|
|
1119
|
+
const jobId = String(jobIdInput || "").trim();
|
|
1120
|
+
if (!jobId) throw new RangeError(`${label} jobId is required`);
|
|
1121
|
+
if (options.pageSize !== void 0 && (!Number.isFinite(options.pageSize) || options.pageSize <= 0)) {
|
|
1122
|
+
throw new RangeError("pageSize must be a positive finite number");
|
|
1123
|
+
}
|
|
1124
|
+
if (options.maxWaitMs !== void 0 && (!Number.isFinite(options.maxWaitMs) || options.maxWaitMs <= 0)) {
|
|
1125
|
+
throw new RangeError("maxWaitMs must be a positive finite number");
|
|
1126
|
+
}
|
|
1127
|
+
if (options.pollIntervalMs !== void 0 && (!Number.isFinite(options.pollIntervalMs) || options.pollIntervalMs <= 0)) {
|
|
1128
|
+
throw new RangeError("pollIntervalMs must be a positive finite number");
|
|
1129
|
+
}
|
|
1130
|
+
const maximumPageSize = path2 === "api/v1/signal-to-copy" ? 100 : 25;
|
|
1131
|
+
const pageSize = Math.min(maximumPageSize, Math.max(1, Math.trunc(options.pageSize ?? maximumPageSize)));
|
|
1132
|
+
const emailJob = path2 === "api/v1/find-email" || path2 === "api/v1/verify-email";
|
|
1133
|
+
const maxWaitMs = options.maxWaitMs ?? (emailJob ? 20 * 6e4 : void 0);
|
|
1134
|
+
const startedAt = Date.now();
|
|
1135
|
+
let observedIdempotencyKey = String(options.idempotencyKey || "").trim();
|
|
1136
|
+
try {
|
|
1137
|
+
let status = await this.client.post(path2, {
|
|
695
1138
|
job_id: jobId,
|
|
696
1139
|
page: 1,
|
|
697
|
-
page_size: pageSize
|
|
1140
|
+
page_size: pageSize,
|
|
1141
|
+
compact_duplicates: options.compactDuplicates === true
|
|
698
1142
|
});
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
pageNumbers,
|
|
706
|
-
async (page) => await this.client.post(path2, {
|
|
707
|
-
job_id: jobId,
|
|
708
|
-
page,
|
|
709
|
-
page_size: pageSize
|
|
710
|
-
}),
|
|
711
|
-
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
1143
|
+
observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
|
|
1144
|
+
assertRecoverableBatchResultsRetained(
|
|
1145
|
+
status,
|
|
1146
|
+
label,
|
|
1147
|
+
jobId,
|
|
1148
|
+
observedIdempotencyKey
|
|
712
1149
|
);
|
|
713
|
-
|
|
714
|
-
if (
|
|
715
|
-
throw new
|
|
1150
|
+
while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
|
|
1151
|
+
if (maxWaitMs !== void 0 && Date.now() - startedAt >= maxWaitMs) {
|
|
1152
|
+
throw new SignalizError({
|
|
1153
|
+
code: "TIMEOUT",
|
|
1154
|
+
message: `${label} batch ${jobId} did not complete within ${maxWaitMs}ms`,
|
|
1155
|
+
errorType: "timeout",
|
|
1156
|
+
retryAfter: Number(status.next_poll_after_seconds || 2),
|
|
1157
|
+
details: {
|
|
1158
|
+
job_id: jobId,
|
|
1159
|
+
...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
|
|
1160
|
+
suggested_action: "check_job_status",
|
|
1161
|
+
retry_eligible: true
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
716
1164
|
}
|
|
717
|
-
|
|
1165
|
+
const retryAfterMs = options.pollIntervalMs ?? Number(status.next_poll_after_seconds || 2) * 1e3;
|
|
1166
|
+
const boundedDelay = maxWaitMs === void 0 ? Math.max(250, retryAfterMs) : Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt));
|
|
1167
|
+
await sleep2(boundedDelay);
|
|
1168
|
+
status = await this.client.post(path2, {
|
|
1169
|
+
job_id: jobId,
|
|
1170
|
+
page: 1,
|
|
1171
|
+
page_size: pageSize,
|
|
1172
|
+
compact_duplicates: options.compactDuplicates === true
|
|
1173
|
+
});
|
|
1174
|
+
observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
|
|
1175
|
+
assertRecoverableBatchResultsRetained(
|
|
1176
|
+
status,
|
|
1177
|
+
label,
|
|
1178
|
+
jobId,
|
|
1179
|
+
observedIdempotencyKey
|
|
1180
|
+
);
|
|
718
1181
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1182
|
+
const totalPages = Math.max(1, Number(status.total_pages || 1));
|
|
1183
|
+
const pages = [Array.isArray(status.results) ? status.results : []];
|
|
1184
|
+
if (totalPages > 1) {
|
|
1185
|
+
const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
|
|
1186
|
+
const remainingPages = await runBatch(
|
|
1187
|
+
pageNumbers,
|
|
1188
|
+
async (page) => await this.client.post(path2, {
|
|
1189
|
+
job_id: jobId,
|
|
1190
|
+
page,
|
|
1191
|
+
page_size: pageSize,
|
|
1192
|
+
compact_duplicates: options.compactDuplicates === true
|
|
1193
|
+
}),
|
|
1194
|
+
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
1195
|
+
);
|
|
1196
|
+
for (const page of remainingPages.results) {
|
|
1197
|
+
if (!page.success || !page.data) {
|
|
1198
|
+
throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
|
|
1199
|
+
}
|
|
1200
|
+
observedIdempotencyKey = String(
|
|
1201
|
+
page.data.idempotency_key || observedIdempotencyKey
|
|
1202
|
+
).trim();
|
|
1203
|
+
assertRecoverableBatchResultsRetained(
|
|
1204
|
+
page.data,
|
|
1205
|
+
label,
|
|
1206
|
+
jobId,
|
|
1207
|
+
observedIdempotencyKey
|
|
1208
|
+
);
|
|
1209
|
+
pages.push(Array.isArray(page.data.results) ? page.data.results : []);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
const rows = pages.flat();
|
|
1213
|
+
const total = Math.max(0, Number(status.total_results ?? status.total ?? status.items_total ?? rows.length));
|
|
1214
|
+
if (rows.length !== total) {
|
|
1215
|
+
throw new Error(`${label} batch returned ${rows.length} results for ${total} job rows`);
|
|
1216
|
+
}
|
|
1217
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1218
|
+
for (const row of rows) {
|
|
1219
|
+
const index = Number(row.index);
|
|
1220
|
+
if (!Number.isInteger(index) || index < 0 || seen.has(index)) {
|
|
1221
|
+
throw new Error(`${label} batch returned an invalid result index`);
|
|
1222
|
+
}
|
|
1223
|
+
seen.add(index);
|
|
729
1224
|
}
|
|
730
|
-
seen.
|
|
1225
|
+
if (seen.size !== total) throw new Error(`${label} batch returned incomplete indexed results`);
|
|
1226
|
+
return { rows, total };
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
if (error instanceof SignalizError && String(error.details?.job_id || "") === jobId) {
|
|
1229
|
+
throw error;
|
|
1230
|
+
}
|
|
1231
|
+
const signalizError = error instanceof SignalizError ? error : null;
|
|
1232
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1233
|
+
throw new SignalizError({
|
|
1234
|
+
code: signalizError?.code || "BATCH_RECOVERY_FAILED",
|
|
1235
|
+
message: `${label} batch ${jobId} could not be fully retrieved. Resume the existing job instead of resubmitting it. ${message}`,
|
|
1236
|
+
errorType: signalizError?.errorType || "provider_error",
|
|
1237
|
+
retryAfter: signalizError?.retryAfter,
|
|
1238
|
+
details: {
|
|
1239
|
+
...signalizError?.details || {},
|
|
1240
|
+
job_id: jobId,
|
|
1241
|
+
...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
|
|
1242
|
+
suggested_action: "check_job_status",
|
|
1243
|
+
retry_eligible: true
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
731
1246
|
}
|
|
732
|
-
if (seen.size !== requests.length) throw new Error(`${label} batch returned incomplete indexed results`);
|
|
733
|
-
return rows;
|
|
734
1247
|
}
|
|
735
1248
|
async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0, idempotencyKey, idempotencyItemIndices = requests.map((_, index) => index)) {
|
|
736
1249
|
const results = new Array(requests.length);
|
|
@@ -795,8 +1308,11 @@ var Signaliz = class {
|
|
|
795
1308
|
(task) => coreProductBatchRequestKey(path2, task.request)
|
|
796
1309
|
);
|
|
797
1310
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
798
|
-
const recoverableBatch = path2 === "api/v1/find-email" ? { label: "Find Email", pageSize:
|
|
799
|
-
|
|
1311
|
+
const recoverableBatch = path2 === "api/v1/find-email" ? { label: "Find Email", pageSize: 25, maxWaitMs: 20 * 6e4 } : path2 === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 25, maxWaitMs: 20 * 6e4 } : void 0;
|
|
1312
|
+
const containsFindRecoveryReads = path2 === "api/v1/find-email" && uniqueTasks.some(
|
|
1313
|
+
(task) => typeof task.request.run_id === "string" && task.request.run_id.trim()
|
|
1314
|
+
);
|
|
1315
|
+
if (recoverableBatch && uniqueTasks.length > 25 && !containsFindRecoveryReads) {
|
|
800
1316
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
801
1317
|
path2,
|
|
802
1318
|
uniqueTasks.map((task) => task.request),
|
|
@@ -922,18 +1438,68 @@ function batchItemFailure(index, error, raw) {
|
|
|
922
1438
|
raw?.retry_after_seconds ?? raw?.retry_after ?? raw?.retryAfterSeconds ?? (Number.isFinite(Number(raw?.retry_after_ms)) ? Number(raw?.retry_after_ms) / 1e3 : void 0)
|
|
923
1439
|
);
|
|
924
1440
|
const retryEligible = raw?.retry_eligible ?? raw?.retryEligible;
|
|
1441
|
+
const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
|
|
1442
|
+
const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
|
|
1443
|
+
const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
|
|
1444
|
+
const runId = [raw?.run_id, raw?.runId, raw?.signal_run_id, raw?.signalRunId].find((value) => typeof value === "string" && value.trim());
|
|
1445
|
+
const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
|
|
1446
|
+
const jobId = raw?.job_id ?? raw?.jobId;
|
|
1447
|
+
const suggestedAction = raw?.suggested_action ?? raw?.suggestedAction;
|
|
925
1448
|
return {
|
|
926
1449
|
index,
|
|
927
1450
|
success: false,
|
|
928
1451
|
error,
|
|
929
1452
|
...errorCode ? { errorCode } : {},
|
|
930
1453
|
...typeof retryEligible === "boolean" ? { retryEligible } : {},
|
|
931
|
-
...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {}
|
|
1454
|
+
...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {},
|
|
1455
|
+
...retryStrategy === "new_idempotency_key" ? { retryStrategy } : {},
|
|
1456
|
+
...creditsUsed === 0 ? { creditsUsed: 0 } : {},
|
|
1457
|
+
...creditsCharged === 0 ? { creditsCharged: 0 } : {},
|
|
1458
|
+
...typeof runId === "string" && runId.trim() ? { runId: runId.trim() } : {},
|
|
1459
|
+
...typeof verificationRunId === "string" && verificationRunId.trim() ? { verificationRunId: verificationRunId.trim() } : {},
|
|
1460
|
+
...typeof jobId === "string" && jobId.trim() ? { jobId: jobId.trim() } : {},
|
|
1461
|
+
...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {}
|
|
932
1462
|
};
|
|
933
1463
|
}
|
|
934
1464
|
function recoverableJobRowFailed(row) {
|
|
935
1465
|
return coreProductResponseFailed(row) || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
|
|
936
1466
|
}
|
|
1467
|
+
function recoverableRowsToRound(rows, label) {
|
|
1468
|
+
return rows.map((raw) => {
|
|
1469
|
+
const index = Number(raw.index);
|
|
1470
|
+
const failed = recoverableJobRowFailed(raw);
|
|
1471
|
+
return {
|
|
1472
|
+
index,
|
|
1473
|
+
success: !failed,
|
|
1474
|
+
raw,
|
|
1475
|
+
error: failed ? raw.error || raw._error || raw.message || raw.failure_reason || `${label} item failed` : void 0
|
|
1476
|
+
};
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
function recoverableSignalCopyRows(rows) {
|
|
1480
|
+
return rows.map((raw) => {
|
|
1481
|
+
const index = Number(raw.index);
|
|
1482
|
+
const duplicateOf = recoveredDuplicateOf(raw);
|
|
1483
|
+
if (duplicateOf !== void 0) return { index, success: true, duplicateOf };
|
|
1484
|
+
if (recoverableJobRowFailed(raw)) {
|
|
1485
|
+
return batchItemFailure(
|
|
1486
|
+
index,
|
|
1487
|
+
raw.error || raw._error || raw.message || "Signal to Copy item failed",
|
|
1488
|
+
raw
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
try {
|
|
1492
|
+
assertTerminalSignalResponse(raw, "Signal to Copy");
|
|
1493
|
+
return { index, success: true, data: normalizeSignalToCopyResult(raw) };
|
|
1494
|
+
} catch (error) {
|
|
1495
|
+
return batchItemFailure(index, error instanceof Error ? error.message : String(error), raw);
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
function recoveredDuplicateOf(raw) {
|
|
1500
|
+
const value = Number(raw.duplicate_of ?? raw.duplicateOf ?? raw._duplicate_of);
|
|
1501
|
+
return Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
1502
|
+
}
|
|
937
1503
|
function newBatchIdempotencyKey(label) {
|
|
938
1504
|
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
939
1505
|
return `signaliz-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${nonce}`;
|
|
@@ -943,6 +1509,7 @@ function compact(input) {
|
|
|
943
1509
|
}
|
|
944
1510
|
function findEmailRequestBody(params) {
|
|
945
1511
|
return compact({
|
|
1512
|
+
run_id: params.runId,
|
|
946
1513
|
company_domain: params.companyDomain,
|
|
947
1514
|
full_name: params.fullName,
|
|
948
1515
|
first_name: params.firstName,
|
|
@@ -954,6 +1521,16 @@ function findEmailRequestBody(params) {
|
|
|
954
1521
|
idempotency_key: params.idempotencyKey
|
|
955
1522
|
});
|
|
956
1523
|
}
|
|
1524
|
+
function verifyEmailBatchRequestBody(input, options) {
|
|
1525
|
+
if (typeof input === "string") {
|
|
1526
|
+
return compact({ email: input, skip_cache: options?.skipCache });
|
|
1527
|
+
}
|
|
1528
|
+
return compact({
|
|
1529
|
+
email: input.email,
|
|
1530
|
+
skip_cache: input.skipCache ?? options?.skipCache,
|
|
1531
|
+
idempotency_key: input.idempotencyKey
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
957
1534
|
function isCoreProductDryRun(data) {
|
|
958
1535
|
return data.dry_run === true && data.status === "planned";
|
|
959
1536
|
}
|
|
@@ -982,76 +1559,111 @@ function normalizeCoreProductDryRunResult(data) {
|
|
|
982
1559
|
};
|
|
983
1560
|
}
|
|
984
1561
|
function normalizeFindEmailResult(data) {
|
|
1562
|
+
const processing = String(data.status || "").toLowerCase() === "processing";
|
|
985
1563
|
const failed = coreProductResponseFailed(data);
|
|
986
1564
|
const rawEmail = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
|
|
987
1565
|
const email = failed ? null : rawEmail;
|
|
988
1566
|
const found = Boolean(email) && data.found !== false;
|
|
989
1567
|
const verificationStatus = data.verification_status ?? (data.is_valid === true ? "valid" : data.status ?? "unknown");
|
|
1568
|
+
const deliverabilityStatus = data.deliverability_status ?? verificationStatus;
|
|
1569
|
+
const verificationVerdict = data.verification_verdict ?? verificationStatus;
|
|
990
1570
|
const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
|
|
991
1571
|
const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
|
|
992
1572
|
const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
|
|
993
1573
|
const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
|
|
994
1574
|
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()));
|
|
995
1575
|
const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
|
|
1576
|
+
const providerUsed = data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown";
|
|
1577
|
+
const verificationSource = data.verification_source ?? providerUsed;
|
|
1578
|
+
const billingMetadata = data.billing_metadata && typeof data.billing_metadata === "object" && !Array.isArray(data.billing_metadata) ? data.billing_metadata : void 0;
|
|
996
1579
|
return {
|
|
997
|
-
success: !failed && found,
|
|
1580
|
+
success: processing || !failed && found,
|
|
998
1581
|
found,
|
|
999
1582
|
email,
|
|
1000
1583
|
firstName: data.first_name,
|
|
1001
1584
|
lastName: data.last_name,
|
|
1002
1585
|
confidence: failed ? 0 : data.confidence ?? data.confidence_score ?? (verified ? 1 : 0),
|
|
1003
1586
|
verificationStatus: failed ? "error" : verificationStatus,
|
|
1587
|
+
deliverabilityStatus: failed ? "error" : deliverabilityStatus,
|
|
1588
|
+
verificationVerdict: failed ? "error" : verificationVerdict,
|
|
1004
1589
|
isVerified: !failed && verified,
|
|
1005
1590
|
isVerifiedForSending: !failed && !needsReverification && data.verified_for_sending === true,
|
|
1591
|
+
isDeliverable: !failed && !needsReverification && data.verified_for_sending === true,
|
|
1006
1592
|
isCatchAll: data.email_is_catch_all === true || data.is_catch_all === true,
|
|
1007
1593
|
verificationFreshness,
|
|
1008
1594
|
verificationAgeDays,
|
|
1009
1595
|
verifiedAt: typeof data.verified_at === "string" ? data.verified_at : null,
|
|
1010
1596
|
needsReverification: failed || needsReverification,
|
|
1011
|
-
providerUsed
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1597
|
+
providerUsed,
|
|
1598
|
+
verificationSource,
|
|
1599
|
+
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1600
|
+
failureReason: data.failure_reason ?? (failed || !found ? "no_verified_email_found" : "none"),
|
|
1601
|
+
creditsUsed: data.credits_used ?? billingMetadata?.credits_used,
|
|
1602
|
+
estimatedExternalCostUsd: data.estimated_external_cost_usd ?? billingMetadata?.estimated_external_cost_usd,
|
|
1603
|
+
billingMetadata,
|
|
1604
|
+
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1605
|
+
resultReturnedFrom: data.result_returned_from ?? billingMetadata?.result_returned_from,
|
|
1606
|
+
cacheHit: typeof data.cache_hit === "boolean" ? data.cache_hit : billingMetadata?.cache_hit,
|
|
1017
1607
|
negativeCacheHit: data.negative_cache_hit,
|
|
1018
1608
|
negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
|
|
1019
1609
|
cacheTier: data.cache_tier,
|
|
1020
|
-
freshEnrichmentUsed: data.fresh_enrichment_used,
|
|
1021
|
-
liveProviderCalled: data.live_provider_called,
|
|
1022
|
-
openrouterCalled: data.openrouter_called,
|
|
1023
|
-
byoOpenrouterUsed: data.byo_openrouter_used,
|
|
1024
|
-
error: found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
|
|
1025
|
-
errorCode: found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
|
|
1610
|
+
freshEnrichmentUsed: typeof data.fresh_enrichment_used === "boolean" ? data.fresh_enrichment_used : billingMetadata?.fresh_enrichment_used,
|
|
1611
|
+
liveProviderCalled: typeof data.live_provider_called === "boolean" ? data.live_provider_called : billingMetadata?.live_provider_called,
|
|
1612
|
+
openrouterCalled: typeof data.openrouter_called === "boolean" ? data.openrouter_called : billingMetadata?.openrouter_called,
|
|
1613
|
+
byoOpenrouterUsed: typeof data.byo_openrouter_used === "boolean" ? data.byo_openrouter_used : billingMetadata?.byo_openrouter_used,
|
|
1614
|
+
error: processing || found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
|
|
1615
|
+
errorCode: processing || found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
|
|
1616
|
+
status: processing ? "processing" : "completed",
|
|
1617
|
+
runId: data.run_id,
|
|
1618
|
+
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1619
|
+
suggestedAction: data.suggested_action,
|
|
1620
|
+
cachePublicationStatus: data.cache_publication_status,
|
|
1621
|
+
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1026
1622
|
raw: data
|
|
1027
1623
|
};
|
|
1028
1624
|
}
|
|
1029
1625
|
function normalizeVerifyEmailResult(email, data) {
|
|
1030
1626
|
const failed = coreProductResponseFailed(data);
|
|
1031
|
-
const verificationStatus = String(data.verification_status ?? "").toLowerCase();
|
|
1032
|
-
const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
|
|
1033
1627
|
const malformed = data.is_malformed === true || data.verification_verdict === "malformed" || data.verification_status === "malformed" || data.deliverability_status === "malformed";
|
|
1628
|
+
const verificationStatus = String(
|
|
1629
|
+
failed ? malformed ? "malformed" : "error" : data.verification_status ?? data.verification_verdict ?? data.deliverability_status ?? "unknown"
|
|
1630
|
+
).toLowerCase();
|
|
1631
|
+
const deliverabilityStatus = String(data.deliverability_status ?? verificationStatus).toLowerCase();
|
|
1632
|
+
const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
|
|
1633
|
+
const isCatchAll = data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false;
|
|
1634
|
+
const verifiedForSending = !failed && data.verified_for_sending === true;
|
|
1635
|
+
const verificationRunId = data.verification_run_id ?? data.run_id;
|
|
1034
1636
|
return {
|
|
1035
1637
|
success: !failed,
|
|
1036
1638
|
error: failed ? coreProductErrorMessage(data) : void 0,
|
|
1037
1639
|
errorCode: failed ? coreProductErrorCode(data) : void 0,
|
|
1038
1640
|
email: data.email ?? email,
|
|
1039
1641
|
status: !failed && String(data.status || "").toLowerCase() === "processing" ? "processing" : "completed",
|
|
1040
|
-
verificationRunId
|
|
1642
|
+
verificationRunId,
|
|
1041
1643
|
retryAfterMs: data.retry_after_ms,
|
|
1042
1644
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1043
1645
|
isValid: failed ? false : data.is_valid ?? data.valid ?? verified,
|
|
1044
1646
|
isDeliverable: failed ? false : data.is_deliverable ?? data.deliverable ?? verified,
|
|
1045
1647
|
isMalformed: malformed,
|
|
1046
|
-
isCatchAll
|
|
1047
|
-
verifiedForSending
|
|
1648
|
+
isCatchAll,
|
|
1649
|
+
verifiedForSending,
|
|
1650
|
+
verificationStatus,
|
|
1651
|
+
deliverabilityStatus,
|
|
1048
1652
|
verificationVerdict: failed ? malformed ? "malformed" : "error" : data.verification_verdict ?? data.verification_status ?? "unknown",
|
|
1653
|
+
isRoleAccount: data.is_role_account === true || data.email_is_role_account === true || data.role === true,
|
|
1654
|
+
quality: typeof data.quality === "string" ? data.quality : null,
|
|
1655
|
+
recommendation: typeof data.recommendation === "string" ? data.recommendation : verifiedForSending ? "send" : ["unknown", "verifier_error"].includes(verificationStatus) ? "manual_review" : "do_not_send",
|
|
1656
|
+
smtpStatus: data.smtp_status ?? (isCatchAll ? "accept_all" : verifiedForSending ? "accepted" : "not_checked"),
|
|
1657
|
+
providerStatus: data.provider_status ?? verificationStatus,
|
|
1658
|
+
failureReason: data.failure_reason ?? (verifiedForSending ? "none" : isCatchAll ? "catch_all_domain_not_send_safe" : "verification_not_send_safe"),
|
|
1049
1659
|
confidenceScore: failed ? 0 : data.confidence_score ?? data.confidence ?? data.diagnostics?.confidence_score ?? (verified ? 1 : 0),
|
|
1050
1660
|
provider: data.provider,
|
|
1051
1661
|
verificationSource: data.verification_source,
|
|
1662
|
+
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1052
1663
|
billingReplayed: data.billing_replayed,
|
|
1053
1664
|
creditsUsed: data.credits_used,
|
|
1054
1665
|
billingMetadata: data.billing_metadata,
|
|
1666
|
+
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1055
1667
|
resultReturnedFrom: data.result_returned_from,
|
|
1056
1668
|
cacheHit: data.cache_hit,
|
|
1057
1669
|
negativeCacheHit: data.negative_cache_hit,
|
|
@@ -1061,6 +1673,11 @@ function normalizeVerifyEmailResult(email, data) {
|
|
|
1061
1673
|
liveProviderCalled: data.live_provider_called,
|
|
1062
1674
|
openrouterCalled: data.openrouter_called,
|
|
1063
1675
|
byoOpenrouterUsed: data.byo_openrouter_used,
|
|
1676
|
+
estimatedExternalCostUsd: data.estimated_external_cost_usd,
|
|
1677
|
+
runId: data.run_id ?? verificationRunId,
|
|
1678
|
+
cachePublicationStatus: data.cache_publication_status,
|
|
1679
|
+
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1680
|
+
suggestedAction: data.suggested_action,
|
|
1064
1681
|
raw: data
|
|
1065
1682
|
};
|
|
1066
1683
|
}
|
|
@@ -1088,6 +1705,54 @@ function companySignalRequestBody(params) {
|
|
|
1088
1705
|
idempotency_key: params.idempotencyKey
|
|
1089
1706
|
});
|
|
1090
1707
|
}
|
|
1708
|
+
function companySignalParamsFromRecoveredRow(row) {
|
|
1709
|
+
return {
|
|
1710
|
+
companyDomain: row.company?.domain ?? row.company_domain,
|
|
1711
|
+
companyName: row.company?.name ?? row.company_name,
|
|
1712
|
+
signalRunId: row.signal_run_id ?? row.run_id
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
var COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS = [
|
|
1716
|
+
"research_prompt",
|
|
1717
|
+
"signal_types",
|
|
1718
|
+
"target_signal_count",
|
|
1719
|
+
"lookback_days",
|
|
1720
|
+
"online",
|
|
1721
|
+
"enable_deep_search",
|
|
1722
|
+
"search_mode",
|
|
1723
|
+
"include_summary",
|
|
1724
|
+
"enable_outreach_intelligence",
|
|
1725
|
+
"enable_predictive_intelligence",
|
|
1726
|
+
"skip_cache",
|
|
1727
|
+
"include_candidate_evidence"
|
|
1728
|
+
];
|
|
1729
|
+
function companySignalLargeBatchSubmission(requests) {
|
|
1730
|
+
if (requests.length <= 25) return null;
|
|
1731
|
+
if (requests.some(
|
|
1732
|
+
(request) => typeof request.signal_run_id === "string" && request.signal_run_id.trim() || typeof request.idempotency_key === "string" && request.idempotency_key.trim()
|
|
1733
|
+
)) {
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
const submissionFields = Object.fromEntries(
|
|
1737
|
+
COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => requests[0]?.[field] !== void 0).map((field) => [field, requests[0][field]])
|
|
1738
|
+
);
|
|
1739
|
+
const controlKey = JSON.stringify(submissionFields);
|
|
1740
|
+
for (const request of requests) {
|
|
1741
|
+
const requestControls = Object.fromEntries(
|
|
1742
|
+
COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => request[field] !== void 0).map((field) => [field, request[field]])
|
|
1743
|
+
);
|
|
1744
|
+
if (JSON.stringify(requestControls) !== controlKey) return null;
|
|
1745
|
+
const hasIdentity = typeof request.company_domain === "string" && request.company_domain.trim() || typeof request.company_name === "string" && request.company_name.trim();
|
|
1746
|
+
if (!hasIdentity) return null;
|
|
1747
|
+
}
|
|
1748
|
+
return {
|
|
1749
|
+
requests: requests.map((request) => compact({
|
|
1750
|
+
company_domain: request.company_domain,
|
|
1751
|
+
company_name: request.company_name
|
|
1752
|
+
})),
|
|
1753
|
+
submissionFields
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1091
1756
|
function normalizeCompanySignalResult(params, data) {
|
|
1092
1757
|
const failed = coreProductResponseFailed(data);
|
|
1093
1758
|
const rawSignals = failed ? [] : data.signals ?? data.signal_feed ?? [];
|
|
@@ -1158,6 +1823,12 @@ function validateSignalDiscoveryParams(params) {
|
|
|
1158
1823
|
if (query && (query.length < 2 || query.length > 2e3)) {
|
|
1159
1824
|
throw new RangeError("query must contain between 2 and 2000 characters");
|
|
1160
1825
|
}
|
|
1826
|
+
if (params.icpContext !== void 0) {
|
|
1827
|
+
const icpContext = typeof params.icpContext === "string" ? params.icpContext.trim() : "";
|
|
1828
|
+
if (icpContext.length < 2 || icpContext.length > 4e3) {
|
|
1829
|
+
throw new RangeError("icpContext must contain between 2 and 4000 characters");
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1161
1832
|
if (params.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit < 1 || params.limit > 1e3)) {
|
|
1162
1833
|
throw new RangeError("limit must be an integer between 1 and 1000");
|
|
1163
1834
|
}
|
|
@@ -1182,6 +1853,7 @@ function signalDiscoveryRequestBody(params) {
|
|
|
1182
1853
|
}
|
|
1183
1854
|
return compact({
|
|
1184
1855
|
query: params.query?.trim(),
|
|
1856
|
+
icp_context: params.icpContext?.trim(),
|
|
1185
1857
|
limit: params.limit ?? 100,
|
|
1186
1858
|
sources: params.sources,
|
|
1187
1859
|
lookback_days: params.lookbackDays,
|
|
@@ -1207,6 +1879,8 @@ function normalizeSignalDiscoveryResult(params, data) {
|
|
|
1207
1879
|
availableCompanySignalCount: Number.isFinite(Number(data.available_company_signal_count)) ? Number(data.available_company_signal_count) : void 0,
|
|
1208
1880
|
sourceLanes: Array.isArray(data.source_lanes) ? data.source_lanes.filter((lane) => typeof lane === "string") : void 0,
|
|
1209
1881
|
evidenceValidation: data.evidence_validation && typeof data.evidence_validation === "object" ? data.evidence_validation : void 0,
|
|
1882
|
+
discoveryDiagnostics: data.discovery_diagnostics && typeof data.discovery_diagnostics === "object" ? data.discovery_diagnostics : void 0,
|
|
1883
|
+
identityResolution: data.identity_resolution && typeof data.identity_resolution === "object" ? data.identity_resolution : void 0,
|
|
1210
1884
|
costGuardrail: normalizeSignalDiscoveryCostGuardrail(data.cost_guardrail),
|
|
1211
1885
|
warnings: Array.isArray(data.warnings) ? data.warnings.filter((warning) => typeof warning === "string") : void 0,
|
|
1212
1886
|
raw: data
|
|
@@ -1235,7 +1909,15 @@ function normalizeSignalDiscoveryCostGuardrail(value) {
|
|
|
1235
1909
|
guardrail.projected_cost_per_requested_company_signal_usd
|
|
1236
1910
|
),
|
|
1237
1911
|
sourceCostUsd: numberOrNullable(guardrail.source_cost_usd),
|
|
1238
|
-
costSource: ["observed", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
|
|
1912
|
+
costSource: ["observed", "catalog_preflight", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
|
|
1913
|
+
identityResolutionCostUsd: numberOrNullable(guardrail.identity_resolution_cost_usd),
|
|
1914
|
+
identityCostSource: [
|
|
1915
|
+
"observed_zero_credit_receipts",
|
|
1916
|
+
"contractual_zero_cost",
|
|
1917
|
+
"not_used",
|
|
1918
|
+
"unavailable"
|
|
1919
|
+
].includes(String(guardrail.identity_cost_source)) ? String(guardrail.identity_cost_source) : void 0,
|
|
1920
|
+
identityProviderRequests: numberOrUndefined(guardrail.identity_provider_requests),
|
|
1239
1921
|
qualifiedCompanySignalCount: numberOrUndefined(guardrail.qualified_company_signal_count),
|
|
1240
1922
|
returnedCompanySignalCount: numberOrUndefined(guardrail.returned_company_signal_count),
|
|
1241
1923
|
availableCompanySignalCount: numberOrUndefined(guardrail.available_company_signal_count),
|
|
@@ -1321,6 +2003,9 @@ function dedupeExactBatchItems(items, keyForItem) {
|
|
|
1321
2003
|
function normalizedBatchText(value) {
|
|
1322
2004
|
return typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, " ") : "";
|
|
1323
2005
|
}
|
|
2006
|
+
function normalizedBatchIdempotencyKey(value) {
|
|
2007
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2008
|
+
}
|
|
1324
2009
|
function normalizedBatchDomain(value) {
|
|
1325
2010
|
return normalizedBatchText(value).replace(/^https?:\/\//, "").replace(/^www\./, "").split(/[/?#]/, 1)[0].replace(/\.+$/, "");
|
|
1326
2011
|
}
|
|
@@ -1339,11 +2024,13 @@ function normalizedFindEmailBatchName(request) {
|
|
|
1339
2024
|
function coreProductBatchRequestKey(path2, request) {
|
|
1340
2025
|
if (path2 === "api/v1/find-email") {
|
|
1341
2026
|
return JSON.stringify([
|
|
2027
|
+
normalizedBatchText(request.run_id),
|
|
1342
2028
|
normalizedFindEmailBatchName(request),
|
|
1343
2029
|
normalizedBatchDomain(request.company_domain),
|
|
1344
2030
|
normalizedBatchLinkedIn(request.linkedin_url),
|
|
1345
2031
|
normalizedBatchText(request.company_name),
|
|
1346
|
-
request.skip_cache === true || request.bypass_cache === true
|
|
2032
|
+
request.skip_cache === true || request.bypass_cache === true,
|
|
2033
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1347
2034
|
]);
|
|
1348
2035
|
}
|
|
1349
2036
|
if (path2 === "api/v1/verify-email") {
|
|
@@ -1351,7 +2038,8 @@ function coreProductBatchRequestKey(path2, request) {
|
|
|
1351
2038
|
normalizedBatchText(request.email),
|
|
1352
2039
|
request.skip_cache === true || request.bypass_cache === true,
|
|
1353
2040
|
request.skip_catch_all_verification === true,
|
|
1354
|
-
request.skip_esp_check === true
|
|
2041
|
+
request.skip_esp_check === true,
|
|
2042
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1355
2043
|
]);
|
|
1356
2044
|
}
|
|
1357
2045
|
if (path2 === "api/v1/company-signals") {
|
|
@@ -1373,7 +2061,8 @@ function coreProductBatchRequestKey(path2, request) {
|
|
|
1373
2061
|
normalizedBatchText(request.search_mode || "balanced"),
|
|
1374
2062
|
request.enable_outreach_intelligence === true,
|
|
1375
2063
|
request.enable_predictive_intelligence === true,
|
|
1376
|
-
request.skip_cache === true || request.bypass_cache === true
|
|
2064
|
+
request.skip_cache === true || request.bypass_cache === true,
|
|
2065
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1377
2066
|
]);
|
|
1378
2067
|
}
|
|
1379
2068
|
return JSON.stringify([
|
|
@@ -1382,9 +2071,11 @@ function coreProductBatchRequestKey(path2, request) {
|
|
|
1382
2071
|
typeof request.title === "string" ? request.title.trim() : "",
|
|
1383
2072
|
typeof request.campaign_offer === "string" ? request.campaign_offer.trim() : "",
|
|
1384
2073
|
typeof request.research_prompt === "string" ? request.research_prompt.trim() : "",
|
|
2074
|
+
Number(request.lookback_days) || null,
|
|
1385
2075
|
typeof request.signal_run_id === "string" ? request.signal_run_id.trim() : "",
|
|
1386
2076
|
request.skip_cache === true,
|
|
1387
|
-
request.enable_deep_search === true
|
|
2077
|
+
request.enable_deep_search === true,
|
|
2078
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1388
2079
|
]);
|
|
1389
2080
|
}
|
|
1390
2081
|
function finalizeCoreBatch(total, startedAt, round, normalize, options) {
|
|
@@ -1422,6 +2113,37 @@ function finalizeCoreBatch(total, startedAt, round, normalize, options) {
|
|
|
1422
2113
|
results: compactedResults
|
|
1423
2114
|
};
|
|
1424
2115
|
}
|
|
2116
|
+
function finalizeRecoveredCoreBatch(total, startedAt, round, normalize, options) {
|
|
2117
|
+
const results = round.map((item) => {
|
|
2118
|
+
const duplicateOf = item.raw ? recoveredDuplicateOf(item.raw) : void 0;
|
|
2119
|
+
if (duplicateOf !== void 0) return { index: item.index, success: true, duplicateOf };
|
|
2120
|
+
if (!item.success || !item.raw) {
|
|
2121
|
+
return batchItemFailure(
|
|
2122
|
+
item.index,
|
|
2123
|
+
item.error || "Signaliz batch item failed",
|
|
2124
|
+
item.raw ?? item
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2127
|
+
try {
|
|
2128
|
+
return { index: item.index, success: true, data: normalize(item) };
|
|
2129
|
+
} catch (error) {
|
|
2130
|
+
return {
|
|
2131
|
+
index: item.index,
|
|
2132
|
+
success: false,
|
|
2133
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
});
|
|
2137
|
+
const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
|
|
2138
|
+
const succeeded = compactedResults.filter((item) => item.success).length;
|
|
2139
|
+
return {
|
|
2140
|
+
total,
|
|
2141
|
+
succeeded,
|
|
2142
|
+
failed: total - succeeded,
|
|
2143
|
+
durationMs: Date.now() - startedAt,
|
|
2144
|
+
results: compactedResults
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
1425
2147
|
function compactExactDuplicateResults(results, enabled) {
|
|
1426
2148
|
if (!enabled) return results;
|
|
1427
2149
|
const sourceIndexByRaw = /* @__PURE__ */ new WeakMap();
|
|
@@ -1437,6 +2159,19 @@ function compactExactDuplicateResults(results, enabled) {
|
|
|
1437
2159
|
return item;
|
|
1438
2160
|
});
|
|
1439
2161
|
}
|
|
2162
|
+
function compactKnownDuplicateBatch(batch, sourceIndexByInput, enabled) {
|
|
2163
|
+
if (!enabled) return batch;
|
|
2164
|
+
const canonicalInputByUniqueIndex = /* @__PURE__ */ new Map();
|
|
2165
|
+
sourceIndexByInput.forEach((uniqueIndex, inputIndex) => {
|
|
2166
|
+
if (!canonicalInputByUniqueIndex.has(uniqueIndex)) canonicalInputByUniqueIndex.set(uniqueIndex, inputIndex);
|
|
2167
|
+
});
|
|
2168
|
+
const results = batch.results.map((item, index) => {
|
|
2169
|
+
const canonicalIndex = canonicalInputByUniqueIndex.get(sourceIndexByInput[index]) ?? index;
|
|
2170
|
+
if (canonicalIndex === index || !item.success || !batch.results[canonicalIndex]?.success) return item;
|
|
2171
|
+
return { index, success: true, duplicateOf: canonicalIndex };
|
|
2172
|
+
});
|
|
2173
|
+
return { ...batch, results };
|
|
2174
|
+
}
|
|
1440
2175
|
function signalToCopyRequestBody(params) {
|
|
1441
2176
|
return compact({
|
|
1442
2177
|
company_domain: params.companyDomain,
|
|
@@ -1444,6 +2179,7 @@ function signalToCopyRequestBody(params) {
|
|
|
1444
2179
|
title: params.title,
|
|
1445
2180
|
campaign_offer: params.campaignOffer,
|
|
1446
2181
|
research_prompt: params.researchPrompt,
|
|
2182
|
+
lookback_days: params.lookbackDays,
|
|
1447
2183
|
signal_run_id: params.signalRunId,
|
|
1448
2184
|
skip_cache: params.skipCache,
|
|
1449
2185
|
enable_deep_search: params.enableDeepSearch,
|
|
@@ -1452,8 +2188,33 @@ function signalToCopyRequestBody(params) {
|
|
|
1452
2188
|
idempotency_key: params.idempotencyKey
|
|
1453
2189
|
});
|
|
1454
2190
|
}
|
|
2191
|
+
function validateLargeAvailableDataSignalCopyBatch(requests) {
|
|
2192
|
+
if (requests.length <= 25) return;
|
|
2193
|
+
const invalidIndex = requests.findIndex(
|
|
2194
|
+
(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()
|
|
2195
|
+
);
|
|
2196
|
+
if (invalidIndex >= 0) {
|
|
2197
|
+
throw new RangeError(
|
|
2198
|
+
`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.`
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
function availableDataSignalCopyRequestBody(params) {
|
|
2203
|
+
return compact({
|
|
2204
|
+
company_domain: params.companyDomain,
|
|
2205
|
+
person_name: params.personName,
|
|
2206
|
+
title: params.title,
|
|
2207
|
+
campaign_offer: params.campaignOffer,
|
|
2208
|
+
research_prompt: params.researchPrompt,
|
|
2209
|
+
lookback_days: params.lookbackDays,
|
|
2210
|
+
signal_run_id: params.signalRunId
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
1455
2213
|
function validateSignalToCopyParams(params) {
|
|
1456
2214
|
validateCoreProductMaxWaitMs(params.maxWaitMs);
|
|
2215
|
+
if (params.lookbackDays !== void 0 && (!Number.isInteger(params.lookbackDays) || params.lookbackDays < 1 || params.lookbackDays > 365)) {
|
|
2216
|
+
throw new RangeError("lookbackDays must be an integer between 1 and 365");
|
|
2217
|
+
}
|
|
1457
2218
|
if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
|
|
1458
2219
|
const missing = [
|
|
1459
2220
|
["companyDomain", params.companyDomain],
|
|
@@ -1582,6 +2343,21 @@ async function waitForNextSignalPoll(data, override, startedAt, maxWaitMs) {
|
|
|
1582
2343
|
function sleep2(ms) {
|
|
1583
2344
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1584
2345
|
}
|
|
2346
|
+
function assertRecoverableBatchResultsRetained(status, label, jobId, idempotencyKey) {
|
|
2347
|
+
if (status.results_expired !== true && status.results_cleaned !== true) return;
|
|
2348
|
+
throw new SignalizError({
|
|
2349
|
+
code: "BATCH_RESULTS_EXPIRED",
|
|
2350
|
+
message: `${label} batch ${jobId} completed, but its retained result pages have expired and cannot be recovered. Do not automatically resubmit provider work.`,
|
|
2351
|
+
errorType: "not_found",
|
|
2352
|
+
details: {
|
|
2353
|
+
job_id: jobId,
|
|
2354
|
+
...idempotencyKey ? { idempotency_key: idempotencyKey } : {},
|
|
2355
|
+
suggested_action: "review_before_resubmitting",
|
|
2356
|
+
retry_eligible: false,
|
|
2357
|
+
results_expired: true
|
|
2358
|
+
}
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
1585
2361
|
async function runBatch(items, execute, options) {
|
|
1586
2362
|
validateBatchSize(items);
|
|
1587
2363
|
const concurrency = validatedBatchConcurrency(options);
|