@signaliz/sdk 1.0.29 → 1.0.31

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
@@ -77,8 +77,10 @@ resumes long-running research through the same `/company-signals` endpoint;
77
77
  callers never need an internal Trigger.dev status URL. The SDK follows the
78
78
  server's adaptive polling hints by default and still honors `pollIntervalMs`
79
79
  when a caller explicitly overrides the cadence.
80
- Signal to Copy batches are transported in 25-row chunks; exact duplicates and
81
- shared company research are single-flighted without changing input order.
80
+ All four products are transported in 25-row REST chunks. Exact duplicate tasks
81
+ are single-flighted across the full batch before chunking, without changing
82
+ input order or result cardinality. Signal to Copy also shares company research
83
+ across copy recipients.
82
84
 
83
85
  Company Signal Enrichment defaults `online` and `enableDeepSearch` to `true`
84
86
  unless the caller explicitly disables them. Signal to Copy AI returns three
@@ -353,89 +353,51 @@ var Signaliz = class {
353
353
  this.client = new HttpClient(config);
354
354
  }
355
355
  async findEmail(params) {
356
- const data = await this.client.post("api/v1/find-email", compact({
357
- company_domain: params.companyDomain,
358
- full_name: params.fullName,
359
- first_name: params.firstName,
360
- last_name: params.lastName,
361
- linkedin_url: params.linkedinUrl,
362
- company_name: params.companyName,
363
- skip_cache: params.skipCache
364
- }));
365
- const email = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
366
- const found = Boolean(email) && data.found !== false;
367
- const verificationStatus = data.verification_status ?? data.status ?? "unknown";
368
- const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
369
- const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
370
- const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
371
- const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
372
- const verified = !explicitlyNotSendSafe && (data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
373
- const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
374
- return {
375
- success: data.success !== false && found,
376
- found,
377
- email,
378
- firstName: data.first_name,
379
- lastName: data.last_name,
380
- confidence: data.confidence ?? data.confidence_score ?? (verified ? 1 : 0),
381
- verificationStatus,
382
- isVerified: verified,
383
- isVerifiedForSending: !needsReverification && data.verified_for_sending === true,
384
- isCatchAll: data.email_is_catch_all === true || data.is_catch_all === true,
385
- verificationFreshness,
386
- verificationAgeDays,
387
- verifiedAt: typeof data.verified_at === "string" ? data.verified_at : null,
388
- needsReverification,
389
- providerUsed: data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown",
390
- error: found ? void 0 : data.error ?? "No verified email found",
391
- errorCode: found ? void 0 : data.error_code ?? "EMAIL_NOT_FOUND",
392
- raw: data
393
- };
356
+ const data = await this.client.post("api/v1/find-email", findEmailRequestBody(params));
357
+ return normalizeFindEmailResult(data);
394
358
  }
395
359
  async findEmails(params, options) {
396
- return runBatch(params, (item) => this.findEmail(item), options);
360
+ validateBatchSize(params);
361
+ const startedAt = Date.now();
362
+ const round = await this.runCoreBatchRound(
363
+ "api/v1/find-email",
364
+ params.map((item, index) => ({ index, request: findEmailRequestBody(item) })),
365
+ options
366
+ );
367
+ return finalizeCoreBatch(
368
+ params.length,
369
+ startedAt,
370
+ round,
371
+ (item) => normalizeFindEmailResult(item.raw)
372
+ );
397
373
  }
398
374
  async verifyEmail(email, options) {
399
375
  const data = await this.client.post("api/v1/verify-email", {
400
376
  email,
401
377
  skip_cache: options?.skipCache
402
378
  });
403
- const verificationStatus = String(data.verification_status ?? "").toLowerCase();
404
- const verified = data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus);
405
- return {
406
- email: data.email ?? email,
407
- isValid: data.is_valid ?? data.valid ?? verified,
408
- isDeliverable: data.is_deliverable ?? data.deliverable ?? verified,
409
- isCatchAll: data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false,
410
- confidenceScore: data.confidence_score ?? data.confidence ?? data.diagnostics?.confidence_score ?? (verified ? 1 : 0),
411
- provider: data.provider,
412
- verificationSource: data.verification_source,
413
- raw: data
414
- };
379
+ return normalizeVerifyEmailResult(email, data);
415
380
  }
