@signaliz/sdk 1.0.61 → 1.0.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -15
- package/dist/{chunk-ENA7AVFB.mjs → chunk-6G2FALRZ.mjs} +812 -100
- package/dist/index.d.mts +86 -7
- package/dist/index.d.ts +86 -7
- package/dist/index.js +812 -100
- package/dist/index.mjs +1 -1
- package/dist/mcp-config.js +812 -100
- 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,209 @@ 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
|
-
const
|
|
1044
|
+
async runRecoverableCoreBatchJob(path2, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
|
|
1045
|
+
const job = await this.submitRecoverableCoreBatchJob(
|
|
1046
|
+
path2,
|
|
1047
|
+
requests,
|
|
1048
|
+
label,
|
|
1049
|
+
idempotencyKey,
|
|
1050
|
+
idempotencyItemIndices,
|
|
1051
|
+
submissionFields
|
|
1052
|
+
);
|
|
1053
|
+
const recovered = await this.readRecoverableCoreBatchJob(path2, job.jobId, label, {
|
|
1054
|
+
pageSize,
|
|
1055
|
+
maxWaitMs,
|
|
1056
|
+
idempotencyKey: job.idempotencyKey
|
|
1057
|
+
});
|
|
1058
|
+
if (recovered.total !== requests.length) {
|
|
1059
|
+
throw new Error(`${label} batch returned ${recovered.total} results for ${requests.length} requests`);
|
|
1060
|
+
}
|
|
1061
|
+
return recovered.rows;
|
|
1062
|
+
}
|
|
1063
|
+
async submitRecoverableCoreBatchJob(path2, requests, label, idempotencyKey, idempotencyItemIndices, submissionFields = {}) {
|
|
685
1064
|
const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
|
|
1065
|
+
const requestBody = {
|
|
1066
|
+
...submissionFields,
|
|
1067
|
+
requests,
|
|
1068
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1069
|
+
batch_item_indices: idempotencyItemIndices
|
|
1070
|
+
};
|
|
1071
|
+
if (path2 === "api/v1/company-signals" || path2 === "api/v1/signal-to-copy") {
|
|
1072
|
+
assertLargeCoreProductInputBounds(path2, requestBody);
|
|
1073
|
+
}
|
|
686
1074
|
let submission;
|
|
687
1075
|
try {
|
|
688
|
-
submission = await this.client.post(path2,
|
|
689
|
-
requests,
|
|
690
|
-
idempotency_key: submissionIdempotencyKey,
|
|
691
|
-
batch_item_indices: idempotencyItemIndices
|
|
692
|
-
});
|
|
1076
|
+
submission = await this.client.post(path2, requestBody);
|
|
693
1077
|
} catch (error) {
|
|
694
1078
|
if (error instanceof SignalizError && !error.isRetryable) throw error;
|
|
695
1079
|
const message = error instanceof Error ? error.message : String(error);
|
|
696
|
-
throw new
|
|
697
|
-
|
|
698
|
-
|
|
1080
|
+
throw new SignalizError({
|
|
1081
|
+
code: "BATCH_SUBMISSION_UNCONFIRMED",
|
|
1082
|
+
message: `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`,
|
|
1083
|
+
errorType: "provider_error",
|
|
1084
|
+
details: {
|
|
1085
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1086
|
+
suggested_action: "retry_with_idempotency_key",
|
|
1087
|
+
retry_eligible: true
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
699
1090
|
}
|
|
700
1091
|
const jobId = String(submission.job_id || "").trim();
|
|
701
|
-
if (!jobId)
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1092
|
+
if (!jobId) {
|
|
1093
|
+
throw new SignalizError({
|
|
1094
|
+
code: "INVALID_JOB_RESPONSE",
|
|
1095
|
+
message: `${label} batch did not return a recoverable job_id`,
|
|
1096
|
+
errorType: "provider_error",
|
|
1097
|
+
details: {
|
|
1098
|
+
idempotency_key: submissionIdempotencyKey,
|
|
1099
|
+
suggested_action: "retry_with_idempotency_key",
|
|
1100
|
+
retry_eligible: true
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
const submissionStatus = String(submission.status || "").toLowerCase();
|
|
1105
|
+
const status = ["queued", "processing", "completed", "partial", "failed"].includes(submissionStatus) ? submissionStatus : "queued";
|
|
1106
|
+
return {
|
|
1107
|
+
success: true,
|
|
1108
|
+
status,
|
|
1109
|
+
capability: path2 === "api/v1/company-signals" ? "company_signals" : "signal_to_copy",
|
|
1110
|
+
jobId,
|
|
1111
|
+
idempotencyKey: String(submission.idempotency_key || submissionIdempotencyKey),
|
|
1112
|
+
total: Math.max(0, Number(submission.total ?? submission.items_total ?? requests.length)),
|
|
1113
|
+
...Number.isFinite(Number(submission.next_poll_after_seconds)) ? {
|
|
1114
|
+
nextPollAfterSeconds: Number(submission.next_poll_after_seconds)
|
|
1115
|
+
} : {}
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
async readRecoverableCoreBatchJob(path2, jobIdInput, label, options = {}) {
|
|
1119
|
+
const jobId = String(jobIdInput || "").trim();
|
|
1120
|
+
if (!jobId) throw new RangeError(`${label} jobId is required`);
|
|
1121
|
+
if (options.pageSize !== void 0 && (!Number.isFinite(options.pageSize) || options.pageSize <= 0)) {
|
|
1122
|
+
throw new RangeError("pageSize must be a positive finite number");
|
|
1123
|
+
}
|
|
1124
|
+
if (options.maxWaitMs !== void 0 && (!Number.isFinite(options.maxWaitMs) || options.maxWaitMs <= 0)) {
|
|
1125
|
+
throw new RangeError("maxWaitMs must be a positive finite number");
|
|
1126
|
+
}
|
|
1127
|
+
if (options.pollIntervalMs !== void 0 && (!Number.isFinite(options.pollIntervalMs) || options.pollIntervalMs <= 0)) {
|
|
1128
|
+
throw new RangeError("pollIntervalMs must be a positive finite number");
|
|
1129
|
+
}
|
|
1130
|
+
const maximumPageSize = path2 === "api/v1/signal-to-copy" ? 100 : 25;
|
|
1131
|
+
const pageSize = Math.min(maximumPageSize, Math.max(1, Math.trunc(options.pageSize ?? maximumPageSize)));
|
|
1132
|
+
const emailJob = path2 === "api/v1/find-email" || path2 === "api/v1/verify-email";
|
|
1133
|
+
const maxWaitMs = options.maxWaitMs ?? (emailJob ? 20 * 6e4 : void 0);
|
|
1134
|
+
const startedAt = Date.now();
|
|
1135
|
+
let observedIdempotencyKey = String(options.idempotencyKey || "").trim();
|
|
1136
|
+
try {
|
|
1137
|
+
let status = await this.client.post(path2, {
|
|
714
1138
|
job_id: jobId,
|
|
715
1139
|
page: 1,
|
|
716
|
-
page_size: pageSize
|
|
1140
|
+
page_size: pageSize,
|
|
1141
|
+
compact_duplicates: options.compactDuplicates === true
|
|
717
1142
|
});
|
|
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) }
|
|
1143
|
+
observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
|
|
1144
|
+
assertRecoverableBatchResultsRetained(
|
|
1145
|
+
status,
|
|
1146
|
+
label,
|
|
1147
|
+
jobId,
|
|
1148
|
+
observedIdempotencyKey
|
|
731
1149
|
);
|
|
732
|
-
|
|
733
|
-
if (
|
|
734
|
-
throw new
|
|
1150
|
+
while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
|
|
1151
|
+
if (maxWaitMs !== void 0 && Date.now() - startedAt >= maxWaitMs) {
|
|
1152
|
+
throw new SignalizError({
|
|
1153
|
+
code: "TIMEOUT",
|
|
1154
|
+
message: `${label} batch ${jobId} did not complete within ${maxWaitMs}ms`,
|
|
1155
|
+
errorType: "timeout",
|
|
1156
|
+
retryAfter: Number(status.next_poll_after_seconds || 2),
|
|
1157
|
+
details: {
|
|
1158
|
+
job_id: jobId,
|
|
1159
|
+
...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
|
|
1160
|
+
suggested_action: "check_job_status",
|
|
1161
|
+
retry_eligible: true
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
735
1164
|
}
|
|
736
|
-
|
|
1165
|
+
const retryAfterMs = options.pollIntervalMs ?? Number(status.next_poll_after_seconds || 2) * 1e3;
|
|
1166
|
+
const boundedDelay = maxWaitMs === void 0 ? Math.max(250, retryAfterMs) : Math.min(Math.max(250, retryAfterMs), maxWaitMs - (Date.now() - startedAt));
|
|
1167
|
+
await sleep2(boundedDelay);
|
|
1168
|
+
status = await this.client.post(path2, {
|
|
1169
|
+
job_id: jobId,
|
|
1170
|
+
page: 1,
|
|
1171
|
+
page_size: pageSize,
|
|
1172
|
+
compact_duplicates: options.compactDuplicates === true
|
|
1173
|
+
});
|
|
1174
|
+
observedIdempotencyKey = String(status.idempotency_key || observedIdempotencyKey).trim();
|
|
1175
|
+
assertRecoverableBatchResultsRetained(
|
|
1176
|
+
status,
|
|
1177
|
+
label,
|
|
1178
|
+
jobId,
|
|
1179
|
+
observedIdempotencyKey
|
|
1180
|
+
);
|
|
737
1181
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
1182
|
+
const totalPages = Math.max(1, Number(status.total_pages || 1));
|
|
1183
|
+
const pages = [Array.isArray(status.results) ? status.results : []];
|
|
1184
|
+
if (totalPages > 1) {
|
|
1185
|
+
const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
|
|
1186
|
+
const remainingPages = await runBatch(
|
|
1187
|
+
pageNumbers,
|
|
1188
|
+
async (page) => await this.client.post(path2, {
|
|
1189
|
+
job_id: jobId,
|
|
1190
|
+
page,
|
|
1191
|
+
page_size: pageSize,
|
|
1192
|
+
compact_duplicates: options.compactDuplicates === true
|
|
1193
|
+
}),
|
|
1194
|
+
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
1195
|
+
);
|
|
1196
|
+
for (const page of remainingPages.results) {
|
|
1197
|
+
if (!page.success || !page.data) {
|
|
1198
|
+
throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
|
|
1199
|
+
}
|
|
1200
|
+
observedIdempotencyKey = String(
|
|
1201
|
+
page.data.idempotency_key || observedIdempotencyKey
|
|
1202
|
+
).trim();
|
|
1203
|
+
assertRecoverableBatchResultsRetained(
|
|
1204
|
+
page.data,
|
|
1205
|
+
label,
|
|
1206
|
+
jobId,
|
|
1207
|
+
observedIdempotencyKey
|
|
1208
|
+
);
|
|
1209
|
+
pages.push(Array.isArray(page.data.results) ? page.data.results : []);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
const rows = pages.flat();
|
|
1213
|
+
const total = Math.max(0, Number(status.total_results ?? status.total ?? status.items_total ?? rows.length));
|
|
1214
|
+
if (rows.length !== total) {
|
|
1215
|
+
throw new Error(`${label} batch returned ${rows.length} results for ${total} job rows`);
|
|
1216
|
+
}
|
|
1217
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1218
|
+
for (const row of rows) {
|
|
1219
|
+
const index = Number(row.index);
|
|
1220
|
+
if (!Number.isInteger(index) || index < 0 || seen.has(index)) {
|
|
1221
|
+
throw new Error(`${label} batch returned an invalid result index`);
|
|
1222
|
+
}
|
|
1223
|
+
seen.add(index);
|
|
1224
|
+
}
|
|
1225
|
+
if (seen.size !== total) throw new Error(`${label} batch returned incomplete indexed results`);
|
|
1226
|
+
return { rows, total };
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
if (error instanceof SignalizError && String(error.details?.job_id || "") === jobId) {
|
|
1229
|
+
throw error;
|
|
748
1230
|
}
|
|
749
|
-
|
|
1231
|
+
const signalizError = error instanceof SignalizError ? error : null;
|
|
1232
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1233
|
+
throw new SignalizError({
|
|
1234
|
+
code: signalizError?.code || "BATCH_RECOVERY_FAILED",
|
|
1235
|
+
message: `${label} batch ${jobId} could not be fully retrieved. Resume the existing job instead of resubmitting it. ${message}`,
|
|
1236
|
+
errorType: signalizError?.errorType || "provider_error",
|
|
1237
|
+
retryAfter: signalizError?.retryAfter,
|
|
1238
|
+
details: {
|
|
1239
|
+
...signalizError?.details || {},
|
|
1240
|
+
job_id: jobId,
|
|
1241
|
+
...observedIdempotencyKey ? { idempotency_key: observedIdempotencyKey } : {},
|
|
1242
|
+
suggested_action: "check_job_status",
|
|
1243
|
+
retry_eligible: true
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
750
1246
|
}
|
|
751
|
-
if (seen.size !== requests.length) throw new Error(`${label} batch returned incomplete indexed results`);
|
|
752
|
-
return rows;
|
|
753
1247
|
}
|
|
754
1248
|
async runSignalCopyBatchChunk(requests, concurrency, rowRateLimitAttempt = 0, idempotencyKey, idempotencyItemIndices = requests.map((_, index) => index)) {
|
|
755
1249
|
const results = new Array(requests.length);
|
|
@@ -814,11 +1308,11 @@ var Signaliz = class {
|
|
|
814
1308
|
(task) => coreProductBatchRequestKey(path2, task.request)
|
|
815
1309
|
);
|
|
816
1310
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
817
|
-
const recoverableBatch = path2 === "api/v1/find-email" ? { label: "Find Email", pageSize:
|
|
1311
|
+
const recoverableBatch = path2 === "api/v1/find-email" ? { label: "Find Email", pageSize: 25, maxWaitMs: 20 * 6e4 } : path2 === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 25, maxWaitMs: 20 * 6e4 } : void 0;
|
|
818
1312
|
const containsFindRecoveryReads = path2 === "api/v1/find-email" && uniqueTasks.some(
|
|
819
1313
|
(task) => typeof task.request.run_id === "string" && task.request.run_id.trim()
|
|
820
1314
|
);
|
|
821
|
-
if (
|
|
1315
|
+
if (recoverableBatch && uniqueTasks.length > 25 && !containsFindRecoveryReads) {
|
|
822
1316
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
823
1317
|
path2,
|
|
824
1318
|
uniqueTasks.map((task) => task.request),
|
|
@@ -947,7 +1441,7 @@ function batchItemFailure(index, error, raw) {
|
|
|
947
1441
|
const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
|
|
948
1442
|
const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
|
|
949
1443
|
const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
|
|
950
|
-
const runId = raw?.run_id
|
|
1444
|
+
const runId = [raw?.run_id, raw?.runId, raw?.signal_run_id, raw?.signalRunId].find((value) => typeof value === "string" && value.trim());
|
|
951
1445
|
const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
|
|
952
1446
|
const jobId = raw?.job_id ?? raw?.jobId;
|
|
953
1447
|
const suggestedAction = raw?.suggested_action ?? raw?.suggestedAction;
|
|
@@ -970,6 +1464,42 @@ function batchItemFailure(index, error, raw) {
|
|
|
970
1464
|
function recoverableJobRowFailed(row) {
|
|
971
1465
|
return coreProductResponseFailed(row) || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
|
|
972
1466
|
}
|
|
1467
|
+
function recoverableRowsToRound(rows, label) {
|
|
1468
|
+
return rows.map((raw) => {
|
|
1469
|
+
const index = Number(raw.index);
|
|
1470
|
+
const failed = recoverableJobRowFailed(raw);
|
|
1471
|
+
return {
|
|
1472
|
+
index,
|
|
1473
|
+
success: !failed,
|
|
1474
|
+
raw,
|
|
1475
|
+
error: failed ? raw.error || raw._error || raw.message || raw.failure_reason || `${label} item failed` : void 0
|
|
1476
|
+
};
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
function recoverableSignalCopyRows(rows) {
|
|
1480
|
+
return rows.map((raw) => {
|
|
1481
|
+
const index = Number(raw.index);
|
|
1482
|
+
const duplicateOf = recoveredDuplicateOf(raw);
|
|
1483
|
+
if (duplicateOf !== void 0) return { index, success: true, duplicateOf };
|
|
1484
|
+
if (recoverableJobRowFailed(raw)) {
|
|
1485
|
+
return batchItemFailure(
|
|
1486
|
+
index,
|
|
1487
|
+
raw.error || raw._error || raw.message || "Signal to Copy item failed",
|
|
1488
|
+
raw
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
try {
|
|
1492
|
+
assertTerminalSignalResponse(raw, "Signal to Copy");
|
|
1493
|
+
return { index, success: true, data: normalizeSignalToCopyResult(raw) };
|
|
1494
|
+
} catch (error) {
|
|
1495
|
+
return batchItemFailure(index, error instanceof Error ? error.message : String(error), raw);
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
function recoveredDuplicateOf(raw) {
|
|
1500
|
+
const value = Number(raw.duplicate_of ?? raw.duplicateOf ?? raw._duplicate_of);
|
|
1501
|
+
return Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
1502
|
+
}
|
|
973
1503
|
function newBatchIdempotencyKey(label) {
|
|
974
1504
|
const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
975
1505
|
return `signaliz-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${nonce}`;
|
|
@@ -991,6 +1521,16 @@ function findEmailRequestBody(params) {
|
|
|
991
1521
|
idempotency_key: params.idempotencyKey
|
|
992
1522
|
});
|
|
993
1523
|
}
|
|
1524
|
+
function verifyEmailBatchRequestBody(input, options) {
|
|
1525
|
+
if (typeof input === "string") {
|
|
1526
|
+
return compact({ email: input, skip_cache: options?.skipCache });
|
|
1527
|
+
}
|
|
1528
|
+
return compact({
|
|
1529
|
+
email: input.email,
|
|
1530
|
+
skip_cache: input.skipCache ?? options?.skipCache,
|
|
1531
|
+
idempotency_key: input.idempotencyKey
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
994
1534
|
function isCoreProductDryRun(data) {
|
|
995
1535
|
return data.dry_run === true && data.status === "planned";
|
|
996
1536
|
}
|
|
@@ -1025,12 +1565,17 @@ function normalizeFindEmailResult(data) {
|
|
|
1025
1565
|
const email = failed ? null : rawEmail;
|
|
1026
1566
|
const found = Boolean(email) && data.found !== false;
|
|
1027
1567
|
const verificationStatus = data.verification_status ?? (data.is_valid === true ? "valid" : data.status ?? "unknown");
|
|
1568
|
+
const deliverabilityStatus = data.deliverability_status ?? verificationStatus;
|
|
1569
|
+
const verificationVerdict = data.verification_verdict ?? verificationStatus;
|
|
1028
1570
|
const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
|
|
1029
1571
|
const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
|
|
1030
1572
|
const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
|
|
1031
1573
|
const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
|
|
1032
1574
|
const verified = !explicitlyNotSendSafe && (data.is_valid === true || data.isValid === true || data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
|
|
1033
1575
|
const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
|
|
1576
|
+
const providerUsed = data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown";
|
|
1577
|
+
const verificationSource = data.verification_source ?? providerUsed;
|
|
1578
|
+
const billingMetadata = data.billing_metadata && typeof data.billing_metadata === "object" && !Array.isArray(data.billing_metadata) ? data.billing_metadata : void 0;
|
|
1034
1579
|
return {
|
|
1035
1580
|
success: processing || !failed && found,
|
|
1036
1581
|
found,
|
|
@@ -1039,61 +1584,86 @@ function normalizeFindEmailResult(data) {
|
|
|
1039
1584
|
lastName: data.last_name,
|
|
1040
1585
|
confidence: failed ? 0 : data.confidence ?? data.confidence_score ?? (verified ? 1 : 0),
|
|
1041
1586
|
verificationStatus: failed ? "error" : verificationStatus,
|
|
1587
|
+
deliverabilityStatus: failed ? "error" : deliverabilityStatus,
|
|
1588
|
+
verificationVerdict: failed ? "error" : verificationVerdict,
|
|
1042
1589
|
isVerified: !failed && verified,
|
|
1043
1590
|
isVerifiedForSending: !failed && !needsReverification && data.verified_for_sending === true,
|
|
1591
|
+
isDeliverable: !failed && !needsReverification && data.verified_for_sending === true,
|
|
1044
1592
|
isCatchAll: data.email_is_catch_all === true || data.is_catch_all === true,
|
|
1045
1593
|
verificationFreshness,
|
|
1046
1594
|
verificationAgeDays,
|
|
1047
1595
|
verifiedAt: typeof data.verified_at === "string" ? data.verified_at : null,
|
|
1048
1596
|
needsReverification: failed || needsReverification,
|
|
1049
|
-
providerUsed
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1597
|
+
providerUsed,
|
|
1598
|
+
verificationSource,
|
|
1599
|
+
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1600
|
+
failureReason: data.failure_reason ?? (failed || !found ? "no_verified_email_found" : "none"),
|
|
1601
|
+
creditsUsed: data.credits_used ?? billingMetadata?.credits_used,
|
|
1602
|
+
estimatedExternalCostUsd: data.estimated_external_cost_usd ?? billingMetadata?.estimated_external_cost_usd,
|
|
1603
|
+
billingMetadata,
|
|
1604
|
+
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1605
|
+
resultReturnedFrom: data.result_returned_from ?? billingMetadata?.result_returned_from,
|
|
1606
|
+
cacheHit: typeof data.cache_hit === "boolean" ? data.cache_hit : billingMetadata?.cache_hit,
|
|
1055
1607
|
negativeCacheHit: data.negative_cache_hit,
|
|
1056
1608
|
negativeCacheTtlSeconds: data.negative_cache_ttl_seconds,
|
|
1057
1609
|
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,
|
|
1610
|
+
freshEnrichmentUsed: typeof data.fresh_enrichment_used === "boolean" ? data.fresh_enrichment_used : billingMetadata?.fresh_enrichment_used,
|
|
1611
|
+
liveProviderCalled: typeof data.live_provider_called === "boolean" ? data.live_provider_called : billingMetadata?.live_provider_called,
|
|
1612
|
+
openrouterCalled: typeof data.openrouter_called === "boolean" ? data.openrouter_called : billingMetadata?.openrouter_called,
|
|
1613
|
+
byoOpenrouterUsed: typeof data.byo_openrouter_used === "boolean" ? data.byo_openrouter_used : billingMetadata?.byo_openrouter_used,
|
|
1062
1614
|
error: processing || found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
|
|
1063
1615
|
errorCode: processing || found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
|
|
1064
1616
|
status: processing ? "processing" : "completed",
|
|
1065
1617
|
runId: data.run_id,
|
|
1066
1618
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1067
1619
|
suggestedAction: data.suggested_action,
|
|
1620
|
+
cachePublicationStatus: data.cache_publication_status,
|
|
1621
|
+
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1068
1622
|
raw: data
|
|
1069
1623
|
};
|
|
1070
1624
|
}
|
|
1071
1625
|
function normalizeVerifyEmailResult(email, data) {
|
|
1072
1626
|
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
1627
|
const malformed = data.is_malformed === true || data.verification_verdict === "malformed" || data.verification_status === "malformed" || data.deliverability_status === "malformed";
|
|
1628
|
+
const verificationStatus = String(
|
|
1629
|
+
failed ? malformed ? "malformed" : "error" : data.verification_status ?? data.verification_verdict ?? data.deliverability_status ?? "unknown"
|
|
1630
|
+
).toLowerCase();
|
|
1631
|
+
const deliverabilityStatus = String(data.deliverability_status ?? verificationStatus).toLowerCase();
|
|
1632
|
+
const verified = !failed && (data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus));
|
|
1633
|
+
const isCatchAll = data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false;
|
|
1634
|
+
const verifiedForSending = !failed && data.verified_for_sending === true;
|
|
1635
|
+
const verificationRunId = data.verification_run_id ?? data.run_id;
|
|
1076
1636
|
return {
|
|
1077
1637
|
success: !failed,
|
|
1078
1638
|
error: failed ? coreProductErrorMessage(data) : void 0,
|
|
1079
1639
|
errorCode: failed ? coreProductErrorCode(data) : void 0,
|
|
1080
1640
|
email: data.email ?? email,
|
|
1081
1641
|
status: !failed && String(data.status || "").toLowerCase() === "processing" ? "processing" : "completed",
|
|
1082
|
-
verificationRunId
|
|
1642
|
+
verificationRunId,
|
|
1083
1643
|
retryAfterMs: data.retry_after_ms,
|
|
1084
1644
|
nextPollAfterSeconds: data.next_poll_after_seconds,
|
|
1085
1645
|
isValid: failed ? false : data.is_valid ?? data.valid ?? verified,
|
|
1086
1646
|
isDeliverable: failed ? false : data.is_deliverable ?? data.deliverable ?? verified,
|
|
1087
1647
|
isMalformed: malformed,
|
|
1088
|
-
isCatchAll
|
|
1089
|
-
verifiedForSending
|
|
1648
|
+
isCatchAll,
|
|
1649
|
+
verifiedForSending,
|
|
1650
|
+
verificationStatus,
|
|
1651
|
+
deliverabilityStatus,
|
|
1090
1652
|
verificationVerdict: failed ? malformed ? "malformed" : "error" : data.verification_verdict ?? data.verification_status ?? "unknown",
|
|
1653
|
+
isRoleAccount: data.is_role_account === true || data.email_is_role_account === true || data.role === true,
|
|
1654
|
+
quality: typeof data.quality === "string" ? data.quality : null,
|
|
1655
|
+
recommendation: typeof data.recommendation === "string" ? data.recommendation : verifiedForSending ? "send" : ["unknown", "verifier_error"].includes(verificationStatus) ? "manual_review" : "do_not_send",
|
|
1656
|
+
smtpStatus: data.smtp_status ?? (isCatchAll ? "accept_all" : verifiedForSending ? "accepted" : "not_checked"),
|
|
1657
|
+
providerStatus: data.provider_status ?? verificationStatus,
|
|
1658
|
+
failureReason: data.failure_reason ?? (verifiedForSending ? "none" : isCatchAll ? "catch_all_domain_not_send_safe" : "verification_not_send_safe"),
|
|
1091
1659
|
confidenceScore: failed ? 0 : data.confidence_score ?? data.confidence ?? data.diagnostics?.confidence_score ?? (verified ? 1 : 0),
|
|
1092
1660
|
provider: data.provider,
|
|
1093
1661
|
verificationSource: data.verification_source,
|
|
1662
|
+
verificationObservedAt: typeof data.verification_observed_at === "string" ? data.verification_observed_at : void 0,
|
|
1094
1663
|
billingReplayed: data.billing_replayed,
|
|
1095
1664
|
creditsUsed: data.credits_used,
|
|
1096
1665
|
billingMetadata: data.billing_metadata,
|
|
1666
|
+
historicalBillingMetadata: data.historical_billing_metadata,
|
|
1097
1667
|
resultReturnedFrom: data.result_returned_from,
|
|
1098
1668
|
cacheHit: data.cache_hit,
|
|
1099
1669
|
negativeCacheHit: data.negative_cache_hit,
|
|
@@ -1103,6 +1673,11 @@ function normalizeVerifyEmailResult(email, data) {
|
|
|
1103
1673
|
liveProviderCalled: data.live_provider_called,
|
|
1104
1674
|
openrouterCalled: data.openrouter_called,
|
|
1105
1675
|
byoOpenrouterUsed: data.byo_openrouter_used,
|
|
1676
|
+
estimatedExternalCostUsd: data.estimated_external_cost_usd,
|
|
1677
|
+
runId: data.run_id ?? verificationRunId,
|
|
1678
|
+
cachePublicationStatus: data.cache_publication_status,
|
|
1679
|
+
cachePublicationRetryEligible: data.cache_publication_retry_eligible,
|
|
1680
|
+
suggestedAction: data.suggested_action,
|
|
1106
1681
|
raw: data
|
|
1107
1682
|
};
|
|
1108
1683
|
}
|
|
@@ -1130,6 +1705,54 @@ function companySignalRequestBody(params) {
|
|
|
1130
1705
|
idempotency_key: params.idempotencyKey
|
|
1131
1706
|
});
|
|
1132
1707
|
}
|
|
1708
|
+
function companySignalParamsFromRecoveredRow(row) {
|
|
1709
|
+
return {
|
|
1710
|
+
companyDomain: row.company?.domain ?? row.company_domain,
|
|
1711
|
+
companyName: row.company?.name ?? row.company_name,
|
|
1712
|
+
signalRunId: row.signal_run_id ?? row.run_id
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
var COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS = [
|
|
1716
|
+
"research_prompt",
|
|
1717
|
+
"signal_types",
|
|
1718
|
+
"target_signal_count",
|
|
1719
|
+
"lookback_days",
|
|
1720
|
+
"online",
|
|
1721
|
+
"enable_deep_search",
|
|
1722
|
+
"search_mode",
|
|
1723
|
+
"include_summary",
|
|
1724
|
+
"enable_outreach_intelligence",
|
|
1725
|
+
"enable_predictive_intelligence",
|
|
1726
|
+
"skip_cache",
|
|
1727
|
+
"include_candidate_evidence"
|
|
1728
|
+
];
|
|
1729
|
+
function companySignalLargeBatchSubmission(requests) {
|
|
1730
|
+
if (requests.length <= 25) return null;
|
|
1731
|
+
if (requests.some(
|
|
1732
|
+
(request) => typeof request.signal_run_id === "string" && request.signal_run_id.trim() || typeof request.idempotency_key === "string" && request.idempotency_key.trim()
|
|
1733
|
+
)) {
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
const submissionFields = Object.fromEntries(
|
|
1737
|
+
COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => requests[0]?.[field] !== void 0).map((field) => [field, requests[0][field]])
|
|
1738
|
+
);
|
|
1739
|
+
const controlKey = JSON.stringify(submissionFields);
|
|
1740
|
+
for (const request of requests) {
|
|
1741
|
+
const requestControls = Object.fromEntries(
|
|
1742
|
+
COMPANY_SIGNAL_LARGE_BATCH_CONTROL_FIELDS.filter((field) => request[field] !== void 0).map((field) => [field, request[field]])
|
|
1743
|
+
);
|
|
1744
|
+
if (JSON.stringify(requestControls) !== controlKey) return null;
|
|
1745
|
+
const hasIdentity = typeof request.company_domain === "string" && request.company_domain.trim() || typeof request.company_name === "string" && request.company_name.trim();
|
|
1746
|
+
if (!hasIdentity) return null;
|
|
1747
|
+
}
|
|
1748
|
+
return {
|
|
1749
|
+
requests: requests.map((request) => compact({
|
|
1750
|
+
company_domain: request.company_domain,
|
|
1751
|
+
company_name: request.company_name
|
|
1752
|
+
})),
|
|
1753
|
+
submissionFields
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1133
1756
|
function normalizeCompanySignalResult(params, data) {
|
|
1134
1757
|
const failed = coreProductResponseFailed(data);
|
|
1135
1758
|
const rawSignals = failed ? [] : data.signals ?? data.signal_feed ?? [];
|
|
@@ -1380,6 +2003,9 @@ function dedupeExactBatchItems(items, keyForItem) {
|
|
|
1380
2003
|
function normalizedBatchText(value) {
|
|
1381
2004
|
return typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, " ") : "";
|
|
1382
2005
|
}
|
|
2006
|
+
function normalizedBatchIdempotencyKey(value) {
|
|
2007
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2008
|
+
}
|
|
1383
2009
|
function normalizedBatchDomain(value) {
|
|
1384
2010
|
return normalizedBatchText(value).replace(/^https?:\/\//, "").replace(/^www\./, "").split(/[/?#]/, 1)[0].replace(/\.+$/, "");
|
|
1385
2011
|
}
|
|
@@ -1403,7 +2029,8 @@ function coreProductBatchRequestKey(path2, request) {
|
|
|
1403
2029
|
normalizedBatchDomain(request.company_domain),
|
|
1404
2030
|
normalizedBatchLinkedIn(request.linkedin_url),
|
|
1405
2031
|
normalizedBatchText(request.company_name),
|
|
1406
|
-
request.skip_cache === true || request.bypass_cache === true
|
|
2032
|
+
request.skip_cache === true || request.bypass_cache === true,
|
|
2033
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1407
2034
|
]);
|
|
1408
2035
|
}
|
|
1409
2036
|
if (path2 === "api/v1/verify-email") {
|
|
@@ -1411,7 +2038,8 @@ function coreProductBatchRequestKey(path2, request) {
|
|
|
1411
2038
|
normalizedBatchText(request.email),
|
|
1412
2039
|
request.skip_cache === true || request.bypass_cache === true,
|
|
1413
2040
|
request.skip_catch_all_verification === true,
|
|
1414
|
-
request.skip_esp_check === true
|
|
2041
|
+
request.skip_esp_check === true,
|
|
2042
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1415
2043
|
]);
|
|
1416
2044
|
}
|
|
1417
2045
|
if (path2 === "api/v1/company-signals") {
|
|
@@ -1433,7 +2061,8 @@ function coreProductBatchRequestKey(path2, request) {
|
|
|
1433
2061
|
normalizedBatchText(request.search_mode || "balanced"),
|
|
1434
2062
|
request.enable_outreach_intelligence === true,
|
|
1435
2063
|
request.enable_predictive_intelligence === true,
|
|
1436
|
-
request.skip_cache === true || request.bypass_cache === true
|
|
2064
|
+
request.skip_cache === true || request.bypass_cache === true,
|
|
2065
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1437
2066
|
]);
|
|
1438
2067
|
}
|
|
1439
2068
|
return JSON.stringify([
|
|
@@ -1442,9 +2071,11 @@ function coreProductBatchRequestKey(path2, request) {
|
|
|
1442
2071
|
typeof request.title === "string" ? request.title.trim() : "",
|
|
1443
2072
|
typeof request.campaign_offer === "string" ? request.campaign_offer.trim() : "",
|
|
1444
2073
|
typeof request.research_prompt === "string" ? request.research_prompt.trim() : "",
|
|
2074
|
+
Number(request.lookback_days) || null,
|
|
1445
2075
|
typeof request.signal_run_id === "string" ? request.signal_run_id.trim() : "",
|
|
1446
2076
|
request.skip_cache === true,
|
|
1447
|
-
request.enable_deep_search === true
|
|
2077
|
+
request.enable_deep_search === true,
|
|
2078
|
+
normalizedBatchIdempotencyKey(request.idempotency_key)
|
|
1448
2079
|
]);
|
|
1449
2080
|
}
|
|
1450
2081
|
function finalizeCoreBatch(total, startedAt, round, normalize, options) {
|
|
@@ -1482,6 +2113,37 @@ function finalizeCoreBatch(total, startedAt, round, normalize, options) {
|
|
|
1482
2113
|
results: compactedResults
|
|
1483
2114
|
};
|
|
1484
2115
|
}
|
|
2116
|
+
function finalizeRecoveredCoreBatch(total, startedAt, round, normalize, options) {
|
|
2117
|
+
const results = round.map((item) => {
|
|
2118
|
+
const duplicateOf = item.raw ? recoveredDuplicateOf(item.raw) : void 0;
|
|
2119
|
+
if (duplicateOf !== void 0) return { index: item.index, success: true, duplicateOf };
|
|
2120
|
+
if (!item.success || !item.raw) {
|
|
2121
|
+
return batchItemFailure(
|
|
2122
|
+
item.index,
|
|
2123
|
+
item.error || "Signaliz batch item failed",
|
|
2124
|
+
item.raw ?? item
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2127
|
+
try {
|
|
2128
|
+
return { index: item.index, success: true, data: normalize(item) };
|
|
2129
|
+
} catch (error) {
|
|
2130
|
+
return {
|
|
2131
|
+
index: item.index,
|
|
2132
|
+
success: false,
|
|
2133
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
});
|
|
2137
|
+
const compactedResults = compactExactDuplicateResults(results, options?.compactDuplicates === true);
|
|
2138
|
+
const succeeded = compactedResults.filter((item) => item.success).length;
|
|
2139
|
+
return {
|
|
2140
|
+
total,
|
|
2141
|
+
succeeded,
|
|
2142
|
+
failed: total - succeeded,
|
|
2143
|
+
durationMs: Date.now() - startedAt,
|
|
2144
|
+
results: compactedResults
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
1485
2147
|
function compactExactDuplicateResults(results, enabled) {
|
|
1486
2148
|
if (!enabled) return results;
|
|
1487
2149
|
const sourceIndexByRaw = /* @__PURE__ */ new WeakMap();
|
|
@@ -1497,6 +2159,19 @@ function compactExactDuplicateResults(results, enabled) {
|
|
|
1497
2159
|
return item;
|
|
1498
2160
|
});
|
|
1499
2161
|
}
|
|
2162
|
+
function compactKnownDuplicateBatch(batch, sourceIndexByInput, enabled) {
|
|
2163
|
+
if (!enabled) return batch;
|
|
2164
|
+
const canonicalInputByUniqueIndex = /* @__PURE__ */ new Map();
|
|
2165
|
+
sourceIndexByInput.forEach((uniqueIndex, inputIndex) => {
|
|
2166
|
+
if (!canonicalInputByUniqueIndex.has(uniqueIndex)) canonicalInputByUniqueIndex.set(uniqueIndex, inputIndex);
|
|
2167
|
+
});
|
|
2168
|
+
const results = batch.results.map((item, index) => {
|
|
2169
|
+
const canonicalIndex = canonicalInputByUniqueIndex.get(sourceIndexByInput[index]) ?? index;
|
|
2170
|
+
if (canonicalIndex === index || !item.success || !batch.results[canonicalIndex]?.success) return item;
|
|
2171
|
+
return { index, success: true, duplicateOf: canonicalIndex };
|
|
2172
|
+
});
|
|
2173
|
+
return { ...batch, results };
|
|
2174
|
+
}
|
|
1500
2175
|
function signalToCopyRequestBody(params) {
|
|
1501
2176
|
return compact({
|
|
1502
2177
|
company_domain: params.companyDomain,
|
|
@@ -1513,6 +2188,28 @@ function signalToCopyRequestBody(params) {
|
|
|
1513
2188
|
idempotency_key: params.idempotencyKey
|
|
1514
2189
|
});
|
|
1515
2190
|
}
|
|
2191
|
+
function validateLargeAvailableDataSignalCopyBatch(requests) {
|
|
2192
|
+
if (requests.length <= 25) return;
|
|
2193
|
+
const invalidIndex = requests.findIndex(
|
|
2194
|
+
(request) => request.skipCache === true || request.enableDeepSearch === true || Boolean(request.idempotencyKey?.trim()) || request.maxWaitMs !== void 0 || request.pollIntervalMs !== void 0 || request.waitForResult === false || !request.companyDomain?.trim() || !request.personName?.trim() || !request.title?.trim() || !request.campaignOffer?.trim()
|
|
2195
|
+
);
|
|
2196
|
+
if (invalidIndex >= 0) {
|
|
2197
|
+
throw new RangeError(
|
|
2198
|
+
`Signal to Copy batches larger than 25 are available-data-only; row ${invalidIndex} requests fresh/deep/synchronous controls or lacks required recipient fields. Split fresh requests into batches of at most 25.`
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
function availableDataSignalCopyRequestBody(params) {
|
|
2203
|
+
return compact({
|
|
2204
|
+
company_domain: params.companyDomain,
|
|
2205
|
+
person_name: params.personName,
|
|
2206
|
+
title: params.title,
|
|
2207
|
+
campaign_offer: params.campaignOffer,
|
|
2208
|
+
research_prompt: params.researchPrompt,
|
|
2209
|
+
lookback_days: params.lookbackDays,
|
|
2210
|
+
signal_run_id: params.signalRunId
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
1516
2213
|
function validateSignalToCopyParams(params) {
|
|
1517
2214
|
validateCoreProductMaxWaitMs(params.maxWaitMs);
|
|
1518
2215
|
if (params.lookbackDays !== void 0 && (!Number.isInteger(params.lookbackDays) || params.lookbackDays < 1 || params.lookbackDays > 365)) {
|
|
@@ -1646,6 +2343,21 @@ 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 assertRecoverableBatchResultsRetained(status, label, jobId, idempotencyKey) {
|
|
2347
|
+
if (status.results_expired !== true && status.results_cleaned !== true) return;
|
|
2348
|
+
throw new SignalizError({
|
|
2349
|
+
code: "BATCH_RESULTS_EXPIRED",
|
|
2350
|
+
message: `${label} batch ${jobId} completed, but its retained result pages have expired and cannot be recovered. Do not automatically resubmit provider work.`,
|
|
2351
|
+
errorType: "not_found",
|
|
2352
|
+
details: {
|
|
2353
|
+
job_id: jobId,
|
|
2354
|
+
...idempotencyKey ? { idempotency_key: idempotencyKey } : {},
|
|
2355
|
+
suggested_action: "review_before_resubmitting",
|
|
2356
|
+
retry_eligible: false,
|
|
2357
|
+
results_expired: true
|
|
2358
|
+
}
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
1649
2361
|
async function runBatch(items, execute, options) {
|
|
1650
2362
|
validateBatchSize(items);
|
|
1651
2363
|
const concurrency = validatedBatchConcurrency(options);
|