@signaliz/sdk 1.0.61 → 1.0.63
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 +53 -17
- package/dist/{chunk-ENA7AVFB.mjs → chunk-VWO2GEYQ.mjs} +821 -101
- package/dist/index.d.mts +88 -7
- package/dist/index.d.ts +88 -7
- package/dist/index.js +821 -101
- package/dist/index.mjs +1 -1
- package/dist/mcp-config.js +821 -101
- package/dist/mcp-config.mjs +1 -1
- package/package.json +1 -1
|
@@ -48,9 +48,13 @@ function publicRetryDetails(body) {
|
|
|
48
48
|
...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
|
|
49
49
|
...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
|
|
50
50
|
...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
|
|
51
|
+
...body?.idempotency_key !== void 0 ? { idempotency_key: body.idempotency_key } : {},
|
|
51
52
|
...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
|
|
52
53
|
...body?.run_id !== void 0 ? { run_id: body.run_id } : {},
|
|
53
54
|
...body?.suggested_action !== void 0 ? { suggested_action: body.suggested_action } : {},
|
|
55
|
+
...body?.do_not_auto_resubmit !== void 0 ? { do_not_auto_resubmit: body.do_not_auto_resubmit } : {},
|
|
56
|
+
...body?.results_cleaned !== void 0 ? { results_cleaned: body.results_cleaned } : {},
|
|
57
|
+
...body?.results_expired !== void 0 ? { results_expired: body.results_expired } : {},
|
|
54
58
|
...body?.signal_run_id !== void 0 ? { signal_run_id: body.signal_run_id } : {},
|
|
55
59
|
...body?.company_signal_run_id !== void 0 ? { company_signal_run_id: body.company_signal_run_id } : {},
|
|
56
60
|
...body?.resume_context_persisted !== void 0 ? { resume_context_persisted: body.resume_context_persisted } : {}
|
|
@@ -66,8 +70,9 @@ function mergePublicErrorDetails(body, explicitDetails) {
|
|
|
66
70
|
}
|
|
67
71
|
function mapErrorType(code, status) {
|
|
68
72
|
if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
|
|
69
|
-
if (code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400) return "validation";
|
|
73
|
+
if (code === "PAYLOAD_TOO_LARGE" || code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400 || status === 413) return "validation";
|
|
70
74
|
if (code === "NOT_FOUND" || status === 404) return "not_found";
|
|
75
|
+
if (code === "BATCH_RESULTS_EXPIRED" || status === 410) return "not_found";
|
|
71
76
|
if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
|
|
72
77
|
if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
|
|
73
78
|
if (code === "INSUFFICIENT_CREDITS" || status === 402) return "insufficient_credits";
|
|
@@ -78,7 +83,7 @@ function mapErrorType(code, status) {
|
|
|
78
83
|
|
|
79
84
|
// src/client.ts
|
|
80
85
|
var DEFAULT_BASE_URL = "https://api.signaliz.com/functions/v1";
|
|
81
|
-
var DEFAULT_TIMEOUT =
|
|
86
|
+
var DEFAULT_TIMEOUT = 13e4;
|
|
82
87
|
var DEFAULT_MAX_RETRIES = 3;
|
|
83
88
|
var HttpClient = class {
|
|
84
89
|
constructor(config) {
|
|
@@ -92,9 +97,9 @@ var HttpClient = class {
|
|
|
92
97
|
throw new Error("Signaliz: provide either apiKey or clientId + clientSecret");
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
|
-
async request(functionName, body, method = "POST") {
|
|
100
|
+
async request(functionName, body, method = "POST", options = {}) {
|
|
96
101
|
let lastError;
|
|
97
|
-
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body) : void 0;
|
|
102
|
+
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body, options.automaticIdempotency !== false) : void 0;
|
|
98
103
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
99
104
|
let timer;
|
|
100
105
|
try {
|
|
@@ -162,8 +167,8 @@ var HttpClient = class {
|
|
|
162
167
|
throw lastError || new Error("Request failed after retries");
|
|
163
168
|
}
|
|
164
169
|
/** Convenience wrapper for POST-based edge function calls */
|
|
165
|
-
async post(functionName, body) {
|
|
166
|
-
return this.request(functionName, body, "POST");
|
|
170
|
+
async post(functionName, body, options = {}) {
|
|
171
|
+
return this.request(functionName, body, "POST", options);
|
|
167
172
|
}
|
|
168
173
|
/** Convenience wrapper for GET-based edge function calls with query params */
|
|
169
174
|
async get(functionName, query = {}) {
|
|
@@ -280,10 +285,12 @@ var HttpClient = class {
|
|
|
280
285
|
return this.accessTokenPromise;
|
|
281
286
|
}
|
|
282
287
|
};
|
|
283
|
-
function requestIdempotencyKey(body) {
|
|
288
|
+
function requestIdempotencyKey(body, automaticIdempotency) {
|
|
284
289
|
const requested = typeof body.idempotency_key === "string" ? body.idempotency_key.trim() : "";
|
|
290
|
+
if (requested) return requested;
|
|
291
|
+
if (!automaticIdempotency) return void 0;
|
|
285
292
|
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
286
|
-
return
|
|
293
|
+
return `sdk_${nonce}`;
|
|
287
294
|
}
|
|
288
295
|
function sleep(ms) {
|
|
289
296
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -385,7 +392,7 @@ function mapMcpErrorType(error) {
|
|
|
385
392
|
function mapErrorTypeFromCode(code) {
|
|
386
393
|
if (!code) return "unknown";
|
|
387
394
|
if (code === "RATE_LIMITED") return "rate_limited";
|
|
388
|
-
if (code === "INVALID_ARGUMENT" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
|
|
395
|
+
if (code === "INVALID_ARGUMENT" || code === "PAYLOAD_TOO_LARGE" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
|
|
389
396
|
if (code === "AUTH_REQUIRED" || code === "AUTH_001") return "auth_expired";
|
|
390
397
|
if (code === "QUOTA_EXCEEDED") return "insufficient_credits";
|
|
391
398
|
if (code === "BUILD_NOT_READY" || code === "ARTIFACT_NOT_READY") return "not_found";
|
|
@@ -393,6 +400,166 @@ function mapErrorTypeFromCode(code) {
|
|
|
393
400
|
return "unknown";
|
|
394
401
|
}
|
|
395
402
|
|
|
403
|
+
// src/email-validation.ts
|
|
404
|
+
function isValidEmailString(value) {
|
|
405
|
+
if (typeof value !== "string") return false;
|
|
406
|
+
const email = value.trim();
|
|
407
|
+
if (!email || email.length > 254) return false;
|
|
408
|
+
const separator = email.indexOf("@");
|
|
409
|
+
if (separator <= 0 || separator !== email.lastIndexOf("@")) return false;
|
|
410
|
+
const local = email.slice(0, separator);
|
|
411
|
+
const domain = email.slice(separator + 1).toLowerCase();
|
|
412
|
+
if (local.length > 64 || domain.length > 253 || local.startsWith(".") || local.endsWith(".") || local.includes("..") || domain.startsWith(".") || domain.endsWith(".") || domain.includes("..")) return false;
|
|
413
|
+
if (!/^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+$/.test(local)) return false;
|
|
414
|
+
const labels = domain.split(".");
|
|
415
|
+
if (labels.length < 2) return false;
|
|
416
|
+
return labels.every(
|
|
417
|
+
(label) => label.length > 0 && label.length <= 63 && /^[a-z0-9-]+$/.test(label) && !label.startsWith("-") && !label.endsWith("-")
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// src/core-product-input-bounds.ts
|
|
422
|
+
var LARGE_BATCH_MAX_ENCODED_BYTES = 8 * 1024 * 1024;
|
|
423
|
+
var DOMAIN_MAX_UTF8_BYTES = 253;
|
|
424
|
+
var NAME_MAX_UTF8_BYTES = 500;
|
|
425
|
+
var TITLE_MAX_UTF8_BYTES = 500;
|
|
426
|
+
var ID_MAX_UTF8_BYTES = 200;
|
|
427
|
+
var RESEARCH_PROMPT_MAX_UTF8_BYTES = 2e3;
|
|
428
|
+
var CAMPAIGN_OFFER_MAX_UTF8_BYTES = 4e3;
|
|
429
|
+
var SIGNAL_TYPE_MAX_UTF8_BYTES = 100;
|
|
430
|
+
var UTF8_ENCODER = new TextEncoder();
|
|
431
|
+
function byteLength(value) {
|
|
432
|
+
return UTF8_ENCODER.encode(value).byteLength;
|
|
433
|
+
}
|
|
434
|
+
function payloadTooLarge(message, details) {
|
|
435
|
+
throw new SignalizError({
|
|
436
|
+
code: "PAYLOAD_TOO_LARGE",
|
|
437
|
+
message,
|
|
438
|
+
errorType: "validation",
|
|
439
|
+
details: {
|
|
440
|
+
...details,
|
|
441
|
+
retry_eligible: false,
|
|
442
|
+
credits_used: 0,
|
|
443
|
+
credits_charged: 0
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
function assertStringBytes(value, field, maxBytes, index) {
|
|
448
|
+
if (typeof value !== "string") return;
|
|
449
|
+
const encodedBytes = byteLength(value);
|
|
450
|
+
if (encodedBytes <= maxBytes) return;
|
|
451
|
+
payloadTooLarge(
|
|
452
|
+
`${field} is ${encodedBytes} UTF-8 bytes; maximum is ${maxBytes} bytes`,
|
|
453
|
+
{
|
|
454
|
+
field,
|
|
455
|
+
...index !== void 0 ? { index } : {},
|
|
456
|
+
encoded_bytes: encodedBytes,
|
|
457
|
+
max_bytes: maxBytes
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
function assertCompanySignalRows(body) {
|
|
462
|
+
const requests = Array.isArray(body.requests) ? body.requests : [];
|
|
463
|
+
assertStringBytes(
|
|
464
|
+
body.research_prompt,
|
|
465
|
+
"research_prompt",
|
|
466
|
+
RESEARCH_PROMPT_MAX_UTF8_BYTES
|
|
467
|
+
);
|
|
468
|
+
if (Array.isArray(body.signal_types)) {
|
|
469
|
+
body.signal_types.forEach(
|
|
470
|
+
(signalType, index) => assertStringBytes(
|
|
471
|
+
signalType,
|
|
472
|
+
`signal_types[${index}]`,
|
|
473
|
+
SIGNAL_TYPE_MAX_UTF8_BYTES,
|
|
474
|
+
index
|
|
475
|
+
)
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
requests.forEach((value, index) => {
|
|
479
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
480
|
+
const request = value;
|
|
481
|
+
assertStringBytes(
|
|
482
|
+
request.company_domain,
|
|
483
|
+
`requests[${index}].company_domain`,
|
|
484
|
+
DOMAIN_MAX_UTF8_BYTES,
|
|
485
|
+
index
|
|
486
|
+
);
|
|
487
|
+
assertStringBytes(
|
|
488
|
+
request.company_name,
|
|
489
|
+
`requests[${index}].company_name`,
|
|
490
|
+
NAME_MAX_UTF8_BYTES,
|
|
491
|
+
index
|
|
492
|
+
);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
function assertSignalCopyRows(body) {
|
|
496
|
+
const requests = Array.isArray(body.requests) ? body.requests : [];
|
|
497
|
+
requests.forEach((value, index) => {
|
|
498
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
499
|
+
const request = value;
|
|
500
|
+
assertStringBytes(
|
|
501
|
+
request.company_domain,
|
|
502
|
+
`requests[${index}].company_domain`,
|
|
503
|
+
DOMAIN_MAX_UTF8_BYTES,
|
|
504
|
+
index
|
|
505
|
+
);
|
|
506
|
+
assertStringBytes(
|
|
507
|
+
request.person_name,
|
|
508
|
+
`requests[${index}].person_name`,
|
|
509
|
+
NAME_MAX_UTF8_BYTES,
|
|
510
|
+
index
|
|
511
|
+
);
|
|
512
|
+
assertStringBytes(
|
|
513
|
+
request.title,
|
|
514
|
+
`requests[${index}].title`,
|
|
515
|
+
TITLE_MAX_UTF8_BYTES,
|
|
516
|
+
index
|
|
517
|
+
);
|
|
518
|
+
assertStringBytes(
|
|
519
|
+
request.campaign_offer,
|
|
520
|
+
`requests[${index}].campaign_offer`,
|
|
521
|
+
CAMPAIGN_OFFER_MAX_UTF8_BYTES,
|
|
522
|
+
index
|
|
523
|
+
);
|
|
524
|
+
assertStringBytes(
|
|
525
|
+
request.research_prompt,
|
|
526
|
+
`requests[${index}].research_prompt`,
|
|
527
|
+
RESEARCH_PROMPT_MAX_UTF8_BYTES,
|
|
528
|
+
index
|
|
529
|
+
);
|
|
530
|
+
assertStringBytes(
|
|
531
|
+
request.signal_run_id,
|
|
532
|
+
`requests[${index}].signal_run_id`,
|
|
533
|
+
ID_MAX_UTF8_BYTES,
|
|
534
|
+
index
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
function assertLargeCoreProductInputBounds(path, body) {
|
|
539
|
+
const requests = Array.isArray(body.requests) ? body.requests : [];
|
|
540
|
+
if (requests.length <= 25) return;
|
|
541
|
+
assertStringBytes(body.idempotency_key, "idempotency_key", ID_MAX_UTF8_BYTES);
|
|
542
|
+
if (path === "api/v1/company-signals") {
|
|
543
|
+
assertCompanySignalRows(body);
|
|
544
|
+
} else {
|
|
545
|
+
assertSignalCopyRows(body);
|
|
546
|
+
}
|
|
547
|
+
const encoded = JSON.stringify(body);
|
|
548
|
+
if (typeof encoded !== "string") {
|
|
549
|
+
throw new TypeError("Core product batch input must be JSON serializable.");
|
|
550
|
+
}
|
|
551
|
+
const encodedBytes = byteLength(encoded);
|
|
552
|
+
if (encodedBytes <= LARGE_BATCH_MAX_ENCODED_BYTES) return;
|
|
553
|
+
payloadTooLarge(
|
|
554
|
+
`${path === "api/v1/company-signals" ? "Company Signal" : "Signal Copy"} batch request is ${encodedBytes} UTF-8 bytes after JSON encoding; maximum is ${LARGE_BATCH_MAX_ENCODED_BYTES} bytes`,
|
|
555
|
+
{
|
|
556
|
+
field: "requests",
|
|
557
|
+
encoded_bytes: encodedBytes,
|
|
558
|
+
max_bytes: LARGE_BATCH_MAX_ENCODED_BYTES
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
396
563
|
// src/index.ts
|
|
397
564
|
var MAX_ROW_RATE_LIMIT_RETRIES = 2;
|
|
398
565
|
var Signaliz = class {
|
|
@@ -426,9 +593,25 @@ var Signaliz = class {
|
|
|
426
593
|
options
|
|
427
594
|
);
|
|
428
595
|
}
|
|
596
|
+
async resumeFindEmailBatch(jobId, options) {
|
|
597
|
+
const startedAt = Date.now();
|
|
598
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
599
|
+
"api/v1/find-email",
|
|
600
|
+
jobId,
|
|
601
|
+
"Find Email",
|
|
602
|
+
options
|
|
603
|
+
);
|
|
604
|
+
return finalizeCoreBatch(
|
|
605
|
+
total,
|
|
606
|
+
startedAt,
|
|
607
|
+
recoverableRowsToRound(rows, "Find Email"),
|
|
608
|
+
(item) => normalizeFindEmailResult(item.raw),
|
|
609
|
+
options
|
|
610
|
+
);
|
|
611
|
+
}
|
|
429
612
|
async verifyEmail(email, options) {
|
|
430
613
|
const normalizedEmail = typeof email === "string" ? email.trim() : "";
|
|
431
|
-
if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail &&
|
|
614
|
+
if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail && !isValidEmailString(normalizedEmail)) {
|
|
432
615
|
const error = `Invalid email format: "${normalizedEmail}"`;
|
|
433
616
|
return normalizeVerifyEmailResult(normalizedEmail, {
|
|
434
617
|
success: false,
|
|
@@ -478,19 +661,20 @@ var Signaliz = class {
|
|
|
478
661
|
}
|
|
479
662
|
async verifyEmails(emails, options) {
|
|
480
663
|
validateBatchSize(emails);
|
|
664
|
+
const requests = emails.map((email) => verifyEmailBatchRequestBody(email, options));
|
|
481
665
|
if (options?.dryRun === true) {
|
|
482
666
|
return this.runCoreProductDryRun(
|
|
483
667
|
"api/v1/verify-email",
|
|
484
|
-
|
|
668
|
+
requests,
|
|
485
669
|
options.idempotencyKey
|
|
486
670
|
);
|
|
487
671
|
}
|
|
488
672
|
const startedAt = Date.now();
|
|
489
673
|
const round = await this.runCoreBatchRound(
|
|
490
674
|
"api/v1/verify-email",
|
|
491
|
-
|
|
675
|
+
requests.map((request, index) => ({
|
|
492
676
|
index,
|
|
493
|
-
request
|
|
677
|
+
request
|
|
494
678
|
})),
|
|
495
679
|
options
|
|
496
680
|
);
|
|
@@ -498,7 +682,23 @@ var Signaliz = class {
|
|
|
498
682
|
emails.length,
|
|
499
683
|
startedAt,
|
|
500
684
|
round,
|
|
501
|
-
(item) => normalizeVerifyEmailResult(
|
|
685
|
+
(item) => normalizeVerifyEmailResult(String(requests[item.index].email ?? ""), item.raw),
|
|
686
|
+
options
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
async resumeVerifyEmailBatch(jobId, options) {
|
|
690
|
+
const startedAt = Date.now();
|
|
691
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
692
|
+
"api/v1/verify-email",
|
|
693
|
+
jobId,
|
|
694
|
+
"Verify Email",
|
|
695
|
+
options
|
|
696
|
+
);
|
|
697
|
+
return finalizeCoreBatch(
|
|
698
|
+
total,
|
|
699
|
+
startedAt,
|
|
700
|
+
recoverableRowsToRound(rows, "Verify Email"),
|
|
701
|
+
(item) => normalizeVerifyEmailResult(String(item.raw?.email || ""), item.raw),
|
|
502
702
|
options
|
|
503
703
|
);
|
|
504
704
|
}
|
|
@@ -514,25 +714,92 @@ var Signaliz = class {
|
|
|
514
714
|
validateSignalDiscoveryParams(params);
|
|
515
715
|
const data = await this.client.post(
|
|
516
716
|
"api/v1/signals",
|
|
517
|
-
signalDiscoveryRequestBody(params)
|
|
717
|
+
signalDiscoveryRequestBody(params),
|
|
718
|
+
// Signals Everything owns a normalized, workspace-scoped automatic key
|
|
719
|
+
// at the API boundary so SDK, CLI, MCP, and direct REST calls coalesce.
|
|
720
|
+
{ automaticIdempotency: false }
|
|
518
721
|
);
|
|
519
722
|
return normalizeSignalDiscoveryResult(params, data);
|
|
520
723
|
}
|
|
521
724
|
async enrichCompanies(companies, options) {
|
|
522
725
|
validateBatchSize(companies);
|
|
523
726
|
companies.forEach((params) => validateCoreProductMaxWaitMs(params.maxWaitMs));
|
|
727
|
+
const requests = companies.map(companySignalRequestBody);
|
|
728
|
+
if (companies.length > 25 && requests.some((request) => request.include_candidate_evidence === true)) {
|
|
729
|
+
throw new RangeError(
|
|
730
|
+
"includeCandidateEvidence is not supported in Company Signals batches larger than 25 because rejected evidence diagnostics are intentionally excluded from durable scale storage; split diagnostic requests into batches of at most 25."
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
const largeBatch = companySignalLargeBatchSubmission(requests);
|
|
734
|
+
if (companies.length > 25 && !largeBatch) {
|
|
735
|
+
throw new RangeError(
|
|
736
|
+
"Company Signals batches larger than 25 require homogeneous new-enrichment rows with company identity only and uniform controls; split recovery or mixed-control rows into batches of at most 25."
|
|
737
|
+
);
|
|
738
|
+
}
|
|
524
739
|
if (options?.dryRun === true) {
|
|
525
740
|
return this.runCoreProductDryRun(
|
|
526
741
|
"api/v1/company-signals",
|
|
527
|
-
|
|
528
|
-
options.idempotencyKey
|
|
742
|
+
largeBatch?.requests || requests,
|
|
743
|
+
options.idempotencyKey,
|
|
744
|
+
largeBatch?.submissionFields
|
|
529
745
|
);
|
|
530
746
|
}
|
|
531
747
|
const startedAt = Date.now();
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
748
|
+
if (options?.waitForResult === false) {
|
|
749
|
+
if (!largeBatch) {
|
|
750
|
+
throw new RangeError(
|
|
751
|
+
"Company Signals --no-wait requires more than 25 homogeneous new-enrichment rows; recovery reads and mixed controls must use batches of at most 25."
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
return this.submitRecoverableCoreBatchJob(
|
|
755
|
+
"api/v1/company-signals",
|
|
756
|
+
largeBatch.requests,
|
|
757
|
+
"Company Signals",
|
|
758
|
+
options.idempotencyKey,
|
|
759
|
+
companies.map((_, index) => index),
|
|
760
|
+
largeBatch.submissionFields
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
if (largeBatch) {
|
|
764
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
765
|
+
"api/v1/company-signals",
|
|
766
|
+
largeBatch.requests,
|
|
767
|
+
"Company Signals",
|
|
768
|
+
options?.idempotencyKey,
|
|
769
|
+
companies.map((_, index) => index),
|
|
770
|
+
largeBatch.submissionFields
|
|
771
|
+
);
|
|
772
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
773
|
+
"api/v1/company-signals",
|
|
774
|
+
job.jobId,
|
|
775
|
+
"Company Signals",
|
|
776
|
+
{
|
|
777
|
+
pageSize: 25,
|
|
778
|
+
maxWaitMs: options?.maxWaitMs,
|
|
779
|
+
pollIntervalMs: options?.pollIntervalMs,
|
|
780
|
+
idempotencyKey: job.idempotencyKey,
|
|
781
|
+
compactDuplicates: options?.compactDuplicates === true
|
|
782
|
+
}
|
|
783
|
+
);
|
|
784
|
+
if (total !== companies.length) {
|
|
785
|
+
throw new Error(`Company Signals batch returned ${total} results for ${companies.length} requests`);
|
|
786
|
+
}
|
|
787
|
+
const batch = finalizeCoreBatch(
|
|
788
|
+
companies.length,
|
|
789
|
+
startedAt,
|
|
790
|
+
recoverableRowsToRound(rows, "Company Signals"),
|
|
791
|
+
(item) => normalizeCompanySignalResult(companies[item.index], item.raw)
|
|
792
|
+
);
|
|
793
|
+
return compactKnownDuplicateBatch(
|
|
794
|
+
batch,
|
|
795
|
+
dedupeExactBatchItems(
|
|
796
|
+
requests,
|
|
797
|
+
(request) => coreProductBatchRequestKey("api/v1/company-signals", request)
|
|
798
|
+
).sourceIndexByInput,
|
|
799
|
+
options?.compactDuplicates === true
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
const initialTasks = requests.map((request, index) => ({ index, request }));
|
|
536
803
|
const round = await this.runCoreBatchRound("api/v1/company-signals", initialTasks, options);
|
|
537
804
|
const results = round.map((item) => {
|
|
538
805
|
if (!item.success || !item.raw) {
|
|
@@ -555,6 +822,22 @@ var Signaliz = class {
|
|
|
555
822
|
results: compactedResults
|
|
556
823
|
};
|
|
557
824
|
}
|
|
825
|
+
async resumeCompanySignalBatch(jobId, options) {
|
|
826
|
+
const startedAt = Date.now();
|
|
827
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
828
|
+
"api/v1/company-signals",
|
|
829
|
+
jobId,
|
|
830
|
+
"Company Signals",
|
|
831
|
+
options
|
|
832
|
+
);
|
|
833
|
+
return finalizeRecoveredCoreBatch(
|
|
834
|
+
total,
|
|
835
|
+
startedAt,
|
|
836
|
+
recoverableRowsToRound(rows, "Company Signals"),
|
|
837
|
+
(item) => normalizeCompanySignalResult(companySignalParamsFromRecoveredRow(item.raw), item.raw),
|
|
838
|
+
options
|
|
839
|
+
);
|
|
840
|
+
}
|
|
558
841
|
async signalToCopy(params) {
|
|
559
842
|
validateSignalToCopyParams(params);
|
|
560
843
|
const request = signalToCopyRequestBody(params);
|
|
@@ -566,6 +849,7 @@ var Signaliz = class {
|
|
|
566
849
|
async createSignalCopyBatch(requests, options) {
|
|
567
850
|
validateBatchSize(requests);
|
|
568
851
|
requests.forEach(validateSignalToCopyParams);
|
|
852
|
+
validateLargeAvailableDataSignalCopyBatch(requests);
|
|
569
853
|
if (options?.dryRun === true) {
|
|
570
854
|
return this.runCoreProductDryRun(
|
|
571
855
|
"api/v1/signal-to-copy",
|
|
@@ -574,6 +858,59 @@ var Signaliz = class {
|
|
|
574
858
|
);
|
|
575
859
|
}
|
|
576
860
|
const startedAt = Date.now();
|
|
861
|
+
if (requests.length > 25) {
|
|
862
|
+
if (options?.waitForResult === false) {
|
|
863
|
+
return this.submitRecoverableCoreBatchJob(
|
|
864
|
+
"api/v1/signal-to-copy",
|
|
865
|
+
requests.map(availableDataSignalCopyRequestBody),
|
|
866
|
+
"Signal to Copy",
|
|
867
|
+
options.idempotencyKey,
|
|
868
|
+
requests.map((_, index) => index)
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
872
|
+
"api/v1/signal-to-copy",
|
|
873
|
+
requests.map(availableDataSignalCopyRequestBody),
|
|
874
|
+
"Signal to Copy",
|
|
875
|
+
options?.idempotencyKey,
|
|
876
|
+
requests.map((_, index) => index)
|
|
877
|
+
);
|
|
878
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
879
|
+
"api/v1/signal-to-copy",
|
|
880
|
+
job.jobId,
|
|
881
|
+
"Signal to Copy",
|
|
882
|
+
{
|
|
883
|
+
pageSize: 100,
|
|
884
|
+
maxWaitMs: options?.maxWaitMs,
|
|
885
|
+
pollIntervalMs: options?.pollIntervalMs,
|
|
886
|
+
idempotencyKey: job.idempotencyKey,
|
|
887
|
+
compactDuplicates: options?.compactDuplicates === true
|
|
888
|
+
}
|
|
889
|
+
);
|
|
890
|
+
if (total !== requests.length) {
|
|
891
|
+
throw new Error(`Signal to Copy batch returned ${total} results for ${requests.length} requests`);
|
|
892
|
+
}
|
|
893
|
+
const results2 = recoverableSignalCopyRows(rows);
|
|
894
|
+
const succeeded2 = results2.filter((item) => item.success).length;
|
|
895
|
+
const batch = {
|
|
896
|
+
total: requests.length,
|
|
897
|
+
succeeded: succeeded2,
|
|
898
|
+
failed: requests.length - succeeded2,
|
|
899
|
+
durationMs: Date.now() - startedAt,
|
|
900
|
+
results: results2
|
|
901
|
+
};
|
|
902
|
+
return compactKnownDuplicateBatch(
|
|
903
|
+
batch,
|
|
904
|
+
dedupeExactBatchItems(
|
|
905
|
+
requests,
|
|
906
|
+
(params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
|
|
907
|
+
).sourceIndexByInput,
|
|
908
|
+
options?.compactDuplicates === true
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
if (options?.waitForResult === false) {
|
|
912
|
+
throw new RangeError("Signal to Copy --no-wait requires a durable batch of more than 25 rows.");
|
|
913
|
+
}
|
|
577
914
|
const deduplicated = dedupeExactBatchItems(
|
|
578
915
|
requests,
|
|
579
916
|
(params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
|
|
@@ -598,12 +935,36 @@ var Signaliz = class {
|
|
|
598
935
|
results: compactedResults
|
|
599
936
|
};
|
|
600
937
|
}
|
|
601
|
-
async
|
|
602
|
-
const
|
|
938
|
+
async resumeSignalCopyBatch(jobId, options) {
|
|
939
|
+
const startedAt = Date.now();
|
|
940
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
941
|
+
"api/v1/signal-to-copy",
|
|
942
|
+
jobId,
|
|
943
|
+
"Signal to Copy",
|
|
944
|
+
options
|
|
945
|
+
);
|
|
946
|
+
const results = recoverableSignalCopyRows(rows);
|
|
947
|
+
const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
|
|
948
|
+
const succeeded = compactedResults.filter((item) => item.success).length;
|
|
949
|
+
return {
|
|
950
|
+
total,
|
|
951
|
+
succeeded,
|
|
952
|
+
failed: total - succeeded,
|
|
953
|
+
durationMs: Date.now() - startedAt,
|
|
954
|
+
results: compactedResults
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
async runCoreProductDryRun(path, requests, idempotencyKey, defaults = {}) {
|
|
958
|
+
const requestBody = compact({
|
|
603
959
|
requests,
|
|
960
|
+
...defaults,
|
|
604
961
|
dry_run: true,
|
|
605
962
|
idempotency_key: idempotencyKey
|
|
606
|
-
})
|
|
963
|
+
});
|
|
964
|
+
if (path === "api/v1/company-signals" || path === "api/v1/signal-to-copy") {
|
|
965
|
+
assertLargeCoreProductInputBounds(path, requestBody);
|
|
966
|
+
}
|
|
967
|
+
const data = await this.client.post(path, requestBody);
|
|
607
968
|
if (!isCoreProductDryRun(data)) {
|
|
608
969
|
throw new Error(`${path} dry run returned a live-result contract`);
|
|
609
970
|
}
|
|
@@ -649,76 +1010,206 @@ var Signaliz = class {
|
|
|
649
1010
|
}
|
|
650
1011
|
return uniqueResults;
|
|
651
1012
|
}
|
|
652
|
-
async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices) {
|
|
653
|
-
|
|
1013
|
+
async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, pollIntervalMs, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
|
|
1014
|
+
validateCoreBatchPollingOptions({ maxWaitMs, pollIntervalMs });
|
|
1015
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
1016
|
+
path,
|
|
1017
|
+
requests,
|
|
1018
|
+
label,
|
|
1019
|
+
idempotencyKey,
|
|
1020
|
+
idempotencyItemIndices,
|
|
1021
|
+
submissionFields
|
|
1022
|
+
);
|
|
1023
|
+
const recovered = await this.readRecoverableCoreBatchJob(path, job.jobId, label, {
|
|
1024
|
+
pageSize,
|
|
1025
|
+
maxWaitMs,
|
|
1026
|
+
pollIntervalMs,
|
|
1027
|
+
idempotencyKey: job.idempotencyKey
|
|
1028
|
+
});
|
|
1029
|
+
if (recovered.total !== requests.length) {
|
|
1030
|
+
throw new Error(`${label} batch returned ${recovered.total} results for ${requests.length} requests`);
|
|
1031
|
+
}
|
|
1032
|
+
return recovered.rows;
|
|
1033
|
+
}
|
|
1034
|
+
async submitRecoverableCoreBatchJob(path, requests, label, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
|
|
654
1035
|
const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
|
|
1036
|
+
const requestBody = {
|
|
1037
|
+
...submissionFields,
|
|
1038
|
+
requests,
|
|
1039
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1040
|
+
batch_item_indices: idempotencyItemIndices
|
|
1041
|
+
};
|
|
1042
|
+
if (path === "api/v1/company-signals" || path === "api/v1/signal-to-copy") {
|
|
1043
|
+
assertLargeCoreProductInputBounds(path, requestBody);
|
|
1044
|
+
}
|
|
655
1045
|
let submission;
|
|
656
1046
|
try {
|
|
657
|
-
submission = await this.client.post(path,
|
|
658
|
-
requests,
|
|
659
|
-
idempotency_key: submissionIdempotencyKey,
|
|
660
|
-
batch_item_indices: idempotencyItemIndices
|
|
661
|
-
});
|
|
1047
|
+
submission = await this.client.post(path, requestBody);
|
|
662
1048
|
} catch (error) {
|
|
663
1049
|
if (error instanceof SignalizError && !error.isRetryable) throw error;
|
|
664
1050
|
const message = error instanceof Error ? error.message : String(error);
|
|
665
|
-
throw new
|
|
666
|
-
|
|
667
|
-
|
|
1051
|
+
throw new SignalizError({
|
|
1052
|
+
code: "BATCH_SUBMISSION_UNCONFIRMED",
|
|
1053
|
+
message: `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`,
|
|
1054
|
+
errorType: "provider_error",
|
|
1055
|
+
details: {
|
|
1056
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1057
|
+
suggested_action: "retry_with_idempotency_key",
|
|
1058
|
+
retry_eligible: true
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
668
1061
|
}
|
|
669
1062
|
const jobId = String(submission.job_id || "").trim();
|
|
670
|
-
if (!jobId)
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1063
|
+
if (!jobId) {
|
|
1064
|
+
throw new SignalizError({
|
|
1065
|
+
code: "INVALID_JOB_RESPONSE",
|
|
1066
|
+
message: `${label} batch did not return a recoverable job_id`,
|
|
1067
|
+
errorType: "provider_error",
|
|
1068
|
+
details: {
|
|
1069
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1070
|
+
suggested_action: "retry_with_idempotency_key",
|
|
1071
|
+
retry_eligible: true
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
const submissionStatus = String(submission.status || "").toLowerCase();
|
|
1076
|
+
const status = ["queued", "processing", "completed", "partial", "failed"].includes(submissionStatus) ? submissionStatus : "queued";
|
|
1077
|
+
return {
|
|
1078
|
+
success: true,
|
|
1079
|
+
status,
|
|
1080
|
+
capability: path === "api/v1/company-signals" ? "company_signals" : "signal_to_copy",
|
|
1081
|
+
jobId,
|
|
1082
|
+
idempotencyKey: String(submission.idempotency_key || submissionIdempotencyKey),
|
|
1083
|
+
total: Math.max(0, Number(submission.total ?? submission.items_total ?? requests.length)),
|
|
1084
|
+
...Number.isFinite(Number(submission.next_poll_after_seconds)) ? {
|
|
1085
|
+
nextPollAfterSeconds: Number(submission.next_poll_after_seconds)
|
|
1086
|
+
} : {}
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
async readRecoverableCoreBatchJob(path, jobIdInput, label, options = {}) {
|
|
1090
|
+
const jobId = String(jobIdInput || "").trim();
|
|
1091
|
+
if (!jobId) throw new RangeError(`${label} jobId is required`);
|
|
1092
|
+
if (options.pageSize !== void 0 && (!Number.isFinite(options.pageSize) || options.pageSize <= 0)) {
|
|
1093
|
+
throw new RangeError("pageSize must be a positive finite number");
|
|
1094
|
+
}
|
|
1095
|
+
validateCoreBatchPollingOptions(options);
|
|
1096
|
+
const maximumPageSize = path === "api/v1/signal-to-copy" ? 100 : 25;
|
|
1097
|
+
const pageSize = Math.min(maximumPageSize, Math.max(1, Math.trunc(options.pageSize ?? maximumPageSize)));
|
|
1098
|
+
const emailJob = path === "api/v1/find-email" || path === "api/v1/verify-email";
|
|
1099
|
+
const maxWaitMs = options.maxWaitMs ?? (emailJob ? 20 * 6e4 : void 0);
|
|
1100
|
+
const startedAt = Date.now();
|
|
1101
|
+
let observedIdempotencyKey = String(options.idempotencyKey || "").trim();
|
|
1102
|
+
try {
|
|
1103
|
+
let status = await this.client.post(path, {
|
|
683
1104
|
job_id: jobId,
|
|
684
1105
|
page: 1,
|
|
685
|
-
page_size: pageSize
|
|
1106
|
+
page_size: pageSize,
|
|
1107
|
+
compact_duplicates: options.compactDuplicates === true
|
|
686
1108
|
});
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
pageNumbers,
|
|
694
|
-
async (page) => await this.client.post(path, {
|
|
695
|
-
job_id: jobId,
|
|
696
|
-
page,
|
|
697
|
-
page_size: pageSize
|
|
698
|
-
}),
|
|
699
|
-
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
1109
|
+
observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
|
|
1110
|
+
assertRecoverableBatchResultsRetained(
|
|
1111
|
+
status,
|
|
1112
|
+
label,
|
|
1113
|
+
jobId,
|
|
1114
|
+
observedIdempotencyKey
|
|
700
1115
|
);
|
|
701
|
-
|
|
702
|
-
if (
|
|
703
|
-
throw new
|
|
1116
|
+
while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
|
|
1117
|
+
if (maxWaitMs !== void 0 && Date.now() - startedAt >= maxWaitMs) {
|
|
1118
|
+
throw new SignalizError({
|
|
1119
|
+
code: "TIMEOUT",
|
|
1120
|
+
message: `${label} batch ${jobId} did not complete within ${maxWaitMs}ms`,
|
|
1121
|
+
errorType: "timeout",
|
|
1122
|
+
retryAfter: Number(status.next_poll_after_seconds || 2),
|
|
1123
|
+
details: {
|
|
1124
|
+
job_id: jobId,
|
|
1125
|
+
...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
|
|
1126
|
+
suggested_action: "check_job_status",
|
|
1127
|
+
retry_eligible: true
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
704
1130
|
}
|
|
705
|
-
|
|
1131
|
+
const retryAfterMs = options.pollIntervalMs ?? Number(status.next_poll_after_seconds || 2) * 1e3;
|
|
1132
|
+
const boundedDelay = maxWaitMs === void 0 ? Math.max(250, retryAfterMs) : Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt));
|
|
1133
|
+
await sleep2(boundedDelay);
|
|
1134
|
+
status = await this.client.post(path, {
|
|
1135
|
+
job_id: jobId,
|
|
1136
|
+
page: 1,
|
|
1137
|
+
page_size: pageSize,
|
|
1138
|
+
compact_duplicates: options.compactDuplicates === true
|
|
1139
|
+
});
|
|
1140
|
+
observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
|
|
1141
|
+
assertRecoverableBatchResultsRetained(
|
|
1142
|
+
status,
|
|
1143
|
+
label,
|
|
1144
|
+
jobId,
|
|
1145
|
+
observedIdempotencyKey
|
|
1146
|
+
);
|
|
706
1147
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
1148
|
+
const totalPages = Math.max(1, Number(status.total_pages || 1));
|
|
1149
|
+
const pages = [Array.isArray(status.results) ? status.results : []];
|
|
1150
|
+
if (totalPages > 1) {
|
|
1151
|
+
const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
|
|
1152
|
+
const remainingPages = await runBatch(
|
|
1153
|
+
pageNumbers,
|
|
1154
|
+
async (page) => await this.client.post(path, {
|
|
1155
|
+
job_id: jobId,
|
|
1156
|
+
page,
|
|
1157
|
+
page_size: pageSize,
|
|
1158
|
+
compact_duplicates: options.compactDuplicates === true
|
|
1159
|
+
}),
|
|
1160
|
+
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
1161
|
+
);
|
|
1162
|
+
for (const page of remainingPages.results) {
|
|
1163
|
+
if (!page.success || !page.data) {
|
|
1164
|
+
throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
|
|
1165
|
+
}
|
|
1166
|
+
observedIdempotencyKey = String(
|
|
1167
|
+
page.data.idempotency_key || observedIdempotencyKey
|
|
1168
|
+
).trim();
|
|
1169
|
+
assertRecoverableBatchResultsRetained(
|
|
1170
|
+
page.data,
|
|
1171
|
+
label,
|
|
1172
|
+
jobId,
|
|
1173
|
+
observedIdempotencyKey
|
|
1174
|
+
);
|
|
1175
|
+
pages.push(Array.isArray(page.data.results) ? page.data.results : []);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
const rows = pages.flat();
|
|
1179
|
+
const total = Math.max(0, Number(status.total_results ?? status.total ?? status.items_total ?? rows.length));
|
|
1180
|
+
if (rows.length !== total) {
|
|
1181
|
+
throw new Error(`${label} batch returned ${rows.length} results for ${total} job rows`);
|
|
1182
|
+
}
|
|
1183
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1184
|
+
for (const row of rows) {
|
|
1185
|
+
const index = Number(row.index);
|
|
1186
|
+
if (!Number.isInteger(index) || index < 0 || seen.has(index)) {
|
|
1187
|
+
throw new Error(`${label} batch returned an invalid result index`);
|
|
1188
|
+
}
|
|
1189
|
+
seen.add(index);
|
|
1190
|
+
}
|
|
1191
|
+
if (seen.size !== total) throw new Error(`${label} batch returned incomplete indexed results`);
|
|
1192
|
+
return { rows, total };
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
if (error instanceof SignalizError && String(error.details?.job_id || "") === jobId) {
|
|
1195
|
+
throw error;
|
|
717
1196
|
}
|
|
718
|
-
|
|
1197
|
+
const signalizError = error instanceof SignalizError ? error : null;
|
|
1198
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1199
|
+
throw new SignalizError({
|
|
1200
|
+
code: signalizError?.code || "BATCH_RECOVERY_FAILED",
|
|
1201
|
+
message: `${label} batch ${jobId} could not be fully retrieved. Resume the existing job instead of resubmitting it. ${message}`,
|
|
1202
|
+
errorType: signalizError?.errorType || "provider_error",
|
|
1203
|
+
retryAfter: signalizError?.retryAfter,
|
|
1204
|
+
details: {
|
|
1205
|
+
...signalizError?.details || {},
|
|
1206
|
+
job_id: jobId,
|
|
1207
|
+
...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
|
|
1208
|
+
suggested_action: "check_job_status",
|
|
1209
|
+
retry_eligible: true
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
719
1212
|
}
|
|
720
|
-
if (seen.size !== requests.length) throw new Error(`${label} batch returned incomplete indexed results`);
|
|
721
|
-
return rows;
|
|
722
1213
|
}
|
|
723
1214
|
async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0, idempotencyKey, idempotencyItemIndices = requests.map((_, index) => index)) {
|
|
724
1215
|
const results = new Array(requests.length);
|
|
@@ -783,17 +1274,18 @@ var Signaliz = class {
|
|
|
783
1274
|
(task) => coreProductBatchRequestKey(path, task.request)
|
|
784
1275
|
);
|
|
785
1276
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
786
|
-
const recoverableBatch = path === "api/v1/find-email" ? { label: "Find Email", pageSize:
|
|
1277
|
+
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;
|
|
787
1278
|
const containsFindRecoveryReads = path === "api/v1/find-email" && uniqueTasks.some(
|
|
788
1279
|
(task) => typeof task.request.run_id === "string" && task.request.run_id.trim()
|
|
789
1280
|
);
|
|
790
|
-
if (
|
|
1281
|
+
if (recoverableBatch && uniqueTasks.length > 25 && !containsFindRecoveryReads) {
|
|
791
1282
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
792
1283
|
path,
|
|
793
1284
|
uniqueTasks.map((task) => task.request),
|
|
794
1285
|
recoverableBatch.label,
|
|
795
1286
|
recoverableBatch.pageSize,
|
|
796
|
-
recoverableBatch.maxWaitMs,
|
|
1287
|
+
options?.maxWaitMs ?? recoverableBatch.maxWaitMs,
|
|
1288
|
+
options?.pollIntervalMs,
|
|
797
1289
|
options?.idempotencyKey,
|
|
798
1290
|
uniqueTasks.map((task) => task.index)
|
|
799
1291
|
);
|
|
@@ -916,7 +1408,7 @@ function batchItemFailure(index, error, raw) {
|
|
|
916
1408
|
const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
|
|
917
1409
|
const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
|
|
918
1410
|
const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
|
|
919
|
-
const runId = raw?.run_id
|
|
1411
|
+
const runId = [raw?.run_id, raw?.runId, raw?.signal_run_id, raw?.signalRunId].find((value) => typeof value === "string" && value.trim());
|
|
920
1412
|
const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
|
|
921
1413
|
const jobId = raw?.job_id ?? raw?.jobId;
|
|
922
1414
|
const suggestedAction = raw?.suggested_action ?? raw?.suggestedAction;
|
|
@@ -939,6 +1431,42 @@ function batchItemFailure(index, error, raw) {
|
|
|
939
1431
|
function recoverableJobRowFailed(row) {
|
|
940
1432
|
return coreProductResponseFailed(row) || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
|
|
941
1433
|
}
|
|
1434
|
+
function recoverableRowsToRound(rows, label) {
|
|
1435
|
+
return rows.map((raw) => {
|
|
1436
|
+
const index = Number(raw.index);
|
|
1437
|
+
const failed = recoverableJobRowFailed(raw);
|
|
1438
|
+
return {
|
|
1439
|
+
index,
|
|
1440
|
+
success: !failed,
|
|
1441
|
+
raw,
|
|
1442
|
+
error: failed ? raw.error || raw._error || raw.message || raw.failure_reason || `${label} item failed` : void 0
|
|
1443
|
+
};
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
function recoverableSignalCopyRows(rows) {
|
|
1447
|
+
return rows.map((raw) => {
|
|
1448
|
+
const index = Number(raw.index);
|
|
1449
|
+
const duplicateOf = recoveredDuplicateOf(raw);
|
|
1450
|
+
if (duplicateOf !== void 0) return { index, success: true, duplicateOf };
|
|
1451
|
+
if (recoverableJobRowFailed(raw)) {
|
|
1452
|
+
return batchItemFailure(
|
|
1453
|
+
index,
|
|
1454
|
+
raw.error || raw._error || raw.message || "Signal to Copy item failed",
|
|
1455
|
+
raw
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
try {
|
|
1459
|
+
assertTerminalSignalResponse(raw, "Signal to Copy");
|
|
1460
|
+
return { index, success: true, data: normalizeSignalToCopyResult(raw) };
|
|
1461
|
+
} catch (error) {
|
|
1462
|
+
return batchItemFailure(index, error instanceof Error ? error.message : String(error), raw);
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
function recoveredDuplicateOf(raw) {
|
|
1467
|
+
const value = Number(raw.duplicate_of ?? raw.duplicateOf ?? raw._duplicate_of);
|
|
1468
|
+
return Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
1469
|
+
}
|
|
942
1470
|
function newBatchIdempotencyKey(label) {
|
|
943
1471
|
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
944
1472
|
return `signaliz-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${nonce}`;
|
|
@@ -960,6 +1488,16 @@ function findEmailRequestBody(params) {
|
|
|
960
1488
|
idempotency_key: params.idempotencyKey
|
|
961
1489
|
});
|
|
962
1490
|
}
|
|
1491
|
+
function verifyEmailBatchRequestBody(input, options) {
|
|
1492
|
+
if (typeof input === "string") {
|
|
1493
|
+
return compact({ email: input, skip_cache: options?.skipCache });
|
|
1494
|
+
}
|
|
1495
|
+
return compact({
|
|
1496
|
+
email: input.email,
|
|
1497
|
+
skip_cache: input.skipCache ?? options?.skipCache,
|
|
1498
|
+
idempotency_key: input.idempotencyKey
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
963
1501
|
function isCoreProductDryRun(data) {
|
|
964
1502
|
return data.dry_run === true && data.status === "planned";
|
|
965
1503
|
}
|
|
@@ -994,12 +1532,17 @@ function normalizeFindEmailResult(data) {
|
|
|
994
1532
|
const email = failed ? null : rawEmail;
|
|
995
1533
|
const found = Boolean(email) && data.found !== false;
|
|
996
1534
|
const verificationStatus = data.verification_status ?? (data.is_valid === true ? "valid" : data.status ?? "unknown");
|
|
1535
|
+
const deliverabilityStatus = data.deliverability_status ?? verificationStatus;
|
|
1536
|
+
const verificationVerdict = data.verification_verdict ?? verificationStatus;
|
|
997
1537
|
const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
|
|
998
1538
|
const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
|
|
999
1539
|
const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
|
|
1000
1540
|
const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
|
|
1001
1541
|
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()));
|
|
1002
1542
|
const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
|
|
1543
|
+
const providerUsed = data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown";
|
|
1544
|
+
const verificationSource = data.verification_source ?? providerUsed;
|
|
1545
|
+
const billingMetadata = data.billing_metadata && typeof data.billing_metadata === "object" && !Array.isArray(data.billing_metadata) ? data.billing_metadata : void 0;
|
|
1003
1546
|
return {
|
|
1004
1547
|
success: processing || !failed && found,
|
|
1005
1548
|
found,
|
|
@@ -1008,61 +1551,86 @@ function normalizeFindEmailResult(data) {
|
|
|
1008
1551
|
lastName: data.last_name,
|
|
1009
1552
|
confidence: failed ? 0 : data.confidence ?? data.confidence_score ?? (verified ? 1 : 0),
|
|
1010
1553
|
verificationStatus: failed ? "error" : verificationStatus,
|
|
1554
|
+
deliverabilityStatus: failed ? "error" : deliverabilityStatus,
|
|
1555
|
+
verificationVerdict: failed ? "error" : verificationVerdict,
|
|
1011
1556
|
isVerified: !failed && verified,
|
|
1012
1557
|
isVerifiedForSending: !failed && !needsReverification && data.verified_for_sending === true,
|
|
1558
|
+
isDeliverable: !failed && !needsReverification && data.verified_for_sending === true,
|
|
1013
1559
|
isCatchAll: data.email_is_catch_all === true || data.is_catch_all === true,
|
|
1014
1560
|
verificationFreshness,
|
|
1015
1561
|
verificationAgeDays,
|
|
1016
1562
|
verifiedAt: typeof data.verified_at === "string" ? data.verified_at : null,
|
|
1017
1563
|
needsReverification: failed || needsReverification,
|
|
1018
|
-
providerUsed
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1564
|
+
providerUsed,
|
|
1565
|
+
verificationSource,
|
|
1566
|
+
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1567
|
+
failureReason: data.failure_reason ?? (failed || !found ? "no_verified_email_found" : "none"),
|
|
1568
|
+
creditsUsed: data.credits_used ?? billingMetadata?.credits_used,
|
|
1569
|
+
estimatedExternalCostUsd: data.estimated_external_cost_usd ?? billingMetadata?.estimated_external_cost_usd,
|
|
1570
|
+
billingMetadata,
|
|
1571
|
+
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1572
|
+
resultReturnedFrom: data.result_returned_from ?? billingMetadata?.result_returned_from,
|
|
1573
|
+
cacheHit: typeof data.cache_hit === "boolean" ? data.cache_hit : billingMetadata?.cache_hit,
|
|
1024
1574
|
negativeCacheHit: data.negative_cache_hit,
|
|
1025
1575
|
negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
|
|
1026
1576
|
cacheTier: data.cache_tier,
|
|
1027
|
-
freshEnrichmentUsed: data.fresh_enrichment_used,
|
|
1028
|
-
liveProviderCalled: data.live_provider_called,
|
|
1029
|
-
openrouterCalled: data.openrouter_called,
|
|
1030
|
-
byoOpenrouterUsed: data.byo_openrouter_used,
|
|
1577
|
+
freshEnrichmentUsed: typeof data.fresh_enrichment_used === "boolean" ? data.fresh_enrichment_used : billingMetadata?.fresh_enrichment_used,
|
|
1578
|
+
liveProviderCalled: typeof data.live_provider_called === "boolean" ? data.live_provider_called : billingMetadata?.live_provider_called,
|
|
1579
|
+
openrouterCalled: typeof data.openrouter_called === "boolean" ? data.openrouter_called : billingMetadata?.openrouter_called,
|
|
1580
|
+
byoOpenrouterUsed: typeof data.byo_openrouter_used === "boolean" ? data.byo_openrouter_used : billingMetadata?.byo_openrouter_used,
|
|
1031
1581
|
error: processing || found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
|
|
1032
1582
|
errorCode: processing || found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
|
|
1033
1583
|
status: processing ? "processing" : "completed",
|
|
1034
1584
|
runId: data.run_id,
|
|
1035
1585
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1036
1586
|
suggestedAction: data.suggested_action,
|
|
1587
|
+
cachePublicationStatus: data.cache_publication_status,
|
|
1588
|
+
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1037
1589
|
raw: data
|
|
1038
1590
|
};
|
|
1039
1591
|
}
|
|
1040
1592
|
function normalizeVerifyEmailResult(email, data) {
|
|
1041
1593
|
const failed = coreProductResponseFailed(data);
|
|
1042
|
-
const verificationStatus = String(data.verification_status ?? "").toLowerCase();
|
|
1043
|
-
const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
|
|
1044
1594
|
const malformed = data.is_malformed === true || data.verification_verdict === "malformed" || data.verification_status === "malformed" || data.deliverability_status === "malformed";
|
|
1595
|
+
const verificationStatus = String(
|
|
1596
|
+
failed ? malformed ? "malformed" : "error" : data.verification_status ?? data.verification_verdict ?? data.deliverability_status ?? "unknown"
|
|
1597
|
+
).toLowerCase();
|
|
1598
|
+
const deliverabilityStatus = String(data.deliverability_status ?? verificationStatus).toLowerCase();
|
|
1599
|
+
const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
|
|
1600
|
+
const isCatchAll = data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false;
|
|
1601
|
+
const verifiedForSending = !failed && data.verified_for_sending === true;
|
|
1602
|
+
const verificationRunId = data.verification_run_id ?? data.run_id;
|
|
1045
1603
|
return {
|
|
1046
1604
|
success: !failed,
|
|
1047
1605
|
error: failed ? coreProductErrorMessage(data) : void 0,
|
|
1048
1606
|
errorCode: failed ? coreProductErrorCode(data) : void 0,
|
|
1049
1607
|
email: data.email ?? email,
|
|
1050
1608
|
status: !failed && String(data.status || "").toLowerCase() === "processing" ? "processing" : "completed",
|
|
1051
|
-
verificationRunId
|
|
1609
|
+
verificationRunId,
|
|
1052
1610
|
retryAfterMs: data.retry_after_ms,
|
|
1053
1611
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1054
1612
|
isValid: failed ? false : data.is_valid ?? data.valid ?? verified,
|
|
1055
1613
|
isDeliverable: failed ? false : data.is_deliverable ?? data.deliverable ?? verified,
|
|
1056
1614
|
isMalformed: malformed,
|
|
1057
|
-
isCatchAll
|
|
1058
|
-
verifiedForSending
|
|
1615
|
+
isCatchAll,
|
|
1616
|
+
verifiedForSending,
|
|
1617
|
+
verificationStatus,
|
|
1618
|
+
deliverabilityStatus,
|
|
1059
1619
|
verificationVerdict: failed ? malformed ? "malformed" : "error" : data.verification_verdict ?? data.verification_status ?? "unknown",
|
|
1620
|
+
isRoleAccount: data.is_role_account === true || data.email_is_role_account === true || data.role === true,
|
|
1621
|
+
quality: typeof data.quality === "string" ? data.quality : null,
|
|
1622
|
+
recommendation: typeof data.recommendation === "string" ? data.recommendation : verifiedForSending ? "send" : ["unknown", "verifier_error"].includes(verificationStatus) ? "manual_review" : "do_not_send",
|
|
1623
|
+
smtpStatus: data.smtp_status ?? (isCatchAll ? "accept_all" : verifiedForSending ? "accepted" : "not_checked"),
|
|
1624
|
+
providerStatus: data.provider_status ?? verificationStatus,
|
|
1625
|
+
failureReason: data.failure_reason ?? (verifiedForSending ? "none" : isCatchAll ? "catch_all_domain_not_send_safe" : "verification_not_send_safe"),
|
|
1060
1626
|
confidenceScore: failed ? 0 : data.confidence_score ?? data.confidence ?? data.diagnostics?.confidence_score ?? (verified ? 1 : 0),
|
|
1061
1627
|
provider: data.provider,
|
|
1062
1628
|
verificationSource: data.verification_source,
|
|
1629
|
+
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1063
1630
|
billingReplayed: data.billing_replayed,
|
|
1064
1631
|
creditsUsed: data.credits_used,
|
|
1065
1632
|
billingMetadata: data.billing_metadata,
|
|
1633
|
+
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1066
1634
|
resultReturnedFrom: data.result_returned_from,
|
|
1067
1635
|
cacheHit: data.cache_hit,
|
|
1068
1636
|
negativeCacheHit: data.negative_cache_hit,
|
|
@@ -1072,6 +1640,11 @@ function normalizeVerifyEmailResult(email, data) {
|
|
|
1072
1640
|
liveProviderCalled: data.live_provider_called,
|
|
1073
1641
|
openrouterCalled: data.openrouter_called,
|
|
1074
1642
|
byoOpenrouterUsed: data.byo_openrouter_used,
|
|
1643
|
+
estimatedExternalCostUsd: data.estimated_external_cost_usd,
|
|
1644
|
+
runId: data.run_id ?? verificationRunId,
|
|
1645
|
+
cachePublicationStatus: data.cache_publication_status,
|
|
1646
|
+
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1647
|
+
suggestedAction: data.suggested_action,
|
|
1075
1648
|
raw: data
|
|
1076
1649
|
};
|
|
1077
1650
|
}
|
|
@@ -1099,6 +1672,54 @@ function companySignalRequestBody(params) {
|
|
|
1099
1672
|
idempotency_key: params.idempotencyKey
|
|
1100
1673
|
});
|
|
1101
1674
|
}
|
|
1675
|
+
function companySignalParamsFromRecoveredRow(row) {
|
|
1676
|
+
return {
|
|
1677
|
+
companyDomain: row.company?.domain ?? row.company_domain,
|
|
1678
|
+
companyName: row.company?.name ?? row.company_name,
|
|
1679
|
+
signalRunId: row.signal_run_id ?? row.run_id
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
var COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS = [
|
|
1683
|
+
"research_prompt",
|
|
1684
|
+
"signal_types",
|
|
1685
|
+
"target_signal_count",
|
|
1686
|
+
"lookback_days",
|
|
1687
|
+
"online",
|
|
1688
|
+
"enable_deep_search",
|
|
1689
|
+
"search_mode",
|
|
1690
|
+
"include_summary",
|
|
1691
|
+
"enable_outreach_intelligence",
|
|
1692
|
+
"enable_predictive_intelligence",
|
|
1693
|
+
"skip_cache",
|
|
1694
|
+
"include_candidate_evidence"
|
|
1695
|
+
];
|
|
1696
|
+
function companySignalLargeBatchSubmission(requests) {
|
|
1697
|
+
if (requests.length <= 25) return null;
|
|
1698
|
+
if (requests.some(
|
|
1699
|
+
(request) => typeof request.signal_run_id === "string" && request.signal_run_id.trim() || typeof request.idempotency_key === "string" && request.idempotency_key.trim()
|
|
1700
|
+
)) {
|
|
1701
|
+
return null;
|
|
1702
|
+
}
|
|
1703
|
+
const submissionFields = Object.fromEntries(
|
|
1704
|
+
COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => requests[0]?.[field] !== void 0).map((field) => [field, requests[0][field]])
|
|
1705
|
+
);
|
|
1706
|
+
const controlKey = JSON.stringify(submissionFields);
|
|
1707
|
+
for (const request of requests) {
|
|
1708
|
+
const requestControls = Object.fromEntries(
|
|
1709
|
+
COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => request[field] !== void 0).map((field) => [field, request[field]])
|
|
1710
|
+
);
|
|
1711
|
+
if (JSON.stringify(requestControls) !== controlKey) return null;
|
|
1712
|
+
const hasIdentity = typeof request.company_domain === "string" && request.company_domain.trim() || typeof request.company_name === "string" && request.company_name.trim();
|
|
1713
|
+
if (!hasIdentity) return null;
|
|
1714
|
+
}
|
|
1715
|
+
return {
|
|
1716
|
+
requests: requests.map((request) => compact({
|
|
1717
|
+
company_domain: request.company_domain,
|
|
1718
|
+
company_name: request.company_name
|
|
1719
|
+
})),
|
|
1720
|
+
submissionFields
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1102
1723
|
function normalizeCompanySignalResult(params, data) {
|
|
1103
1724
|
const failed = coreProductResponseFailed(data);
|
|
1104
1725
|
const rawSignals = failed ? [] : data.signals ?? data.signal_feed ?? [];
|
|
@@ -1349,6 +1970,9 @@ function dedupeExactBatchItems(items, keyForItem) {
|
|
|
1349
1970
|
function normalizedBatchText(value) {
|
|
1350
1971
|
return typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, " ") : "";
|
|
1351
1972
|
}
|
|
1973
|
+
function normalizedBatchIdempotencyKey(value) {
|
|
1974
|
+
return typeof value === "string" ? value.trim() : "";
|
|
1975
|
+
}
|
|
1352
1976
|
function normalizedBatchDomain(value) {
|
|
1353
1977
|
return normalizedBatchText(value).replace(/^https?:\/\//, "").replace(/^www\./, "").split(/[/?#]/, 1)[0].replace(/\.+$/, "");
|
|
1354
1978
|
}
|
|
@@ -1372,7 +1996,8 @@ function coreProductBatchRequestKey(path, request) {
|
|
|
1372
1996
|
normalizedBatchDomain(request.company_domain),
|
|
1373
1997
|
normalizedBatchLinkedIn(request.linkedin_url),
|
|
1374
1998
|
normalizedBatchText(request.company_name),
|
|
1375
|
-
request.skip_cache === true || request.bypass_cache === true
|
|
1999
|
+
request.skip_cache === true || request.bypass_cache === true,
|
|
2000
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1376
2001
|
]);
|
|
1377
2002
|
}
|
|
1378
2003
|
if (path === "api/v1/verify-email") {
|
|
@@ -1380,7 +2005,8 @@ function coreProductBatchRequestKey(path, request) {
|
|
|
1380
2005
|
normalizedBatchText(request.email),
|
|
1381
2006
|
request.skip_cache === true || request.bypass_cache === true,
|
|
1382
2007
|
request.skip_catch_all_verification === true,
|
|
1383
|
-
request.skip_esp_check === true
|
|
2008
|
+
request.skip_esp_check === true,
|
|
2009
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1384
2010
|
]);
|
|
1385
2011
|
}
|
|
1386
2012
|
if (path === "api/v1/company-signals") {
|
|
@@ -1402,7 +2028,8 @@ function coreProductBatchRequestKey(path, request) {
|
|
|
1402
2028
|
normalizedBatchText(request.search_mode || "balanced"),
|
|
1403
2029
|
request.enable_outreach_intelligence === true,
|
|
1404
2030
|
request.enable_predictive_intelligence === true,
|
|
1405
|
-
request.skip_cache === true || request.bypass_cache === true
|
|
2031
|
+
request.skip_cache === true || request.bypass_cache === true,
|
|
2032
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1406
2033
|
]);
|
|
1407
2034
|
}
|
|
1408
2035
|
return JSON.stringify([
|
|
@@ -1411,9 +2038,11 @@ function coreProductBatchRequestKey(path, request) {
|
|
|
1411
2038
|
typeof request.title === "string" ? request.title.trim() : "",
|
|
1412
2039
|
typeof request.campaign_offer === "string" ? request.campaign_offer.trim() : "",
|
|
1413
2040
|
typeof request.research_prompt === "string" ? request.research_prompt.trim() : "",
|
|
2041
|
+
Number(request.lookback_days) || null,
|
|
1414
2042
|
typeof request.signal_run_id === "string" ? request.signal_run_id.trim() : "",
|
|
1415
2043
|
request.skip_cache === true,
|
|
1416
|
-
request.enable_deep_search === true
|
|
2044
|
+
request.enable_deep_search === true,
|
|
2045
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1417
2046
|
]);
|
|
1418
2047
|
}
|
|
1419
2048
|
function finalizeCoreBatch(total, startedAt, round, normalize, options) {
|
|
@@ -1451,6 +2080,37 @@ function finalizeCoreBatch(total, startedAt, round, normalize, options) {
|
|
|
1451
2080
|
results: compactedResults
|
|
1452
2081
|
};
|
|
1453
2082
|
}
|
|
2083
|
+
function finalizeRecoveredCoreBatch(total, startedAt, round, normalize, options) {
|
|
2084
|
+
const results = round.map((item) => {
|
|
2085
|
+
const duplicateOf = item.raw ? recoveredDuplicateOf(item.raw) : void 0;
|
|
2086
|
+
if (duplicateOf !== void 0) return { index: item.index, success: true, duplicateOf };
|
|
2087
|
+
if (!item.success || !item.raw) {
|
|
2088
|
+
return batchItemFailure(
|
|
2089
|
+
item.index,
|
|
2090
|
+
item.error || "Signaliz batch item failed",
|
|
2091
|
+
item.raw ?? item
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
try {
|
|
2095
|
+
return { index: item.index, success: true, data: normalize(item) };
|
|
2096
|
+
} catch (error) {
|
|
2097
|
+
return {
|
|
2098
|
+
index: item.index,
|
|
2099
|
+
success: false,
|
|
2100
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
});
|
|
2104
|
+
const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
|
|
2105
|
+
const succeeded = compactedResults.filter((item) => item.success).length;
|
|
2106
|
+
return {
|
|
2107
|
+
total,
|
|
2108
|
+
succeeded,
|
|
2109
|
+
failed: total - succeeded,
|
|
2110
|
+
durationMs: Date.now() - startedAt,
|
|
2111
|
+
results: compactedResults
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
1454
2114
|
function compactExactDuplicateResults(results, enabled) {
|
|
1455
2115
|
if (!enabled) return results;
|
|
1456
2116
|
const sourceIndexByRaw = /* @__PURE__ */ new WeakMap();
|
|
@@ -1466,6 +2126,19 @@ function compactExactDuplicateResults(results, enabled) {
|
|
|
1466
2126
|
return item;
|
|
1467
2127
|
});
|
|
1468
2128
|
}
|
|
2129
|
+
function compactKnownDuplicateBatch(batch, sourceIndexByInput, enabled) {
|
|
2130
|
+
if (!enabled) return batch;
|
|
2131
|
+
const canonicalInputByUniqueIndex = /* @__PURE__ */ new Map();
|
|
2132
|
+
sourceIndexByInput.forEach((uniqueIndex, inputIndex) => {
|
|
2133
|
+
if (!canonicalInputByUniqueIndex.has(uniqueIndex)) canonicalInputByUniqueIndex.set(uniqueIndex, inputIndex);
|
|
2134
|
+
});
|
|
2135
|
+
const results = batch.results.map((item, index) => {
|
|
2136
|
+
const canonicalIndex = canonicalInputByUniqueIndex.get(sourceIndexByInput[index]) ?? index;
|
|
2137
|
+
if (canonicalIndex === index || !item.success || !batch.results[canonicalIndex]?.success) return item;
|
|
2138
|
+
return { index, success: true, duplicateOf: canonicalIndex };
|
|
2139
|
+
});
|
|
2140
|
+
return { ...batch, results };
|
|
2141
|
+
}
|
|
1469
2142
|
function signalToCopyRequestBody(params) {
|
|
1470
2143
|
return compact({
|
|
1471
2144
|
company_domain: params.companyDomain,
|
|
@@ -1482,6 +2155,28 @@ function signalToCopyRequestBody(params) {
|
|
|
1482
2155
|
idempotency_key: params.idempotencyKey
|
|
1483
2156
|
});
|
|
1484
2157
|
}
|
|
2158
|
+
function validateLargeAvailableDataSignalCopyBatch(requests) {
|
|
2159
|
+
if (requests.length <= 25) return;
|
|
2160
|
+
const invalidIndex = requests.findIndex(
|
|
2161
|
+
(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()
|
|
2162
|
+
);
|
|
2163
|
+
if (invalidIndex >= 0) {
|
|
2164
|
+
throw new RangeError(
|
|
2165
|
+
`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.`
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
function availableDataSignalCopyRequestBody(params) {
|
|
2170
|
+
return compact({
|
|
2171
|
+
company_domain: params.companyDomain,
|
|
2172
|
+
person_name: params.personName,
|
|
2173
|
+
title: params.title,
|
|
2174
|
+
campaign_offer: params.campaignOffer,
|
|
2175
|
+
research_prompt: params.researchPrompt,
|
|
2176
|
+
lookback_days: params.lookbackDays,
|
|
2177
|
+
signal_run_id: params.signalRunId
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
1485
2180
|
function validateSignalToCopyParams(params) {
|
|
1486
2181
|
validateCoreProductMaxWaitMs(params.maxWaitMs);
|
|
1487
2182
|
if (params.lookbackDays !== void 0 && (!Number.isInteger(params.lookbackDays) || params.lookbackDays < 1 || params.lookbackDays > 365)) {
|
|
@@ -1532,6 +2227,8 @@ function normalizeSignalToCopyResult(data) {
|
|
|
1532
2227
|
liveProviderCalled: data.live_provider_called,
|
|
1533
2228
|
aiProviderCalled: data.ai_provider_called,
|
|
1534
2229
|
byoAiUsed: data.byo_ai_used,
|
|
2230
|
+
unlimitedSignalToCopy: data.unlimited_signal_to_copy,
|
|
2231
|
+
signalToCopyEntitlement: data.signal_to_copy_entitlement,
|
|
1535
2232
|
variations: (failed ? [] : data.variations ?? []).map((variation) => ({
|
|
1536
2233
|
style: variation.style,
|
|
1537
2234
|
subjectLine: variation.subject_line ?? "",
|
|
@@ -1615,6 +2312,29 @@ async function waitForNextSignalPoll(data, override, startedAt, maxWaitMs) {
|
|
|
1615
2312
|
function sleep2(ms) {
|
|
1616
2313
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1617
2314
|
}
|
|
2315
|
+
function validateCoreBatchPollingOptions(options) {
|
|
2316
|
+
if (options.maxWaitMs !== void 0 && (!Number.isFinite(options.maxWaitMs) || options.maxWaitMs <= 0)) {
|
|
2317
|
+
throw new RangeError("maxWaitMs must be a positive finite number");
|
|
2318
|
+
}
|
|
2319
|
+
if (options.pollIntervalMs !== void 0 && (!Number.isFinite(options.pollIntervalMs) || options.pollIntervalMs <= 0)) {
|
|
2320
|
+
throw new RangeError("pollIntervalMs must be a positive finite number");
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
function assertRecoverableBatchResultsRetained(status, label, jobId, idempotencyKey) {
|
|
2324
|
+
if (status.results_expired !== true && status.results_cleaned !== true) return;
|
|
2325
|
+
throw new SignalizError({
|
|
2326
|
+
code: "BATCH_RESULTS_EXPIRED",
|
|
2327
|
+
message: `${label} batch ${jobId} completed, but its retained result pages have expired and cannot be recovered. Do not automatically resubmit provider work.`,
|
|
2328
|
+
errorType: "not_found",
|
|
2329
|
+
details: {
|
|
2330
|
+
job_id: jobId,
|
|
2331
|
+
...idempotencyKey ? { idempotency_key: idempotencyKey } : {},
|
|
2332
|
+
suggested_action: "review_before_resubmitting",
|
|
2333
|
+
retry_eligible: false,
|
|
2334
|
+
results_expired: true
|
|
2335
|
+
}
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
1618
2338
|
async function runBatch(items, execute, options) {
|
|
1619
2339
|
validateBatchSize(items);
|
|
1620
2340
|
const concurrency = validatedBatchConcurrency(options);
|