416
381
  async verifyEmails(emails, options) {
417
- return runBatch(emails, (email) => this.verifyEmail(email, { skipCache: options?.skipCache }), options);
382
+ validateBatchSize(emails);
383
+ const startedAt = Date.now();
384
+ const round = await this.runCoreBatchRound(
385
+ "api/v1/verify-email",
386
+ emails.map((email, index) => ({
387
+ index,
388
+ request: { email, skip_cache: options?.skipCache }
389
+ })),
390
+ options
391
+ );
392
+ return finalizeCoreBatch(
393
+ emails.length,
394
+ startedAt,
395
+ round,
396
+ (item) => normalizeVerifyEmailResult(emails[item.index], item.raw)
397
+ );
418
398
  }
419
399
  async enrichCompanySignals(params) {
420
- const online = params.online ?? true;
421
- const request = compact({
422
- signal_run_id: params.signalRunId,
423
- company_domain: params.companyDomain,
424
- company_name: params.companyName,
425
- research_prompt: params.researchPrompt,
426
- signal_types: params.signalTypes,
427
- target_signal_count: params.targetSignalCount,
428
- lookback_days: params.lookbackDays,
429
- online,
430
- enable_deep_search: params.enableDeepSearch ?? online,
431
- enable_outreach_intelligence: params.enableOutreachIntelligence,
432
- enable_predictive_intelligence: params.enablePredictiveIntelligence,
433
- skip_cache: params.skipCache,
434
- // REST always returns a resumable run instead of holding the connection;
435
- // waitForResult controls this SDK's polling loop below.
436
- wait_for_result: false,
437
- max_wait_ms: params.maxWaitMs
438
- });
400
+ const request = companySignalRequestBody(params);
439
401
  let data = await this.client.post("api/v1/company-signals", request);
440
402
  if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
441
403
  const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
@@ -463,47 +425,68 @@ var Signaliz = class {
463
425
  throw new Error(`Company signal enrichment ${data.run_id} did not complete within ${maxWaitMs}ms`);
464
426
  }
465
427
  }
466
- return {
467
- success: data.success ?? true,
468
- status: data.status,
469
- signalRunId: data.signal_run_id ?? data.run_id,
470
- company: {
471
- name: data.company?.name ?? params.companyName ?? null,
472
- domain: data.company?.domain ?? params.companyDomain ?? null
473
- },
474
- signals: (data.signals ?? []).map((signal) => {
475
- const metadata = signal.metadata && typeof signal.metadata === "object" && !Array.isArray(signal.metadata) ? signal.metadata : void 0;
476
- return {
477
- id: signal.id,
478
- title: signal.title ?? "",
479
- type: signal.type ?? signal.signal_type ?? "unknown",
480
- content: signal.content ?? signal.description ?? "",
481
- date: signal.date ?? signal.detected_at ?? null,
482
- datePrecision: signal.date_precision,
483
- sourceUrl: signal.source_url ?? signal.url ?? null,
484
- sourceType: signal.source_type,
485
- sourceProvenance: signal.source_provenance,
486
- confidence: signal.confidence ?? signal.confidence_score ?? null,
487
- entityConfidence: signal.entity_confidence ?? metadata?.entity_confidence ?? null,
488
- signalFamily: signal.signal_family,
489
- eventType: signal.signal_event_type,
490
- eventLabel: signal.signal_event_label,
491
- classification: signal.signal_classification ?? null,
492
- derived: signal.derived === true,
493
- evidencePublishedDate: signal.evidence_published_date ?? metadata?.evidence_published_date ?? null,
494
- signalFeedSource: signal.signal_feed_source ?? metadata?.signal_feed_source,
495
- metadata
496
- };
497
- }),
498
- signalCount: data.signal_count ?? data.total_signals ?? data.signals?.length ?? 0,
499
- intelligence: data.intelligence ?? null,
500
- credits: data.credits,
501
- metadata: data.metadata,
502
- raw: data
503
- };
428
+ return normalizeCompanySignalResult(params, data);
504
429
  }
