@signaliz/sdk 1.0.34 → 1.0.37
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 +9 -4
- package/dist/{chunk-BGVNI5E2.mjs → chunk-WHD7INKU.mjs} +86 -28
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +86 -28
- package/dist/index.mjs +1 -1
- package/dist/mcp-config.js +86 -28
- package/dist/mcp-config.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,6 +72,9 @@ const signalBatch = await signaliz.enrichCompanies(companies, { concurrency: 5 }
|
|
|
72
72
|
const copyBatch = await signaliz.createSignalCopyBatch(copyRequests, { concurrency: 5 });
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
For an ambiguous submission failure, retry with the same
|
|
76
|
+
`idempotencyKey` to recover the original job without duplicate work.
|
|
77
|
+
|
|
75
78
|
Each batch accepts up to 5,000 items. Company Signal Enrichment transparently
|
|
76
79
|
resumes long-running research through the same `/company-signals` endpoint;
|
|
77
80
|
callers never need an internal Trigger.dev status URL. The SDK follows the
|
|
@@ -80,10 +83,12 @@ when a caller explicitly overrides the cadence.
|
|
|
80
83
|
Exact duplicate tasks are single-flighted across each full batch without
|
|
81
84
|
changing input order or result cardinality. Find Email and Verify Email batches
|
|
82
85
|
larger than 25 use one cache-aware recoverable REST job with 500-row result
|
|
83
|
-
pages
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
pages. Uniform Company Signal Enrichment batches larger than 25 use one
|
|
87
|
+
cache-aware recoverable REST job with 25-row result pages; heterogeneous,
|
|
88
|
+
no-wait, and per-run recovery inputs keep the 25-row request path. Signal to
|
|
89
|
+
Copy batches larger than 25 use one recoverable REST job when they can reuse
|
|
90
|
+
available signal data; fresh or deep-search requests keep the 25-row path.
|
|
91
|
+
Signal to Copy also shares company research across copy recipients.
|
|
87
92
|
|
|
88
93
|
Company Signal Enrichment defaults `online` and `enableDeepSearch` to `true`
|
|
89
94
|
unless the caller explicitly disables them. Signal to Copy AI returns three
|
|
@@ -22,16 +22,19 @@ function parseError(status, body) {
|
|
|
22
22
|
details: body.error.details
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
+
const code = typeof body?.error_code === "string" && body.error_code.trim() ? body.error_code.trim() : `HTTP_${status}`;
|
|
25
26
|
return new SignalizError({
|
|
26
|
-
code
|
|
27
|
+
code,
|
|
27
28
|
message: body?.message || body?.error || `Request failed with status ${status}`,
|
|
28
|
-
errorType: mapErrorType(
|
|
29
|
+
errorType: mapErrorType(code, status),
|
|
30
|
+
details: body?.details || (body?.retry_eligible !== void 0 ? { retry_eligible: body.retry_eligible } : void 0)
|
|
29
31
|
});
|
|
30
32
|
}
|
|
31
33
|
function mapErrorType(code, status) {
|
|
32
34
|
if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
|
|
33
35
|
if (code === "VALIDATION_ERROR" || status === 400) return "validation";
|
|
34
36
|
if (code === "NOT_FOUND" || status === 404) return "not_found";
|
|
37
|
+
if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
|
|
35
38
|
if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
|
|
36
39
|
if (code === "INSUFFICIENT_CREDITS" || status === 402) return "insufficient_credits";
|
|
37
40
|
if (code === "TIMEOUT" || status === 504) return "timeout";
|
|
@@ -431,12 +434,21 @@ var Signaliz = class {
|
|
|
431
434
|
validateBatchSize(companies);
|
|
432
435
|
const startedAt = Date.now();
|
|
433
436
|
const results = new Array(companies.length);
|
|
434
|
-
|
|
437
|
+
const initialTasks = companies.map((params, index) => ({
|
|
435
438
|
index,
|
|
436
439
|
request: companySignalRequestBody(params)
|
|
437
440
|
}));
|
|
441
|
+
const recoverableBatch = companySignalRecoverableBatchOptions(companies, initialTasks);
|
|
442
|
+
let pending = initialTasks;
|
|
443
|
+
let firstRound = true;
|
|
438
444
|
while (pending.length > 0) {
|
|
439
|
-
const round = await this.runCoreBatchRound(
|
|
445
|
+
const round = await this.runCoreBatchRound(
|
|
446
|
+
"api/v1/company-signals",
|
|
447
|
+
pending,
|
|
448
|
+
options,
|
|
449
|
+
firstRound ? recoverableBatch : void 0
|
|
450
|
+
);
|
|
451
|
+
firstRound = false;
|
|
440
452
|
const taskByIndex = new Map(pending.map((task) => [task.index, task]));
|
|
441
453
|
const nextPending = [];
|
|
442
454
|
let nextDelayMs = 6e4;
|
|
@@ -521,7 +533,7 @@ var Signaliz = class {
|
|
|
521
533
|
const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
|
|
522
534
|
(params) => params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
|
|
523
535
|
);
|
|
524
|
-
const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
|
|
536
|
+
const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
|
|
525
537
|
const results = requests.map((_, index) => ({
|
|
526
538
|
...uniqueResults[deduplicated.sourceIndexByInput[index]],
|
|
527
539
|
index
|
|
@@ -565,11 +577,14 @@ var Signaliz = class {
|
|
|
565
577
|
}
|
|
566
578
|
return uniqueResults;
|
|
567
579
|
}
|
|
568
|
-
async runAvailableSignalCopyBatchJob(requests) {
|
|
580
|
+
async runAvailableSignalCopyBatchJob(requests, options) {
|
|
569
581
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
570
582
|
"api/v1/signal-to-copy",
|
|
571
583
|
requests.map(signalToCopyRequestBody),
|
|
572
|
-
"Signal to Copy"
|
|
584
|
+
"Signal to Copy",
|
|
585
|
+
500,
|
|
586
|
+
20 * 6e4,
|
|
587
|
+
options?.idempotencyKey
|
|
573
588
|
);
|
|
574
589
|
const results = new Array(requests.length);
|
|
575
590
|
for (const row of rows) {
|
|
@@ -578,19 +593,28 @@ var Signaliz = class {
|
|
|
578
593
|
}
|
|
579
594
|
return results;
|
|
580
595
|
}
|
|
581
|
-
async runRecoverableCoreBatchJob(path, requests, label) {
|
|
596
|
+
async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
|
|
582
597
|
const startedAt = Date.now();
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
598
|
+
const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
|
|
599
|
+
let submission;
|
|
600
|
+
try {
|
|
601
|
+
submission = await this.client.post(path, {
|
|
602
|
+
requests,
|
|
603
|
+
idempotency_key: submissionIdempotencyKey
|
|
604
|
+
});
|
|
605
|
+
} catch (error) {
|
|
606
|
+
if (error instanceof SignalizError && !error.isRetryable) throw error;
|
|
607
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
608
|
+
throw new Error(
|
|
609
|
+
`${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`
|
|
610
|
+
);
|
|
611
|
+
}
|
|
588
612
|
const jobId = String(submission.job_id || "").trim();
|
|
589
613
|
if (!jobId) throw new Error(`${label} batch did not return a recoverable job_id`);
|
|
590
614
|
let status = await this.client.post(path, {
|
|
591
615
|
job_id: jobId,
|
|
592
616
|
page: 1,
|
|
593
|
-
page_size:
|
|
617
|
+
page_size: pageSize
|
|
594
618
|
});
|
|
595
619
|
while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
|
|
596
620
|
if (Date.now() - startedAt >= maxWaitMs) {
|
|
@@ -601,21 +625,28 @@ var Signaliz = class {
|
|
|
601
625
|
status = await this.client.post(path, {
|
|
602
626
|
job_id: jobId,
|
|
603
627
|
page: 1,
|
|
604
|
-
page_size:
|
|
628
|
+
page_size: pageSize
|
|
605
629
|
});
|
|
606
630
|
}
|
|
607
631
|
const totalPages = Math.max(1, Number(status.total_pages || 1));
|
|
608
632
|
const pages = [Array.isArray(status.results) ? status.results : []];
|
|
609
633
|
if (totalPages > 1) {
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
634
|
+
const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
|
|
635
|
+
const remainingPages = await runBatch(
|
|
636
|
+
pageNumbers,
|
|
637
|
+
async (page) => await this.client.post(path, {
|
|
613
638
|
job_id: jobId,
|
|
614
|
-
page
|
|
615
|
-
page_size:
|
|
616
|
-
})
|
|
617
|
-
|
|
618
|
-
|
|
639
|
+
page,
|
|
640
|
+
page_size: pageSize
|
|
641
|
+
}),
|
|
642
|
+
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
643
|
+
);
|
|
644
|
+
for (const page of remainingPages.results) {
|
|
645
|
+
if (!page.success || !page.data) {
|
|
646
|
+
throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
|
|
647
|
+
}
|
|
648
|
+
pages.push(Array.isArray(page.data.results) ? page.data.results : []);
|
|
649
|
+
}
|
|
619
650
|
}
|
|
620
651
|
const rows = pages.flat();
|
|
621
652
|
if (rows.length !== requests.length) {
|
|
@@ -694,22 +725,25 @@ var Signaliz = class {
|
|
|
694
725
|
}
|
|
695
726
|
return results;
|
|
696
727
|
}
|
|
697
|
-
async runCoreBatchRound(path, tasks, options) {
|
|
728
|
+
async runCoreBatchRound(path, tasks, options, companyRecoverableBatch) {
|
|
698
729
|
const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
|
|
699
730
|
const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
|
|
700
731
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
701
|
-
|
|
702
|
-
|
|
732
|
+
const recoverableBatch = path === "api/v1/find-email" ? { label: "Find Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : path === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : companyRecoverableBatch;
|
|
733
|
+
if (recoverableBatch && uniqueTasks.length > 25) {
|
|
703
734
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
704
735
|
path,
|
|
705
736
|
uniqueTasks.map((task) => task.request),
|
|
706
|
-
label
|
|
737
|
+
recoverableBatch.label,
|
|
738
|
+
recoverableBatch.pageSize,
|
|
739
|
+
recoverableBatch.maxWaitMs,
|
|
740
|
+
options?.idempotencyKey
|
|
707
741
|
);
|
|
708
742
|
const uniqueResults2 = rows.map((raw) => ({
|
|
709
743
|
index: uniqueTasks[Number(raw.index)].index,
|
|
710
744
|
success: !recoverableJobRowFailed(raw),
|
|
711
745
|
raw,
|
|
712
|
-
error: recoverableJobRowFailed(raw) ? raw.error || raw._error || raw.message || raw.failure_reason || `${label} item failed` : void 0
|
|
746
|
+
error: recoverableJobRowFailed(raw) ? raw.error || raw._error || raw.message || raw.failure_reason || `${recoverableBatch.label} item failed` : void 0
|
|
713
747
|
}));
|
|
714
748
|
return tasks.map((task, index) => ({
|
|
715
749
|
...uniqueResults2[deduplicated.sourceIndexByInput[index]],
|
|
@@ -851,6 +885,7 @@ function companySignalRequestBody(params) {
|
|
|
851
885
|
lookback_days: params.lookbackDays,
|
|
852
886
|
online,
|
|
853
887
|
enable_deep_search: params.enableDeepSearch ?? online,
|
|
888
|
+
include_summary: params.includeSummary,
|
|
854
889
|
enable_outreach_intelligence: params.enableOutreachIntelligence,
|
|
855
890
|
enable_predictive_intelligence: params.enablePredictiveIntelligence,
|
|
856
891
|
skip_cache: params.skipCache,
|
|
@@ -860,6 +895,29 @@ function companySignalRequestBody(params) {
|
|
|
860
895
|
max_wait_ms: params.maxWaitMs
|
|
861
896
|
});
|
|
862
897
|
}
|
|
898
|
+
function companySignalRecoverableBatchOptions(companies, tasks) {
|
|
899
|
+
if (companies.length <= 25 || companies.some((params) => params.waitForResult === false || Boolean(params.signalRunId))) {
|
|
900
|
+
return void 0;
|
|
901
|
+
}
|
|
902
|
+
const maxWaitMs = Math.max(1e3, companies[0].maxWaitMs ?? 20 * 6e4);
|
|
903
|
+
if (companies.some((params) => Math.max(1e3, params.maxWaitMs ?? 20 * 6e4) !== maxWaitMs)) {
|
|
904
|
+
return void 0;
|
|
905
|
+
}
|
|
906
|
+
const configKey = (request) => {
|
|
907
|
+
const {
|
|
908
|
+
company_domain: _companyDomain,
|
|
909
|
+
company_name: _companyName,
|
|
910
|
+
signal_run_id: _signalRunId,
|
|
911
|
+
wait_for_result: _waitForResult,
|
|
912
|
+
max_wait_ms: _maxWaitMs,
|
|
913
|
+
...config
|
|
914
|
+
} = request;
|
|
915
|
+
return JSON.stringify(config);
|
|
916
|
+
};
|
|
917
|
+
const sharedConfig = configKey(tasks[0].request);
|
|
918
|
+
if (tasks.some((task) => configKey(task.request) !== sharedConfig)) return void 0;
|
|
919
|
+
return { label: "Company Signals", pageSize: 25, maxWaitMs };
|
|
920
|
+
}
|
|
863
921
|
function normalizeCompanySignalResult(params, data) {
|
|
864
922
|
return {
|
|
865
923
|
success: data.success ?? true,
|
package/dist/index.d.mts
CHANGED
|
@@ -9,6 +9,8 @@ interface SignalizConfig {
|
|
|
9
9
|
interface BatchOptions {
|
|
10
10
|
/** Maximum simultaneous API requests. Defaults to 10; allowed range is 1-50. */
|
|
11
11
|
concurrency?: number;
|
|
12
|
+
/** Stable key for safely retrying an ambiguous recoverable batch submission. */
|
|
13
|
+
idempotencyKey?: string;
|
|
12
14
|
}
|
|
13
15
|
interface BatchItemResult<T> {
|
|
14
16
|
index: number;
|
|
@@ -88,6 +90,7 @@ interface CompanySignalEnrichmentParams {
|
|
|
88
90
|
lookbackDays?: number;
|
|
89
91
|
online?: boolean;
|
|
90
92
|
enableDeepSearch?: boolean;
|
|
93
|
+
includeSummary?: boolean;
|
|
91
94
|
enableOutreachIntelligence?: boolean;
|
|
92
95
|
enablePredictiveIntelligence?: boolean;
|
|
93
96
|
skipCache?: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ interface SignalizConfig {
|
|
|
9
9
|
interface BatchOptions {
|
|
10
10
|
/** Maximum simultaneous API requests. Defaults to 10; allowed range is 1-50. */
|
|
11
11
|
concurrency?: number;
|
|
12
|
+
/** Stable key for safely retrying an ambiguous recoverable batch submission. */
|
|
13
|
+
idempotencyKey?: string;
|
|
12
14
|
}
|
|
13
15
|
interface BatchItemResult<T> {
|
|
14
16
|
index: number;
|
|
@@ -88,6 +90,7 @@ interface CompanySignalEnrichmentParams {
|
|
|
88
90
|
lookbackDays?: number;
|
|
89
91
|
online?: boolean;
|
|
90
92
|
enableDeepSearch?: boolean;
|
|
93
|
+
includeSummary?: boolean;
|
|
91
94
|
enableOutreachIntelligence?: boolean;
|
|
92
95
|
enablePredictiveIntelligence?: boolean;
|
|
93
96
|
skipCache?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -49,16 +49,19 @@ function parseError(status, body) {
|
|
|
49
49
|
details: body.error.details
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
+
const code = typeof body?.error_code === "string" && body.error_code.trim() ? body.error_code.trim() : `HTTP_${status}`;
|
|
52
53
|
return new SignalizError({
|
|
53
|
-
code
|
|
54
|
+
code,
|
|
54
55
|
message: body?.message || body?.error || `Request failed with status ${status}`,
|
|
55
|
-
errorType: mapErrorType(
|
|
56
|
+
errorType: mapErrorType(code, status),
|
|
57
|
+
details: body?.details || (body?.retry_eligible !== void 0 ? { retry_eligible: body.retry_eligible } : void 0)
|
|
56
58
|
});
|
|
57
59
|
}
|
|
58
60
|
function mapErrorType(code, status) {
|
|
59
61
|
if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
|
|
60
62
|
if (code === "VALIDATION_ERROR" || status === 400) return "validation";
|
|
61
63
|
if (code === "NOT_FOUND" || status === 404) return "not_found";
|
|
64
|
+
if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
|
|
62
65
|
if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
|
|
63
66
|
if (code === "INSUFFICIENT_CREDITS" || status === 402) return "insufficient_credits";
|
|
64
67
|
if (code === "TIMEOUT" || status === 504) return "timeout";
|
|
@@ -458,12 +461,21 @@ var Signaliz = class {
|
|
|
458
461
|
validateBatchSize(companies);
|
|
459
462
|
const startedAt = Date.now();
|
|
460
463
|
const results = new Array(companies.length);
|
|
461
|
-
|
|
464
|
+
const initialTasks = companies.map((params, index) => ({
|
|
462
465
|
index,
|
|
463
466
|
request: companySignalRequestBody(params)
|
|
464
467
|
}));
|
|
468
|
+
const recoverableBatch = companySignalRecoverableBatchOptions(companies, initialTasks);
|
|
469
|
+
let pending = initialTasks;
|
|
470
|
+
let firstRound = true;
|
|
465
471
|
while (pending.length > 0) {
|
|
466
|
-
const round = await this.runCoreBatchRound(
|
|
472
|
+
const round = await this.runCoreBatchRound(
|
|
473
|
+
"api/v1/company-signals",
|
|
474
|
+
pending,
|
|
475
|
+
options,
|
|
476
|
+
firstRound ? recoverableBatch : void 0
|
|
477
|
+
);
|
|
478
|
+
firstRound = false;
|
|
467
479
|
const taskByIndex = new Map(pending.map((task) => [task.index, task]));
|
|
468
480
|
const nextPending = [];
|
|
469
481
|
let nextDelayMs = 6e4;
|
|
@@ -548,7 +560,7 @@ var Signaliz = class {
|
|
|
548
560
|
const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
|
|
549
561
|
(params) => params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
|
|
550
562
|
);
|
|
551
|
-
const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
|
|
563
|
+
const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
|
|
552
564
|
const results = requests.map((_, index) => ({
|
|
553
565
|
...uniqueResults[deduplicated.sourceIndexByInput[index]],
|
|
554
566
|
index
|
|
@@ -592,11 +604,14 @@ var Signaliz = class {
|
|
|
592
604
|
}
|
|
593
605
|
return uniqueResults;
|
|
594
606
|
}
|
|
595
|
-
async runAvailableSignalCopyBatchJob(requests) {
|
|
607
|
+
async runAvailableSignalCopyBatchJob(requests, options) {
|
|
596
608
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
597
609
|
"api/v1/signal-to-copy",
|
|
598
610
|
requests.map(signalToCopyRequestBody),
|
|
599
|
-
"Signal to Copy"
|
|
611
|
+
"Signal to Copy",
|
|
612
|
+
500,
|
|
613
|
+
20 * 6e4,
|
|
614
|
+
options?.idempotencyKey
|
|
600
615
|
);
|
|
601
616
|
const results = new Array(requests.length);
|
|
602
617
|
for (const row of rows) {
|
|
@@ -605,19 +620,28 @@ var Signaliz = class {
|
|
|
605
620
|
}
|
|
606
621
|
return results;
|
|
607
622
|
}
|
|
608
|
-
async runRecoverableCoreBatchJob(path, requests, label) {
|
|
623
|
+
async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
|
|
609
624
|
const startedAt = Date.now();
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
625
|
+
const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
|
|
626
|
+
let submission;
|
|
627
|
+
try {
|
|
628
|
+
submission = await this.client.post(path, {
|
|
629
|
+
requests,
|
|
630
|
+
idempotency_key: submissionIdempotencyKey
|
|
631
|
+
});
|
|
632
|
+
} catch (error) {
|
|
633
|
+
if (error instanceof SignalizError && !error.isRetryable) throw error;
|
|
634
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
635
|
+
throw new Error(
|
|
636
|
+
`${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`
|
|
637
|
+
);
|
|
638
|
+
}
|
|
615
639
|
const jobId = String(submission.job_id || "").trim();
|
|
616
640
|
if (!jobId) throw new Error(`${label} batch did not return a recoverable job_id`);
|
|
617
641
|
let status = await this.client.post(path, {
|
|
618
642
|
job_id: jobId,
|
|
619
643
|
page: 1,
|
|
620
|
-
page_size:
|
|
644
|
+
page_size: pageSize
|
|
621
645
|
});
|
|
622
646
|
while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
|
|
623
647
|
if (Date.now() - startedAt >= maxWaitMs) {
|
|
@@ -628,21 +652,28 @@ var Signaliz = class {
|
|
|
628
652
|
status = await this.client.post(path, {
|
|
629
653
|
job_id: jobId,
|
|
630
654
|
page: 1,
|
|
631
|
-
page_size:
|
|
655
|
+
page_size: pageSize
|
|
632
656
|
});
|
|
633
657
|
}
|
|
634
658
|
const totalPages = Math.max(1, Number(status.total_pages || 1));
|
|
635
659
|
const pages = [Array.isArray(status.results) ? status.results : []];
|
|
636
660
|
if (totalPages > 1) {
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
661
|
+
const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
|
|
662
|
+
const remainingPages = await runBatch(
|
|
663
|
+
pageNumbers,
|
|
664
|
+
async (page) => await this.client.post(path, {
|
|
640
665
|
job_id: jobId,
|
|
641
|
-
page
|
|
642
|
-
page_size:
|
|
643
|
-
})
|
|
644
|
-
|
|
645
|
-
|
|
666
|
+
page,
|
|
667
|
+
page_size: pageSize
|
|
668
|
+
}),
|
|
669
|
+
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
670
|
+
);
|
|
671
|
+
for (const page of remainingPages.results) {
|
|
672
|
+
if (!page.success || !page.data) {
|
|
673
|
+
throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
|
|
674
|
+
}
|
|
675
|
+
pages.push(Array.isArray(page.data.results) ? page.data.results : []);
|
|
676
|
+
}
|
|
646
677
|
}
|
|
647
678
|
const rows = pages.flat();
|
|
648
679
|
if (rows.length !== requests.length) {
|
|
@@ -721,22 +752,25 @@ var Signaliz = class {
|
|
|
721
752
|
}
|
|
722
753
|
return results;
|
|
723
754
|
}
|
|
724
|
-
async runCoreBatchRound(path, tasks, options) {
|
|
755
|
+
async runCoreBatchRound(path, tasks, options, companyRecoverableBatch) {
|
|
725
756
|
const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
|
|
726
757
|
const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
|
|
727
758
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
728
|
-
|
|
729
|
-
|
|
759
|
+
const recoverableBatch = path === "api/v1/find-email" ? { label: "Find Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : path === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : companyRecoverableBatch;
|
|
760
|
+
if (recoverableBatch && uniqueTasks.length > 25) {
|
|
730
761
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
731
762
|
path,
|
|
732
763
|
uniqueTasks.map((task) => task.request),
|
|
733
|
-
label
|
|
764
|
+
recoverableBatch.label,
|
|
765
|
+
recoverableBatch.pageSize,
|
|
766
|
+
recoverableBatch.maxWaitMs,
|
|
767
|
+
options?.idempotencyKey
|
|
734
768
|
);
|
|
735
769
|
const uniqueResults2 = rows.map((raw) => ({
|
|
736
770
|
index: uniqueTasks[Number(raw.index)].index,
|
|
737
771
|
success: !recoverableJobRowFailed(raw),
|
|
738
772
|
raw,
|
|
739
|
-
error: recoverableJobRowFailed(raw) ? raw.error || raw._error || raw.message || raw.failure_reason || `${label} item failed` : void 0
|
|
773
|
+
error: recoverableJobRowFailed(raw) ? raw.error || raw._error || raw.message || raw.failure_reason || `${recoverableBatch.label} item failed` : void 0
|
|
740
774
|
}));
|
|
741
775
|
return tasks.map((task, index) => ({
|
|
742
776
|
...uniqueResults2[deduplicated.sourceIndexByInput[index]],
|
|
@@ -878,6 +912,7 @@ function companySignalRequestBody(params) {
|
|
|
878
912
|
lookback_days: params.lookbackDays,
|
|
879
913
|
online,
|
|
880
914
|
enable_deep_search: params.enableDeepSearch ?? online,
|
|
915
|
+
include_summary: params.includeSummary,
|
|
881
916
|
enable_outreach_intelligence: params.enableOutreachIntelligence,
|
|
882
917
|
enable_predictive_intelligence: params.enablePredictiveIntelligence,
|
|
883
918
|
skip_cache: params.skipCache,
|
|
@@ -887,6 +922,29 @@ function companySignalRequestBody(params) {
|
|
|
887
922
|
max_wait_ms: params.maxWaitMs
|
|
888
923
|
});
|
|
889
924
|
}
|
|
925
|
+
function companySignalRecoverableBatchOptions(companies, tasks) {
|
|
926
|
+
if (companies.length <= 25 || companies.some((params) => params.waitForResult === false || Boolean(params.signalRunId))) {
|
|
927
|
+
return void 0;
|
|
928
|
+
}
|
|
929
|
+
const maxWaitMs = Math.max(1e3, companies[0].maxWaitMs ?? 20 * 6e4);
|
|
930
|
+
if (companies.some((params) => Math.max(1e3, params.maxWaitMs ?? 20 * 6e4) !== maxWaitMs)) {
|
|
931
|
+
return void 0;
|
|
932
|
+
}
|
|
933
|
+
const configKey = (request) => {
|
|
934
|
+
const {
|
|
935
|
+
company_domain: _companyDomain,
|
|
936
|
+
company_name: _companyName,
|
|
937
|
+
signal_run_id: _signalRunId,
|
|
938
|
+
wait_for_result: _waitForResult,
|
|
939
|
+
max_wait_ms: _maxWaitMs,
|
|
940
|
+
...config
|
|
941
|
+
} = request;
|
|
942
|
+
return JSON.stringify(config);
|
|
943
|
+
};
|
|
944
|
+
const sharedConfig = configKey(tasks[0].request);
|
|
945
|
+
if (tasks.some((task) => configKey(task.request) !== sharedConfig)) return void 0;
|
|
946
|
+
return { label: "Company Signals", pageSize: 25, maxWaitMs };
|
|
947
|
+
}
|
|
890
948
|
function normalizeCompanySignalResult(params, data) {
|
|
891
949
|
return {
|
|
892
950
|
success: data.success ?? true,
|
package/dist/index.mjs
CHANGED
package/dist/mcp-config.js
CHANGED
|
@@ -53,16 +53,19 @@ function parseError(status, body) {
|
|
|
53
53
|
details: body.error.details
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
+
const code = typeof body?.error_code === "string" && body.error_code.trim() ? body.error_code.trim() : `HTTP_${status}`;
|
|
56
57
|
return new SignalizError({
|
|
57
|
-
code
|
|
58
|
+
code,
|
|
58
59
|
message: body?.message || body?.error || `Request failed with status ${status}`,
|
|
59
|
-
errorType: mapErrorType(
|
|
60
|
+
errorType: mapErrorType(code, status),
|
|
61
|
+
details: body?.details || (body?.retry_eligible !== void 0 ? { retry_eligible: body.retry_eligible } : void 0)
|
|
60
62
|
});
|
|
61
63
|
}
|
|
62
64
|
function mapErrorType(code, status) {
|
|
63
65
|
if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
|
|
64
66
|
if (code === "VALIDATION_ERROR" || status === 400) return "validation";
|
|
65
67
|
if (code === "NOT_FOUND" || status === 404) return "not_found";
|
|
68
|
+
if (code === "NO_SUPPORTED_SIGNAL") return "not_found";
|
|
66
69
|
if (code === "AUTH_EXPIRED" || status === 401) return "auth_expired";
|
|
67
70
|
if (code === "INSUFFICIENT_CREDITS" || status === 402) return "insufficient_credits";
|
|
68
71
|
if (code === "TIMEOUT" || status === 504) return "timeout";
|
|
@@ -462,12 +465,21 @@ var Signaliz = class {
|
|
|
462
465
|
validateBatchSize(companies);
|
|
463
466
|
const startedAt = Date.now();
|
|
464
467
|
const results = new Array(companies.length);
|
|
465
|
-
|
|
468
|
+
const initialTasks = companies.map((params, index) => ({
|
|
466
469
|
index,
|
|
467
470
|
request: companySignalRequestBody(params)
|
|
468
471
|
}));
|
|
472
|
+
const recoverableBatch = companySignalRecoverableBatchOptions(companies, initialTasks);
|
|
473
|
+
let pending = initialTasks;
|
|
474
|
+
let firstRound = true;
|
|
469
475
|
while (pending.length > 0) {
|
|
470
|
-
const round = await this.runCoreBatchRound(
|
|
476
|
+
const round = await this.runCoreBatchRound(
|
|
477
|
+
"api/v1/company-signals",
|
|
478
|
+
pending,
|
|
479
|
+
options,
|
|
480
|
+
firstRound ? recoverableBatch : void 0
|
|
481
|
+
);
|
|
482
|
+
firstRound = false;
|
|
471
483
|
const taskByIndex = new Map(pending.map((task) => [task.index, task]));
|
|
472
484
|
const nextPending = [];
|
|
473
485
|
let nextDelayMs = 6e4;
|
|
@@ -552,7 +564,7 @@ var Signaliz = class {
|
|
|
552
564
|
const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
|
|
553
565
|
(params) => params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
|
|
554
566
|
);
|
|
555
|
-
const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
|
|
567
|
+
const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
|
|
556
568
|
const results = requests.map((_, index) => ({
|
|
557
569
|
...uniqueResults[deduplicated.sourceIndexByInput[index]],
|
|
558
570
|
index
|
|
@@ -596,11 +608,14 @@ var Signaliz = class {
|
|
|
596
608
|
}
|
|
597
609
|
return uniqueResults;
|
|
598
610
|
}
|
|
599
|
-
async runAvailableSignalCopyBatchJob(requests) {
|
|
611
|
+
async runAvailableSignalCopyBatchJob(requests, options) {
|
|
600
612
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
601
613
|
"api/v1/signal-to-copy",
|
|
602
614
|
requests.map(signalToCopyRequestBody),
|
|
603
|
-
"Signal to Copy"
|
|
615
|
+
"Signal to Copy",
|
|
616
|
+
500,
|
|
617
|
+
20 * 6e4,
|
|
618
|
+
options?.idempotencyKey
|
|
604
619
|
);
|
|
605
620
|
const results = new Array(requests.length);
|
|
606
621
|
for (const row of rows) {
|
|
@@ -609,19 +624,28 @@ var Signaliz = class {
|
|
|
609
624
|
}
|
|
610
625
|
return results;
|
|
611
626
|
}
|
|
612
|
-
async runRecoverableCoreBatchJob(path2, requests, label) {
|
|
627
|
+
async runRecoverableCoreBatchJob(path2, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
|
|
613
628
|
const startedAt = Date.now();
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
629
|
+
const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
|
|
630
|
+
let submission;
|
|
631
|
+
try {
|
|
632
|
+
submission = await this.client.post(path2, {
|
|
633
|
+
requests,
|
|
634
|
+
idempotency_key: submissionIdempotencyKey
|
|
635
|
+
});
|
|
636
|
+
} catch (error) {
|
|
637
|
+
if (error instanceof SignalizError && !error.isRetryable) throw error;
|
|
638
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
639
|
+
throw new Error(
|
|
640
|
+
`${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
619
643
|
const jobId = String(submission.job_id || "").trim();
|
|
620
644
|
if (!jobId) throw new Error(`${label} batch did not return a recoverable job_id`);
|
|
621
645
|
let status = await this.client.post(path2, {
|
|
622
646
|
job_id: jobId,
|
|
623
647
|
page: 1,
|
|
624
|
-
page_size:
|
|
648
|
+
page_size: pageSize
|
|
625
649
|
});
|
|
626
650
|
while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
|
|
627
651
|
if (Date.now() - startedAt >= maxWaitMs) {
|
|
@@ -632,21 +656,28 @@ var Signaliz = class {
|
|
|
632
656
|
status = await this.client.post(path2, {
|
|
633
657
|
job_id: jobId,
|
|
634
658
|
page: 1,
|
|
635
|
-
page_size:
|
|
659
|
+
page_size: pageSize
|
|
636
660
|
});
|
|
637
661
|
}
|
|
638
662
|
const totalPages = Math.max(1, Number(status.total_pages || 1));
|
|
639
663
|
const pages = [Array.isArray(status.results) ? status.results : []];
|
|
640
664
|
if (totalPages > 1) {
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
665
|
+
const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
|
|
666
|
+
const remainingPages = await runBatch(
|
|
667
|
+
pageNumbers,
|
|
668
|
+
async (page) => await this.client.post(path2, {
|
|
644
669
|
job_id: jobId,
|
|
645
|
-
page
|
|
646
|
-
page_size:
|
|
647
|
-
})
|
|
648
|
-
|
|
649
|
-
|
|
670
|
+
page,
|
|
671
|
+
page_size: pageSize
|
|
672
|
+
}),
|
|
673
|
+
{ concurrency: Math.min(10, pageNumbers.length) }
|
|
674
|
+
);
|
|
675
|
+
for (const page of remainingPages.results) {
|
|
676
|
+
if (!page.success || !page.data) {
|
|
677
|
+
throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
|
|
678
|
+
}
|
|
679
|
+
pages.push(Array.isArray(page.data.results) ? page.data.results : []);
|
|
680
|
+
}
|
|
650
681
|
}
|
|
651
682
|
const rows = pages.flat();
|
|
652
683
|
if (rows.length !== requests.length) {
|
|
@@ -725,22 +756,25 @@ var Signaliz = class {
|
|
|
725
756
|
}
|
|
726
757
|
return results;
|
|
727
758
|
}
|
|
728
|
-
async runCoreBatchRound(path2, tasks, options) {
|
|
759
|
+
async runCoreBatchRound(path2, tasks, options, companyRecoverableBatch) {
|
|
729
760
|
const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
|
|
730
761
|
const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
|
|
731
762
|
const uniqueTasks = deduplicated.uniqueItems;
|
|
732
|
-
|
|
733
|
-
|
|
763
|
+
const recoverableBatch = path2 === "api/v1/find-email" ? { label: "Find Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : path2 === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : companyRecoverableBatch;
|
|
764
|
+
if (recoverableBatch && uniqueTasks.length > 25) {
|
|
734
765
|
const rows = await this.runRecoverableCoreBatchJob(
|
|
735
766
|
path2,
|
|
736
767
|
uniqueTasks.map((task) => task.request),
|
|
737
|
-
label
|
|
768
|
+
recoverableBatch.label,
|
|
769
|
+
recoverableBatch.pageSize,
|
|
770
|
+
recoverableBatch.maxWaitMs,
|
|
771
|
+
options?.idempotencyKey
|
|
738
772
|
);
|
|
739
773
|
const uniqueResults2 = rows.map((raw) => ({
|
|
740
774
|
index: uniqueTasks[Number(raw.index)].index,
|
|
741
775
|
success: !recoverableJobRowFailed(raw),
|
|
742
776
|
raw,
|
|
743
|
-
error: recoverableJobRowFailed(raw) ? raw.error || raw._error || raw.message || raw.failure_reason || `${label} item failed` : void 0
|
|
777
|
+
error: recoverableJobRowFailed(raw) ? raw.error || raw._error || raw.message || raw.failure_reason || `${recoverableBatch.label} item failed` : void 0
|
|
744
778
|
}));
|
|
745
779
|
return tasks.map((task, index) => ({
|
|
746
780
|
...uniqueResults2[deduplicated.sourceIndexByInput[index]],
|
|
@@ -882,6 +916,7 @@ function companySignalRequestBody(params) {
|
|
|
882
916
|
lookback_days: params.lookbackDays,
|
|
883
917
|
online,
|
|
884
918
|
enable_deep_search: params.enableDeepSearch ?? online,
|
|
919
|
+
include_summary: params.includeSummary,
|
|
885
920
|
enable_outreach_intelligence: params.enableOutreachIntelligence,
|
|
886
921
|
enable_predictive_intelligence: params.enablePredictiveIntelligence,
|
|
887
922
|
skip_cache: params.skipCache,
|
|
@@ -891,6 +926,29 @@ function companySignalRequestBody(params) {
|
|
|
891
926
|
max_wait_ms: params.maxWaitMs
|
|
892
927
|
});
|
|
893
928
|
}
|
|
929
|
+
function companySignalRecoverableBatchOptions(companies, tasks) {
|
|
930
|
+
if (companies.length <= 25 || companies.some((params) => params.waitForResult === false || Boolean(params.signalRunId))) {
|
|
931
|
+
return void 0;
|
|
932
|
+
}
|
|
933
|
+
const maxWaitMs = Math.max(1e3, companies[0].maxWaitMs ?? 20 * 6e4);
|
|
934
|
+
if (companies.some((params) => Math.max(1e3, params.maxWaitMs ?? 20 * 6e4) !== maxWaitMs)) {
|
|
935
|
+
return void 0;
|
|
936
|
+
}
|
|
937
|
+
const configKey = (request) => {
|
|
938
|
+
const {
|
|
939
|
+
company_domain: _companyDomain,
|
|
940
|
+
company_name: _companyName,
|
|
941
|
+
signal_run_id: _signalRunId,
|
|
942
|
+
wait_for_result: _waitForResult,
|
|
943
|
+
max_wait_ms: _maxWaitMs,
|
|
944
|
+
...config
|
|
945
|
+
} = request;
|
|
946
|
+
return JSON.stringify(config);
|
|
947
|
+
};
|
|
948
|
+
const sharedConfig = configKey(tasks[0].request);
|
|
949
|
+
if (tasks.some((task) => configKey(task.request) !== sharedConfig)) return void 0;
|
|
950
|
+
return { label: "Company Signals", pageSize: 25, maxWaitMs };
|
|
951
|
+
}
|
|
894
952
|
function normalizeCompanySignalResult(params, data) {
|
|
895
953
|
return {
|
|
896
954
|
success: data.success ?? true,
|
package/dist/mcp-config.mjs
CHANGED
package/package.json
CHANGED