@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
package/dist/index.js
CHANGED
|
@@ -75,9 +75,13 @@ function publicRetryDetails(body) {
|
|
|
75
75
|
...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
|
|
76
76
|
...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
|
|
77
77
|
...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
|
|
78
|
+
...body?.idempotency_key !== void 0 ? { idempotency_key: body.idempotency_key } : {},
|
|
78
79
|
...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
|
|
79
80
|
...body?.run_id !== void 0 ? { run_id: body.run_id } : {},
|
|
80
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 } : {},
|
|
81
85
|
...body?.signal_run_id !== void 0 ? { signal_run_id: body.signal_run_id } : {},
|
|
82
86
|
...body?.company_signal_run_id !== void 0 ? { company_signal_run_id: body.company_signal_run_id } : {},
|
|
83
87
|
...body?.resume_context_persisted !== void 0 ? { resume_context_persisted: body.resume_context_persisted } : {}
|
|
@@ -93,8 +97,9 @@ function mergePublicErrorDetails(body, explicitDetails) {
|
|
|
93
97
|
}
|
|
94
98
|
function mapErrorType(code, status) {
|
|
95
99
|
if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
|
|
96
|
-
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";
|
|
97
101
|
if (code === "NOT_FOUND" || status === 404) return "not_found";
|
|
102
|
+
if (code === "BATCH_RESULTS_EXPIRED" || status === 410) return "not_found";
|
|
98
103
|
if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
|
|
99
104
|
if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
|
|
100
105
|
if (code === "INSUFFICIENT_CREDITS" || status === 402) return "insufficient_credits";
|
|
@@ -105,7 +110,7 @@ function mapErrorType(code, status) {
|
|
|
105
110
|
|
|
106
111
|
// src/client.ts
|
|
107
112
|
var DEFAULT_BASE_URL = "https://api.signaliz.com/functions/v1";
|
|
108
|
-
var DEFAULT_TIMEOUT =
|
|
113
|
+
var DEFAULT_TIMEOUT = 13e4;
|
|
109
114
|
var DEFAULT_MAX_RETRIES = 3;
|
|
110
115
|
var HttpClient = class {
|
|
111
116
|
constructor(config) {
|
|
@@ -119,9 +124,9 @@ var HttpClient = class {
|
|
|
119
124
|
throw new Error("Signaliz: provide either apiKey or clientId + clientSecret");
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
|
-
async request(functionName, body, method = "POST") {
|
|
127
|
+
async request(functionName, body, method = "POST", options = {}) {
|
|
123
128
|
let lastError;
|
|
124
|
-
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body) : void 0;
|
|
129
|
+
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body, options.automaticIdempotency !== false) : void 0;
|
|
125
130
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
126
131
|
let timer;
|
|
127
132
|
try {
|
|
@@ -189,8 +194,8 @@ var HttpClient = class {
|
|
|
189
194
|
throw lastError || new Error("Request failed after retries");
|
|
190
195
|
}
|
|
191
196
|
/** Convenience wrapper for POST-based edge function calls */
|
|
192
|
-
async post(functionName, body) {
|
|
193
|
-
return this.request(functionName, body, "POST");
|
|
197
|
+
async post(functionName, body, options = {}) {
|
|
198
|
+
return this.request(functionName, body, "POST", options);
|
|
194
199
|
}
|
|
195
200
|
/** Convenience wrapper for GET-based edge function calls with query params */
|
|
196
201
|
async get(functionName, query = {}) {
|
|
@@ -307,10 +312,12 @@ var HttpClient = class {
|
|
|
307
312
|
return this.accessTokenPromise;
|
|
308
313
|
}
|
|
309
314
|
};
|
|
310
|
-
function requestIdempotencyKey(body) {
|
|
315
|
+
function requestIdempotencyKey(body, automaticIdempotency) {
|
|
311
316
|
const requested = typeof body.idempotency_key === "string" ? body.idempotency_key.trim() : "";
|
|
317
|
+
if (requested) return requested;
|
|
318
|
+
if (!automaticIdempotency) return void 0;
|
|
312
319
|
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
313
|
-
return
|
|
320
|
+
return `sdk_${nonce}`;
|
|
314
321
|
}
|
|
315
322
|
function sleep(ms) {
|
|
316
323
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -412,7 +419,7 @@ function mapMcpErrorType(error) {
|
|
|
412
419
|
function mapErrorTypeFromCode(code) {
|
|
413
420
|
if (!code) return "unknown";
|
|
414
421
|
if (code === "RATE_LIMITED") return "rate_limited";
|
|
415
|
-
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";
|
|
416
423
|
if (code === "AUTH_REQUIRED" || code === "AUTH_001") return "auth_expired";
|
|
417
424
|
if (code === "QUOTA_EXCEEDED") return "insufficient_credits";
|
|
418
425
|
if (code === "BUILD_NOT_READY" || code === "ARTIFACT_NOT_READY") return "not_found";
|
|
@@ -420,6 +427,166 @@ function mapErrorTypeFromCode(code) {
|
|
|
420
427
|
return "unknown";
|
|
421
428
|
}
|
|
422
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
|
+
|
|
423
590
|
// src/index.ts
|
|
424
591
|
var MAX_ROW_RATE_LIMIT_RETRIES = 2;
|
|
425
592
|
var Signaliz = class {
|
|
@@ -453,9 +620,25 @@ var Signaliz = class {
|
|
|
453
620
|
options
|
|
454
621
|
);
|
|
455
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
|
+
}
|
|
456
639
|
async verifyEmail(email, options) {
|
|
457
640
|
const normalizedEmail = typeof email === "string" ? email.trim() : "";
|
|
458
|
-
if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail &&
|
|
641
|
+
if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail && !isValidEmailString(normalizedEmail)) {
|
|
459
642
|
const error = `Invalid email format: "${normalizedEmail}"`;
|
|
460
643
|
return normalizeVerifyEmailResult(normalizedEmail, {
|
|
461
644
|
success: false,
|
|
@@ -505,19 +688,20 @@ var Signaliz = class {
|
|
|
505
688
|
}
|
|
506
689
|
async verifyEmails(emails, options) {
|
|
507
690
|
validateBatchSize(emails);
|
|
691
|
+
const requests = emails.map((email) => verifyEmailBatchRequestBody(email, options));
|
|
508
692
|
if (options?.dryRun === true) {
|
|
509
693
|
return this.runCoreProductDryRun(
|
|
510
694
|
"api/v1/verify-email",
|
|
511
|
-
|
|
695
|
+
requests,
|
|
512
696
|
options.idempotencyKey
|
|
513
697
|
);
|
|
514
698
|
}
|
|
515
699
|
const startedAt = Date.now();
|
|
516
700
|
const round = await this.runCoreBatchRound(
|
|
517
701
|
"api/v1/verify-email",
|
|
518
|
-
|
|
702
|
+
requests.map((request, index) => ({
|
|
519
703
|
index,
|
|
520
|
-
request
|
|
704
|
+
request
|
|
521
705
|
})),
|
|
522
706
|
options
|
|
523
707
|
);
|
|
@@ -525,7 +709,23 @@ var Signaliz = class {
|
|
|
525
709
|
emails.length,
|
|
526
710
|
startedAt,
|
|
527
711
|
round,
|
|
528
|
-
(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),
|
|
529
729
|
options
|
|
530
730
|
);
|
|
531
731
|
}
|
|
@@ -541,25 +741,92 @@ var Signaliz = class {
|
|
|
541
741
|
validateSignalDiscoveryParams(params);
|
|
542
742
|
const data = await this.client.post(
|
|
543
743
|
"api/v1/signals",
|
|
544
|
-
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 }
|
|
545
748
|
);
|
|
546
749
|
return normalizeSignalDiscoveryResult(params, data);
|
|
547
750
|
}
|
|
548
751
|
async enrichCompanies(companies, options) {
|
|
549
752
|
validateBatchSize(companies);
|
|
550
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
|
+
}
|
|
551
766
|
if (options?.dryRun === true) {
|
|
552
767
|
return this.runCoreProductDryRun(
|
|
553
768
|
"api/v1/company-signals",
|
|
554
|
-
|
|
555
|
-
options.idempotencyKey
|
|
769
|
+
largeBatch?.requests || requests,
|
|
770
|
+
options.idempotencyKey,
|
|
771
|
+
largeBatch?.submissionFields
|
|
556
772
|
);
|
|
557
773
|
}
|
|
558
774
|
const startedAt = Date.now();
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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 }));
|
|
563
830
|
const round = await this.runCoreBatchRound("api/v1/company-signals", initialTasks, options);
|
|
564
831
|
const results = round.map((item) => {
|
|
565
832
|
if (!item.success || !item.raw) {
|
|
@@ -582,6 +849,22 @@ var Signaliz = class {
|
|
|
582
849
|
results: compactedResults
|
|
583
850
|
};
|
|
584
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
|
+
}
|
|
585
868
|
async signalToCopy(params) {
|
|
586
869
|
validateSignalToCopyParams(params);
|
|
587
870
|
const request = signalToCopyRequestBody(params);
|
|
@@ -593,6 +876,7 @@ var Signaliz = class {
|
|
|
593
876
|
async createSignalCopyBatch(requests, options) {
|
|
594
877
|
validateBatchSize(requests);
|
|
595
878
|
requests.forEach(validateSignalToCopyParams);
|
|
879
|
+
validateLargeAvailableDataSignalCopyBatch(requests);
|
|
596
880
|
if (options?.dryRun === true) {
|
|
597
881
|
return this.runCoreProductDryRun(
|
|
598
882
|
"api/v1/signal-to-copy",
|
|
@@ -601,6 +885,59 @@ var Signaliz = class {
|
|
|
601
885
|
);
|
|
602
886
|
}
|
|
603
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
|
+
}
|
|
604
941
|
const deduplicated = dedupeExactBatchItems(
|
|
605
942
|
requests,
|
|
606
943
|
(params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
|
|
@@ -625,12 +962,36 @@ var Signaliz = class {
|
|
|
625
962
|
results: compactedResults
|
|
626
963
|
};
|
|
627
964
|
}
|
|
628
|
-
async
|
|
629
|
-
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({
|
|
630
986
|
requests,
|
|
987
|
+
...defaults,
|
|
631
988
|
dry_run: true,
|
|
632
989
|
idempotency_key: idempotencyKey
|
|
633
|
-
})
|
|
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);
|
|
634
995
|
if (!isCoreProductDryRun(data)) {
|
|
635
996
|
throw new Error(`${path} dry run returned a live-result contract`);
|
|
636
997
|
}
|
|
@@ -676,76 +1037,206 @@ var Signaliz = class {
|
|
|
676
1037
|
}
|
|
677
1038
|
return uniqueResults;
|
|
678
1039
|
}
|
|
679
|
-
async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices) {
|
|
680
|
-
|
|
1040
|
+
async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, pollIntervalMs, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
|
|
1041
|
+
validateCoreBatchPollingOptions({ maxWaitMs, pollIntervalMs });
|
|
1042
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
1043
|
+
path,
|
|
1044
|
+
requests,
|
|
1045
|
+
label,
|
|
1046
|
+
idempotencyKey,
|
|
1047
|
+
idempotencyItemIndices,
|
|
1048
|
+
submissionFields
|
|
1049
|
+
);
|
|
1050
|
+
const recovered = await this.readRecoverableCoreBatchJob(path, job.jobId, label, {
|
|
1051
|
+
pageSize,
|
|
1052
|
+
maxWaitMs,
|
|
1053
|
+
pollIntervalMs,
|
|
1054
|
+
idempotencyKey: job.idempotencyKey
|
|
1055
|
+
});
|
|
1056
|
+
if (recovered.total !== requests.length) {
|
|
1057
|
+
throw new Error(`${label} batch returned ${recovered.total} results for ${requests.length} requests`);
|
|
1058
|
+
}
|
|
1059
|
+
return recovered.rows;
|
|
1060
|
+
}
|
|
1061
|
+
async submitRecoverableCoreBatchJob(path, requests, label, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
|
|
681
1062
|
const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
|
|
1063
|
+
const requestBody = {
|
|
1064
|
+
...submissionFields,
|
|
1065
|
+
requests,
|
|
1066
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1067
|
+
batch_item_indices: idempotencyItemIndices
|
|
1068
|
+
};
|
|
1069
|
+
if (path === "api/v1/company-signals" || path === "api/v1/signal-to-copy") {
|
|
1070
|
+
assertLargeCoreProductInputBounds(path, requestBody);
|
|
1071
|
+
}
|
|
682
1072
|
let submission;
|
|
683
1073
|
try {
|
|
684
|
-
submission = await this.client.post(path,
|
|
685
|
-
requests,
|
|
686
|
-
idempotency_key: submissionIdempotencyKey,
|
|
687
|
-
batch_item_indices: idempotencyItemIndices
|
|
688
|
-
});
|
|
1074
|
+
submission = await this.client.post(path, requestBody);
|
|
689
1075
|
} catch (error) {
|
|
690
1076
|
if (error instanceof SignalizError && !error.isRetryable) throw error;
|
|
691
1077
|
const message = error instanceof Error ? error.message : String(error);
|
|
692
|
-
throw new
|
|
693
|
-
|
|
694
|
-
|
|
1078
|
+
throw new SignalizError({
|
|
1079
|
+
code: "BATCH_SUBMISSION_UNCONFIRMED",
|
|
1080
|
+
message: `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`,
|
|
1081
|
+
errorType: "provider_error",
|
|
1082
|
+
details: {
|
|
1083
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1084
|
+
suggested_action: "retry_with_idempotency_key",
|
|
1085
|
+
retry_eligible: true
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
695
1088
|
}
|
|
696
1089
|
const jobId = String(submission.job_id || "").trim();
|
|
697
|
-
if (!jobId)
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
1090
|
+
if (!jobId) {
|
|
1091
|
+
throw new SignalizError({
|
|
1092
|
+
code: "INVALID_JOB_RESPONSE",
|
|
1093
|
+
message: `${label} batch did not return a recoverable job_id`,
|
|
1094
|
+
errorType: "provider_error",
|
|
1095
|
+
details: {
|
|
1096
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1097
|
+
suggested_action: "retry_with_idempotency_key",
|
|
1098
|
+
retry_eligible: true
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
const submissionStatus = String(submission.status || "").toLowerCase();
|
|
1103
|
+
const status = ["queued", "processing", "completed", "partial", "failed"].includes(submissionStatus) ? submissionStatus : "queued";
|
|
1104
|
+
return {
|
|
1105
|
+
success: true,
|
|
1106
|
+
status,
|
|
1107
|
+
capability: path === "api/v1/company-signals" ? "company_signals" : "signal_to_copy",
|
|
1108
|
+
jobId,
|
|
1109
|
+
idempotencyKey: String(submission.idempotency_key || submissionIdempotencyKey),
|
|
1110
|
+
total: Math.max(0, Number(submission.total ?? submission.items_total ?? requests.length)),
|
|
1111
|
+
...Number.isFinite(Number(submission.next_poll_after_seconds)) ? {
|
|
1112
|
+
nextPollAfterSeconds: Number(submission.next_poll_after_seconds)
|
|
1113
|
+
} : {}
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
async readRecoverableCoreBatchJob(path, jobIdInput, label, options = {}) {
|
|
1117
|
+
const jobId = String(jobIdInput || "").trim();
|
|
1118
|
+
if (!jobId) throw new RangeError(`${label} jobId is required`);
|
|
1119
|
+
if (options.pageSize !== void 0 && (!Number.isFinite(options.pageSize) || options.pageSize <= 0)) {
|
|
1120
|
+
throw new RangeError("pageSize must be a positive finite number");
|
|
1121
|
+
}
|
|
1122
|
+
validateCoreBatchPollingOptions(options);
|
|
1123
|
+
const maximumPageSize = path === "api/v1/signal-to-copy" ? 100 : 25;
|
|
1124
|
+
const pageSize = Math.min(maximumPageSize, Math.max(1, Math.trunc(options.pageSize ?? maximumPageSize)));
|
|
1125
|
+
const emailJob = path === "api/v1/find-email" || path === "api/v1/verify-email";
|
|
1126
|
+
const maxWaitMs = options.maxWaitMs ?? (emailJob ? 20 * 6e4 : void 0);
|
|
1127
|
+
const startedAt = Date.now();
|
|
1128
|
+
let observedIdempotencyKey = String(options.idempotencyKey || "").trim();
|
|
1129
|
+
try {
|
|
1130
|
+
let status = await this.client.post(path, {
|
|
710
1131
|
job_id: jobId,
|
|
711
1132
|
page: 1,
|
|
712
|
-
page_size: pageSize
|
|
1133
|
+
page_size: pageSize,
|
|
1134
|
+
compact_duplicates: options.compactDuplicates === true
|
|
713
1135
|
});
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
pageNumbers,
|
|
721
|
-
async (page) => await this.client.post(path, {
|
|
722
|
-
job_id: jobId,
|
|
723
|
-
page,
|
|
724
|
-
page_size: pageSize
|
|
725
|
-
}),
|
|
726
|
-
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
1136
|
+
observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
|
|
1137
|
+
assertRecoverableBatchResultsRetained(
|
|
1138
|
+
status,
|
|
1139
|
+
label,
|
|
1140
|
+
jobId,
|
|
1141
|
+
observedIdempotencyKey
|
|
727
1142
|
);
|
|
728
|
-
|
|
729
|
-
if (
|
|
730
|
-
throw new
|
|
1143
|
+
while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
|
|
1144
|
+
if (maxWaitMs !== void 0 && Date.now() - startedAt >= maxWaitMs) {
|
|
1145
|
+
throw new SignalizError({
|
|
1146
|
+
code: "TIMEOUT",
|
|
1147
|
+
message: `${label} batch ${jobId} did not complete within ${maxWaitMs}ms`,
|
|
1148
|
+
errorType: "timeout",
|
|
1149
|
+
retryAfter: Number(status.next_poll_after_seconds || 2),
|
|
1150
|
+
details: {
|
|
1151
|
+
job_id: jobId,
|
|
1152
|
+
...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
|
|
1153
|
+
suggested_action: "check_job_status",
|
|
1154
|
+
retry_eligible: true
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
731
1157
|
}
|
|
732
|
-
|
|
1158
|
+
const retryAfterMs = options.pollIntervalMs ?? Number(status.next_poll_after_seconds || 2) * 1e3;
|
|
1159
|
+
const boundedDelay = maxWaitMs === void 0 ? Math.max(250, retryAfterMs) : Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt));
|
|
1160
|
+
await sleep2(boundedDelay);
|
|
1161
|
+
status = await this.client.post(path, {
|
|
1162
|
+
job_id: jobId,
|
|
1163
|
+
page: 1,
|
|
1164
|
+
page_size: pageSize,
|
|
1165
|
+
compact_duplicates: options.compactDuplicates === true
|
|
1166
|
+
});
|
|
1167
|
+
observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
|
|
1168
|
+
assertRecoverableBatchResultsRetained(
|
|
1169
|
+
status,
|
|
1170
|
+
label,
|
|
1171
|
+
jobId,
|
|
1172
|
+
observedIdempotencyKey
|
|
1173
|
+
);
|
|
733
1174
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1175
|
+
const totalPages = Math.max(1, Number(status.total_pages || 1));
|
|
1176
|
+
const pages = [Array.isArray(status.results) ? status.results : []];
|
|
1177
|
+
if (totalPages > 1) {
|
|
1178
|
+
const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
|
|
1179
|
+
const remainingPages = await runBatch(
|
|
1180
|
+
pageNumbers,
|
|
1181
|
+
async (page) => await this.client.post(path, {
|
|
1182
|
+
job_id: jobId,
|
|
1183
|
+
page,
|
|
1184
|
+
page_size: pageSize,
|
|
1185
|
+
compact_duplicates: options.compactDuplicates === true
|
|
1186
|
+
}),
|
|
1187
|
+
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
1188
|
+
);
|
|
1189
|
+
for (const page of remainingPages.results) {
|
|
1190
|
+
if (!page.success || !page.data) {
|
|
1191
|
+
throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
|
|
1192
|
+
}
|
|
1193
|
+
observedIdempotencyKey = String(
|
|
1194
|
+
page.data.idempotency_key || observedIdempotencyKey
|
|
1195
|
+
).trim();
|
|
1196
|
+
assertRecoverableBatchResultsRetained(
|
|
1197
|
+
page.data,
|
|
1198
|
+
label,
|
|
1199
|
+
jobId,
|
|
1200
|
+
observedIdempotencyKey
|
|
1201
|
+
);
|
|
1202
|
+
pages.push(Array.isArray(page.data.results) ? page.data.results : []);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
const rows = pages.flat();
|
|
1206
|
+
const total = Math.max(0, Number(status.total_results ?? status.total ?? status.items_total ?? rows.length));
|
|
1207
|
+
if (rows.length !== total) {
|
|
1208
|
+
throw new Error(`${label} batch returned ${rows.length} results for ${total} job rows`);
|
|
1209
|
+
}
|
|
1210
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1211
|
+
for (const row of rows) {
|
|
1212
|
+
const index = Number(row.index);
|
|
1213
|
+
if (!Number.isInteger(index) || index < 0 || seen.has(index)) {
|
|
1214
|
+
throw new Error(`${label} batch returned an invalid result index`);
|
|
1215
|
+
}
|
|
1216
|
+
seen.add(index);
|
|
1217
|
+
}
|
|
1218
|
+
if (seen.size !== total) throw new Error(`${label} batch returned incomplete indexed results`);
|
|
1219
|
+
return { rows, total };
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
if (error instanceof SignalizError && String(error.details?.job_id || "") === jobId) {
|
|
1222
|
+
throw error;
|
|
744
1223
|
}
|
|
745
|
-
|
|
1224
|
+
const signalizError = error instanceof SignalizError ? error : null;
|
|
1225
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1226
|
+
throw new SignalizError({
|
|
1227
|
+
code: signalizError?.code || "BATCH_RECOVERY_FAILED",
|
|
1228
|
+
message: `${label} batch ${jobId} could not be fully retrieved. Resume the existing job instead of resubmitting it. ${message}`,
|
|
1229
|
+
errorType: signalizError?.errorType || "provider_error",
|
|
1230
|
+
retryAfter: signalizError?.retryAfter,
|
|
1231
|
+
details: {
|
|
1232
|
+
...signalizError?.details || {},
|
|
1233
|
+
job_id: jobId,
|
|
1234
|
+
...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
|
|
1235
|
+
suggested_action: "check_job_status",
|
|
1236
|
+
retry_eligible: true
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
746
1239
|
}
|
|
747
|
-
if (seen.size !== requests.length) throw new Error(`${label} batch returned incomplete indexed results`);
|
|
748
|
-
return rows;
|
|
749
1240
|
}
|
|
750
1241
|
async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0, idempotencyKey, idempotencyItemIndices = requests.map((_, index) => index)) {
|
|
751
1242
|
const results = new Array(requests.length);
|
|
@@ -810,17 +1301,18 @@ var Signaliz = class {
|
|
|
810
1301
|
(task) => coreProductBatchRequestKey(path, task.request)
|
|
811
1302
|
);
|
|
812
1303
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
813
|
-
const recoverableBatch = path === "api/v1/find-email" ? { label: "Find Email", pageSize:
|
|
1304
|
+
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;
|
|
814
1305
|
const containsFindRecoveryReads = path === "api/v1/find-email" && uniqueTasks.some(
|
|
815
1306
|
(task) => typeof task.request.run_id === "string" && task.request.run_id.trim()
|
|
816
1307
|
);
|
|
817
|
-
if (
|
|
1308
|
+
if (recoverableBatch && uniqueTasks.length > 25 && !containsFindRecoveryReads) {
|
|
818
1309
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
819
1310
|
path,
|
|
820
1311
|
uniqueTasks.map((task) => task.request),
|
|
821
1312
|
recoverableBatch.label,
|
|
822
1313
|
recoverableBatch.pageSize,
|
|
823
|
-
recoverableBatch.maxWaitMs,
|
|
1314
|
+
options?.maxWaitMs ?? recoverableBatch.maxWaitMs,
|
|
1315
|
+
options?.pollIntervalMs,
|
|
824
1316
|
options?.idempotencyKey,
|
|
825
1317
|
uniqueTasks.map((task) => task.index)
|
|
826
1318
|
);
|
|
@@ -943,7 +1435,7 @@ function batchItemFailure(index, error, raw) {
|
|
|
943
1435
|
const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
|
|
944
1436
|
const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
|
|
945
1437
|
const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
|
|
946
|
-
const runId = raw?.run_id
|
|
1438
|
+
const runId = [raw?.run_id, raw?.runId, raw?.signal_run_id, raw?.signalRunId].find((value) => typeof value === "string" && value.trim());
|
|
947
1439
|
const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
|
|
948
1440
|
const jobId = raw?.job_id ?? raw?.jobId;
|
|
949
1441
|
const suggestedAction = raw?.suggested_action ?? raw?.suggestedAction;
|
|
@@ -966,6 +1458,42 @@ function batchItemFailure(index, error, raw) {
|
|
|
966
1458
|
function recoverableJobRowFailed(row) {
|
|
967
1459
|
return coreProductResponseFailed(row) || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
|
|
968
1460
|
}
|
|
1461
|
+
function recoverableRowsToRound(rows, label) {
|
|
1462
|
+
return rows.map((raw) => {
|
|
1463
|
+
const index = Number(raw.index);
|
|
1464
|
+
const failed = recoverableJobRowFailed(raw);
|
|
1465
|
+
return {
|
|
1466
|
+
index,
|
|
1467
|
+
success: !failed,
|
|
1468
|
+
raw,
|
|
1469
|
+
error: failed ? raw.error || raw._error || raw.message || raw.failure_reason || `${label} item failed` : void 0
|
|
1470
|
+
};
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
function recoverableSignalCopyRows(rows) {
|
|
1474
|
+
return rows.map((raw) => {
|
|
1475
|
+
const index = Number(raw.index);
|
|
1476
|
+
const duplicateOf = recoveredDuplicateOf(raw);
|
|
1477
|
+
if (duplicateOf !== void 0) return { index, success: true, duplicateOf };
|
|
1478
|
+
if (recoverableJobRowFailed(raw)) {
|
|
1479
|
+
return batchItemFailure(
|
|
1480
|
+
index,
|
|
1481
|
+
raw.error || raw._error || raw.message || "Signal to Copy item failed",
|
|
1482
|
+
raw
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
try {
|
|
1486
|
+
assertTerminalSignalResponse(raw, "Signal to Copy");
|
|
1487
|
+
return { index, success: true, data: normalizeSignalToCopyResult(raw) };
|
|
1488
|
+
} catch (error) {
|
|
1489
|
+
return batchItemFailure(index, error instanceof Error ? error.message : String(error), raw);
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
function recoveredDuplicateOf(raw) {
|
|
1494
|
+
const value = Number(raw.duplicate_of ?? raw.duplicateOf ?? raw._duplicate_of);
|
|
1495
|
+
return Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
1496
|
+
}
|
|
969
1497
|
function newBatchIdempotencyKey(label) {
|
|
970
1498
|
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
971
1499
|
return `signaliz-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${nonce}`;
|
|
@@ -987,6 +1515,16 @@ function findEmailRequestBody(params) {
|
|
|
987
1515
|
idempotency_key: params.idempotencyKey
|
|
988
1516
|
});
|
|
989
1517
|
}
|
|
1518
|
+
function verifyEmailBatchRequestBody(input, options) {
|
|
1519
|
+
if (typeof input === "string") {
|
|
1520
|
+
return compact({ email: input, skip_cache: options?.skipCache });
|
|
1521
|
+
}
|
|
1522
|
+
return compact({
|
|
1523
|
+
email: input.email,
|
|
1524
|
+
skip_cache: input.skipCache ?? options?.skipCache,
|
|
1525
|
+
idempotency_key: input.idempotencyKey
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
990
1528
|
function isCoreProductDryRun(data) {
|
|
991
1529
|
return data.dry_run === true && data.status === "planned";
|
|
992
1530
|
}
|
|
@@ -1021,12 +1559,17 @@ function normalizeFindEmailResult(data) {
|
|
|
1021
1559
|
const email = failed ? null : rawEmail;
|
|
1022
1560
|
const found = Boolean(email) && data.found !== false;
|
|
1023
1561
|
const verificationStatus = data.verification_status ?? (data.is_valid === true ? "valid" : data.status ?? "unknown");
|
|
1562
|
+
const deliverabilityStatus = data.deliverability_status ?? verificationStatus;
|
|
1563
|
+
const verificationVerdict = data.verification_verdict ?? verificationStatus;
|
|
1024
1564
|
const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
|
|
1025
1565
|
const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
|
|
1026
1566
|
const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
|
|
1027
1567
|
const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
|
|
1028
1568
|
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()));
|
|
1029
1569
|
const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
|
|
1570
|
+
const providerUsed = data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown";
|
|
1571
|
+
const verificationSource = data.verification_source ?? providerUsed;
|
|
1572
|
+
const billingMetadata = data.billing_metadata && typeof data.billing_metadata === "object" && !Array.isArray(data.billing_metadata) ? data.billing_metadata : void 0;
|
|
1030
1573
|
return {
|
|
1031
1574
|
success: processing || !failed && found,
|
|
1032
1575
|
found,
|
|
@@ -1035,61 +1578,86 @@ function normalizeFindEmailResult(data) {
|
|
|
1035
1578
|
lastName: data.last_name,
|
|
1036
1579
|
confidence: failed ? 0 : data.confidence ?? data.confidence_score ?? (verified ? 1 : 0),
|
|
1037
1580
|
verificationStatus: failed ? "error" : verificationStatus,
|
|
1581
|
+
deliverabilityStatus: failed ? "error" : deliverabilityStatus,
|
|
1582
|
+
verificationVerdict: failed ? "error" : verificationVerdict,
|
|
1038
1583
|
isVerified: !failed && verified,
|
|
1039
1584
|
isVerifiedForSending: !failed && !needsReverification && data.verified_for_sending === true,
|
|
1585
|
+
isDeliverable: !failed && !needsReverification && data.verified_for_sending === true,
|
|
1040
1586
|
isCatchAll: data.email_is_catch_all === true || data.is_catch_all === true,
|
|
1041
1587
|
verificationFreshness,
|
|
1042
1588
|
verificationAgeDays,
|
|
1043
1589
|
verifiedAt: typeof data.verified_at === "string" ? data.verified_at : null,
|
|
1044
1590
|
needsReverification: failed || needsReverification,
|
|
1045
|
-
providerUsed
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1591
|
+
providerUsed,
|
|
1592
|
+
verificationSource,
|
|
1593
|
+
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1594
|
+
failureReason: data.failure_reason ?? (failed || !found ? "no_verified_email_found" : "none"),
|
|
1595
|
+
creditsUsed: data.credits_used ?? billingMetadata?.credits_used,
|
|
1596
|
+
estimatedExternalCostUsd: data.estimated_external_cost_usd ?? billingMetadata?.estimated_external_cost_usd,
|
|
1597
|
+
billingMetadata,
|
|
1598
|
+
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1599
|
+
resultReturnedFrom: data.result_returned_from ?? billingMetadata?.result_returned_from,
|
|
1600
|
+
cacheHit: typeof data.cache_hit === "boolean" ? data.cache_hit : billingMetadata?.cache_hit,
|
|
1051
1601
|
negativeCacheHit: data.negative_cache_hit,
|
|
1052
1602
|
negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
|
|
1053
1603
|
cacheTier: data.cache_tier,
|
|
1054
|
-
freshEnrichmentUsed: data.fresh_enrichment_used,
|
|
1055
|
-
liveProviderCalled: data.live_provider_called,
|
|
1056
|
-
openrouterCalled: data.openrouter_called,
|
|
1057
|
-
byoOpenrouterUsed: data.byo_openrouter_used,
|
|
1604
|
+
freshEnrichmentUsed: typeof data.fresh_enrichment_used === "boolean" ? data.fresh_enrichment_used : billingMetadata?.fresh_enrichment_used,
|
|
1605
|
+
liveProviderCalled: typeof data.live_provider_called === "boolean" ? data.live_provider_called : billingMetadata?.live_provider_called,
|
|
1606
|
+
openrouterCalled: typeof data.openrouter_called === "boolean" ? data.openrouter_called : billingMetadata?.openrouter_called,
|
|
1607
|
+
byoOpenrouterUsed: typeof data.byo_openrouter_used === "boolean" ? data.byo_openrouter_used : billingMetadata?.byo_openrouter_used,
|
|
1058
1608
|
error: processing || found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
|
|
1059
1609
|
errorCode: processing || found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
|
|
1060
1610
|
status: processing ? "processing" : "completed",
|
|
1061
1611
|
runId: data.run_id,
|
|
1062
1612
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1063
1613
|
suggestedAction: data.suggested_action,
|
|
1614
|
+
cachePublicationStatus: data.cache_publication_status,
|
|
1615
|
+
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1064
1616
|
raw: data
|
|
1065
1617
|
};
|
|
1066
1618
|
}
|
|
1067
1619
|
function normalizeVerifyEmailResult(email, data) {
|
|
1068
1620
|
const failed = coreProductResponseFailed(data);
|
|
1069
|
-
const verificationStatus = String(data.verification_status ?? "").toLowerCase();
|
|
1070
|
-
const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
|
|
1071
1621
|
const malformed = data.is_malformed === true || data.verification_verdict === "malformed" || data.verification_status === "malformed" || data.deliverability_status === "malformed";
|
|
1622
|
+
const verificationStatus = String(
|
|
1623
|
+
failed ? malformed ? "malformed" : "error" : data.verification_status ?? data.verification_verdict ?? data.deliverability_status ?? "unknown"
|
|
1624
|
+
).toLowerCase();
|
|
1625
|
+
const deliverabilityStatus = String(data.deliverability_status ?? verificationStatus).toLowerCase();
|
|
1626
|
+
const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
|
|
1627
|
+
const isCatchAll = data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false;
|
|
1628
|
+
const verifiedForSending = !failed && data.verified_for_sending === true;
|
|
1629
|
+
const verificationRunId = data.verification_run_id ?? data.run_id;
|
|
1072
1630
|
return {
|
|
1073
1631
|
success: !failed,
|
|
1074
1632
|
error: failed ? coreProductErrorMessage(data) : void 0,
|
|
1075
1633
|
errorCode: failed ? coreProductErrorCode(data) : void 0,
|
|
1076
1634
|
email: data.email ?? email,
|
|
1077
1635
|
status: !failed && String(data.status || "").toLowerCase() === "processing" ? "processing" : "completed",
|
|
1078
|
-
verificationRunId
|
|
1636
|
+
verificationRunId,
|
|
1079
1637
|
retryAfterMs: data.retry_after_ms,
|
|
1080
1638
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1081
1639
|
isValid: failed ? false : data.is_valid ?? data.valid ?? verified,
|
|
1082
1640
|
isDeliverable: failed ? false : data.is_deliverable ?? data.deliverable ?? verified,
|
|
1083
1641
|
isMalformed: malformed,
|
|
1084
|
-
isCatchAll
|
|
1085
|
-
verifiedForSending
|
|
1642
|
+
isCatchAll,
|
|
1643
|
+
verifiedForSending,
|
|
1644
|
+
verificationStatus,
|
|
1645
|
+
deliverabilityStatus,
|
|
1086
1646
|
verificationVerdict: failed ? malformed ? "malformed" : "error" : data.verification_verdict ?? data.verification_status ?? "unknown",
|
|
1647
|
+
isRoleAccount: data.is_role_account === true || data.email_is_role_account === true || data.role === true,
|
|
1648
|
+
quality: typeof data.quality === "string" ? data.quality : null,
|
|
1649
|
+
recommendation: typeof data.recommendation === "string" ? data.recommendation : verifiedForSending ? "send" : ["unknown", "verifier_error"].includes(verificationStatus) ? "manual_review" : "do_not_send",
|
|
1650
|
+
smtpStatus: data.smtp_status ?? (isCatchAll ? "accept_all" : verifiedForSending ? "accepted" : "not_checked"),
|
|
1651
|
+
providerStatus: data.provider_status ?? verificationStatus,
|
|
1652
|
+
failureReason: data.failure_reason ?? (verifiedForSending ? "none" : isCatchAll ? "catch_all_domain_not_send_safe" : "verification_not_send_safe"),
|
|
1087
1653
|
confidenceScore: failed ? 0 : data.confidence_score ?? data.confidence ?? data.diagnostics?.confidence_score ?? (verified ? 1 : 0),
|
|
1088
1654
|
provider: data.provider,
|
|
1089
1655
|
verificationSource: data.verification_source,
|
|
1656
|
+
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1090
1657
|
billingReplayed: data.billing_replayed,
|
|
1091
1658
|
creditsUsed: data.credits_used,
|
|
1092
1659
|
billingMetadata: data.billing_metadata,
|
|
1660
|
+
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1093
1661
|
resultReturnedFrom: data.result_returned_from,
|
|
1094
1662
|
cacheHit: data.cache_hit,
|
|
1095
1663
|
negativeCacheHit: data.negative_cache_hit,
|
|
@@ -1099,6 +1667,11 @@ function normalizeVerifyEmailResult(email, data) {
|
|
|
1099
1667
|
liveProviderCalled: data.live_provider_called,
|
|
1100
1668
|
openrouterCalled: data.openrouter_called,
|
|
1101
1669
|
byoOpenrouterUsed: data.byo_openrouter_used,
|
|
1670
|
+
estimatedExternalCostUsd: data.estimated_external_cost_usd,
|
|
1671
|
+
runId: data.run_id ?? verificationRunId,
|
|
1672
|
+
cachePublicationStatus: data.cache_publication_status,
|
|
1673
|
+
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1674
|
+
suggestedAction: data.suggested_action,
|
|
1102
1675
|
raw: data
|
|
1103
1676
|
};
|
|
1104
1677
|
}
|
|
@@ -1126,6 +1699,54 @@ function companySignalRequestBody(params) {
|
|
|
1126
1699
|
idempotency_key: params.idempotencyKey
|
|
1127
1700
|
});
|
|
1128
1701
|
}
|
|
1702
|
+
function companySignalParamsFromRecoveredRow(row) {
|
|
1703
|
+
return {
|
|
1704
|
+
companyDomain: row.company?.domain ?? row.company_domain,
|
|
1705
|
+
companyName: row.company?.name ?? row.company_name,
|
|
1706
|
+
signalRunId: row.signal_run_id ?? row.run_id
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
var COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS = [
|
|
1710
|
+
"research_prompt",
|
|
1711
|
+
"signal_types",
|
|
1712
|
+
"target_signal_count",
|
|
1713
|
+
"lookback_days",
|
|
1714
|
+
"online",
|
|
1715
|
+
"enable_deep_search",
|
|
1716
|
+
"search_mode",
|
|
1717
|
+
"include_summary",
|
|
1718
|
+
"enable_outreach_intelligence",
|
|
1719
|
+
"enable_predictive_intelligence",
|
|
1720
|
+
"skip_cache",
|
|
1721
|
+
"include_candidate_evidence"
|
|
1722
|
+
];
|
|
1723
|
+
function companySignalLargeBatchSubmission(requests) {
|
|
1724
|
+
if (requests.length <= 25) return null;
|
|
1725
|
+
if (requests.some(
|
|
1726
|
+
(request) => typeof request.signal_run_id === "string" && request.signal_run_id.trim() || typeof request.idempotency_key === "string" && request.idempotency_key.trim()
|
|
1727
|
+
)) {
|
|
1728
|
+
return null;
|
|
1729
|
+
}
|
|
1730
|
+
const submissionFields = Object.fromEntries(
|
|
1731
|
+
COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => requests[0]?.[field] !== void 0).map((field) => [field, requests[0][field]])
|
|
1732
|
+
);
|
|
1733
|
+
const controlKey = JSON.stringify(submissionFields);
|
|
1734
|
+
for (const request of requests) {
|
|
1735
|
+
const requestControls = Object.fromEntries(
|
|
1736
|
+
COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => request[field] !== void 0).map((field) => [field, request[field]])
|
|
1737
|
+
);
|
|
1738
|
+
if (JSON.stringify(requestControls) !== controlKey) return null;
|
|
1739
|
+
const hasIdentity = typeof request.company_domain === "string" && request.company_domain.trim() || typeof request.company_name === "string" && request.company_name.trim();
|
|
1740
|
+
if (!hasIdentity) return null;
|
|
1741
|
+
}
|
|
1742
|
+
return {
|
|
1743
|
+
requests: requests.map((request) => compact({
|
|
1744
|
+
company_domain: request.company_domain,
|
|
1745
|
+
company_name: request.company_name
|
|
1746
|
+
})),
|
|
1747
|
+
submissionFields
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1129
1750
|
function normalizeCompanySignalResult(params, data) {
|
|
1130
1751
|
const failed = coreProductResponseFailed(data);
|
|
1131
1752
|
const rawSignals = failed ? [] : data.signals ?? data.signal_feed ?? [];
|
|
@@ -1376,6 +1997,9 @@ function dedupeExactBatchItems(items, keyForItem) {
|
|
|
1376
1997
|
function normalizedBatchText(value) {
|
|
1377
1998
|
return typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, " ") : "";
|
|
1378
1999
|
}
|
|
2000
|
+
function normalizedBatchIdempotencyKey(value) {
|
|
2001
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2002
|
+
}
|
|
1379
2003
|
function normalizedBatchDomain(value) {
|
|
1380
2004
|
return normalizedBatchText(value).replace(/^https?:\/\//, "").replace(/^www\./, "").split(/[/?#]/, 1)[0].replace(/\.+$/, "");
|
|
1381
2005
|
}
|
|
@@ -1399,7 +2023,8 @@ function coreProductBatchRequestKey(path, request) {
|
|
|
1399
2023
|
normalizedBatchDomain(request.company_domain),
|
|
1400
2024
|
normalizedBatchLinkedIn(request.linkedin_url),
|
|
1401
2025
|
normalizedBatchText(request.company_name),
|
|
1402
|
-
request.skip_cache === true || request.bypass_cache === true
|
|
2026
|
+
request.skip_cache === true || request.bypass_cache === true,
|
|
2027
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1403
2028
|
]);
|
|
1404
2029
|
}
|
|
1405
2030
|
if (path === "api/v1/verify-email") {
|
|
@@ -1407,7 +2032,8 @@ function coreProductBatchRequestKey(path, request) {
|
|
|
1407
2032
|
normalizedBatchText(request.email),
|
|
1408
2033
|
request.skip_cache === true || request.bypass_cache === true,
|
|
1409
2034
|
request.skip_catch_all_verification === true,
|
|
1410
|
-
request.skip_esp_check === true
|
|
2035
|
+
request.skip_esp_check === true,
|
|
2036
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1411
2037
|
]);
|
|
1412
2038
|
}
|
|
1413
2039
|
if (path === "api/v1/company-signals") {
|
|
@@ -1429,7 +2055,8 @@ function coreProductBatchRequestKey(path, request) {
|
|
|
1429
2055
|
normalizedBatchText(request.search_mode || "balanced"),
|
|
1430
2056
|
request.enable_outreach_intelligence === true,
|
|
1431
2057
|
request.enable_predictive_intelligence === true,
|
|
1432
|
-
request.skip_cache === true || request.bypass_cache === true
|
|
2058
|
+
request.skip_cache === true || request.bypass_cache === true,
|
|
2059
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1433
2060
|
]);
|
|
1434
2061
|
}
|
|
1435
2062
|
return JSON.stringify([
|
|
@@ -1438,9 +2065,11 @@ function coreProductBatchRequestKey(path, request) {
|
|
|
1438
2065
|
typeof request.title === "string" ? request.title.trim() : "",
|
|
1439
2066
|
typeof request.campaign_offer === "string" ? request.campaign_offer.trim() : "",
|
|
1440
2067
|
typeof request.research_prompt === "string" ? request.research_prompt.trim() : "",
|
|
2068
|
+
Number(request.lookback_days) || null,
|
|
1441
2069
|
typeof request.signal_run_id === "string" ? request.signal_run_id.trim() : "",
|
|
1442
2070
|
request.skip_cache === true,
|
|
1443
|
-
request.enable_deep_search === true
|
|
2071
|
+
request.enable_deep_search === true,
|
|
2072
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1444
2073
|
]);
|
|
1445
2074
|
}
|
|
1446
2075
|
function finalizeCoreBatch(total, startedAt, round, normalize, options) {
|
|
@@ -1478,6 +2107,37 @@ function finalizeCoreBatch(total, startedAt, round, normalize, options) {
|
|
|
1478
2107
|
results: compactedResults
|
|
1479
2108
|
};
|
|
1480
2109
|
}
|
|
2110
|
+
function finalizeRecoveredCoreBatch(total, startedAt, round, normalize, options) {
|
|
2111
|
+
const results = round.map((item) => {
|
|
2112
|
+
const duplicateOf = item.raw ? recoveredDuplicateOf(item.raw) : void 0;
|
|
2113
|
+
if (duplicateOf !== void 0) return { index: item.index, success: true, duplicateOf };
|
|
2114
|
+
if (!item.success || !item.raw) {
|
|
2115
|
+
return batchItemFailure(
|
|
2116
|
+
item.index,
|
|
2117
|
+
item.error || "Signaliz batch item failed",
|
|
2118
|
+
item.raw ?? item
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2121
|
+
try {
|
|
2122
|
+
return { index: item.index, success: true, data: normalize(item) };
|
|
2123
|
+
} catch (error) {
|
|
2124
|
+
return {
|
|
2125
|
+
index: item.index,
|
|
2126
|
+
success: false,
|
|
2127
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
});
|
|
2131
|
+
const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
|
|
2132
|
+
const succeeded = compactedResults.filter((item) => item.success).length;
|
|
2133
|
+
return {
|
|
2134
|
+
total,
|
|
2135
|
+
succeeded,
|
|
2136
|
+
failed: total - succeeded,
|
|
2137
|
+
durationMs: Date.now() - startedAt,
|
|
2138
|
+
results: compactedResults
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
1481
2141
|
function compactExactDuplicateResults(results, enabled) {
|
|
1482
2142
|
if (!enabled) return results;
|
|
1483
2143
|
const sourceIndexByRaw = /* @__PURE__ */ new WeakMap();
|
|
@@ -1493,6 +2153,19 @@ function compactExactDuplicateResults(results, enabled) {
|
|
|
1493
2153
|
return item;
|
|
1494
2154
|
});
|
|
1495
2155
|
}
|
|
2156
|
+
function compactKnownDuplicateBatch(batch, sourceIndexByInput, enabled) {
|
|
2157
|
+
if (!enabled) return batch;
|
|
2158
|
+
const canonicalInputByUniqueIndex = /* @__PURE__ */ new Map();
|
|
2159
|
+
sourceIndexByInput.forEach((uniqueIndex, inputIndex) => {
|
|
2160
|
+
if (!canonicalInputByUniqueIndex.has(uniqueIndex)) canonicalInputByUniqueIndex.set(uniqueIndex, inputIndex);
|
|
2161
|
+
});
|
|
2162
|
+
const results = batch.results.map((item, index) => {
|
|
2163
|
+
const canonicalIndex = canonicalInputByUniqueIndex.get(sourceIndexByInput[index]) ?? index;
|
|
2164
|
+
if (canonicalIndex === index || !item.success || !batch.results[canonicalIndex]?.success) return item;
|
|
2165
|
+
return { index, success: true, duplicateOf: canonicalIndex };
|
|
2166
|
+
});
|
|
2167
|
+
return { ...batch, results };
|
|
2168
|
+
}
|
|
1496
2169
|
function signalToCopyRequestBody(params) {
|
|
1497
2170
|
return compact({
|
|
1498
2171
|
company_domain: params.companyDomain,
|
|
@@ -1509,6 +2182,28 @@ function signalToCopyRequestBody(params) {
|
|
|
1509
2182
|
idempotency_key: params.idempotencyKey
|
|
1510
2183
|
});
|
|
1511
2184
|
}
|
|
2185
|
+
function validateLargeAvailableDataSignalCopyBatch(requests) {
|
|
2186
|
+
if (requests.length <= 25) return;
|
|
2187
|
+
const invalidIndex = requests.findIndex(
|
|
2188
|
+
(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()
|
|
2189
|
+
);
|
|
2190
|
+
if (invalidIndex >= 0) {
|
|
2191
|
+
throw new RangeError(
|
|
2192
|
+
`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.`
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
function availableDataSignalCopyRequestBody(params) {
|
|
2197
|
+
return compact({
|
|
2198
|
+
company_domain: params.companyDomain,
|
|
2199
|
+
person_name: params.personName,
|
|
2200
|
+
title: params.title,
|
|
2201
|
+
campaign_offer: params.campaignOffer,
|
|
2202
|
+
research_prompt: params.researchPrompt,
|
|
2203
|
+
lookback_days: params.lookbackDays,
|
|
2204
|
+
signal_run_id: params.signalRunId
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
1512
2207
|
function validateSignalToCopyParams(params) {
|
|
1513
2208
|
validateCoreProductMaxWaitMs(params.maxWaitMs);
|
|
1514
2209
|
if (params.lookbackDays !== void 0 && (!Number.isInteger(params.lookbackDays) || params.lookbackDays < 1 || params.lookbackDays > 365)) {
|
|
@@ -1559,6 +2254,8 @@ function normalizeSignalToCopyResult(data) {
|
|
|
1559
2254
|
liveProviderCalled: data.live_provider_called,
|
|
1560
2255
|
aiProviderCalled: data.ai_provider_called,
|
|
1561
2256
|
byoAiUsed: data.byo_ai_used,
|
|
2257
|
+
unlimitedSignalToCopy: data.unlimited_signal_to_copy,
|
|
2258
|
+
signalToCopyEntitlement: data.signal_to_copy_entitlement,
|
|
1562
2259
|
variations: (failed ? [] : data.variations ?? []).map((variation) => ({
|
|
1563
2260
|
style: variation.style,
|
|
1564
2261
|
subjectLine: variation.subject_line ?? "",
|
|
@@ -1642,6 +2339,29 @@ async function waitForNextSignalPoll(data, override, startedAt, maxWaitMs) {
|
|
|
1642
2339
|
function sleep2(ms) {
|
|
1643
2340
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1644
2341
|
}
|
|
2342
|
+
function validateCoreBatchPollingOptions(options) {
|
|
2343
|
+
if (options.maxWaitMs !== void 0 && (!Number.isFinite(options.maxWaitMs) || options.maxWaitMs <= 0)) {
|
|
2344
|
+
throw new RangeError("maxWaitMs must be a positive finite number");
|
|
2345
|
+
}
|
|
2346
|
+
if (options.pollIntervalMs !== void 0 && (!Number.isFinite(options.pollIntervalMs) || options.pollIntervalMs <= 0)) {
|
|
2347
|
+
throw new RangeError("pollIntervalMs must be a positive finite number");
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
function assertRecoverableBatchResultsRetained(status, label, jobId, idempotencyKey) {
|
|
2351
|
+
if (status.results_expired !== true && status.results_cleaned !== true) return;
|
|
2352
|
+
throw new SignalizError({
|
|
2353
|
+
code: "BATCH_RESULTS_EXPIRED",
|
|
2354
|
+
message: `${label} batch ${jobId} completed, but its retained result pages have expired and cannot be recovered. Do not automatically resubmit provider work.`,
|
|
2355
|
+
errorType: "not_found",
|
|
2356
|
+
details: {
|
|
2357
|
+
job_id: jobId,
|
|
2358
|
+
...idempotencyKey ? { idempotency_key: idempotencyKey } : {},
|
|
2359
|
+
suggested_action: "review_before_resubmitting",
|
|
2360
|
+
retry_eligible: false,
|
|
2361
|
+
results_expired: true
|
|
2362
|
+
}
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
1645
2365
|
async function runBatch(items, execute, options) {
|
|
1646
2366
|
validateBatchSize(items);
|
|
1647
2367
|
const concurrency = validatedBatchConcurrency(options);
|