505
430
  async enrichCompanies(companies, options) {
506
- return runBatch(companies, (company) => this.enrichCompanySignals(company), options);
431
+ validateBatchSize(companies);
432
+ const startedAt = Date.now();
433
+ const results = new Array(companies.length);
434
+ let pending = companies.map((params, index) => ({
435
+ index,
436
+ request: companySignalRequestBody(params)
437
+ }));
438
+ while (pending.length > 0) {
439
+ const round = await this.runCoreBatchRound("api/v1/company-signals", pending, options);
440
+ const taskByIndex = new Map(pending.map((task) => [task.index, task]));
441
+ const nextPending = [];
442
+ let nextDelayMs = 6e4;
443
+ for (const item of round) {
444
+ const params = companies[item.index];
445
+ const task = taskByIndex.get(item.index);
446
+ if (!item.success || !item.raw) {
447
+ results[item.index] = { index: item.index, success: false, error: item.error || "Company Signal batch request failed" };
448
+ continue;
449
+ }
450
+ if (!isPendingSignalResponse(item.raw) || params.waitForResult === false) {
451
+ results[item.index] = { index: item.index, success: true, data: normalizeCompanySignalResult(params, item.raw) };
452
+ continue;
453
+ }
454
+ const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
455
+ if (Date.now() - startedAt >= maxWaitMs) {
456
+ results[item.index] = {
457
+ index: item.index,
458
+ success: false,
459
+ error: `Company signal enrichment ${item.raw.signal_run_id || item.raw.run_id || ""} did not complete within ${maxWaitMs}ms`
460
+ };
461
+ continue;
462
+ }
463
+ const signalRunId = item.raw.signal_run_id || item.raw.run_id;
464
+ if (!signalRunId) {
465
+ results[item.index] = { index: item.index, success: false, error: "Company signal enrichment is processing without a signal_run_id" };
466
+ continue;
467
+ }
468
+ nextDelayMs = Math.min(
469
+ nextDelayMs,
470
+ signalPollDelayMs(item.raw, params.pollIntervalMs),
471
+ maxWaitMs - (Date.now() - startedAt)
472
+ );
473
+ nextPending.push({
474
+ ...task,
475
+ request: { ...task.request, signal_run_id: signalRunId }
476
+ });
477
+ }
478
+ if (nextPending.length === 0) break;
479
+ await sleep2(Math.max(1, nextDelayMs));
480
+ pending = nextPending;
481
+ }
482
+ const succeeded = results.filter((item) => item.success).length;
483
+ return {
484
+ total: companies.length,
485
+ succeeded,
486
+ failed: companies.length - succeeded,
487
+ durationMs: Date.now() - startedAt,
488
+ results
489
+ };
507
490
  }
508
491
  async signalToCopy(params) {
509
492
  const request = signalToCopyRequestBody(params);
@@ -526,29 +509,31 @@ var Signaliz = class {
526
509
  return normalizeSignalToCopyResult(data);
527
510
  }
528
511
  async createSignalCopyBatch(requests, options) {
529
- if (!Array.isArray(requests) || requests.length === 0) {
530
- throw new Error("Signaliz batch requests require at least one item");
531
- }
532
- if (requests.length > 5e3) {
533
- throw new Error(`Signaliz batch requests support at most 5,000 items; received ${requests.length}`);
534
- }
512
+ validateBatchSize(requests);
535
513
  const startedAt = Date.now();
514
+ const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
515
+ request: signalToCopyRequestBody(params),
516
+ wait_for_result: params.waitForResult,
517
+ max_wait_ms: params.maxWaitMs,
518
+ poll_interval_ms: params.pollIntervalMs
519
+ }));
520
+ const uniqueRequests = deduplicated.uniqueItems;
536
521
  const chunks = [];
