@signaliz/sdk 1.0.33 → 1.0.36

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,18 +72,23 @@ 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
78
81
  server's adaptive polling hints by default and still honors `pollIntervalMs`
79
82
  when a caller explicitly overrides the cadence.
80
83
  Exact duplicate tasks are single-flighted across each full batch without
81
- changing input order or result cardinality. Verify Email batches larger than 25
82
- use one cache-aware recoverable REST job with 500-row result pages; Find Email
83
- and Company Signal Enrichment use 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.
84
+ changing input order or result cardinality. Find Email and Verify Email batches
85
+ larger than 25 use one cache-aware recoverable REST job with 500-row result
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
@@ -431,12 +431,21 @@ var Signaliz = class {
431
431
  validateBatchSize(companies);
432
432
  const startedAt = Date.now();
433
433
  const results = new Array(companies.length);
434
- let pending = companies.map((params, index) => ({
434
+ const initialTasks = companies.map((params, index) => ({
435
435
  index,
436
436
  request: companySignalRequestBody(params)
437
437
  }));
438
+ const recoverableBatch = companySignalRecoverableBatchOptions(companies, initialTasks);
439
+ let pending = initialTasks;
440
+ let firstRound = true;
438
441
  while (pending.length > 0) {
439
- const round = await this.runCoreBatchRound("api/v1/company-signals", pending, options);
442
+ const round = await this.runCoreBatchRound(
443
+ "api/v1/company-signals",
444
+ pending,
445
+ options,
446
+ firstRound ? recoverableBatch : void 0
447
+ );
448
+ firstRound = false;
440
449
  const taskByIndex = new Map(pending.map((task) => [task.index, task]));
441
450
  const nextPending = [];
442
451
  let nextDelayMs = 6e4;
@@ -521,7 +530,7 @@ var Signaliz = class {
521
530
  const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
522
531
  (params) => params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
523
532
  );
524
- const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
533
+ const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
525
534
  const results = requests.map((_, index) => ({
526
535
  ...uniqueResults[deduplicated.sourceIndexByInput[index]],
527
536
  index
@@ -565,11 +574,14 @@ var Signaliz = class {
565
574
  }
566
575
  return uniqueResults;
567
576
  }
568
- async runAvailableSignalCopyBatchJob(requests) {
577
+ async runAvailableSignalCopyBatchJob(requests, options) {
569
578
  const rows = await this.runRecoverableCoreBatchJob(
570
579
  "api/v1/signal-to-copy",
571
580
  requests.map(signalToCopyRequestBody),
572
- "Signal to Copy"
581
+ "Signal to Copy",
582
+ 500,
583
+ 20 * 6e4,
584
+ options?.idempotencyKey
573
585
  );
574
586
  const results = new Array(requests.length);
575
587
  for (const row of rows) {
@@ -578,19 +590,28 @@ var Signaliz = class {
578
590
  }
579
591
  return results;
580
592
  }
581
- async runRecoverableCoreBatchJob(path, requests, label) {
593
+ async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
582
594
  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
- });
595
+ const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
596
+ let submission;
597
+ try {
598
+ submission = await this.client.post(path, {
599
+ requests,
600
+ idempotency_key: submissionIdempotencyKey
601
+ });
602
+ } catch (error) {
603
+ if (error instanceof SignalizError && !error.isRetryable) throw error;
604
+ const message = error instanceof Error ? error.message : String(error);
605
+ throw new Error(
606
+ `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`
607
+ );
608
+ }
588
609
  const jobId = String(submission.job_id || "").trim();
589
610
  if (!jobId) throw new Error(`${label} batch did not return a recoverable job_id`);
590
611
  let status = await this.client.post(path, {
591
612
  job_id: jobId,
592
613
  page: 1,
593
- page_size: 500
614
+ page_size: pageSize
594
615
  });
595
616
  while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
596
617
  if (Date.now() - startedAt >= maxWaitMs) {
@@ -601,21 +622,28 @@ var Signaliz = class {
601
622
  status = await this.client.post(path, {
602
623
  job_id: jobId,
603
624
  page: 1,
604
- page_size: 500
625
+ page_size: pageSize
605
626
  });
606
627
  }
607
628
  const totalPages = Math.max(1, Number(status.total_pages || 1));
608
629
  const pages = [Array.isArray(status.results) ? status.results : []];
609
630
  if (totalPages > 1) {
610
- const remainingPages = await Promise.all(Array.from(
611
- { length: totalPages - 1 },
612
- (_, index) => this.client.post(path, {
631
+ const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
632
+ const remainingPages = await runBatch(
633
+ pageNumbers,
634
+ async (page) => await this.client.post(path, {
613
635
  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 : []);
636
+ page,
637
+ page_size: pageSize
638
+ }),
639
+ { concurrency: Math.min(10, pageNumbers.length) }
640
+ );
641
+ for (const page of remainingPages.results) {
642
+ if (!page.success || !page.data) {
643
+ throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
644
+ }
645
+ pages.push(Array.isArray(page.data.results) ? page.data.results : []);
646
+ }
619
647
  }
620
648
  const rows = pages.flat();
621
649
  if (rows.length !== requests.length) {
@@ -694,21 +722,25 @@ var Signaliz = class {
694
722
  }
695
723
  return results;
696
724
  }
697
- async runCoreBatchRound(path, tasks, options) {
725
+ async runCoreBatchRound(path, tasks, options, companyRecoverableBatch) {
698
726
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
699
727
  const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
700
728
  const uniqueTasks = deduplicated.uniqueItems;
701
- if (path === "api/v1/verify-email" && uniqueTasks.length > 25) {
729
+ 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;
730
+ if (recoverableBatch && uniqueTasks.length > 25) {
702
731
  const rows = await this.runRecoverableCoreBatchJob(
703
732
  path,
704
733
  uniqueTasks.map((task) => task.request),
705
- "Verify Email"
734
+ recoverableBatch.label,
735
+ recoverableBatch.pageSize,
736
+ recoverableBatch.maxWaitMs,
737
+ options?.idempotencyKey
706
738
  );
707
739
  const uniqueResults2 = rows.map((raw) => ({
708
740
  index: uniqueTasks[Number(raw.index)].index,
709
741
  success: !recoverableJobRowFailed(raw),
710
742
  raw,
711
- error: recoverableJobRowFailed(raw) ? raw.error || raw._error || raw.message || raw.failure_reason || "Verify Email item failed" : void 0
743
+ error: recoverableJobRowFailed(raw) ? raw.error || raw._error || raw.message || raw.failure_reason || `${recoverableBatch.label} item failed` : void 0
712
744
  }));
713
745
  return tasks.map((task, index) => ({
714
746
  ...uniqueResults2[deduplicated.sourceIndexByInput[index]],
@@ -773,7 +805,7 @@ var Signaliz = class {
773
805
  }
774
806
  };
775
807
  function recoverableJobRowFailed(row) {
776
- return row.success === false || String(row._status || "").toLowerCase() === "failed";
808
+ return row.success === false || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
777
809
  }
778
810
  function newBatchIdempotencyKey(label) {
779
811
  const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
@@ -850,6 +882,7 @@ function companySignalRequestBody(params) {
850
882
  lookback_days: params.lookbackDays,
851
883
  online,
852
884
  enable_deep_search: params.enableDeepSearch ?? online,
885
+ include_summary: params.includeSummary,
853
886
  enable_outreach_intelligence: params.enableOutreachIntelligence,
854
887
  enable_predictive_intelligence: params.enablePredictiveIntelligence,
855
888
  skip_cache: params.skipCache,
@@ -859,6 +892,29 @@ function companySignalRequestBody(params) {
859
892
  max_wait_ms: params.maxWaitMs
860
893
  });
861
894
  }
895
+ function companySignalRecoverableBatchOptions(companies, tasks) {
896
+ if (companies.length <= 25 || companies.some((params) => params.waitForResult === false || Boolean(params.signalRunId))) {
897
+ return void 0;
898
+ }
899
+ const maxWaitMs = Math.max(1e3, companies[0].maxWaitMs ?? 20 * 6e4);
900
+ if (companies.some((params) => Math.max(1e3, params.maxWaitMs ?? 20 * 6e4) !== maxWaitMs)) {
901
+ return void 0;
902
+ }
903
+ const configKey = (request) => {
904
+ const {
905
+ company_domain: _companyDomain,
906
+ company_name: _companyName,
907
+ signal_run_id: _signalRunId,
908
+ wait_for_result: _waitForResult,
909
+ max_wait_ms: _maxWaitMs,
910
+ ...config
911
+ } = request;
912
+ return JSON.stringify(config);
913
+ };
914
+ const sharedConfig = configKey(tasks[0].request);
915
+ if (tasks.some((task) => configKey(task.request) !== sharedConfig)) return void 0;
916
+ return { label: "Company Signals", pageSize: 25, maxWaitMs };
917
+ }
862
918
  function normalizeCompanySignalResult(params, data) {
863
919
  return {
864
920
  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
@@ -458,12 +458,21 @@ var Signaliz = class {
458
458
  validateBatchSize(companies);
459
459
  const startedAt = Date.now();
460
460
  const results = new Array(companies.length);
461
- let pending = companies.map((params, index) => ({
461
+ const initialTasks = companies.map((params, index) => ({
462
462
  index,
463
463
  request: companySignalRequestBody(params)
464
464
  }));
465
+ const recoverableBatch = companySignalRecoverableBatchOptions(companies, initialTasks);
466
+ let pending = initialTasks;
467
+ let firstRound = true;
465
468
  while (pending.length > 0) {
466
- const round = await this.runCoreBatchRound("api/v1/company-signals", pending, options);
469
+ const round = await this.runCoreBatchRound(
470
+ "api/v1/company-signals",
471
+ pending,
472
+ options,
473
+ firstRound ? recoverableBatch : void 0
474
+ );
475
+ firstRound = false;
467
476
  const taskByIndex = new Map(pending.map((task) => [task.index, task]));
468
477
  const nextPending = [];
469
478
  let nextDelayMs = 6e4;
@@ -548,7 +557,7 @@ var Signaliz = class {
548
557
  const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
549
558
  (params) => params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
550
559
  );
551
- const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
560
+ const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
552
561
  const results = requests.map((_, index) => ({
553
562
  ...uniqueResults[deduplicated.sourceIndexByInput[index]],
554
563
  index
@@ -592,11 +601,14 @@ var Signaliz = class {
592
601
  }
593
602
  return uniqueResults;
594
603
  }
595
- async runAvailableSignalCopyBatchJob(requests) {
604
+ async runAvailableSignalCopyBatchJob(requests, options) {
596
605
  const rows = await this.runRecoverableCoreBatchJob(
597
606
  "api/v1/signal-to-copy",
598
607
  requests.map(signalToCopyRequestBody),
599
- "Signal to Copy"
608
+ "Signal to Copy",
609
+ 500,
610
+ 20 * 6e4,
611
+ options?.idempotencyKey
600
612
  );
601
613
  const results = new Array(requests.length);
602
614
  for (const row of rows) {
@@ -605,19 +617,28 @@ var Signaliz = class {
605
617
  }
606
618
  return results;
607
619
  }
608
- async runRecoverableCoreBatchJob(path, requests, label) {
620
+ async runRecoverableCoreBatchJob(path, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
609
621
  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
- });
622
+ const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
623
+ let submission;
624
+ try {
625
+ submission = await this.client.post(path, {
626
+ requests,
627
+ idempotency_key: submissionIdempotencyKey
628
+ });
629
+ } catch (error) {
630
+ if (error instanceof SignalizError && !error.isRetryable) throw error;
631
+ const message = error instanceof Error ? error.message : String(error);
632
+ throw new Error(
633
+ `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`
634
+ );
635
+ }
615
636
  const jobId = String(submission.job_id || "").trim();
616
637
  if (!jobId) throw new Error(`${label} batch did not return a recoverable job_id`);
617
638
  let status = await this.client.post(path, {
618
639
  job_id: jobId,
619
640
  page: 1,
620
- page_size: 500
641
+ page_size: pageSize
621
642
  });
622
643
  while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
623
644
  if (Date.now() - startedAt >= maxWaitMs) {
@@ -628,21 +649,28 @@ var Signaliz = class {
628
649
  status = await this.client.post(path, {
629
650
  job_id: jobId,
630
651
  page: 1,
631
- page_size: 500
652
+ page_size: pageSize
632
653
  });
633
654
  }
634
655
  const totalPages = Math.max(1, Number(status.total_pages || 1));
635
656
  const pages = [Array.isArray(status.results) ? status.results : []];
636
657
  if (totalPages > 1) {
637
- const remainingPages = await Promise.all(Array.from(
638
- { length: totalPages - 1 },
639
- (_, index) => this.client.post(path, {
658
+ const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
659
+ const remainingPages = await runBatch(
660
+ pageNumbers,
661
+ async (page) => await this.client.post(path, {
640
662
  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 : []);
663
+ page,
664
+ page_size: pageSize
665
+ }),
666
+ { concurrency: Math.min(10, pageNumbers.length) }
667
+ );
668
+ for (const page of remainingPages.results) {
669
+ if (!page.success || !page.data) {
670
+ throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
671
+ }
672
+ pages.push(Array.isArray(page.data.results) ? page.data.results : []);
673
+ }
646
674
  }
647
675
  const rows = pages.flat();
648
676
  if (rows.length !== requests.length) {
@@ -721,21 +749,25 @@ var Signaliz = class {
721
749
  }
722
750
  return results;
723
751
  }
724
- async runCoreBatchRound(path, tasks, options) {
752
+ async runCoreBatchRound(path, tasks, options, companyRecoverableBatch) {
725
753
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
726
754
  const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
727
755
  const uniqueTasks = deduplicated.uniqueItems;
728
- if (path === "api/v1/verify-email" && uniqueTasks.length > 25) {
756
+ 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;
757
+ if (recoverableBatch && uniqueTasks.length > 25) {
729
758
  const rows = await this.runRecoverableCoreBatchJob(
730
759
  path,
731
760
  uniqueTasks.map((task) => task.request),
732
- "Verify Email"
761
+ recoverableBatch.label,
762
+ recoverableBatch.pageSize,
763
+ recoverableBatch.maxWaitMs,
764
+ options?.idempotencyKey
733
765
  );
734
766
  const uniqueResults2 = rows.map((raw) => ({
735
767
  index: uniqueTasks[Number(raw.index)].index,
736
768
  success: !recoverableJobRowFailed(raw),
737
769
  raw,
738
- error: recoverableJobRowFailed(raw) ? raw.error || raw._error || raw.message || raw.failure_reason || "Verify Email item failed" : void 0
770
+ error: recoverableJobRowFailed(raw) ? raw.error || raw._error || raw.message || raw.failure_reason || `${recoverableBatch.label} item failed` : void 0
739
771
  }));
740
772
  return tasks.map((task, index) => ({
741
773
  ...uniqueResults2[deduplicated.sourceIndexByInput[index]],
@@ -800,7 +832,7 @@ var Signaliz = class {
800
832
  }
801
833
  };
802
834
  function recoverableJobRowFailed(row) {
803
- return row.success === false || String(row._status || "").toLowerCase() === "failed";
835
+ return row.success === false || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
804
836
  }
805
837
  function newBatchIdempotencyKey(label) {
806
838
  const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
@@ -877,6 +909,7 @@ function companySignalRequestBody(params) {
877
909
  lookback_days: params.lookbackDays,
878
910
  online,
879
911
  enable_deep_search: params.enableDeepSearch ?? online,
912
+ include_summary: params.includeSummary,
880
913
  enable_outreach_intelligence: params.enableOutreachIntelligence,
881
914
  enable_predictive_intelligence: params.enablePredictiveIntelligence,
882
915
  skip_cache: params.skipCache,
@@ -886,6 +919,29 @@ function companySignalRequestBody(params) {
886
919
  max_wait_ms: params.maxWaitMs
887
920
  });
888
921
  }
922
+ function companySignalRecoverableBatchOptions(companies, tasks) {
923
+ if (companies.length <= 25 || companies.some((params) => params.waitForResult === false || Boolean(params.signalRunId))) {
924
+ return void 0;
925
+ }
926
+ const maxWaitMs = Math.max(1e3, companies[0].maxWaitMs ?? 20 * 6e4);
927
+ if (companies.some((params) => Math.max(1e3, params.maxWaitMs ?? 20 * 6e4) !== maxWaitMs)) {
928
+ return void 0;
929
+ }
930
+ const configKey = (request) => {
931
+ const {
932
+ company_domain: _companyDomain,
933
+ company_name: _companyName,
934
+ signal_run_id: _signalRunId,
935
+ wait_for_result: _waitForResult,
936
+ max_wait_ms: _maxWaitMs,
937
+ ...config
938
+ } = request;
939
+ return JSON.stringify(config);
940
+ };
941
+ const sharedConfig = configKey(tasks[0].request);
942
+ if (tasks.some((task) => configKey(task.request) !== sharedConfig)) return void 0;
943
+ return { label: "Company Signals", pageSize: 25, maxWaitMs };
944
+ }
889
945
  function normalizeCompanySignalResult(params, data) {
890
946
  return {
891
947
  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-H6HOY73X.mjs";
4
+ } from "./chunk-WKE2PNXT.mjs";
5
5
  export {
6
6
  Signaliz,
7
7
  SignalizError
@@ -462,12 +462,21 @@ var Signaliz = class {
462
462
  validateBatchSize(companies);
463
463
  const startedAt = Date.now();
464
464
  const results = new Array(companies.length);
465
- let pending = companies.map((params, index) => ({
465
+ const initialTasks = companies.map((params, index) => ({
466
466
  index,
467
467
  request: companySignalRequestBody(params)
468
468
  }));
469
+ const recoverableBatch = companySignalRecoverableBatchOptions(companies, initialTasks);
470
+ let pending = initialTasks;
471
+ let firstRound = true;
469
472
  while (pending.length > 0) {
470
- const round = await this.runCoreBatchRound("api/v1/company-signals", pending, options);
473
+ const round = await this.runCoreBatchRound(
474
+ "api/v1/company-signals",
475
+ pending,
476
+ options,
477
+ firstRound ? recoverableBatch : void 0
478
+ );
479
+ firstRound = false;
471
480
  const taskByIndex = new Map(pending.map((task) => [task.index, task]));
472
481
  const nextPending = [];
473
482
  let nextDelayMs = 6e4;
@@ -552,7 +561,7 @@ var Signaliz = class {
552
561
  const canUseRecoverableAvailableDataJob = uniqueRequests.length > 25 && uniqueRequests.every(
553
562
  (params) => params.waitForResult !== false && params.skipCache !== true && params.enableDeepSearch !== true && params.maxWaitMs === void 0 && params.pollIntervalMs === void 0
554
563
  );
555
- const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
564
+ const uniqueResults = canUseRecoverableAvailableDataJob ? await this.runAvailableSignalCopyBatchJob(uniqueRequests, options) : await this.runSignalCopyBatchChunks(uniqueRequests, options);
556
565
  const results = requests.map((_, index) => ({
557
566
  ...uniqueResults[deduplicated.sourceIndexByInput[index]],
558
567
  index
@@ -596,11 +605,14 @@ var Signaliz = class {
596
605
  }
597
606
  return uniqueResults;
598
607
  }
599
- async runAvailableSignalCopyBatchJob(requests) {
608
+ async runAvailableSignalCopyBatchJob(requests, options) {
600
609
  const rows = await this.runRecoverableCoreBatchJob(
601
610
  "api/v1/signal-to-copy",
602
611
  requests.map(signalToCopyRequestBody),
603
- "Signal to Copy"
612
+ "Signal to Copy",
613
+ 500,
614
+ 20 * 6e4,
615
+ options?.idempotencyKey
604
616
  );
605
617
  const results = new Array(requests.length);
606
618
  for (const row of rows) {
@@ -609,19 +621,28 @@ var Signaliz = class {
609
621
  }
610
622
  return results;
611
623
  }
612
- async runRecoverableCoreBatchJob(path2, requests, label) {
624
+ async runRecoverableCoreBatchJob(path2, requests, label, pageSize = 500, maxWaitMs = 20 * 6e4, idempotencyKey) {
613
625
  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
- });
626
+ const submissionIdempotencyKey = idempotencyKey?.trim() || newBatchIdempotencyKey(label);
627
+ let submission;
628
+ try {
629
+ submission = await this.client.post(path2, {
630
+ requests,
631
+ idempotency_key: submissionIdempotencyKey
632
+ });
633
+ } catch (error) {
634
+ if (error instanceof SignalizError && !error.isRetryable) throw error;
635
+ const message = error instanceof Error ? error.message : String(error);
636
+ throw new Error(
637
+ `${label} batch submission was not confirmed. Retry with idempotency key "${submissionIdempotencyKey}" to recover the same job. ${message}`
638
+ );
639
+ }
619
640
  const jobId = String(submission.job_id || "").trim();
620
641
  if (!jobId) throw new Error(`${label} batch did not return a recoverable job_id`);
621
642
  let status = await this.client.post(path2, {
622
643
  job_id: jobId,
623
644
  page: 1,
624
- page_size: 500
645
+ page_size: pageSize
625
646
  });
626
647
  while (!["completed", "partial", "failed"].includes(String(status.status || "").toLowerCase())) {
627
648
  if (Date.now() - startedAt >= maxWaitMs) {
@@ -632,21 +653,28 @@ var Signaliz = class {
632
653
  status = await this.client.post(path2, {
633
654
  job_id: jobId,
634
655
  page: 1,
635
- page_size: 500
656
+ page_size: pageSize
636
657
  });
637
658
  }
638
659
  const totalPages = Math.max(1, Number(status.total_pages || 1));
639
660
  const pages = [Array.isArray(status.results) ? status.results : []];
640
661
  if (totalPages > 1) {
641
- const remainingPages = await Promise.all(Array.from(
642
- { length: totalPages - 1 },
643
- (_, index) => this.client.post(path2, {
662
+ const pageNumbers = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
663
+ const remainingPages = await runBatch(
664
+ pageNumbers,
665
+ async (page) => await this.client.post(path2, {
644
666
  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 : []);
667
+ page,
668
+ page_size: pageSize
669
+ }),
670
+ { concurrency: Math.min(10, pageNumbers.length) }
671
+ );
672
+ for (const page of remainingPages.results) {
673
+ if (!page.success || !page.data) {
674
+ throw new Error(`${label} batch result page ${page.index + 2} failed: ${page.error || "unknown error"}`);
675
+ }
676
+ pages.push(Array.isArray(page.data.results) ? page.data.results : []);
677
+ }
650
678
  }
651
679
  const rows = pages.flat();
652
680
  if (rows.length !== requests.length) {
@@ -725,21 +753,25 @@ var Signaliz = class {
725
753
  }
726
754
  return results;
727
755
  }
728
- async runCoreBatchRound(path2, tasks, options) {
756
+ async runCoreBatchRound(path2, tasks, options, companyRecoverableBatch) {
729
757
  const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
730
758
  const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
731
759
  const uniqueTasks = deduplicated.uniqueItems;
732
- if (path2 === "api/v1/verify-email" && uniqueTasks.length > 25) {
760
+ 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;
761
+ if (recoverableBatch && uniqueTasks.length > 25) {
733
762
  const rows = await this.runRecoverableCoreBatchJob(
734
763
  path2,
735
764
  uniqueTasks.map((task) => task.request),
736
- "Verify Email"
765
+ recoverableBatch.label,
766
+ recoverableBatch.pageSize,
767
+ recoverableBatch.maxWaitMs,
768
+ options?.idempotencyKey
737
769
  );
738
770
  const uniqueResults2 = rows.map((raw) => ({
739
771
  index: uniqueTasks[Number(raw.index)].index,
740
772
  success: !recoverableJobRowFailed(raw),
741
773
  raw,
742
- error: recoverableJobRowFailed(raw) ? raw.error || raw._error || raw.message || raw.failure_reason || "Verify Email item failed" : void 0
774
+ error: recoverableJobRowFailed(raw) ? raw.error || raw._error || raw.message || raw.failure_reason || `${recoverableBatch.label} item failed` : void 0
743
775
  }));
744
776
  return tasks.map((task, index) => ({
745
777
  ...uniqueResults2[deduplicated.sourceIndexByInput[index]],
@@ -804,7 +836,7 @@ var Signaliz = class {
804
836
  }
805
837
  };
806
838
  function recoverableJobRowFailed(row) {
807
- return row.success === false || String(row._status || "").toLowerCase() === "failed";
839
+ return row.success === false || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
808
840
  }
809
841
  function newBatchIdempotencyKey(label) {
810
842
  const nonce = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
@@ -881,6 +913,7 @@ function companySignalRequestBody(params) {
881
913
  lookback_days: params.lookbackDays,
882
914
  online,
883
915
  enable_deep_search: params.enableDeepSearch ?? online,
916
+ include_summary: params.includeSummary,
884
917
  enable_outreach_intelligence: params.enableOutreachIntelligence,
885
918
  enable_predictive_intelligence: params.enablePredictiveIntelligence,
886
919
  skip_cache: params.skipCache,
@@ -890,6 +923,29 @@ function companySignalRequestBody(params) {
890
923
  max_wait_ms: params.maxWaitMs
891
924
  });
892
925
  }
926
+ function companySignalRecoverableBatchOptions(companies, tasks) {
927
+ if (companies.length <= 25 || companies.some((params) => params.waitForResult === false || Boolean(params.signalRunId))) {
928
+ return void 0;
929
+ }
930
+ const maxWaitMs = Math.max(1e3, companies[0].maxWaitMs ?? 20 * 6e4);
931
+ if (companies.some((params) => Math.max(1e3, params.maxWaitMs ?? 20 * 6e4) !== maxWaitMs)) {
932
+ return void 0;
933
+ }
934
+ const configKey = (request) => {
935
+ const {
936
+ company_domain: _companyDomain,
937
+ company_name: _companyName,
938
+ signal_run_id: _signalRunId,
939
+ wait_for_result: _waitForResult,
940
+ max_wait_ms: _maxWaitMs,
941
+ ...config
942
+ } = request;
943
+ return JSON.stringify(config);
944
+ };
945
+ const sharedConfig = configKey(tasks[0].request);
946
+ if (tasks.some((task) => configKey(task.request) !== sharedConfig)) return void 0;
947
+ return { label: "Company Signals", pageSize: 25, maxWaitMs };
948
+ }
893
949
  function normalizeCompanySignalResult(params, data) {
894
950
  return {
895
951
  success: data.success ?? true,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Signaliz
4
- } from "./chunk-H6HOY73X.mjs";
4
+ } from "./chunk-WKE2PNXT.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.33",
3
+ "version": "1.0.36",
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",