@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/mcp-config.js
CHANGED
|
@@ -79,9 +79,13 @@ function publicRetryDetails(body) {
|
|
|
79
79
|
...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
|
|
80
80
|
...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
|
|
81
81
|
...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
|
|
82
|
+
...body?.idempotency_key !== void 0 ? { idempotency_key: body.idempotency_key } : {},
|
|
82
83
|
...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
|
|
83
84
|
...body?.run_id !== void 0 ? { run_id: body.run_id } : {},
|
|
84
85
|
...body?.suggested_action !== void 0 ? { suggested_action: body.suggested_action } : {},
|
|
86
|
+
...body?.do_not_auto_resubmit !== void 0 ? { do_not_auto_resubmit: body.do_not_auto_resubmit } : {},
|
|
87
|
+
...body?.results_cleaned !== void 0 ? { results_cleaned: body.results_cleaned } : {},
|
|
88
|
+
...body?.results_expired !== void 0 ? { results_expired: body.results_expired } : {},
|
|
85
89
|
...body?.signal_run_id !== void 0 ? { signal_run_id: body.signal_run_id } : {},
|
|
86
90
|
...body?.company_signal_run_id !== void 0 ? { company_signal_run_id: body.company_signal_run_id } : {},
|
|
87
91
|
...body?.resume_context_persisted !== void 0 ? { resume_context_persisted: body.resume_context_persisted } : {}
|
|
@@ -97,8 +101,9 @@ function mergePublicErrorDetails(body, explicitDetails) {
|
|
|
97
101
|
}
|
|
98
102
|
function mapErrorType(code, status) {
|
|
99
103
|
if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
|
|
100
|
-
if (code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400) return "validation";
|
|
104
|
+
if (code === "PAYLOAD_TOO_LARGE" || code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400 || status === 413) return "validation";
|
|
101
105
|
if (code === "NOT_FOUND" || status === 404) return "not_found";
|
|
106
|
+
if (code === "BATCH_RESULTS_EXPIRED" || status === 410) return "not_found";
|
|
102
107
|
if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
|
|
103
108
|
if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
|
|
104
109
|
if (code === "INSUFFICIENT_CREDITS" || status === 402) return "insufficient_credits";
|
|
@@ -109,7 +114,7 @@ function mapErrorType(code, status) {
|
|
|
109
114
|
|
|
110
115
|
// src/client.ts
|
|
111
116
|
var DEFAULT_BASE_URL = "https://api.signaliz.com/functions/v1";
|
|
112
|
-
var DEFAULT_TIMEOUT =
|
|
117
|
+
var DEFAULT_TIMEOUT = 13e4;
|
|
113
118
|
var DEFAULT_MAX_RETRIES = 3;
|
|
114
119
|
var HttpClient = class {
|
|
115
120
|
constructor(config) {
|
|
@@ -123,9 +128,9 @@ var HttpClient = class {
|
|
|
123
128
|
throw new Error("Signaliz: provide either apiKey or clientId + clientSecret");
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
|
-
async request(functionName, body, method = "POST") {
|
|
131
|
+
async request(functionName, body, method = "POST", options = {}) {
|
|
127
132
|
let lastError;
|
|
128
|
-
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body) : void 0;
|
|
133
|
+
const idempotencyKey = method === "POST" ? requestIdempotencyKey(body, options.automaticIdempotency !== false) : void 0;
|
|
129
134
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
130
135
|
let timer;
|
|
131
136
|
try {
|
|
@@ -193,8 +198,8 @@ var HttpClient = class {
|
|
|
193
198
|
throw lastError || new Error("Request failed after retries");
|
|
194
199
|
}
|
|
195
200
|
/** Convenience wrapper for POST-based edge function calls */
|
|
196
|
-
async post(functionName, body) {
|
|
197
|
-
return this.request(functionName, body, "POST");
|
|
201
|
+
async post(functionName, body, options = {}) {
|
|
202
|
+
return this.request(functionName, body, "POST", options);
|
|
198
203
|
}
|
|
199
204
|
/** Convenience wrapper for GET-based edge function calls with query params */
|
|
200
205
|
async get(functionName, query = {}) {
|
|
@@ -311,10 +316,12 @@ var HttpClient = class {
|
|
|
311
316
|
return this.accessTokenPromise;
|
|
312
317
|
}
|
|
313
318
|
};
|
|
314
|
-
function requestIdempotencyKey(body) {
|
|
319
|
+
function requestIdempotencyKey(body, automaticIdempotency) {
|
|
315
320
|
const requested = typeof body.idempotency_key === "string" ? body.idempotency_key.trim() : "";
|
|
321
|
+
if (requested) return requested;
|
|
322
|
+
if (!automaticIdempotency) return void 0;
|
|
316
323
|
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
317
|
-
return
|
|
324
|
+
return `sdk_${nonce}`;
|
|
318
325
|
}
|
|
319
326
|
function sleep(ms) {
|
|
320
327
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -416,7 +423,7 @@ function mapMcpErrorType(error) {
|
|
|
416
423
|
function mapErrorTypeFromCode(code) {
|
|
417
424
|
if (!code) return "unknown";
|
|
418
425
|
if (code === "RATE_LIMITED") return "rate_limited";
|
|
419
|
-
if (code === "INVALID_ARGUMENT" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
|
|
426
|
+
if (code === "INVALID_ARGUMENT" || code === "PAYLOAD_TOO_LARGE" || code === "VALIDATION_ERROR" || code === "VALIDATION_001" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || code === "APPROVAL_REQUIRED" || code === "WEBHOOK_APPROVAL_REQUIRED" || code === "TARGET_LIMIT_EXCEEDED") return "validation";
|
|
420
427
|
if (code === "AUTH_REQUIRED" || code === "AUTH_001") return "auth_expired";
|
|
421
428
|
if (code === "QUOTA_EXCEEDED") return "insufficient_credits";
|
|
422
429
|
if (code === "BUILD_NOT_READY" || code === "ARTIFACT_NOT_READY") return "not_found";
|
|
@@ -424,6 +431,166 @@ function mapErrorTypeFromCode(code) {
|
|
|
424
431
|
return "unknown";
|
|
425
432
|
}
|
|
426
433
|
|
|
434
|
+
// src/email-validation.ts
|
|
435
|
+
function isValidEmailString(value) {
|
|
436
|
+
if (typeof value !== "string") return false;
|
|
437
|
+
const email = value.trim();
|
|
438
|
+
if (!email || email.length > 254) return false;
|
|
439
|
+
const separator = email.indexOf("@");
|
|
440
|
+
if (separator <= 0 || separator !== email.lastIndexOf("@")) return false;
|
|
441
|
+
const local = email.slice(0, separator);
|
|
442
|
+
const domain = email.slice(separator + 1).toLowerCase();
|
|
443
|
+
if (local.length > 64 || domain.length > 253 || local.startsWith(".") || local.endsWith(".") || local.includes("..") || domain.startsWith(".") || domain.endsWith(".") || domain.includes("..")) return false;
|
|
444
|
+
if (!/^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+$/.test(local)) return false;
|
|
445
|
+
const labels = domain.split(".");
|
|
446
|
+
if (labels.length < 2) return false;
|
|
447
|
+
return labels.every(
|
|
448
|
+
(label) => label.length > 0 && label.length <= 63 && /^[a-z0-9-]+$/.test(label) && !label.startsWith("-") && !label.endsWith("-")
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/core-product-input-bounds.ts
|
|
453
|
+
var LARGE_BATCH_MAX_ENCODED_BYTES = 8 * 1024 * 1024;
|
|
454
|
+
var DOMAIN_MAX_UTF8_BYTES = 253;
|
|
455
|
+
var NAME_MAX_UTF8_BYTES = 500;
|
|
456
|
+
var TITLE_MAX_UTF8_BYTES = 500;
|
|
457
|
+
var ID_MAX_UTF8_BYTES = 200;
|
|
458
|
+
var RESEARCH_PROMPT_MAX_UTF8_BYTES = 2e3;
|
|
459
|
+
var CAMPAIGN_OFFER_MAX_UTF8_BYTES = 4e3;
|
|
460
|
+
var SIGNAL_TYPE_MAX_UTF8_BYTES = 100;
|
|
461
|
+
var UTF8_ENCODER = new TextEncoder();
|
|
462
|
+
function byteLength(value) {
|
|
463
|
+
return UTF8_ENCODER.encode(value).byteLength;
|
|
464
|
+
}
|
|
465
|
+
function payloadTooLarge(message, details) {
|
|
466
|
+
throw new SignalizError({
|
|
467
|
+
code: "PAYLOAD_TOO_LARGE",
|
|
468
|
+
message,
|
|
469
|
+
errorType: "validation",
|
|
470
|
+
details: {
|
|
471
|
+
...details,
|
|
472
|
+
retry_eligible: false,
|
|
473
|
+
credits_used: 0,
|
|
474
|
+
credits_charged: 0
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
function assertStringBytes(value, field, maxBytes, index) {
|
|
479
|
+
if (typeof value !== "string") return;
|
|
480
|
+
const encodedBytes = byteLength(value);
|
|
481
|
+
if (encodedBytes <= maxBytes) return;
|
|
482
|
+
payloadTooLarge(
|
|
483
|
+
`${field} is ${encodedBytes} UTF-8 bytes; maximum is ${maxBytes} bytes`,
|
|
484
|
+
{
|
|
485
|
+
field,
|
|
486
|
+
...index !== void 0 ? { index } : {},
|
|
487
|
+
encoded_bytes: encodedBytes,
|
|
488
|
+
max_bytes: maxBytes
|
|
489
|
+
}
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
function assertCompanySignalRows(body) {
|
|
493
|
+
const requests = Array.isArray(body.requests) ? body.requests : [];
|
|
494
|
+
assertStringBytes(
|
|
495
|
+
body.research_prompt,
|
|
496
|
+
"research_prompt",
|
|
497
|
+
RESEARCH_PROMPT_MAX_UTF8_BYTES
|
|
498
|
+
);
|
|
499
|
+
if (Array.isArray(body.signal_types)) {
|
|
500
|
+
body.signal_types.forEach(
|
|
501
|
+
(signalType, index) => assertStringBytes(
|
|
502
|
+
signalType,
|
|
503
|
+
`signal_types[${index}]`,
|
|
504
|
+
SIGNAL_TYPE_MAX_UTF8_BYTES,
|
|
505
|
+
index
|
|
506
|
+
)
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
requests.forEach((value, index) => {
|
|
510
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
511
|
+
const request = value;
|
|
512
|
+
assertStringBytes(
|
|
513
|
+
request.company_domain,
|
|
514
|
+
`requests[${index}].company_domain`,
|
|
515
|
+
DOMAIN_MAX_UTF8_BYTES,
|
|
516
|
+
index
|
|
517
|
+
);
|
|
518
|
+
assertStringBytes(
|
|
519
|
+
request.company_name,
|
|
520
|
+
`requests[${index}].company_name`,
|
|
521
|
+
NAME_MAX_UTF8_BYTES,
|
|
522
|
+
index
|
|
523
|
+
);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
function assertSignalCopyRows(body) {
|
|
527
|
+
const requests = Array.isArray(body.requests) ? body.requests : [];
|
|
528
|
+
requests.forEach((value, index) => {
|
|
529
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
530
|
+
const request = value;
|
|
531
|
+
assertStringBytes(
|
|
532
|
+
request.company_domain,
|
|
533
|
+
`requests[${index}].company_domain`,
|
|
534
|
+
DOMAIN_MAX_UTF8_BYTES,
|
|
535
|
+
index
|
|
536
|
+
);
|
|
537
|
+
assertStringBytes(
|
|
538
|
+
request.person_name,
|
|
539
|
+
`requests[${index}].person_name`,
|
|
540
|
+
NAME_MAX_UTF8_BYTES,
|
|
541
|
+
index
|
|
542
|
+
);
|
|
543
|
+
assertStringBytes(
|
|
544
|
+
request.title,
|
|
545
|
+
`requests[${index}].title`,
|
|
546
|
+
TITLE_MAX_UTF8_BYTES,
|
|
547
|
+
index
|
|
548
|
+
);
|
|
549
|
+
assertStringBytes(
|
|
550
|
+
request.campaign_offer,
|
|
551
|
+
`requests[${index}].campaign_offer`,
|
|
552
|
+
CAMPAIGN_OFFER_MAX_UTF8_BYTES,
|
|
553
|
+
index
|
|
554
|
+
);
|
|
555
|
+
assertStringBytes(
|
|
556
|
+
request.research_prompt,
|
|
557
|
+
`requests[${index}].research_prompt`,
|
|
558
|
+
RESEARCH_PROMPT_MAX_UTF8_BYTES,
|
|
559
|
+
index
|
|
560
|
+
);
|
|
561
|
+
assertStringBytes(
|
|
562
|
+
request.signal_run_id,
|
|
563
|
+
`requests[${index}].signal_run_id`,
|
|
564
|
+
ID_MAX_UTF8_BYTES,
|
|
565
|
+
index
|
|
566
|
+
);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
function assertLargeCoreProductInputBounds(path2, body) {
|
|
570
|
+
const requests = Array.isArray(body.requests) ? body.requests : [];
|
|
571
|
+
if (requests.length <= 25) return;
|
|
572
|
+
assertStringBytes(body.idempotency_key, "idempotency_key", ID_MAX_UTF8_BYTES);
|
|
573
|
+
if (path2 === "api/v1/company-signals") {
|
|
574
|
+
assertCompanySignalRows(body);
|
|
575
|
+
} else {
|
|
576
|
+
assertSignalCopyRows(body);
|
|
577
|
+
}
|
|
578
|
+
const encoded = JSON.stringify(body);
|
|
579
|
+
if (typeof encoded !== "string") {
|
|
580
|
+
throw new TypeError("Core product batch input must be JSON serializable.");
|
|
581
|
+
}
|
|
582
|
+
const encodedBytes = byteLength(encoded);
|
|
583
|
+
if (encodedBytes <= LARGE_BATCH_MAX_ENCODED_BYTES) return;
|
|
584
|
+
payloadTooLarge(
|
|
585
|
+
`${path2 === "api/v1/company-signals" ? "Company Signal" : "Signal Copy"} batch request is ${encodedBytes} UTF-8 bytes after JSON encoding; maximum is ${LARGE_BATCH_MAX_ENCODED_BYTES} bytes`,
|
|
586
|
+
{
|
|
587
|
+
field: "requests",
|
|
588
|
+
encoded_bytes: encodedBytes,
|
|
589
|
+
max_bytes: LARGE_BATCH_MAX_ENCODED_BYTES
|
|
590
|
+
}
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
427
594
|
// src/index.ts
|
|
428
595
|
var MAX_ROW_RATE_LIMIT_RETRIES = 2;
|
|
429
596
|
var Signaliz = class {
|
|
@@ -457,9 +624,25 @@ var Signaliz = class {
|
|
|
457
624
|
options
|
|
458
625
|
);
|
|
459
626
|
}
|
|
627
|
+
async resumeFindEmailBatch(jobId, options) {
|
|
628
|
+
const startedAt = Date.now();
|
|
629
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
630
|
+
"api/v1/find-email",
|
|
631
|
+
jobId,
|
|
632
|
+
"Find Email",
|
|
633
|
+
options
|
|
634
|
+
);
|
|
635
|
+
return finalizeCoreBatch(
|
|
636
|
+
total,
|
|
637
|
+
startedAt,
|
|
638
|
+
recoverableRowsToRound(rows, "Find Email"),
|
|
639
|
+
(item) => normalizeFindEmailResult(item.raw),
|
|
640
|
+
options
|
|
641
|
+
);
|
|
642
|
+
}
|
|
460
643
|
async verifyEmail(email, options) {
|
|
461
644
|
const normalizedEmail = typeof email === "string" ? email.trim() : "";
|
|
462
|
-
if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail &&
|
|
645
|
+
if (options?.dryRun !== true && !options?.verificationRunId && normalizedEmail && !isValidEmailString(normalizedEmail)) {
|
|
463
646
|
const error = `Invalid email format: "${normalizedEmail}"`;
|
|
464
647
|
return normalizeVerifyEmailResult(normalizedEmail, {
|
|
465
648
|
success: false,
|
|
@@ -509,19 +692,20 @@ var Signaliz = class {
|
|
|
509
692
|
}
|
|
510
693
|
async verifyEmails(emails, options) {
|
|
511
694
|
validateBatchSize(emails);
|
|
695
|
+
const requests = emails.map((email) => verifyEmailBatchRequestBody(email, options));
|
|
512
696
|
if (options?.dryRun === true) {
|
|
513
697
|
return this.runCoreProductDryRun(
|
|
514
698
|
"api/v1/verify-email",
|
|
515
|
-
|
|
699
|
+
requests,
|
|
516
700
|
options.idempotencyKey
|
|
517
701
|
);
|
|
518
702
|
}
|
|
519
703
|
const startedAt = Date.now();
|
|
520
704
|
const round = await this.runCoreBatchRound(
|
|
521
705
|
"api/v1/verify-email",
|
|
522
|
-
|
|
706
|
+
requests.map((request, index) => ({
|
|
523
707
|
index,
|
|
524
|
-
request
|
|
708
|
+
request
|
|
525
709
|
})),
|
|
526
710
|
options
|
|
527
711
|
);
|
|
@@ -529,7 +713,23 @@ var Signaliz = class {
|
|
|
529
713
|
emails.length,
|
|
530
714
|
startedAt,
|
|
531
715
|
round,
|
|
532
|
-
(item) => normalizeVerifyEmailResult(
|
|
716
|
+
(item) => normalizeVerifyEmailResult(String(requests[item.index].email ?? ""), item.raw),
|
|
717
|
+
options
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
async resumeVerifyEmailBatch(jobId, options) {
|
|
721
|
+
const startedAt = Date.now();
|
|
722
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
723
|
+
"api/v1/verify-email",
|
|
724
|
+
jobId,
|
|
725
|
+
"Verify Email",
|
|
726
|
+
options
|
|
727
|
+
);
|
|
728
|
+
return finalizeCoreBatch(
|
|
729
|
+
total,
|
|
730
|
+
startedAt,
|
|
731
|
+
recoverableRowsToRound(rows, "Verify Email"),
|
|
732
|
+
(item) => normalizeVerifyEmailResult(String(item.raw?.email || ""), item.raw),
|
|
533
733
|
options
|
|
534
734
|
);
|
|
535
735
|
}
|
|
@@ -545,25 +745,92 @@ var Signaliz = class {
|
|
|
545
745
|
validateSignalDiscoveryParams(params);
|
|
546
746
|
const data = await this.client.post(
|
|
547
747
|
"api/v1/signals",
|
|
548
|
-
signalDiscoveryRequestBody(params)
|
|
748
|
+
signalDiscoveryRequestBody(params),
|
|
749
|
+
// Signals Everything owns a normalized, workspace-scoped automatic key
|
|
750
|
+
// at the API boundary so SDK, CLI, MCP, and direct REST calls coalesce.
|
|
751
|
+
{ automaticIdempotency: false }
|
|
549
752
|
);
|
|
550
753
|
return normalizeSignalDiscoveryResult(params, data);
|
|
551
754
|
}
|
|
552
755
|
async enrichCompanies(companies, options) {
|
|
553
756
|
validateBatchSize(companies);
|
|
554
757
|
companies.forEach((params) => validateCoreProductMaxWaitMs(params.maxWaitMs));
|
|
758
|
+
const requests = companies.map(companySignalRequestBody);
|
|
759
|
+
if (companies.length > 25 && requests.some((request) => request.include_candidate_evidence === true)) {
|
|
760
|
+
throw new RangeError(
|
|
761
|
+
"includeCandidateEvidence is not supported in Company Signals batches larger than 25 because rejected evidence diagnostics are intentionally excluded from durable scale storage; split diagnostic requests into batches of at most 25."
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
const largeBatch = companySignalLargeBatchSubmission(requests);
|
|
765
|
+
if (companies.length > 25 && !largeBatch) {
|
|
766
|
+
throw new RangeError(
|
|
767
|
+
"Company Signals batches larger than 25 require homogeneous new-enrichment rows with company identity only and uniform controls; split recovery or mixed-control rows into batches of at most 25."
|
|
768
|
+
);
|
|
769
|
+
}
|
|
555
770
|
if (options?.dryRun === true) {
|
|
556
771
|
return this.runCoreProductDryRun(
|
|
557
772
|
"api/v1/company-signals",
|
|
558
|
-
|
|
559
|
-
options.idempotencyKey
|
|
773
|
+
largeBatch?.requests || requests,
|
|
774
|
+
options.idempotencyKey,
|
|
775
|
+
largeBatch?.submissionFields
|
|
560
776
|
);
|
|
561
777
|
}
|
|
562
778
|
const startedAt = Date.now();
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
779
|
+
if (options?.waitForResult === false) {
|
|
780
|
+
if (!largeBatch) {
|
|
781
|
+
throw new RangeError(
|
|
782
|
+
"Company Signals --no-wait requires more than 25 homogeneous new-enrichment rows; recovery reads and mixed controls must use batches of at most 25."
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
return this.submitRecoverableCoreBatchJob(
|
|
786
|
+
"api/v1/company-signals",
|
|
787
|
+
largeBatch.requests,
|
|
788
|
+
"Company Signals",
|
|
789
|
+
options.idempotencyKey,
|
|
790
|
+
companies.map((_, index) => index),
|
|
791
|
+
largeBatch.submissionFields
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
if (largeBatch) {
|
|
795
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
796
|
+
"api/v1/company-signals",
|
|
797
|
+
largeBatch.requests,
|
|
798
|
+
"Company Signals",
|
|
799
|
+
options?.idempotencyKey,
|
|
800
|
+
companies.map((_, index) => index),
|
|
801
|
+
largeBatch.submissionFields
|
|
802
|
+
);
|
|
803
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
804
|
+
"api/v1/company-signals",
|
|
805
|
+
job.jobId,
|
|
806
|
+
"Company Signals",
|
|
807
|
+
{
|
|
808
|
+
pageSize: 25,
|
|
809
|
+
maxWaitMs: options?.maxWaitMs,
|
|
810
|
+
pollIntervalMs: options?.pollIntervalMs,
|
|
811
|
+
idempotencyKey: job.idempotencyKey,
|
|
812
|
+
compactDuplicates: options?.compactDuplicates === true
|
|
813
|
+
}
|
|
814
|
+
);
|
|
815
|
+
if (total !== companies.length) {
|
|
816
|
+
throw new Error(`Company Signals batch returned ${total} results for ${companies.length} requests`);
|
|
817
|
+
}
|
|
818
|
+
const batch = finalizeCoreBatch(
|
|
819
|
+
companies.length,
|
|
820
|
+
startedAt,
|
|
821
|
+
recoverableRowsToRound(rows, "Company Signals"),
|
|
822
|
+
(item) => normalizeCompanySignalResult(companies[item.index], item.raw)
|
|
823
|
+
);
|
|
824
|
+
return compactKnownDuplicateBatch(
|
|
825
|
+
batch,
|
|
826
|
+
dedupeExactBatchItems(
|
|
827
|
+
requests,
|
|
828
|
+
(request) => coreProductBatchRequestKey("api/v1/company-signals", request)
|
|
829
|
+
).sourceIndexByInput,
|
|
830
|
+
options?.compactDuplicates === true
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
const initialTasks = requests.map((request, index) => ({ index, request }));
|
|
567
834
|
const round = await this.runCoreBatchRound("api/v1/company-signals", initialTasks, options);
|
|
568
835
|
const results = round.map((item) => {
|
|
569
836
|
if (!item.success || !item.raw) {
|
|
@@ -586,6 +853,22 @@ var Signaliz = class {
|
|
|
586
853
|
results: compactedResults
|
|
587
854
|
};
|
|
588
855
|
}
|
|
856
|
+
async resumeCompanySignalBatch(jobId, options) {
|
|
857
|
+
const startedAt = Date.now();
|
|
858
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
859
|
+
"api/v1/company-signals",
|
|
860
|
+
jobId,
|
|
861
|
+
"Company Signals",
|
|
862
|
+
options
|
|
863
|
+
);
|
|
864
|
+
return finalizeRecoveredCoreBatch(
|
|
865
|
+
total,
|
|
866
|
+
startedAt,
|
|
867
|
+
recoverableRowsToRound(rows, "Company Signals"),
|
|
868
|
+
(item) => normalizeCompanySignalResult(companySignalParamsFromRecoveredRow(item.raw), item.raw),
|
|
869
|
+
options
|
|
870
|
+
);
|
|
871
|
+
}
|
|
589
872
|
async signalToCopy(params) {
|
|
590
873
|
validateSignalToCopyParams(params);
|
|
591
874
|
const request = signalToCopyRequestBody(params);
|
|
@@ -597,6 +880,7 @@ var Signaliz = class {
|
|
|
597
880
|
async createSignalCopyBatch(requests, options) {
|
|
598
881
|
validateBatchSize(requests);
|
|
599
882
|
requests.forEach(validateSignalToCopyParams);
|
|
883
|
+
validateLargeAvailableDataSignalCopyBatch(requests);
|
|
600
884
|
if (options?.dryRun === true) {
|
|
601
885
|
return this.runCoreProductDryRun(
|
|
602
886
|
"api/v1/signal-to-copy",
|
|
@@ -605,6 +889,59 @@ var Signaliz = class {
|
|
|
605
889
|
);
|
|
606
890
|
}
|
|
607
891
|
const startedAt = Date.now();
|
|
892
|
+
if (requests.length > 25) {
|
|
893
|
+
if (options?.waitForResult === false) {
|
|
894
|
+
return this.submitRecoverableCoreBatchJob(
|
|
895
|
+
"api/v1/signal-to-copy",
|
|
896
|
+
requests.map(availableDataSignalCopyRequestBody),
|
|
897
|
+
"Signal to Copy",
|
|
898
|
+
options.idempotencyKey,
|
|
899
|
+
requests.map((_, index) => index)
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
903
|
+
"api/v1/signal-to-copy",
|
|
904
|
+
requests.map(availableDataSignalCopyRequestBody),
|
|
905
|
+
"Signal to Copy",
|
|
906
|
+
options?.idempotencyKey,
|
|
907
|
+
requests.map((_, index) => index)
|
|
908
|
+
);
|
|
909
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
910
|
+
"api/v1/signal-to-copy",
|
|
911
|
+
job.jobId,
|
|
912
|
+
"Signal to Copy",
|
|
913
|
+
{
|
|
914
|
+
pageSize: 100,
|
|
915
|
+
maxWaitMs: options?.maxWaitMs,
|
|
916
|
+
pollIntervalMs: options?.pollIntervalMs,
|
|
917
|
+
idempotencyKey: job.idempotencyKey,
|
|
918
|
+
compactDuplicates: options?.compactDuplicates === true
|
|
919
|
+
}
|
|
920
|
+
);
|
|
921
|
+
if (total !== requests.length) {
|
|
922
|
+
throw new Error(`Signal to Copy batch returned ${total} results for ${requests.length} requests`);
|
|
923
|
+
}
|
|
924
|
+
const results2 = recoverableSignalCopyRows(rows);
|
|
925
|
+
const succeeded2 = results2.filter((item) => item.success).length;
|
|
926
|
+
const batch = {
|
|
927
|
+
total: requests.length,
|
|
928
|
+
succeeded: succeeded2,
|
|
929
|
+
failed: requests.length - succeeded2,
|
|
930
|
+
durationMs: Date.now() - startedAt,
|
|
931
|
+
results: results2
|
|
932
|
+
};
|
|
933
|
+
return compactKnownDuplicateBatch(
|
|
934
|
+
batch,
|
|
935
|
+
dedupeExactBatchItems(
|
|
936
|
+
requests,
|
|
937
|
+
(params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
|
|
938
|
+
).sourceIndexByInput,
|
|
939
|
+
options?.compactDuplicates === true
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
if (options?.waitForResult === false) {
|
|
943
|
+
throw new RangeError("Signal to Copy --no-wait requires a durable batch of more than 25 rows.");
|
|
944
|
+
}
|
|
608
945
|
const deduplicated = dedupeExactBatchItems(
|
|
609
946
|
requests,
|
|
610
947
|
(params) => coreProductBatchRequestKey("api/v1/signal-to-copy", signalToCopyRequestBody(params))
|
|
@@ -629,12 +966,36 @@ var Signaliz = class {
|
|
|
629
966
|
results: compactedResults
|
|
630
967
|
};
|
|
631
968
|
}
|
|
632
|
-
async
|
|
633
|
-
const
|
|
969
|
+
async resumeSignalCopyBatch(jobId, options) {
|
|
970
|
+
const startedAt = Date.now();
|
|
971
|
+
const { rows, total } = await this.readRecoverableCoreBatchJob(
|
|
972
|
+
"api/v1/signal-to-copy",
|
|
973
|
+
jobId,
|
|
974
|
+
"Signal to Copy",
|
|
975
|
+
options
|
|
976
|
+
);
|
|
977
|
+
const results = recoverableSignalCopyRows(rows);
|
|
978
|
+
const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
|
|
979
|
+
const succeeded = compactedResults.filter((item) => item.success).length;
|
|
980
|
+
return {
|
|
981
|
+
total,
|
|
982
|
+
succeeded,
|
|
983
|
+
failed: total - succeeded,
|
|
984
|
+
durationMs: Date.now() - startedAt,
|
|
985
|
+
results: compactedResults
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
async runCoreProductDryRun(path2, requests, idempotencyKey, defaults = {}) {
|
|
989
|
+
const requestBody = compact({
|
|
634
990
|
requests,
|
|
991
|
+
...defaults,
|
|
635
992
|
dry_run: true,
|
|
636
993
|
idempotency_key: idempotencyKey
|
|
637
|
-
})
|
|
994
|
+
});
|
|
995
|
+
if (path2 === "api/v1/company-signals" || path2 === "api/v1/signal-to-copy") {
|
|
996
|
+
assertLargeCoreProductInputBounds(path2, requestBody);
|
|
997
|
+
}
|
|
998
|
+
const data = await this.client.post(path2, requestBody);
|
|
638
999
|
if (!isCoreProductDryRun(data)) {
|
|
639
1000
|
throw new Error(`${path2} dry run returned a live-result contract`);
|
|
640
1001
|
}
|
|
@@ -680,76 +1041,206 @@ var Signaliz = class {
|
|
|
680
1041
|
}
|
|
681
1042
|
return uniqueResults;
|
|
682
1043
|
}
|
|
683
|
-
async runRecoverableCoreBatchJob(path2, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices) {
|
|
684
|
-
|
|
1044
|
+
async runRecoverableCoreBatchJob(path2, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, pollIntervalMs, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
|
|
1045
|
+
validateCoreBatchPollingOptions({ maxWaitMs, pollIntervalMs });
|
|
1046
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
1047
|
+
path2,
|
|
1048
|
+
requests,
|
|
1049
|
+
label,
|
|
1050
|
+
idempotencyKey,
|
|
1051
|
+
idempotencyItemIndices,
|
|
1052
|
+
submissionFields
|
|
1053
|
+
);
|
|
1054
|
+
const recovered = await this.readRecoverableCoreBatchJob(path2, job.jobId, label, {
|
|
1055
|
+
pageSize,
|
|
1056
|
+
maxWaitMs,
|
|
1057
|
+
pollIntervalMs,
|
|
1058
|
+
idempotencyKey: job.idempotencyKey
|
|
1059
|
+
});
|
|
1060
|
+
if (recovered.total !== requests.length) {
|
|
1061
|
+
throw new Error(`${label} batch returned ${recovered.total} results for ${requests.length} requests`);
|
|
1062
|
+
}
|
|
1063
|
+
return recovered.rows;
|
|
1064
|
+
}
|
|
1065
|
+
async submitRecoverableCoreBatchJob(path2, requests, label, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
|
|
685
1066
|
const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
|
|
1067
|
+
const requestBody = {
|
|
1068
|
+
...submissionFields,
|
|
1069
|
+
requests,
|
|
1070
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1071
|
+
batch_item_indices: idempotencyItemIndices
|
|
1072
|
+
};
|
|
1073
|
+
if (path2 === "api/v1/company-signals" || path2 === "api/v1/signal-to-copy") {
|
|
1074
|
+
assertLargeCoreProductInputBounds(path2, requestBody);
|
|
1075
|
+
}
|
|
686
1076
|
let submission;
|
|
687
1077
|
try {
|
|
688
|
-
submission = await this.client.post(path2,
|
|
689
|
-
requests,
|
|
690
|
-
idempotency_key: submissionIdempotencyKey,
|
|
691
|
-
batch_item_indices: idempotencyItemIndices
|
|
692
|
-
});
|
|
1078
|
+
submission = await this.client.post(path2, requestBody);
|
|
693
1079
|
} catch (error) {
|
|
694
1080
|
if (error instanceof SignalizError && !error.isRetryable) throw error;
|
|
695
1081
|
const message = error instanceof Error ? error.message : String(error);
|
|
696
|
-
throw new
|
|
697
|
-
|
|
698
|
-
|
|
1082
|
+
throw new SignalizError({
|
|
1083
|
+
code: "BATCH_SUBMISSION_UNCONFIRMED",
|
|
1084
|
+
message: `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`,
|
|
1085
|
+
errorType: "provider_error",
|
|
1086
|
+
details: {
|
|
1087
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1088
|
+
suggested_action: "retry_with_idempotency_key",
|
|
1089
|
+
retry_eligible: true
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
699
1092
|
}
|
|
700
1093
|
const jobId = String(submission.job_id || "").trim();
|
|
701
|
-
if (!jobId)
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1094
|
+
if (!jobId) {
|
|
1095
|
+
throw new SignalizError({
|
|
1096
|
+
code: "INVALID_JOB_RESPONSE",
|
|
1097
|
+
message: `${label} batch did not return a recoverable job_id`,
|
|
1098
|
+
errorType: "provider_error",
|
|
1099
|
+
details: {
|
|
1100
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1101
|
+
suggested_action: "retry_with_idempotency_key",
|
|
1102
|
+
retry_eligible: true
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
const submissionStatus = String(submission.status || "").toLowerCase();
|
|
1107
|
+
const status = ["queued", "processing", "completed", "partial", "failed"].includes(submissionStatus) ? submissionStatus : "queued";
|
|
1108
|
+
return {
|
|
1109
|
+
success: true,
|
|
1110
|
+
status,
|
|
1111
|
+
capability: path2 === "api/v1/company-signals" ? "company_signals" : "signal_to_copy",
|
|
1112
|
+
jobId,
|
|
1113
|
+
idempotencyKey: String(submission.idempotency_key || submissionIdempotencyKey),
|
|
1114
|
+
total: Math.max(0, Number(submission.total ?? submission.items_total ?? requests.length)),
|
|
1115
|
+
...Number.isFinite(Number(submission.next_poll_after_seconds)) ? {
|
|
1116
|
+
nextPollAfterSeconds: Number(submission.next_poll_after_seconds)
|
|
1117
|
+
} : {}
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
async readRecoverableCoreBatchJob(path2, jobIdInput, label, options = {}) {
|
|
1121
|
+
const jobId = String(jobIdInput || "").trim();
|
|
1122
|
+
if (!jobId) throw new RangeError(`${label} jobId is required`);
|
|
1123
|
+
if (options.pageSize !== void 0 && (!Number.isFinite(options.pageSize) || options.pageSize <= 0)) {
|
|
1124
|
+
throw new RangeError("pageSize must be a positive finite number");
|
|
1125
|
+
}
|
|
1126
|
+
validateCoreBatchPollingOptions(options);
|
|
1127
|
+
const maximumPageSize = path2 === "api/v1/signal-to-copy" ? 100 : 25;
|
|
1128
|
+
const pageSize = Math.min(maximumPageSize, Math.max(1, Math.trunc(options.pageSize ?? maximumPageSize)));
|
|
1129
|
+
const emailJob = path2 === "api/v1/find-email" || path2 === "api/v1/verify-email";
|
|
1130
|
+
const maxWaitMs = options.maxWaitMs ?? (emailJob ? 20 * 6e4 : void 0);
|
|
1131
|
+
const startedAt = Date.now();
|
|
1132
|
+
let observedIdempotencyKey = String(options.idempotencyKey || "").trim();
|
|
1133
|
+
try {
|
|
1134
|
+
let status = await this.client.post(path2, {
|
|
714
1135
|
job_id: jobId,
|
|
715
1136
|
page: 1,
|
|
716
|
-
page_size: pageSize
|
|
1137
|
+
page_size: pageSize,
|
|
1138
|
+
compact_duplicates: options.compactDuplicates === true
|
|
717
1139
|
});
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
pageNumbers,
|
|
725
|
-
async (page) => await this.client.post(path2, {
|
|
726
|
-
job_id: jobId,
|
|
727
|
-
page,
|
|
728
|
-
page_size: pageSize
|
|
729
|
-
}),
|
|
730
|
-
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
1140
|
+
observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
|
|
1141
|
+
assertRecoverableBatchResultsRetained(
|
|
1142
|
+
status,
|
|
1143
|
+
label,
|
|
1144
|
+
jobId,
|
|
1145
|
+
observedIdempotencyKey
|
|
731
1146
|
);
|
|
732
|
-
|
|
733
|
-
if (
|
|
734
|
-
throw new
|
|
1147
|
+
while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
|
|
1148
|
+
if (maxWaitMs !== void 0 && Date.now() - startedAt >= maxWaitMs) {
|
|
1149
|
+
throw new SignalizError({
|
|
1150
|
+
code: "TIMEOUT",
|
|
1151
|
+
message: `${label} batch ${jobId} did not complete within ${maxWaitMs}ms`,
|
|
1152
|
+
errorType: "timeout",
|
|
1153
|
+
retryAfter: Number(status.next_poll_after_seconds || 2),
|
|
1154
|
+
details: {
|
|
1155
|
+
job_id: jobId,
|
|
1156
|
+
...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
|
|
1157
|
+
suggested_action: "check_job_status",
|
|
1158
|
+
retry_eligible: true
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
735
1161
|
}
|
|
736
|
-
|
|
1162
|
+
const retryAfterMs = options.pollIntervalMs ?? Number(status.next_poll_after_seconds || 2) * 1e3;
|
|
1163
|
+
const boundedDelay = maxWaitMs === void 0 ? Math.max(250, retryAfterMs) : Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt));
|
|
1164
|
+
await sleep2(boundedDelay);
|
|
1165
|
+
status = await this.client.post(path2, {
|
|
1166
|
+
job_id: jobId,
|
|
1167
|
+
page: 1,
|
|
1168
|
+
page_size: pageSize,
|
|
1169
|
+
compact_duplicates: options.compactDuplicates === true
|
|
1170
|
+
});
|
|
1171
|
+
observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
|
|
1172
|
+
assertRecoverableBatchResultsRetained(
|
|
1173
|
+
status,
|
|
1174
|
+
label,
|
|
1175
|
+
jobId,
|
|
1176
|
+
observedIdempotencyKey
|
|
1177
|
+
);
|
|
737
1178
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
1179
|
+
const totalPages = Math.max(1, Number(status.total_pages || 1));
|
|
1180
|
+
const pages = [Array.isArray(status.results) ? status.results : []];
|
|
1181
|
+
if (totalPages > 1) {
|
|
1182
|
+
const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
|
|
1183
|
+
const remainingPages = await runBatch(
|
|
1184
|
+
pageNumbers,
|
|
1185
|
+
async (page) => await this.client.post(path2, {
|
|
1186
|
+
job_id: jobId,
|
|
1187
|
+
page,
|
|
1188
|
+
page_size: pageSize,
|
|
1189
|
+
compact_duplicates: options.compactDuplicates === true
|
|
1190
|
+
}),
|
|
1191
|
+
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
1192
|
+
);
|
|
1193
|
+
for (const page of remainingPages.results) {
|
|
1194
|
+
if (!page.success || !page.data) {
|
|
1195
|
+
throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
|
|
1196
|
+
}
|
|
1197
|
+
observedIdempotencyKey = String(
|
|
1198
|
+
page.data.idempotency_key || observedIdempotencyKey
|
|
1199
|
+
).trim();
|
|
1200
|
+
assertRecoverableBatchResultsRetained(
|
|
1201
|
+
page.data,
|
|
1202
|
+
label,
|
|
1203
|
+
jobId,
|
|
1204
|
+
observedIdempotencyKey
|
|
1205
|
+
);
|
|
1206
|
+
pages.push(Array.isArray(page.data.results) ? page.data.results : []);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
const rows = pages.flat();
|
|
1210
|
+
const total = Math.max(0, Number(status.total_results ?? status.total ?? status.items_total ?? rows.length));
|
|
1211
|
+
if (rows.length !== total) {
|
|
1212
|
+
throw new Error(`${label} batch returned ${rows.length} results for ${total} job rows`);
|
|
1213
|
+
}
|
|
1214
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1215
|
+
for (const row of rows) {
|
|
1216
|
+
const index = Number(row.index);
|
|
1217
|
+
if (!Number.isInteger(index) || index < 0 || seen.has(index)) {
|
|
1218
|
+
throw new Error(`${label} batch returned an invalid result index`);
|
|
1219
|
+
}
|
|
1220
|
+
seen.add(index);
|
|
1221
|
+
}
|
|
1222
|
+
if (seen.size !== total) throw new Error(`${label} batch returned incomplete indexed results`);
|
|
1223
|
+
return { rows, total };
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
if (error instanceof SignalizError && String(error.details?.job_id || "") === jobId) {
|
|
1226
|
+
throw error;
|
|
748
1227
|
}
|
|
749
|
-
|
|
1228
|
+
const signalizError = error instanceof SignalizError ? error : null;
|
|
1229
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1230
|
+
throw new SignalizError({
|
|
1231
|
+
code: signalizError?.code || "BATCH_RECOVERY_FAILED",
|
|
1232
|
+
message: `${label} batch ${jobId} could not be fully retrieved. Resume the existing job instead of resubmitting it. ${message}`,
|
|
1233
|
+
errorType: signalizError?.errorType || "provider_error",
|
|
1234
|
+
retryAfter: signalizError?.retryAfter,
|
|
1235
|
+
details: {
|
|
1236
|
+
...signalizError?.details || {},
|
|
1237
|
+
job_id: jobId,
|
|
1238
|
+
...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
|
|
1239
|
+
suggested_action: "check_job_status",
|
|
1240
|
+
retry_eligible: true
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
750
1243
|
}
|
|
751
|
-
if (seen.size !== requests.length) throw new Error(`${label} batch returned incomplete indexed results`);
|
|
752
|
-
return rows;
|
|
753
1244
|
}
|
|
754
1245
|
async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0, idempotencyKey, idempotencyItemIndices = requests.map((_, index) => index)) {
|
|
755
1246
|
const results = new Array(requests.length);
|
|
@@ -814,17 +1305,18 @@ var Signaliz = class {
|
|
|
814
1305
|
(task) => coreProductBatchRequestKey(path2, task.request)
|
|
815
1306
|
);
|
|
816
1307
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
817
|
-
const recoverableBatch = path2 === "api/v1/find-email" ? { label: "Find Email", pageSize:
|
|
1308
|
+
const recoverableBatch = path2 === "api/v1/find-email" ? { label: "Find Email", pageSize: 25, maxWaitMs: 20 * 6e4 } : path2 === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 25, maxWaitMs: 20 * 6e4 } : void 0;
|
|
818
1309
|
const containsFindRecoveryReads = path2 === "api/v1/find-email" && uniqueTasks.some(
|
|
819
1310
|
(task) => typeof task.request.run_id === "string" && task.request.run_id.trim()
|
|
820
1311
|
);
|
|
821
|
-
if (
|
|
1312
|
+
if (recoverableBatch && uniqueTasks.length > 25 && !containsFindRecoveryReads) {
|
|
822
1313
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
823
1314
|
path2,
|
|
824
1315
|
uniqueTasks.map((task) => task.request),
|
|
825
1316
|
recoverableBatch.label,
|
|
826
1317
|
recoverableBatch.pageSize,
|
|
827
|
-
recoverableBatch.maxWaitMs,
|
|
1318
|
+
options?.maxWaitMs ?? recoverableBatch.maxWaitMs,
|
|
1319
|
+
options?.pollIntervalMs,
|
|
828
1320
|
options?.idempotencyKey,
|
|
829
1321
|
uniqueTasks.map((task) => task.index)
|
|
830
1322
|
);
|
|
@@ -947,7 +1439,7 @@ function batchItemFailure(index, error, raw) {
|
|
|
947
1439
|
const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
|
|
948
1440
|
const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
|
|
949
1441
|
const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
|
|
950
|
-
const runId = raw?.run_id
|
|
1442
|
+
const runId = [raw?.run_id, raw?.runId, raw?.signal_run_id, raw?.signalRunId].find((value) => typeof value === "string" && value.trim());
|
|
951
1443
|
const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
|
|
952
1444
|
const jobId = raw?.job_id ?? raw?.jobId;
|
|
953
1445
|
const suggestedAction = raw?.suggested_action ?? raw?.suggestedAction;
|
|
@@ -970,6 +1462,42 @@ function batchItemFailure(index, error, raw) {
|
|
|
970
1462
|
function recoverableJobRowFailed(row) {
|
|
971
1463
|
return coreProductResponseFailed(row) || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
|
|
972
1464
|
}
|
|
1465
|
+
function recoverableRowsToRound(rows, label) {
|
|
1466
|
+
return rows.map((raw) => {
|
|
1467
|
+
const index = Number(raw.index);
|
|
1468
|
+
const failed = recoverableJobRowFailed(raw);
|
|
1469
|
+
return {
|
|
1470
|
+
index,
|
|
1471
|
+
success: !failed,
|
|
1472
|
+
raw,
|
|
1473
|
+
error: failed ? raw.error || raw._error || raw.message || raw.failure_reason || `${label} item failed` : void 0
|
|
1474
|
+
};
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
function recoverableSignalCopyRows(rows) {
|
|
1478
|
+
return rows.map((raw) => {
|
|
1479
|
+
const index = Number(raw.index);
|
|
1480
|
+
const duplicateOf = recoveredDuplicateOf(raw);
|
|
1481
|
+
if (duplicateOf !== void 0) return { index, success: true, duplicateOf };
|
|
1482
|
+
if (recoverableJobRowFailed(raw)) {
|
|
1483
|
+
return batchItemFailure(
|
|
1484
|
+
index,
|
|
1485
|
+
raw.error || raw._error || raw.message || "Signal to Copy item failed",
|
|
1486
|
+
raw
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
try {
|
|
1490
|
+
assertTerminalSignalResponse(raw, "Signal to Copy");
|
|
1491
|
+
return { index, success: true, data: normalizeSignalToCopyResult(raw) };
|
|
1492
|
+
} catch (error) {
|
|
1493
|
+
return batchItemFailure(index, error instanceof Error ? error.message : String(error), raw);
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
function recoveredDuplicateOf(raw) {
|
|
1498
|
+
const value = Number(raw.duplicate_of ?? raw.duplicateOf ?? raw._duplicate_of);
|
|
1499
|
+
return Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
1500
|
+
}
|
|
973
1501
|
function newBatchIdempotencyKey(label) {
|
|
974
1502
|
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
975
1503
|
return `signaliz-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${nonce}`;
|
|
@@ -991,6 +1519,16 @@ function findEmailRequestBody(params) {
|
|
|
991
1519
|
idempotency_key: params.idempotencyKey
|
|
992
1520
|
});
|
|
993
1521
|
}
|
|
1522
|
+
function verifyEmailBatchRequestBody(input, options) {
|
|
1523
|
+
if (typeof input === "string") {
|
|
1524
|
+
return compact({ email: input, skip_cache: options?.skipCache });
|
|
1525
|
+
}
|
|
1526
|
+
return compact({
|
|
1527
|
+
email: input.email,
|
|
1528
|
+
skip_cache: input.skipCache ?? options?.skipCache,
|
|
1529
|
+
idempotency_key: input.idempotencyKey
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
994
1532
|
function isCoreProductDryRun(data) {
|
|
995
1533
|
return data.dry_run === true && data.status === "planned";
|
|
996
1534
|
}
|
|
@@ -1025,12 +1563,17 @@ function normalizeFindEmailResult(data) {
|
|
|
1025
1563
|
const email = failed ? null : rawEmail;
|
|
1026
1564
|
const found = Boolean(email) && data.found !== false;
|
|
1027
1565
|
const verificationStatus = data.verification_status ?? (data.is_valid === true ? "valid" : data.status ?? "unknown");
|
|
1566
|
+
const deliverabilityStatus = data.deliverability_status ?? verificationStatus;
|
|
1567
|
+
const verificationVerdict = data.verification_verdict ?? verificationStatus;
|
|
1028
1568
|
const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
|
|
1029
1569
|
const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
|
|
1030
1570
|
const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
|
|
1031
1571
|
const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
|
|
1032
1572
|
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()));
|
|
1033
1573
|
const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
|
|
1574
|
+
const providerUsed = data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown";
|
|
1575
|
+
const verificationSource = data.verification_source ?? providerUsed;
|
|
1576
|
+
const billingMetadata = data.billing_metadata && typeof data.billing_metadata === "object" && !Array.isArray(data.billing_metadata) ? data.billing_metadata : void 0;
|
|
1034
1577
|
return {
|
|
1035
1578
|
success: processing || !failed && found,
|
|
1036
1579
|
found,
|
|
@@ -1039,61 +1582,86 @@ function normalizeFindEmailResult(data) {
|
|
|
1039
1582
|
lastName: data.last_name,
|
|
1040
1583
|
confidence: failed ? 0 : data.confidence ?? data.confidence_score ?? (verified ? 1 : 0),
|
|
1041
1584
|
verificationStatus: failed ? "error" : verificationStatus,
|
|
1585
|
+
deliverabilityStatus: failed ? "error" : deliverabilityStatus,
|
|
1586
|
+
verificationVerdict: failed ? "error" : verificationVerdict,
|
|
1042
1587
|
isVerified: !failed && verified,
|
|
1043
1588
|
isVerifiedForSending: !failed && !needsReverification && data.verified_for_sending === true,
|
|
1589
|
+
isDeliverable: !failed && !needsReverification && data.verified_for_sending === true,
|
|
1044
1590
|
isCatchAll: data.email_is_catch_all === true || data.is_catch_all === true,
|
|
1045
1591
|
verificationFreshness,
|
|
1046
1592
|
verificationAgeDays,
|
|
1047
1593
|
verifiedAt: typeof data.verified_at === "string" ? data.verified_at : null,
|
|
1048
1594
|
needsReverification: failed || needsReverification,
|
|
1049
|
-
providerUsed
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1595
|
+
providerUsed,
|
|
1596
|
+
verificationSource,
|
|
1597
|
+
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1598
|
+
failureReason: data.failure_reason ?? (failed || !found ? "no_verified_email_found" : "none"),
|
|
1599
|
+
creditsUsed: data.credits_used ?? billingMetadata?.credits_used,
|
|
1600
|
+
estimatedExternalCostUsd: data.estimated_external_cost_usd ?? billingMetadata?.estimated_external_cost_usd,
|
|
1601
|
+
billingMetadata,
|
|
1602
|
+
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1603
|
+
resultReturnedFrom: data.result_returned_from ?? billingMetadata?.result_returned_from,
|
|
1604
|
+
cacheHit: typeof data.cache_hit === "boolean" ? data.cache_hit : billingMetadata?.cache_hit,
|
|
1055
1605
|
negativeCacheHit: data.negative_cache_hit,
|
|
1056
1606
|
negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
|
|
1057
1607
|
cacheTier: data.cache_tier,
|
|
1058
|
-
freshEnrichmentUsed: data.fresh_enrichment_used,
|
|
1059
|
-
liveProviderCalled: data.live_provider_called,
|
|
1060
|
-
openrouterCalled: data.openrouter_called,
|
|
1061
|
-
byoOpenrouterUsed: data.byo_openrouter_used,
|
|
1608
|
+
freshEnrichmentUsed: typeof data.fresh_enrichment_used === "boolean" ? data.fresh_enrichment_used : billingMetadata?.fresh_enrichment_used,
|
|
1609
|
+
liveProviderCalled: typeof data.live_provider_called === "boolean" ? data.live_provider_called : billingMetadata?.live_provider_called,
|
|
1610
|
+
openrouterCalled: typeof data.openrouter_called === "boolean" ? data.openrouter_called : billingMetadata?.openrouter_called,
|
|
1611
|
+
byoOpenrouterUsed: typeof data.byo_openrouter_used === "boolean" ? data.byo_openrouter_used : billingMetadata?.byo_openrouter_used,
|
|
1062
1612
|
error: processing || found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
|
|
1063
1613
|
errorCode: processing || found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
|
|
1064
1614
|
status: processing ? "processing" : "completed",
|
|
1065
1615
|
runId: data.run_id,
|
|
1066
1616
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1067
1617
|
suggestedAction: data.suggested_action,
|
|
1618
|
+
cachePublicationStatus: data.cache_publication_status,
|
|
1619
|
+
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1068
1620
|
raw: data
|
|
1069
1621
|
};
|
|
1070
1622
|
}
|
|
1071
1623
|
function normalizeVerifyEmailResult(email, data) {
|
|
1072
1624
|
const failed = coreProductResponseFailed(data);
|
|
1073
|
-
const verificationStatus = String(data.verification_status ?? "").toLowerCase();
|
|
1074
|
-
const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
|
|
1075
1625
|
const malformed = data.is_malformed === true || data.verification_verdict === "malformed" || data.verification_status === "malformed" || data.deliverability_status === "malformed";
|
|
1626
|
+
const verificationStatus = String(
|
|
1627
|
+
failed ? malformed ? "malformed" : "error" : data.verification_status ?? data.verification_verdict ?? data.deliverability_status ?? "unknown"
|
|
1628
|
+
).toLowerCase();
|
|
1629
|
+
const deliverabilityStatus = String(data.deliverability_status ?? verificationStatus).toLowerCase();
|
|
1630
|
+
const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
|
|
1631
|
+
const isCatchAll = data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false;
|
|
1632
|
+
const verifiedForSending = !failed && data.verified_for_sending === true;
|
|
1633
|
+
const verificationRunId = data.verification_run_id ?? data.run_id;
|
|
1076
1634
|
return {
|
|
1077
1635
|
success: !failed,
|
|
1078
1636
|
error: failed ? coreProductErrorMessage(data) : void 0,
|
|
1079
1637
|
errorCode: failed ? coreProductErrorCode(data) : void 0,
|
|
1080
1638
|
email: data.email ?? email,
|
|
1081
1639
|
status: !failed && String(data.status || "").toLowerCase() === "processing" ? "processing" : "completed",
|
|
1082
|
-
verificationRunId
|
|
1640
|
+
verificationRunId,
|
|
1083
1641
|
retryAfterMs: data.retry_after_ms,
|
|
1084
1642
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1085
1643
|
isValid: failed ? false : data.is_valid ?? data.valid ?? verified,
|
|
1086
1644
|
isDeliverable: failed ? false : data.is_deliverable ?? data.deliverable ?? verified,
|
|
1087
1645
|
isMalformed: malformed,
|
|
1088
|
-
isCatchAll
|
|
1089
|
-
verifiedForSending
|
|
1646
|
+
isCatchAll,
|
|
1647
|
+
verifiedForSending,
|
|
1648
|
+
verificationStatus,
|
|
1649
|
+
deliverabilityStatus,
|
|
1090
1650
|
verificationVerdict: failed ? malformed ? "malformed" : "error" : data.verification_verdict ?? data.verification_status ?? "unknown",
|
|
1651
|
+
isRoleAccount: data.is_role_account === true || data.email_is_role_account === true || data.role === true,
|
|
1652
|
+
quality: typeof data.quality === "string" ? data.quality : null,
|
|
1653
|
+
recommendation: typeof data.recommendation === "string" ? data.recommendation : verifiedForSending ? "send" : ["unknown", "verifier_error"].includes(verificationStatus) ? "manual_review" : "do_not_send",
|
|
1654
|
+
smtpStatus: data.smtp_status ?? (isCatchAll ? "accept_all" : verifiedForSending ? "accepted" : "not_checked"),
|
|
1655
|
+
providerStatus: data.provider_status ?? verificationStatus,
|
|
1656
|
+
failureReason: data.failure_reason ?? (verifiedForSending ? "none" : isCatchAll ? "catch_all_domain_not_send_safe" : "verification_not_send_safe"),
|
|
1091
1657
|
confidenceScore: failed ? 0 : data.confidence_score ?? data.confidence ?? data.diagnostics?.confidence_score ?? (verified ? 1 : 0),
|
|
1092
1658
|
provider: data.provider,
|
|
1093
1659
|
verificationSource: data.verification_source,
|
|
1660
|
+
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1094
1661
|
billingReplayed: data.billing_replayed,
|
|
1095
1662
|
creditsUsed: data.credits_used,
|
|
1096
1663
|
billingMetadata: data.billing_metadata,
|
|
1664
|
+
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1097
1665
|
resultReturnedFrom: data.result_returned_from,
|
|
1098
1666
|
cacheHit: data.cache_hit,
|
|
1099
1667
|
negativeCacheHit: data.negative_cache_hit,
|
|
@@ -1103,6 +1671,11 @@ function normalizeVerifyEmailResult(email, data) {
|
|
|
1103
1671
|
liveProviderCalled: data.live_provider_called,
|
|
1104
1672
|
openrouterCalled: data.openrouter_called,
|
|
1105
1673
|
byoOpenrouterUsed: data.byo_openrouter_used,
|
|
1674
|
+
estimatedExternalCostUsd: data.estimated_external_cost_usd,
|
|
1675
|
+
runId: data.run_id ?? verificationRunId,
|
|
1676
|
+
cachePublicationStatus: data.cache_publication_status,
|
|
1677
|
+
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1678
|
+
suggestedAction: data.suggested_action,
|
|
1106
1679
|
raw: data
|
|
1107
1680
|
};
|
|
1108
1681
|
}
|
|
@@ -1130,6 +1703,54 @@ function companySignalRequestBody(params) {
|
|
|
1130
1703
|
idempotency_key: params.idempotencyKey
|
|
1131
1704
|
});
|
|
1132
1705
|
}
|
|
1706
|
+
function companySignalParamsFromRecoveredRow(row) {
|
|
1707
|
+
return {
|
|
1708
|
+
companyDomain: row.company?.domain ?? row.company_domain,
|
|
1709
|
+
companyName: row.company?.name ?? row.company_name,
|
|
1710
|
+
signalRunId: row.signal_run_id ?? row.run_id
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
var COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS = [
|
|
1714
|
+
"research_prompt",
|
|
1715
|
+
"signal_types",
|
|
1716
|
+
"target_signal_count",
|
|
1717
|
+
"lookback_days",
|
|
1718
|
+
"online",
|
|
1719
|
+
"enable_deep_search",
|
|
1720
|
+
"search_mode",
|
|
1721
|
+
"include_summary",
|
|
1722
|
+
"enable_outreach_intelligence",
|
|
1723
|
+
"enable_predictive_intelligence",
|
|
1724
|
+
"skip_cache",
|
|
1725
|
+
"include_candidate_evidence"
|
|
1726
|
+
];
|
|
1727
|
+
function companySignalLargeBatchSubmission(requests) {
|
|
1728
|
+
if (requests.length <= 25) return null;
|
|
1729
|
+
if (requests.some(
|
|
1730
|
+
(request) => typeof request.signal_run_id === "string" && request.signal_run_id.trim() || typeof request.idempotency_key === "string" && request.idempotency_key.trim()
|
|
1731
|
+
)) {
|
|
1732
|
+
return null;
|
|
1733
|
+
}
|
|
1734
|
+
const submissionFields = Object.fromEntries(
|
|
1735
|
+
COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => requests[0]?.[field] !== void 0).map((field) => [field, requests[0][field]])
|
|
1736
|
+
);
|
|
1737
|
+
const controlKey = JSON.stringify(submissionFields);
|
|
1738
|
+
for (const request of requests) {
|
|
1739
|
+
const requestControls = Object.fromEntries(
|
|
1740
|
+
COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => request[field] !== void 0).map((field) => [field, request[field]])
|
|
1741
|
+
);
|
|
1742
|
+
if (JSON.stringify(requestControls) !== controlKey) return null;
|
|
1743
|
+
const hasIdentity = typeof request.company_domain === "string" && request.company_domain.trim() || typeof request.company_name === "string" && request.company_name.trim();
|
|
1744
|
+
if (!hasIdentity) return null;
|
|
1745
|
+
}
|
|
1746
|
+
return {
|
|
1747
|
+
requests: requests.map((request) => compact({
|
|
1748
|
+
company_domain: request.company_domain,
|
|
1749
|
+
company_name: request.company_name
|
|
1750
|
+
})),
|
|
1751
|
+
submissionFields
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1133
1754
|
function normalizeCompanySignalResult(params, data) {
|
|
1134
1755
|
const failed = coreProductResponseFailed(data);
|
|
1135
1756
|
const rawSignals = failed ? [] : data.signals ?? data.signal_feed ?? [];
|
|
@@ -1380,6 +2001,9 @@ function dedupeExactBatchItems(items, keyForItem) {
|
|
|
1380
2001
|
function normalizedBatchText(value) {
|
|
1381
2002
|
return typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, " ") : "";
|
|
1382
2003
|
}
|
|
2004
|
+
function normalizedBatchIdempotencyKey(value) {
|
|
2005
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2006
|
+
}
|
|
1383
2007
|
function normalizedBatchDomain(value) {
|
|
1384
2008
|
return normalizedBatchText(value).replace(/^https?:\/\//, "").replace(/^www\./, "").split(/[/?#]/, 1)[0].replace(/\.+$/, "");
|
|
1385
2009
|
}
|
|
@@ -1403,7 +2027,8 @@ function coreProductBatchRequestKey(path2, request) {
|
|
|
1403
2027
|
normalizedBatchDomain(request.company_domain),
|
|
1404
2028
|
normalizedBatchLinkedIn(request.linkedin_url),
|
|
1405
2029
|
normalizedBatchText(request.company_name),
|
|
1406
|
-
request.skip_cache === true || request.bypass_cache === true
|
|
2030
|
+
request.skip_cache === true || request.bypass_cache === true,
|
|
2031
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1407
2032
|
]);
|
|
1408
2033
|
}
|
|
1409
2034
|
if (path2 === "api/v1/verify-email") {
|
|
@@ -1411,7 +2036,8 @@ function coreProductBatchRequestKey(path2, request) {
|
|
|
1411
2036
|
normalizedBatchText(request.email),
|
|
1412
2037
|
request.skip_cache === true || request.bypass_cache === true,
|
|
1413
2038
|
request.skip_catch_all_verification === true,
|
|
1414
|
-
request.skip_esp_check === true
|
|
2039
|
+
request.skip_esp_check === true,
|
|
2040
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1415
2041
|
]);
|
|
1416
2042
|
}
|
|
1417
2043
|
if (path2 === "api/v1/company-signals") {
|
|
@@ -1433,7 +2059,8 @@ function coreProductBatchRequestKey(path2, request) {
|
|
|
1433
2059
|
normalizedBatchText(request.search_mode || "balanced"),
|
|
1434
2060
|
request.enable_outreach_intelligence === true,
|
|
1435
2061
|
request.enable_predictive_intelligence === true,
|
|
1436
|
-
request.skip_cache === true || request.bypass_cache === true
|
|
2062
|
+
request.skip_cache === true || request.bypass_cache === true,
|
|
2063
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1437
2064
|
]);
|
|
1438
2065
|
}
|
|
1439
2066
|
return JSON.stringify([
|
|
@@ -1442,9 +2069,11 @@ function coreProductBatchRequestKey(path2, request) {
|
|
|
1442
2069
|
typeof request.title === "string" ? request.title.trim() : "",
|
|
1443
2070
|
typeof request.campaign_offer === "string" ? request.campaign_offer.trim() : "",
|
|
1444
2071
|
typeof request.research_prompt === "string" ? request.research_prompt.trim() : "",
|
|
2072
|
+
Number(request.lookback_days) || null,
|
|
1445
2073
|
typeof request.signal_run_id === "string" ? request.signal_run_id.trim() : "",
|
|
1446
2074
|
request.skip_cache === true,
|
|
1447
|
-
request.enable_deep_search === true
|
|
2075
|
+
request.enable_deep_search === true,
|
|
2076
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1448
2077
|
]);
|
|
1449
2078
|
}
|
|
1450
2079
|
function finalizeCoreBatch(total, startedAt, round, normalize, options) {
|
|
@@ -1482,6 +2111,37 @@ function finalizeCoreBatch(total, startedAt, round, normalize, options) {
|
|
|
1482
2111
|
results: compactedResults
|
|
1483
2112
|
};
|
|
1484
2113
|
}
|
|
2114
|
+
function finalizeRecoveredCoreBatch(total, startedAt, round, normalize, options) {
|
|
2115
|
+
const results = round.map((item) => {
|
|
2116
|
+
const duplicateOf = item.raw ? recoveredDuplicateOf(item.raw) : void 0;
|
|
2117
|
+
if (duplicateOf !== void 0) return { index: item.index, success: true, duplicateOf };
|
|
2118
|
+
if (!item.success || !item.raw) {
|
|
2119
|
+
return batchItemFailure(
|
|
2120
|
+
item.index,
|
|
2121
|
+
item.error || "Signaliz batch item failed",
|
|
2122
|
+
item.raw ?? item
|
|
2123
|
+
);
|
|
2124
|
+
}
|
|
2125
|
+
try {
|
|
2126
|
+
return { index: item.index, success: true, data: normalize(item) };
|
|
2127
|
+
} catch (error) {
|
|
2128
|
+
return {
|
|
2129
|
+
index: item.index,
|
|
2130
|
+
success: false,
|
|
2131
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
});
|
|
2135
|
+
const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
|
|
2136
|
+
const succeeded = compactedResults.filter((item) => item.success).length;
|
|
2137
|
+
return {
|
|
2138
|
+
total,
|
|
2139
|
+
succeeded,
|
|
2140
|
+
failed: total - succeeded,
|
|
2141
|
+
durationMs: Date.now() - startedAt,
|
|
2142
|
+
results: compactedResults
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
1485
2145
|
function compactExactDuplicateResults(results, enabled) {
|
|
1486
2146
|
if (!enabled) return results;
|
|
1487
2147
|
const sourceIndexByRaw = /* @__PURE__ */ new WeakMap();
|
|
@@ -1497,6 +2157,19 @@ function compactExactDuplicateResults(results, enabled) {
|
|
|
1497
2157
|
return item;
|
|
1498
2158
|
});
|
|
1499
2159
|
}
|
|
2160
|
+
function compactKnownDuplicateBatch(batch, sourceIndexByInput, enabled) {
|
|
2161
|
+
if (!enabled) return batch;
|
|
2162
|
+
const canonicalInputByUniqueIndex = /* @__PURE__ */ new Map();
|
|
2163
|
+
sourceIndexByInput.forEach((uniqueIndex, inputIndex) => {
|
|
2164
|
+
if (!canonicalInputByUniqueIndex.has(uniqueIndex)) canonicalInputByUniqueIndex.set(uniqueIndex, inputIndex);
|
|
2165
|
+
});
|
|
2166
|
+
const results = batch.results.map((item, index) => {
|
|
2167
|
+
const canonicalIndex = canonicalInputByUniqueIndex.get(sourceIndexByInput[index]) ?? index;
|
|
2168
|
+
if (canonicalIndex === index || !item.success || !batch.results[canonicalIndex]?.success) return item;
|
|
2169
|
+
return { index, success: true, duplicateOf: canonicalIndex };
|
|
2170
|
+
});
|
|
2171
|
+
return { ...batch, results };
|
|
2172
|
+
}
|
|
1500
2173
|
function signalToCopyRequestBody(params) {
|
|
1501
2174
|
return compact({
|
|
1502
2175
|
company_domain: params.companyDomain,
|
|
@@ -1513,6 +2186,28 @@ function signalToCopyRequestBody(params) {
|
|
|
1513
2186
|
idempotency_key: params.idempotencyKey
|
|
1514
2187
|
});
|
|
1515
2188
|
}
|
|
2189
|
+
function validateLargeAvailableDataSignalCopyBatch(requests) {
|
|
2190
|
+
if (requests.length <= 25) return;
|
|
2191
|
+
const invalidIndex = requests.findIndex(
|
|
2192
|
+
(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()
|
|
2193
|
+
);
|
|
2194
|
+
if (invalidIndex >= 0) {
|
|
2195
|
+
throw new RangeError(
|
|
2196
|
+
`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.`
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
function availableDataSignalCopyRequestBody(params) {
|
|
2201
|
+
return compact({
|
|
2202
|
+
company_domain: params.companyDomain,
|
|
2203
|
+
person_name: params.personName,
|
|
2204
|
+
title: params.title,
|
|
2205
|
+
campaign_offer: params.campaignOffer,
|
|
2206
|
+
research_prompt: params.researchPrompt,
|
|
2207
|
+
lookback_days: params.lookbackDays,
|
|
2208
|
+
signal_run_id: params.signalRunId
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
1516
2211
|
function validateSignalToCopyParams(params) {
|
|
1517
2212
|
validateCoreProductMaxWaitMs(params.maxWaitMs);
|
|
1518
2213
|
if (params.lookbackDays !== void 0 && (!Number.isInteger(params.lookbackDays) || params.lookbackDays < 1 || params.lookbackDays > 365)) {
|
|
@@ -1563,6 +2258,8 @@ function normalizeSignalToCopyResult(data) {
|
|
|
1563
2258
|
liveProviderCalled: data.live_provider_called,
|
|
1564
2259
|
aiProviderCalled: data.ai_provider_called,
|
|
1565
2260
|
byoAiUsed: data.byo_ai_used,
|
|
2261
|
+
unlimitedSignalToCopy: data.unlimited_signal_to_copy,
|
|
2262
|
+
signalToCopyEntitlement: data.signal_to_copy_entitlement,
|
|
1566
2263
|
variations: (failed ? [] : data.variations ?? []).map((variation) => ({
|
|
1567
2264
|
style: variation.style,
|
|
1568
2265
|
subjectLine: variation.subject_line ?? "",
|
|
@@ -1646,6 +2343,29 @@ async function waitForNextSignalPoll(data, override, startedAt, maxWaitMs) {
|
|
|
1646
2343
|
function sleep2(ms) {
|
|
1647
2344
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1648
2345
|
}
|
|
2346
|
+
function validateCoreBatchPollingOptions(options) {
|
|
2347
|
+
if (options.maxWaitMs !== void 0 && (!Number.isFinite(options.maxWaitMs) || options.maxWaitMs <= 0)) {
|
|
2348
|
+
throw new RangeError("maxWaitMs must be a positive finite number");
|
|
2349
|
+
}
|
|
2350
|
+
if (options.pollIntervalMs !== void 0 && (!Number.isFinite(options.pollIntervalMs) || options.pollIntervalMs <= 0)) {
|
|
2351
|
+
throw new RangeError("pollIntervalMs must be a positive finite number");
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
function assertRecoverableBatchResultsRetained(status, label, jobId, idempotencyKey) {
|
|
2355
|
+
if (status.results_expired !== true && status.results_cleaned !== true) return;
|
|
2356
|
+
throw new SignalizError({
|
|
2357
|
+
code: "BATCH_RESULTS_EXPIRED",
|
|
2358
|
+
message: `${label} batch ${jobId} completed, but its retained result pages have expired and cannot be recovered. Do not automatically resubmit provider work.`,
|
|
2359
|
+
errorType: "not_found",
|
|
2360
|
+
details: {
|
|
2361
|
+
job_id: jobId,
|
|
2362
|
+
...idempotencyKey ? { idempotency_key: idempotencyKey } : {},
|
|
2363
|
+
suggested_action: "review_before_resubmitting",
|
|
2364
|
+
retry_eligible: false,
|
|
2365
|
+
results_expired: true
|
|
2366
|
+
}
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
1649
2369
|
async function runBatch(items, execute, options) {
|
|
1650
2370
|
validateBatchSize(items);
|
|
1651
2371
|
const concurrency = validatedBatchConcurrency(options);
|