537
- for (let offset = 0; offset < requests.length; offset += 25) {
538
- chunks.push({ offset, requests: requests.slice(offset, offset + 25) });
522
+ for (let offset = 0; offset < uniqueRequests.length; offset += 25) {
523
+ chunks.push({ offset, requests: uniqueRequests.slice(offset, offset + 25) });
539
524
  }
540
- const serverConcurrency = Math.min(10, options?.concurrency ?? 10);
525
+ const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
541
526
  const chunkBatch = await runBatch(
542
527
  chunks,
543
528
  (chunk) => this.runSignalCopyBatchChunk(chunk.requests, serverConcurrency),
544
- options
529
+ { concurrency: chunkConcurrency }
545
530
  );
546
- const results = new Array(requests.length);
531
+ const uniqueResults = new Array(uniqueRequests.length);
547
532
  for (const chunkResult of chunkBatch.results) {
548
533
  const chunk = chunks[chunkResult.index];
549
534
  if (!chunkResult.success || !chunkResult.data) {
550
535
  for (let index = 0; index < chunk.requests.length; index += 1) {
551
- results[chunk.offset + index] = {
536
+ uniqueResults[chunk.offset + index] = {
552
537
  index: chunk.offset + index,
553
538
  success: false,
554
539
  error: chunkResult.error || "Signal to Copy batch request failed"
@@ -557,9 +542,13 @@ var Signaliz = class {
557
542
  continue;
558
543
  }
559
544
  for (const item of chunkResult.data) {
560
- results[chunk.offset + item.index] = { ...item, index: chunk.offset + item.index };
545
+ uniqueResults[chunk.offset + item.index] = { ...item, index: chunk.offset + item.index };
561
546
  }
562
547
  }
548
+ const results = requests.map((_, index) => ({
549
+ ...uniqueResults[deduplicated.sourceIndexByInput[index]],
550
+ index
551
+ }));
563
552
  const succeeded = results.filter((item) => item.success).length;
564
553
  return {
565
554
  total: requests.length,
@@ -631,6 +620,55 @@ var Signaliz = class {
631
620
  }
632
621
  return results;
633
622
  }
623
+ async runCoreBatchRound(path, tasks, options) {
624
+ const { serverConcurrency, chunkConcurrency } = batchConcurrencyPlan(options);
625
+ const deduplicated = dedupeExactBatchItems(tasks, (task) => JSON.stringify(task.request));
626
+ const uniqueTasks = deduplicated.uniqueItems;
627
+ const chunks = [];
628
+ for (let offset = 0; offset < uniqueTasks.length; offset += 25) {
629
+ chunks.push({ offset, tasks: uniqueTasks.slice(offset, offset + 25) });
630
+ }
631
+ const chunkBatch = await runBatch(chunks, async (chunk) => {
632
+ const data = await this.client.post(path, {
633
+ requests: chunk.tasks.map((task) => task.request),
634
+ concurrency: serverConcurrency
635
+ });
636
+ if (!Array.isArray(data.results) || data.results.length !== chunk.tasks.length) {
637
+ throw new Error(`${path} batch returned an invalid result count`);
638
+ }
639
+ if (data.results.some((row, index) => row.index !== index)) {
640
+ throw new Error(`${path} batch returned results out of order`);
641
+ }
642
+ return data.results;
643
+ }, { concurrency: chunkConcurrency });
644
+ const uniqueResults = new Array(uniqueTasks.length);
645
+ for (const chunkResult of chunkBatch.results) {
646
+ const chunk = chunks[chunkResult.index];
647
+ if (!chunkResult.success || !chunkResult.data) {
648
+ for (let index = 0; index < chunk.tasks.length; index += 1) {
649
+ uniqueResults[chunk.offset + index] = {
650
+ index: chunk.tasks[index].index,
651
+ success: false,
652
+ error: chunkResult.error || `${path} batch request failed`
653
+ };
654
+ }
655
+ continue;
656
+ }
657
+ for (let index = 0; index < chunkResult.data.length; index += 1) {
658
+ const raw = chunkResult.data[index];
659
+ uniqueResults[chunk.offset + index] = {
660
+ index: chunk.tasks[index].index,
661
+ success: raw.success !== false,
662
+ raw,
663
+ error: raw.success === false ? raw.error || raw.message || raw.error_code || `${path} item failed` : void 0
664
+ };
665
+ }
666
+ }
667
+ return tasks.map((task, index) => ({
668
+ ...uniqueResults[deduplicated.sourceIndexByInput[index]],
669
+ index: task.index
670
+ }));
671
+ }
634
672
  async listTools() {
635
673
  const result = await this.client.mcp("tools/list");
636
674
  return result.tools ?? [];
@@ -646,6 +684,188 @@ var Signaliz = class {
646
684
  function compact(input) {
647
685
  return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
648
686
  }
687
+ function findEmailRequestBody(params) {
688
+ return compact({
689
+ company_domain: params.companyDomain,
690
+ full_name: params.fullName,
691
+ first_name: params.firstName,
692
+ last_name: params.lastName,
693
+ linkedin_url: params.linkedinUrl,
694
+ company_name: params.companyName,
695
+ skip_cache: params.skipCache
696
+ });
697
+ }
698
+ function normalizeFindEmailResult(data) {
699
+ const email = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
700
+ const found = Boolean(email) && data.found !== false;
701
+ const verificationStatus = data.verification_status ?? data.status ?? "unknown";
702
+ const rawFreshness = String(data.verification_freshness ?? "").toLowerCase();
703
+ const verificationFreshness = rawFreshness === "fresh" || rawFreshness === "stale" ? rawFreshness : "unknown";
704
+ const needsReverification = data.needs_reverification === true || verificationFreshness === "stale";
705
+ const explicitlyNotSendSafe = data.verified_for_sending === false || needsReverification;
706
+ const verified = !explicitlyNotSendSafe && (data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
707
+ const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
708
+ return {
709
+ success: data.success !== false && found,
710
+ found,
711
+ email,
712
+ firstName: data.first_name,
713
+ lastName: data.last_name,
714
+ confidence: data.confidence ?? data.confidence_score ?? (verified ? 1 : 0),
715
+ verificationStatus,
716
+ isVerified: verified,
717
+ isVerifiedForSending: !needsReverification && data.verified_for_sending === true,
718
+ isCatchAll: data.email_is_catch_all === true || data.is_catch_all === true,
719
+ verificationFreshness,
720
+ verificationAgeDays,
721
+ verifiedAt: typeof data.verified_at === "string" ? data.verified_at : null,
722
+ needsReverification,
723
+ providerUsed: data.provider_used ?? data.provider ?? data.email_source ?? data.source ?? "unknown",
724
+ error: found ? void 0 : data.error ?? "No verified email found",
725
+ errorCode: found ? void 0 : data.error_code ?? "EMAIL_NOT_FOUND",
726
+ raw: data
727
+ };
728
+ }
729
+ function normalizeVerifyEmailResult(email, data) {
730
+ const verificationStatus = String(data.verification_status ?? "").toLowerCase();
731
+ const verified = data.email_verified === true || ["valid", "deliverable"].includes(verificationStatus);
732
+ return {
733
+ email: data.email ?? email,
734
+ isValid: data.is_valid ?? data.valid ?? verified,
735
+ isDeliverable: data.is_deliverable ?? data.deliverable ?? verified,
736
+ isCatchAll: data.email_is_catch_all ?? data.is_catch_all ?? data.catch_all ?? data.diagnostics?.is_catch_all ?? false,
737
+ confidenceScore: data.confidence_score ?? data.confidence ?? data.diagnostics?.confidence_score ?? (verified ? 1 : 0),
738
+ provider: data.provider,
739
+ verificationSource: data.verification_source,
740
+ raw: data
741
+ };
742
+ }
743
+ function companySignalRequestBody(params) {
744
+ const online = params.online ?? true;
745
+ return compact({
746
+ signal_run_id: params.signalRunId,
747
+ company_domain: params.companyDomain,
748
+ company_name: params.companyName,
749
+ research_prompt: params.researchPrompt,
750
+ signal_types: params.signalTypes,
751
+ target_signal_count: params.targetSignalCount,
752
+ lookback_days: params.lookbackDays,
753
+ online,
754
+ enable_deep_search: params.enableDeepSearch ?? online,
755
+ enable_outreach_intelligence: params.enableOutreachIntelligence,
756
+ enable_predictive_intelligence: params.enablePredictiveIntelligence,
757
+ skip_cache: params.skipCache,
758
+ // REST always returns a resumable run instead of holding the connection;
759
+ // waitForResult controls the SDK polling loop.
760
+ wait_for_result: false,
761
+ max_wait_ms: params.maxWaitMs
762
+ });
763
+ }
764
+ function normalizeCompanySignalResult(params, data) {
765
+ return {
766
+ success: data.success ?? true,
767
+ status: data.status,
768
+ signalRunId: data.signal_run_id ?? data.run_id,
769
+ company: {
770
+ name: data.company?.name ?? params.companyName ?? null,
771
+ domain: data.company?.domain ?? params.companyDomain ?? null
772
+ },
773
+ signals: (data.signals ?? []).map((signal) => {
774
+ const metadata = signal.metadata && typeof signal.metadata === "object" && !Array.isArray(signal.metadata) ? signal.metadata : void 0;
775
+ return {
776
+ id: signal.id,
777
+ title: signal.title ?? "",
778
+ type: signal.type ?? signal.signal_type ?? "unknown",
779
+ content: signal.content ?? signal.description ?? "",
780
+ date: signal.date ?? signal.detected_at ?? null,
781
+ datePrecision: signal.date_precision,
782
+ sourceUrl: signal.source_url ?? signal.url ?? null,
783
+ sourceType: signal.source_type,
784
+ sourceProvenance: signal.source_provenance,
785
+ confidence: signal.confidence ?? signal.confidence_score ?? null,
786
+ entityConfidence: signal.entity_confidence ?? metadata?.entity_confidence ?? null,
787
+ signalFamily: signal.signal_family,
788
+ eventType: signal.signal_event_type,
789
+ eventLabel: signal.signal_event_label,
790
+ classification: signal.signal_classification ?? null,
791
+ derived: signal.derived === true,
792
+ evidencePublishedDate: signal.evidence_published_date ?? metadata?.evidence_published_date ?? null,
793
+ signalFeedSource: signal.signal_feed_source ?? metadata?.signal_feed_source,
794
+ metadata
795
+ };
796
+ }),
797
+ signalCount: data.signal_count ?? data.total_signals ?? data.signals?.length ?? 0,
798
+ intelligence: data.intelligence ?? null,
799
+ credits: data.credits,
800
+ metadata: data.metadata,
801
+ raw: data
802
+ };
803
+ }
804
+ function validateBatchSize(items) {
805
+ if (!Array.isArray(items) || items.length === 0) {
806
+ throw new Error("Signaliz batch requests require at least one item");
807
+ }
808
+ if (items.length > 5e3) {
809
+ throw new Error(`Signaliz batch requests support at most 5,000 items; received ${items.length}`);
810
+ }
811
+ }
812
+ function validatedBatchConcurrency(options) {
813
+ const concurrency = options?.concurrency ?? 10;
814
+ if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 50) {
815
+ throw new Error("Signaliz batch concurrency must be an integer between 1 and 50");
816
+ }
817
+ return concurrency;
818
+ }
819
+ function batchConcurrencyPlan(options) {
820
+ const concurrency = validatedBatchConcurrency(options);
821
+ const serverConcurrency = Math.min(10, concurrency);
822
+ return {
823
+ serverConcurrency,
824
+ chunkConcurrency: Math.max(1, Math.floor(concurrency / serverConcurrency))
825
+ };
826
+ }
827
+ function dedupeExactBatchItems(items, keyForItem) {
828
+ const uniqueItems = [];
829
+ const sourceIndexByInput = [];
830
+ const uniqueIndexByKey = /* @__PURE__ */ new Map();
831
+ for (const item of items) {
832
+ const key = keyForItem(item);
833
+ let uniqueIndex = uniqueIndexByKey.get(key);
834
+ if (uniqueIndex === void 0) {
835
+ uniqueIndex = uniqueItems.length;
836
+ uniqueIndexByKey.set(key, uniqueIndex);
837
+ uniqueItems.push(item);
838
+ }
839
+ sourceIndexByInput.push(uniqueIndex);
840
+ }
841
+ return { uniqueItems, sourceIndexByInput };
842
+ }
843
+ function finalizeCoreBatch(total, startedAt, round, normalize) {
844
+ const results = new Array(total);
845
+ for (const item of round) {
846
+ if (!item.success || !item.raw) {
847
+ results[item.index] = { index: item.index, success: false, error: item.error || "Signaliz batch item failed" };
848
+ continue;
849
+ }
850
+ try {
851
+ results[item.index] = { index: item.index, success: true, data: normalize(item) };
852
+ } catch (error) {
853
+ results[item.index] = {
854
+ index: item.index,
855
+ success: false,
856
+ error: error instanceof Error ? error.message : String(error)
857
+ };
858
+ }
859
+ }
860
+ const succeeded = results.filter((item) => item.success).length;
861
+ return {
862
+ total,
863
+ succeeded,
864
+ failed: total - succeeded,
865
+ durationMs: Date.now() - startedAt,
866
+ results
867
+ };
868
+ }
649
869
  function signalToCopyRequestBody(params) {
650
870
  return compact({
651
871
  company_domain: params.companyDomain,
@@ -707,16 +927,8 @@ function sleep2(ms) {
707
927
  return new Promise((resolve) => setTimeout(resolve, ms));
708
928
  }
709
929
  async function runBatch(items, execute, options) {
710
- if (!Array.isArray(items) || items.length === 0) {
711
- throw new Error("Signaliz batch requests require at least one item");
712
- }
713
- if (items.length > 5e3) {
714
- throw new Error(`Signaliz batch requests support at most 5,000 items; received ${items.length}`);
715
- }
716
- const concurrency = options?.concurrency ?? 10;
717
- if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 50) {
718
- throw new Error("Signaliz batch concurrency must be an integer between 1 and 50");
719
- }
930
+ validateBatchSize(items);
931
+ const concurrency = validatedBatchConcurrency(options);
720
932
  const startedAt = Date.now();
721
933
  const results = new Array(items.length);
722
934
  let nextIndex = 0;
package/dist/index.d.mts CHANGED
@@ -204,6 +204,7 @@ declare class Signaliz {
204
204
  signalToCopy(params: SignalToCopyParams): Promise<SignalToCopyResult>;
205
205
  createSignalCopyBatch(requests: SignalToCopyParams[], options?: BatchOptions): Promise<BatchResult<SignalToCopyResult>>;
206
206
  private runSignalCopyBatchChunk;
207
+ private runCoreBatchRound;
207
208
  listTools(): Promise<MCPToolInfo[]>;
208
209
  health(): Promise<PlatformHealth>;
209
210
  }
package/dist/index.d.ts CHANGED
@@ -204,6 +204,7 @@ declare class Signaliz {
204
204
  signalToCopy(params: SignalToCopyParams): Promise<SignalToCopyResult>;
205
205
  createSignalCopyBatch(requests: SignalToCopyParams[], options?: BatchOptions): Promise<BatchResult<SignalToCopyResult>>;
206
206
  private runSignalCopyBatchChunk;
207
+ private runCoreBatchRound;
207
208
  listTools(): Promise<MCPToolInfo[]>;
208
209
  health(): Promise<PlatformHealth>;
209
210
  }