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