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