@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 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; Company Signal Enrichment uses 25-row REST chunks. Signal to Copy batches
84
- larger than 25 use one recoverable REST job when they can reuse available signal
85
- data; fresh or deep-search requests keep the 25-row path. Signal to Copy also
86
- shares company research across copy recipients.
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: `HTTP_${status}`,
27
+ code,
27
28
  message: body?.message || body?.error || `Request failed with status ${status}`,
28
- errorType: mapErrorType(void 0, status)
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
- let pending = companies.map((params, index) => ({
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("api/v1/company-signals", pending, options);
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 maxWaitMs = 20 * 6e4;
584
- const submission = await this.client.post(path, {
585
- requests,
586
- idempotency_key: newBatchIdempotencyKey(label)
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: 500
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: 500
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 remainingPages = await Promise.all(Array.from(
611
- { length: totalPages - 1 },
612
- (_, index) => this.client.post(path, {
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: index + 2,
615
- page_size: 500
616
- })
617
- ));
618
- for (const page of remainingPages) pages.push(Array.isArray(page.results) ? page.results : []);
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
- if ((path === "api/v1/find-email" || path === "api/v1/verify-email") && uniqueTasks.length > 25) {
702
- const label = path === "api/v1/find-email" ? "Find Email" : "Verify Email";
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: `HTTP_${status}`,
54
+ code,
54
55
  message: body?.message || body?.error || `Request failed with status ${status}`,
55
- errorType: mapErrorType(void 0, status)
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
- let pending = companies.map((params, index) => ({
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("api/v1/company-signals", pending, options);
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 maxWaitMs = 20 * 6e4;
611
- const submission = await this.client.post(path, {
612
- requests,
613
- idempotency_key: newBatchIdempotencyKey(label)
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: 500
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: 500
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 remainingPages = await Promise.all(Array.from(
638
- { length: totalPages - 1 },
639
- (_, index) => this.client.post(path, {
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: index + 2,
642
- page_size: 500
643
- })
644
- ));
645
- for (const page of remainingPages) pages.push(Array.isArray(page.results) ? page.results : []);
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
- if ((path === "api/v1/find-email" || path === "api/v1/verify-email") && uniqueTasks.length > 25) {
729
- const label = path === "api/v1/find-email" ? "Find Email" : "Verify Email";
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
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Signaliz,
3
3
  SignalizError
4
- } from "./chunk-BGVNI5E2.mjs";
4
+ } from "./chunk-WHD7INKU.mjs";
5
5
  export {
6
6
  Signaliz,
7
7
  SignalizError
@@ -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: `HTTP_${status}`,
58
+ code,
58
59
  message: body?.message || body?.error || `Request failed with status ${status}`,
59
- errorType: mapErrorType(void 0, status)
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
- let pending = companies.map((params, index) => ({
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("api/v1/company-signals", pending, options);
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 maxWaitMs = 20 * 6e4;
615
- const submission = await this.client.post(path2, {
616
- requests,
617
- idempotency_key: newBatchIdempotencyKey(label)
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: 500
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: 500
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 remainingPages = await Promise.all(Array.from(
642
- { length: totalPages - 1 },
643
- (_, index) => this.client.post(path2, {
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: index + 2,
646
- page_size: 500
647
- })
648
- ));
649
- for (const page of remainingPages) pages.push(Array.isArray(page.results) ? page.results : []);
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
- if ((path2 === "api/v1/find-email" || path2 === "api/v1/verify-email") && uniqueTasks.length > 25) {
733
- const label = path2 === "api/v1/find-email" ? "Find Email" : "Verify Email";
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,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Signaliz
4
- } from "./chunk-BGVNI5E2.mjs";
4
+ } from "./chunk-WHD7INKU.mjs";
5
5
 
6
6
  // src/mcp-config.ts
7
7
  import * as fs from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaliz/sdk",
3
- "version": "1.0.34",
3
+ "version": "1.0.37",
4
4
  "description": "Signaliz SDK for Find Email, Verify Email, Company Signal Enrichment, and Signal to Copy AI.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",