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