@signaliz/sdk 1.0.45 → 1.0.47

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
@@ -20,6 +20,15 @@ const signaliz = new Signaliz({
20
20
  apiKey: process.env.SIGNALIZ_API_KEY,
21
21
  });
22
22
 
23
+ // Every product supports a strict no-spend preflight. Batch methods accept the
24
+ // same option and return one plan for the full input.
25
+ const plan = await signaliz.findEmail({
26
+ companyDomain: 'example.com',
27
+ fullName: 'Jane Doe',
28
+ dryRun: true,
29
+ });
30
+ console.log(plan.estimatedCredits.max, plan.creditsCharged); // 2, 0
31
+
23
32
  const found = await signaliz.findEmail({
24
33
  companyDomain: 'example.com',
25
34
  fullName: 'Jane Doe',
@@ -100,8 +109,13 @@ const compactSignals = await signaliz.enrichCompanies(companies, {
100
109
  });
101
110
  ```
102
111
 
103
- For an ambiguous submission failure, retry with the same
104
- `idempotencyKey` to recover the original job without duplicate work.
112
+ Failed rows preserve `errorCode`, `retryEligible`, and `retryAfterSeconds`
113
+ when the REST API supplies them, including after automatic row retries are
114
+ exhausted.
115
+
116
+ For an ambiguous single request or batch submission failure, retry with the
117
+ same `idempotencyKey` to recover the original request or job without duplicate
118
+ work.
105
119
 
106
120
  Each batch accepts up to 5,000 items. Company Signal Enrichment transparently
107
121
  resumes long-running research through the same `/company-signals` endpoint;
@@ -378,10 +378,17 @@ var Signaliz = class {
378
378
  }
379
379
  async findEmail(params) {
380
380
  const data = await this.client.post("api/v1/find-email", findEmailRequestBody(params));
381
- return normalizeFindEmailResult(data);
381
+ return isCoreProductDryRun(data) ? normalizeCoreProductDryRunResult(data) : normalizeFindEmailResult(data);
382
382
  }
383
383
  async findEmails(params, options) {
384
384
  validateBatchSize(params);
385
+ if (options?.dryRun === true) {
386
+ return this.runCoreProductDryRun(
387
+ "api/v1/find-email",
388
+ params.map(findEmailRequestBody),
389
+ options.idempotencyKey
390
+ );
391
+ }
385
392
  const startedAt = Date.now();
386
393
  const round = await this.runCoreBatchRound(
387
394
  "api/v1/find-email",
@@ -400,9 +407,12 @@ var Signaliz = class {
400
407
  const request = compact({
401
408
  email: email || void 0,
402
409
  verification_run_id: options?.verificationRunId,
403
- skip_cache: options?.skipCache
410
+ skip_cache: options?.skipCache,
411
+ dry_run: options?.dryRun,
412
+ idempotency_key: options?.idempotencyKey
404
413
  });
405
414
  let data = await this.client.post("api/v1/verify-email", request);
415
+ if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
406
416
  if (options?.waitForResult !== false && isPendingSignalResponse(data) && data.verification_run_id) {
407
417
  const maxWaitMs = Math.max(1e3, options?.maxWaitMs ?? 10 * 6e4);
408
418
  const startedAt = Date.now();
@@ -422,6 +432,13 @@ var Signaliz = class {
422
432
  }
423
433
  async verifyEmails(emails, options) {
424
434
  validateBatchSize(emails);
435
+ if (options?.dryRun === true) {
436
+ return this.runCoreProductDryRun(
437
+ "api/v1/verify-email",
438
+ emails.map((email) => ({ email, skip_cache: options.skipCache })),
439
+ options.idempotencyKey
440
+ );
441
+ }
425
442
  const startedAt = Date.now();
426
443
  const round = await this.runCoreBatchRound(
427
444
  "api/v1/verify-email",
@@ -442,6 +459,7 @@ var Signaliz = class {
442
459
  async enrichCompanySignals(params) {
443
460
  const request = companySignalRequestBody(params);
444
461
  let data = await this.client.post("api/v1/company-signals", request);
462
+ if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
445
463
  if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
446
464
  const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
447
465
  const startedAt = Date.now();
@@ -472,6 +490,13 @@ var Signaliz = class {
472
490
  }
473
491
  async enrichCompanies(companies, options) {
474
492
  validateBatchSize(companies);
493
+ if (options?.dryRun === true) {
494
+ return this.runCoreProductDryRun(
495
+ "api/v1/company-signals",
496
+ companies.map(companySignalRequestBody),
497
+ options.idempotencyKey
498
+ );
499
+ }
475
500
  const startedAt = Date.now();
476
501
  const results = new Array(companies.length);
477
502
  const initialTasks = companies.map((params, index) => ({
@@ -496,7 +521,11 @@ var Signaliz = class {
496
521
  const params = companies[item.index];
497
522
  const task = taskByIndex.get(item.index);
498
523
  if (!item.success || !item.raw) {
499
- results[item.index] = { index: item.index, success: false, error: item.error || "Company Signal batch request failed" };
524
+ results[item.index] = batchItemFailure(
525
+ item.index,
526
+ item.error || "Company Signal batch request failed",
527
+ item.raw ?? item
528
+ );
500
529
  continue;
501
530
  }
502
531
  if (!isPendingSignalResponse(item.raw) || params.waitForResult === false) {
@@ -545,6 +574,7 @@ var Signaliz = class {
545
574
  validateSignalToCopyParams(params);
546
575
  const request = signalToCopyRequestBody(params);
547
576
  let data = await this.client.post("api/v1/signal-to-copy", request);
577
+ if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
548
578
  if (params.waitForResult !== false && (data.signal_run_id || data.run_id) && isPendingSignalResponse(data)) {
549
579
  const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
550
580
  const startedAt = Date.now();
@@ -563,6 +593,13 @@ var Signaliz = class {
563
593
  async createSignalCopyBatch(requests, options) {
564
594
  validateBatchSize(requests);
565
595
  requests.forEach(validateSignalToCopyParams);
596
+ if (options?.dryRun === true) {
597
+ return this.runCoreProductDryRun(
598
+ "api/v1/signal-to-copy",
599
+ requests.map(signalToCopyRequestBody),
600
+ options.idempotencyKey
601
+ );
602
+ }
566
603
  const startedAt = Date.now();
567
604
  const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
568
605
  request: signalToCopyRequestBody(params),
@@ -589,6 +626,17 @@ var Signaliz = class {
589
626
  results: compactedResults
590
627
  };
591
628
  }
629
+ async runCoreProductDryRun(path, requests, idempotencyKey) {
630
+ const data = await this.client.post(path, compact({
631
+ requests,
632
+ dry_run: true,
633
+ idempotency_key: idempotencyKey
634
+ }));
635
+ if (!isCoreProductDryRun(data)) {
636
+ throw new Error(`${path} dry run returned a live-result contract`);
637
+ }
638
+ return normalizeCoreProductDryRunResult(data);
639
+ }
592
640
  async runSignalCopyBatchChunks(uniqueRequests, options) {
593
641
  const chunks = [];
594
642
  for (let offset = 0; offset < uniqueRequests.length; offset += 25) {
@@ -605,11 +653,11 @@ var Signaliz = class {
605
653
  const chunk = chunks[chunkResult.index];
606
654
  if (!chunkResult.success || !chunkResult.data) {
607
655
  for (let index = 0; index < chunk.requests.length; index += 1) {
608
- uniqueResults[chunk.offset + index] = {
609
- index: chunk.offset + index,
610
- success: false,
611
- error: chunkResult.error || "Signal to Copy batch request failed"
612
- };
656
+ uniqueResults[chunk.offset + index] = batchItemFailure(
657
+ chunk.offset + index,
658
+ chunkResult.error || "Signal to Copy batch request failed",
659
+ chunkResult
660
+ );
613
661
  }
614
662
  continue;
615
663
  }
@@ -631,7 +679,7 @@ var Signaliz = class {
631
679
  const results = new Array(requests.length);
632
680
  for (const row of rows) {
633
681
  const index = Number(row.index);
634
- results[index] = recoverableJobRowFailed(row) ? { index, success: false, error: row.error || row.message || "Signal to Copy failed" } : { index, success: true, data: normalizeSignalToCopyResult(row) };
682
+ results[index] = recoverableJobRowFailed(row) ? batchItemFailure(index, row.error || row.message || "Signal to Copy failed", row) : { index, success: true, data: normalizeSignalToCopyResult(row) };
635
683
  }
636
684
  return results;
637
685
  }
@@ -736,7 +784,11 @@ var Signaliz = class {
736
784
  rateLimited.push({ index: task.index, params: task.params, raw: item });
737
785
  continue;
738
786
  }
739
- results[task.index] = { index: task.index, success: false, error: item.error || item.message || "Signal to Copy failed" };
787
+ results[task.index] = batchItemFailure(
788
+ task.index,
789
+ item.error || item.message || "Signal to Copy failed",
790
+ item
791
+ );
740
792
  continue;
741
793
  }
742
794
  if (!isPendingSignalResponse(item)) {
@@ -841,7 +893,10 @@ var Signaliz = class {
841
893
  uniqueResults[chunk.offset + index] = {
842
894
  index: chunk.tasks[index].index,
843
895
  success: false,
844
- error: chunkResult.error || `${path} batch request failed`
896
+ error: chunkResult.error || `${path} batch request failed`,
897
+ errorCode: chunkResult.errorCode,
898
+ retryEligible: chunkResult.retryEligible,
899
+ retryAfterSeconds: chunkResult.retryAfterSeconds
845
900
  };
846
901
  }
847
902
  continue;
@@ -907,6 +962,21 @@ function rowRateLimitDelayMs(items) {
907
962
  });
908
963
  return Math.max(1, Math.min(6e4, Math.max(...delays)));
909
964
  }
965
+ function batchItemFailure(index, error, raw) {
966
+ const errorCode = String(raw?.error_code ?? raw?.code ?? raw?.errorCode ?? "").trim();
967
+ const retryAfterSeconds2 = Number(
968
+ raw?.retry_after_seconds ?? raw?.retry_after ?? raw?.retryAfterSeconds ?? (Number.isFinite(Number(raw?.retry_after_ms)) ? Number(raw?.retry_after_ms) / 1e3 : void 0)
969
+ );
970
+ const retryEligible = raw?.retry_eligible ?? raw?.retryEligible;
971
+ return {
972
+ index,
973
+ success: false,
974
+ error,
975
+ ...errorCode ? { errorCode } : {},
976
+ ...typeof retryEligible === "boolean" ? { retryEligible } : {},
977
+ ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {}
978
+ };
979
+ }
910
980
  function recoverableJobRowFailed(row) {
911
981
  return row.success === false || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
912
982
  }
@@ -925,9 +995,34 @@ function findEmailRequestBody(params) {
925
995
  last_name: params.lastName,
926
996
  linkedin_url: params.linkedinUrl,
927
997
  company_name: params.companyName,
928
- skip_cache: params.skipCache
998
+ skip_cache: params.skipCache,
999
+ dry_run: params.dryRun,
1000
+ idempotency_key: params.idempotencyKey
929
1001
  });
930
1002
  }
1003
+ function isCoreProductDryRun(data) {
1004
+ return data.dry_run === true && data.status === "planned";
1005
+ }
1006
+ function normalizeCoreProductDryRunResult(data) {
1007
+ const estimatedCredits = data.estimated_credits && typeof data.estimated_credits === "object" ? data.estimated_credits : { min: 0, max: 0 };
1008
+ return {
1009
+ success: true,
1010
+ dryRun: true,
1011
+ status: "planned",
1012
+ capability: String(data.capability || ""),
1013
+ inputCount: Number(data.input_count || 0),
1014
+ recoveryReadCount: Number(data.recovery_read_count || 0),
1015
+ plan: data.plan && typeof data.plan === "object" ? data.plan : {},
1016
+ estimate: data.estimate && typeof data.estimate === "object" ? data.estimate : {},
1017
+ estimatedCredits: {
1018
+ min: Number(estimatedCredits.min || 0),
1019
+ max: Number(estimatedCredits.max || 0)
1020
+ },
1021
+ creditsCharged: 0,
1022
+ sideEffects: Array.isArray(data.side_effects) ? data.side_effects : [],
1023
+ raw: data
1024
+ };
1025
+ }
931
1026
  function normalizeFindEmailResult(data) {
932
1027
  const email = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
933
1028
  const found = Boolean(email) && data.found !== false;
@@ -996,7 +1091,9 @@ function companySignalRequestBody(params) {
996
1091
  // REST always returns a resumable run instead of holding the connection;
997
1092
  // waitForResult controls the SDK polling loop.
998
1093
  wait_for_result: false,
999
- max_wait_ms: params.maxWaitMs
1094
+ max_wait_ms: params.maxWaitMs,
1095
+ dry_run: params.dryRun,
1096
+ idempotency_key: params.idempotencyKey
1000
1097
  });
1001
1098
  }
1002
1099
  function companySignalRecoverableBatchOptions(companies, tasks) {
@@ -1117,7 +1214,11 @@ function finalizeCoreBatch(total, startedAt, round, normalize, options) {
1117
1214
  const results = new Array(total);
1118
1215
  for (const item of round) {
1119
1216
  if (!item.success || !item.raw) {
1120
- results[item.index] = { index: item.index, success: false, error: item.error || "Signaliz batch item failed" };
1217
+ results[item.index] = batchItemFailure(
1218
+ item.index,
1219
+ item.error || "Signaliz batch item failed",
1220
+ item.raw ?? item
1221
+ );
1121
1222
  continue;
1122
1223
  }
1123
1224
  try {
@@ -1164,7 +1265,9 @@ function signalToCopyRequestBody(params) {
1164
1265
  research_prompt: params.researchPrompt,
1165
1266
  signal_run_id: params.signalRunId,
1166
1267
  skip_cache: params.skipCache,
1167
- enable_deep_search: params.enableDeepSearch
1268
+ enable_deep_search: params.enableDeepSearch,
1269
+ dry_run: params.dryRun,
1270
+ idempotency_key: params.idempotencyKey
1168
1271
  });
1169
1272
  }
1170
1273
  function validateSignalToCopyParams(params) {
@@ -1245,11 +1348,15 @@ async function runBatch(items, execute, options) {
1245
1348
  try {
1246
1349
  results[index] = { index, success: true, data: await execute(items[index]) };
1247
1350
  } catch (error) {
1248
- results[index] = {
1351
+ results[index] = batchItemFailure(
1249
1352
  index,
1250
- success: false,
1251
- error: error instanceof Error ? error.message : String(error)
1252
- };
1353
+ error instanceof Error ? error.message : String(error),
1354
+ error instanceof SignalizError ? {
1355
+ error_code: error.code,
1356
+ retry_eligible: error.isRetryable,
1357
+ retry_after_seconds: error.retryAfter
1358
+ } : void 0
1359
+ );
1253
1360
  }
1254
1361
  }
1255
1362
  };
package/dist/index.d.mts CHANGED
@@ -13,12 +13,20 @@ interface BatchOptions {
13
13
  idempotencyKey?: string;
14
14
  /** Return exact repeated successes as duplicateOf references. Defaults to false. */
15
15
  compactDuplicates?: boolean;
16
+ /** Return one no-spend plan for the full batch without provider calls or jobs. */
17
+ dryRun?: boolean;
16
18
  }
17
19
  interface BatchItemResult<T> {
18
20
  index: number;
19
21
  success: boolean;
20
22
  data?: T;
21
23
  error?: string;
24
+ /** Stable public error code for a failed row. */
25
+ errorCode?: string;
26
+ /** Whether retrying the failed row is safe. */
27
+ retryEligible?: boolean;
28
+ /** Server-provided delay before retrying the failed row. */
29
+ retryAfterSeconds?: number;
22
30
  /** Zero-based input index containing the full result for this exact duplicate. */
23
31
  duplicateOf?: number;
24
32
  }
@@ -37,6 +45,23 @@ interface SignalizErrorData {
37
45
  retryAfter?: number;
38
46
  details?: Record<string, unknown>;
39
47
  }
48
+ interface CoreProductDryRunResult {
49
+ success: true;
50
+ dryRun: true;
51
+ status: 'planned';
52
+ capability: string;
53
+ inputCount: number;
54
+ recoveryReadCount: number;
55
+ plan: Record<string, unknown>;
56
+ estimate: Record<string, unknown>;
57
+ estimatedCredits: {
58
+ min: number;
59
+ max: number;
60
+ };
61
+ creditsCharged: 0;
62
+ sideEffects: unknown[];
63
+ raw: Record<string, unknown>;
64
+ }
40
65
  interface FindEmailParams {
41
66
  companyDomain: string;
42
67
  fullName?: string;
@@ -46,6 +71,10 @@ interface FindEmailParams {
46
71
  companyName?: string;
47
72
  /** Bypass cached email and verification data for a fresh-provider run. */
48
73
  skipCache?: boolean;
74
+ /** Return a no-spend plan without provider calls or jobs. */
75
+ dryRun?: boolean;
76
+ /** Stable retry key for recovering the same request after an ambiguous response. */
77
+ idempotencyKey?: string;
49
78
  }
50
79
  interface FindEmailResult {
51
80
  success: boolean;
@@ -94,6 +123,10 @@ interface VerifyEmailOptions {
94
123
  maxWaitMs?: number;
95
124
  /** Override the server-provided polling delay. */
96
125
  pollIntervalMs?: number;
126
+ /** Return a no-spend plan without provider calls or jobs. */
127
+ dryRun?: boolean;
128
+ /** Stable retry key for recovering the same request after an ambiguous response. */
129
+ idempotencyKey?: string;
97
130
  }
98
131
  interface VerifyEmailBatchOptions extends BatchOptions, VerifyEmailOptions {
99
132
  }
@@ -118,6 +151,10 @@ interface CompanySignalEnrichmentParams {
118
151
  maxWaitMs?: number;
119
152
  /** Delay between queued-enrichment status checks. Defaults to 2 seconds. */
120
153
  pollIntervalMs?: number;
154
+ /** Return a no-spend plan without provider calls or jobs. */
155
+ dryRun?: boolean;
156
+ /** Stable retry key for recovering the same request after an ambiguous response. */
157
+ idempotencyKey?: string;
121
158
  }
122
159
  interface CompanySignal {
123
160
  id?: string;
@@ -180,6 +217,10 @@ interface SignalToCopyParams {
180
217
  waitForResult?: boolean;
181
218
  maxWaitMs?: number;
182
219
  pollIntervalMs?: number;
220
+ /** Return a no-spend plan without provider calls or jobs. */
221
+ dryRun?: boolean;
222
+ /** Stable retry key for recovering the same request after an ambiguous response. */
223
+ idempotencyKey?: string;
183
224
  }
184
225
  interface SignalToCopyVariation {
185
226
  style: 'signal_led' | 'business_case' | 'predictive';
@@ -231,14 +272,39 @@ declare class SignalizError extends Error {
231
272
  declare class Signaliz {
232
273
  private readonly client;
233
274
  constructor(config: SignalizConfig);
275
+ findEmail(params: FindEmailParams & {
276
+ dryRun: true;
277
+ }): Promise<CoreProductDryRunResult>;
234
278
  findEmail(params: FindEmailParams): Promise<FindEmailResult>;
279
+ findEmails(params: FindEmailParams[], options: BatchOptions & {
280
+ dryRun: true;
281
+ }): Promise<CoreProductDryRunResult>;
235
282
  findEmails(params: FindEmailParams[], options?: BatchOptions): Promise<BatchResult<FindEmailResult>>;
283
+ verifyEmail(email: string | undefined, options: VerifyEmailOptions & {
284
+ dryRun: true;
285
+ }): Promise<CoreProductDryRunResult>;
236
286
  verifyEmail(email: string | undefined, options?: VerifyEmailOptions): Promise<VerifyEmailResult>;
287
+ verifyEmails(emails: string[], options: VerifyEmailBatchOptions & {
288
+ dryRun: true;
289
+ }): Promise<CoreProductDryRunResult>;
237
290
  verifyEmails(emails: string[], options?: VerifyEmailBatchOptions): Promise<BatchResult<VerifyEmailResult>>;
291
+ enrichCompanySignals(params: CompanySignalEnrichmentParams & {
292
+ dryRun: true;
293
+ }): Promise<CoreProductDryRunResult>;
238
294
  enrichCompanySignals(params: CompanySignalEnrichmentParams): Promise<CompanySignalEnrichmentResult>;
295
+ enrichCompanies(companies: CompanySignalEnrichmentParams[], options: BatchOptions & {
296
+ dryRun: true;
297
+ }): Promise<CoreProductDryRunResult>;
239
298
  enrichCompanies(companies: CompanySignalEnrichmentParams[], options?: BatchOptions): Promise<BatchResult<CompanySignalEnrichmentResult>>;
299
+ signalToCopy(params: SignalToCopyParams & {
300
+ dryRun: true;
301
+ }): Promise<CoreProductDryRunResult>;
240
302
  signalToCopy(params: SignalToCopyParams): Promise<SignalToCopyResult>;
303
+ createSignalCopyBatch(requests: SignalToCopyParams[], options: BatchOptions & {
304
+ dryRun: true;
305
+ }): Promise<CoreProductDryRunResult>;
241
306
  createSignalCopyBatch(requests: SignalToCopyParams[], options?: BatchOptions): Promise<BatchResult<SignalToCopyResult>>;
307
+ private runCoreProductDryRun;
242
308
  private runSignalCopyBatchChunks;
243
309
  private runAvailableSignalCopyBatchJob;
244
310
  private runRecoverableCoreBatchJob;
package/dist/index.d.ts CHANGED
@@ -13,12 +13,20 @@ interface BatchOptions {
13
13
  idempotencyKey?: string;
14
14
  /** Return exact repeated successes as duplicateOf references. Defaults to false. */
15
15
  compactDuplicates?: boolean;
16
+ /** Return one no-spend plan for the full batch without provider calls or jobs. */
17
+ dryRun?: boolean;
16
18
  }
17
19
  interface BatchItemResult<T> {
18
20
  index: number;
19
21
  success: boolean;
20
22
  data?: T;
21
23
  error?: string;
24
+ /** Stable public error code for a failed row. */
25
+ errorCode?: string;
26
+ /** Whether retrying the failed row is safe. */
27
+ retryEligible?: boolean;
28
+ /** Server-provided delay before retrying the failed row. */
29
+ retryAfterSeconds?: number;
22
30
  /** Zero-based input index containing the full result for this exact duplicate. */
23
31
  duplicateOf?: number;
24
32
  }
@@ -37,6 +45,23 @@ interface SignalizErrorData {
37
45
  retryAfter?: number;
38
46
  details?: Record<string, unknown>;
39
47
  }
48
+ interface CoreProductDryRunResult {
49
+ success: true;
50
+ dryRun: true;
51
+ status: 'planned';
52
+ capability: string;
53
+ inputCount: number;
54
+ recoveryReadCount: number;
55
+ plan: Record<string, unknown>;
56
+ estimate: Record<string, unknown>;
57
+ estimatedCredits: {
58
+ min: number;
59
+ max: number;
60
+ };
61
+ creditsCharged: 0;
62
+ sideEffects: unknown[];
63
+ raw: Record<string, unknown>;
64
+ }
40
65
  interface FindEmailParams {
41
66
  companyDomain: string;
42
67
  fullName?: string;
@@ -46,6 +71,10 @@ interface FindEmailParams {
46
71
  companyName?: string;
47
72
  /** Bypass cached email and verification data for a fresh-provider run. */
48
73
  skipCache?: boolean;
74
+ /** Return a no-spend plan without provider calls or jobs. */
75
+ dryRun?: boolean;
76
+ /** Stable retry key for recovering the same request after an ambiguous response. */
77
+ idempotencyKey?: string;
49
78
  }
50
79
  interface FindEmailResult {
51
80
  success: boolean;
@@ -94,6 +123,10 @@ interface VerifyEmailOptions {
94
123
  maxWaitMs?: number;
95
124
  /** Override the server-provided polling delay. */
96
125
  pollIntervalMs?: number;
126
+ /** Return a no-spend plan without provider calls or jobs. */
127
+ dryRun?: boolean;
128
+ /** Stable retry key for recovering the same request after an ambiguous response. */
129
+ idempotencyKey?: string;
97
130
  }
98
131
  interface VerifyEmailBatchOptions extends BatchOptions, VerifyEmailOptions {
99
132
  }
@@ -118,6 +151,10 @@ interface CompanySignalEnrichmentParams {
118
151
  maxWaitMs?: number;
119
152
  /** Delay between queued-enrichment status checks. Defaults to 2 seconds. */
120
153
  pollIntervalMs?: number;
154
+ /** Return a no-spend plan without provider calls or jobs. */
155
+ dryRun?: boolean;
156
+ /** Stable retry key for recovering the same request after an ambiguous response. */
157
+ idempotencyKey?: string;
121
158
  }
122
159
  interface CompanySignal {
123
160
  id?: string;
@@ -180,6 +217,10 @@ interface SignalToCopyParams {
180
217
  waitForResult?: boolean;
181
218
  maxWaitMs?: number;
182
219
  pollIntervalMs?: number;
220
+ /** Return a no-spend plan without provider calls or jobs. */
221
+ dryRun?: boolean;
222
+ /** Stable retry key for recovering the same request after an ambiguous response. */
223
+ idempotencyKey?: string;
183
224
  }
184
225
  interface SignalToCopyVariation {
185
226
  style: 'signal_led' | 'business_case' | 'predictive';
@@ -231,14 +272,39 @@ declare class SignalizError extends Error {
231
272
  declare class Signaliz {
232
273
  private readonly client;
233
274
  constructor(config: SignalizConfig);
275
+ findEmail(params: FindEmailParams & {
276
+ dryRun: true;
277
+ }): Promise<CoreProductDryRunResult>;
234
278
  findEmail(params: FindEmailParams): Promise<FindEmailResult>;
279
+ findEmails(params: FindEmailParams[], options: BatchOptions & {
280
+ dryRun: true;
281
+ }): Promise<CoreProductDryRunResult>;
235
282
  findEmails(params: FindEmailParams[], options?: BatchOptions): Promise<BatchResult<FindEmailResult>>;
283
+ verifyEmail(email: string | undefined, options: VerifyEmailOptions & {
284
+ dryRun: true;
285
+ }): Promise<CoreProductDryRunResult>;
236
286
  verifyEmail(email: string | undefined, options?: VerifyEmailOptions): Promise<VerifyEmailResult>;
287
+ verifyEmails(emails: string[], options: VerifyEmailBatchOptions & {
288
+ dryRun: true;
289
+ }): Promise<CoreProductDryRunResult>;
237
290
  verifyEmails(emails: string[], options?: VerifyEmailBatchOptions): Promise<BatchResult<VerifyEmailResult>>;
291
+ enrichCompanySignals(params: CompanySignalEnrichmentParams & {
292
+ dryRun: true;
293
+ }): Promise<CoreProductDryRunResult>;
238
294
  enrichCompanySignals(params: CompanySignalEnrichmentParams): Promise<CompanySignalEnrichmentResult>;
295
+ enrichCompanies(companies: CompanySignalEnrichmentParams[], options: BatchOptions & {
296
+ dryRun: true;
297
+ }): Promise<CoreProductDryRunResult>;
239
298
  enrichCompanies(companies: CompanySignalEnrichmentParams[], options?: BatchOptions): Promise<BatchResult<CompanySignalEnrichmentResult>>;
299
+ signalToCopy(params: SignalToCopyParams & {
300
+ dryRun: true;
301
+ }): Promise<CoreProductDryRunResult>;
240
302
  signalToCopy(params: SignalToCopyParams): Promise<SignalToCopyResult>;
303
+ createSignalCopyBatch(requests: SignalToCopyParams[], options: BatchOptions & {
304
+ dryRun: true;
305
+ }): Promise<CoreProductDryRunResult>;
241
306
  createSignalCopyBatch(requests: SignalToCopyParams[], options?: BatchOptions): Promise<BatchResult<SignalToCopyResult>>;
307
+ private runCoreProductDryRun;
242
308
  private runSignalCopyBatchChunks;
243
309
  private runAvailableSignalCopyBatchJob;
244
310
  private runRecoverableCoreBatchJob;
package/dist/index.js CHANGED
@@ -405,10 +405,17 @@ var Signaliz = class {
405
405
  }
406
406
  async findEmail(params) {
407
407
  const data = await this.client.post("api/v1/find-email", findEmailRequestBody(params));
408
- return normalizeFindEmailResult(data);
408
+ return isCoreProductDryRun(data) ? normalizeCoreProductDryRunResult(data) : normalizeFindEmailResult(data);
409
409
  }
410
410
  async findEmails(params, options) {
411
411
  validateBatchSize(params);
412
+ if (options?.dryRun === true) {
413
+ return this.runCoreProductDryRun(
414
+ "api/v1/find-email",
415
+ params.map(findEmailRequestBody),
416
+ options.idempotencyKey
417
+ );
418
+ }
412
419
  const startedAt = Date.now();
413
420
  const round = await this.runCoreBatchRound(
414
421
  "api/v1/find-email",
@@ -427,9 +434,12 @@ var Signaliz = class {
427
434
  const request = compact({
428
435
  email: email || void 0,
429
436
  verification_run_id: options?.verificationRunId,
430
- skip_cache: options?.skipCache
437
+ skip_cache: options?.skipCache,
438
+ dry_run: options?.dryRun,
439
+ idempotency_key: options?.idempotencyKey
431
440
  });
432
441
  let data = await this.client.post("api/v1/verify-email", request);
442
+ if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
433
443
  if (options?.waitForResult !== false && isPendingSignalResponse(data) && data.verification_run_id) {
434
444
  const maxWaitMs = Math.max(1e3, options?.maxWaitMs ?? 10 * 6e4);
435
445
  const startedAt = Date.now();
@@ -449,6 +459,13 @@ var Signaliz = class {
449
459
  }
450
460
  async verifyEmails(emails, options) {
451
461
  validateBatchSize(emails);
462
+ if (options?.dryRun === true) {
463
+ return this.runCoreProductDryRun(
464
+ "api/v1/verify-email",
465
+ emails.map((email) => ({ email, skip_cache: options.skipCache })),
466
+ options.idempotencyKey
467
+ );
468
+ }
452
469
  const startedAt = Date.now();
453
470
  const round = await this.runCoreBatchRound(
454
471
  "api/v1/verify-email",
@@ -469,6 +486,7 @@ var Signaliz = class {
469
486
  async enrichCompanySignals(params) {
470
487
  const request = companySignalRequestBody(params);
471
488
  let data = await this.client.post("api/v1/company-signals", request);
489
+ if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
472
490
  if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
473
491
  const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
474
492
  const startedAt = Date.now();
@@ -499,6 +517,13 @@ var Signaliz = class {
499
517
  }
500
518
  async enrichCompanies(companies, options) {
501
519
  validateBatchSize(companies);
520
+ if (options?.dryRun === true) {
521
+ return this.runCoreProductDryRun(
522
+ "api/v1/company-signals",
523
+ companies.map(companySignalRequestBody),
524
+ options.idempotencyKey
525
+ );
526
+ }
502
527
  const startedAt = Date.now();
503
528
  const results = new Array(companies.length);
504
529
  const initialTasks = companies.map((params, index) => ({
@@ -523,7 +548,11 @@ var Signaliz = class {
523
548
  const params = companies[item.index];
524
549
  const task = taskByIndex.get(item.index);
525
550
  if (!item.success || !item.raw) {
526
- results[item.index] = { index: item.index, success: false, error: item.error || "Company Signal batch request failed" };
551
+ results[item.index] = batchItemFailure(
552
+ item.index,
553
+ item.error || "Company Signal batch request failed",
554
+ item.raw ?? item
555
+ );
527
556
  continue;
528
557
  }
529
558
  if (!isPendingSignalResponse(item.raw) || params.waitForResult === false) {
@@ -572,6 +601,7 @@ var Signaliz = class {
572
601
  validateSignalToCopyParams(params);
573
602
  const request = signalToCopyRequestBody(params);
574
603
  let data = await this.client.post("api/v1/signal-to-copy", request);
604
+ if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
575
605
  if (params.waitForResult !== false && (data.signal_run_id || data.run_id) && isPendingSignalResponse(data)) {
576
606
  const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
577
607
  const startedAt = Date.now();
@@ -590,6 +620,13 @@ var Signaliz = class {
590
620
  async createSignalCopyBatch(requests, options) {
591
621
  validateBatchSize(requests);
592
622
  requests.forEach(validateSignalToCopyParams);
623
+ if (options?.dryRun === true) {
624
+ return this.runCoreProductDryRun(
625
+ "api/v1/signal-to-copy",
626
+ requests.map(signalToCopyRequestBody),
627
+ options.idempotencyKey
628
+ );
629
+ }
593
630
  const startedAt = Date.now();
594
631
  const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
595
632
  request: signalToCopyRequestBody(params),
@@ -616,6 +653,17 @@ var Signaliz = class {
616
653
  results: compactedResults
617
654
  };
618
655
  }
656
+ async runCoreProductDryRun(path, requests, idempotencyKey) {
657
+ const data = await this.client.post(path, compact({
658
+ requests,
659
+ dry_run: true,
660
+ idempotency_key: idempotencyKey
661
+ }));
662
+ if (!isCoreProductDryRun(data)) {
663
+ throw new Error(`${path} dry run returned a live-result contract`);
664
+ }
665
+ return normalizeCoreProductDryRunResult(data);
666
+ }
619
667
  async runSignalCopyBatchChunks(uniqueRequests, options) {
620
668
  const chunks = [];
621
669
  for (let offset = 0; offset < uniqueRequests.length; offset += 25) {
@@ -632,11 +680,11 @@ var Signaliz = class {
632
680
  const chunk = chunks[chunkResult.index];
633
681
  if (!chunkResult.success || !chunkResult.data) {
634
682
  for (let index = 0; index < chunk.requests.length; index += 1) {
635
- uniqueResults[chunk.offset + index] = {
636
- index: chunk.offset + index,
637
- success: false,
638
- error: chunkResult.error || "Signal to Copy batch request failed"
639
- };
683
+ uniqueResults[chunk.offset + index] = batchItemFailure(
684
+ chunk.offset + index,
685
+ chunkResult.error || "Signal to Copy batch request failed",
686
+ chunkResult
687
+ );
640
688
  }
641
689
  continue;
642
690
  }
@@ -658,7 +706,7 @@ var Signaliz = class {
658
706
  const results = new Array(requests.length);
659
707
  for (const row of rows) {
660
708
  const index = Number(row.index);
661
- results[index] = recoverableJobRowFailed(row) ? { index, success: false, error: row.error || row.message || "Signal to Copy failed" } : { index, success: true, data: normalizeSignalToCopyResult(row) };
709
+ results[index] = recoverableJobRowFailed(row) ? batchItemFailure(index, row.error || row.message || "Signal to Copy failed", row) : { index, success: true, data: normalizeSignalToCopyResult(row) };
662
710
  }
663
711
  return results;
664
712
  }
@@ -763,7 +811,11 @@ var Signaliz = class {
763
811
  rateLimited.push({ index: task.index, params: task.params, raw: item });
764
812
  continue;
765
813
  }
766
- results[task.index] = { index: task.index, success: false, error: item.error || item.message || "Signal to Copy failed" };
814
+ results[task.index] = batchItemFailure(
815
+ task.index,
816
+ item.error || item.message || "Signal to Copy failed",
817
+ item
818
+ );
767
819
  continue;
768
820
  }
769
821
  if (!isPendingSignalResponse(item)) {
@@ -868,7 +920,10 @@ var Signaliz = class {
868
920
  uniqueResults[chunk.offset + index] = {
869
921
  index: chunk.tasks[index].index,
870
922
  success: false,
871
- error: chunkResult.error || `${path} batch request failed`
923
+ error: chunkResult.error || `${path} batch request failed`,
924
+ errorCode: chunkResult.errorCode,
925
+ retryEligible: chunkResult.retryEligible,
926
+ retryAfterSeconds: chunkResult.retryAfterSeconds
872
927
  };
873
928
  }
874
929
  continue;
@@ -934,6 +989,21 @@ function rowRateLimitDelayMs(items) {
934
989
  });
935
990
  return Math.max(1, Math.min(6e4, Math.max(...delays)));
936
991
  }
992
+ function batchItemFailure(index, error, raw) {
993
+ const errorCode = String(raw?.error_code ?? raw?.code ?? raw?.errorCode ?? "").trim();
994
+ const retryAfterSeconds2 = Number(
995
+ raw?.retry_after_seconds ?? raw?.retry_after ?? raw?.retryAfterSeconds ?? (Number.isFinite(Number(raw?.retry_after_ms)) ? Number(raw?.retry_after_ms) / 1e3 : void 0)
996
+ );
997
+ const retryEligible = raw?.retry_eligible ?? raw?.retryEligible;
998
+ return {
999
+ index,
1000
+ success: false,
1001
+ error,
1002
+ ...errorCode ? { errorCode } : {},
1003
+ ...typeof retryEligible === "boolean" ? { retryEligible } : {},
1004
+ ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {}
1005
+ };
1006
+ }
937
1007
  function recoverableJobRowFailed(row) {
938
1008
  return row.success === false || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
939
1009
  }
@@ -952,9 +1022,34 @@ function findEmailRequestBody(params) {
952
1022
  last_name: params.lastName,
953
1023
  linkedin_url: params.linkedinUrl,
954
1024
  company_name: params.companyName,
955
- skip_cache: params.skipCache
1025
+ skip_cache: params.skipCache,
1026
+ dry_run: params.dryRun,
1027
+ idempotency_key: params.idempotencyKey
956
1028
  });
957
1029
  }
1030
+ function isCoreProductDryRun(data) {
1031
+ return data.dry_run === true && data.status === "planned";
1032
+ }
1033
+ function normalizeCoreProductDryRunResult(data) {
1034
+ const estimatedCredits = data.estimated_credits && typeof data.estimated_credits === "object" ? data.estimated_credits : { min: 0, max: 0 };
1035
+ return {
1036
+ success: true,
1037
+ dryRun: true,
1038
+ status: "planned",
1039
+ capability: String(data.capability || ""),
1040
+ inputCount: Number(data.input_count || 0),
1041
+ recoveryReadCount: Number(data.recovery_read_count || 0),
1042
+ plan: data.plan && typeof data.plan === "object" ? data.plan : {},
1043
+ estimate: data.estimate && typeof data.estimate === "object" ? data.estimate : {},
1044
+ estimatedCredits: {
1045
+ min: Number(estimatedCredits.min || 0),
1046
+ max: Number(estimatedCredits.max || 0)
1047
+ },
1048
+ creditsCharged: 0,
1049
+ sideEffects: Array.isArray(data.side_effects) ? data.side_effects : [],
1050
+ raw: data
1051
+ };
1052
+ }
958
1053
  function normalizeFindEmailResult(data) {
959
1054
  const email = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
960
1055
  const found = Boolean(email) && data.found !== false;
@@ -1023,7 +1118,9 @@ function companySignalRequestBody(params) {
1023
1118
  // REST always returns a resumable run instead of holding the connection;
1024
1119
  // waitForResult controls the SDK polling loop.
1025
1120
  wait_for_result: false,
1026
- max_wait_ms: params.maxWaitMs
1121
+ max_wait_ms: params.maxWaitMs,
1122
+ dry_run: params.dryRun,
1123
+ idempotency_key: params.idempotencyKey
1027
1124
  });
1028
1125
  }
1029
1126
  function companySignalRecoverableBatchOptions(companies, tasks) {
@@ -1144,7 +1241,11 @@ function finalizeCoreBatch(total, startedAt, round, normalize, options) {
1144
1241
  const results = new Array(total);
1145
1242
  for (const item of round) {
1146
1243
  if (!item.success || !item.raw) {
1147
- results[item.index] = { index: item.index, success: false, error: item.error || "Signaliz batch item failed" };
1244
+ results[item.index] = batchItemFailure(
1245
+ item.index,
1246
+ item.error || "Signaliz batch item failed",
1247
+ item.raw ?? item
1248
+ );
1148
1249
  continue;
1149
1250
  }
1150
1251
  try {
@@ -1191,7 +1292,9 @@ function signalToCopyRequestBody(params) {
1191
1292
  research_prompt: params.researchPrompt,
1192
1293
  signal_run_id: params.signalRunId,
1193
1294
  skip_cache: params.skipCache,
1194
- enable_deep_search: params.enableDeepSearch
1295
+ enable_deep_search: params.enableDeepSearch,
1296
+ dry_run: params.dryRun,
1297
+ idempotency_key: params.idempotencyKey
1195
1298
  });
1196
1299
  }
1197
1300
  function validateSignalToCopyParams(params) {
@@ -1272,11 +1375,15 @@ async function runBatch(items, execute, options) {
1272
1375
  try {
1273
1376
  results[index] = { index, success: true, data: await execute(items[index]) };
1274
1377
  } catch (error) {
1275
- results[index] = {
1378
+ results[index] = batchItemFailure(
1276
1379
  index,
1277
- success: false,
1278
- error: error instanceof Error ? error.message : String(error)
1279
- };
1380
+ error instanceof Error ? error.message : String(error),
1381
+ error instanceof SignalizError ? {
1382
+ error_code: error.code,
1383
+ retry_eligible: error.isRetryable,
1384
+ retry_after_seconds: error.retryAfter
1385
+ } : void 0
1386
+ );
1280
1387
  }
1281
1388
  }
1282
1389
  };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Signaliz,
3
3
  SignalizError
4
- } from "./chunk-NAJO2CAD.mjs";
4
+ } from "./chunk-KTWDTUCI.mjs";
5
5
  export {
6
6
  Signaliz,
7
7
  SignalizError
@@ -409,10 +409,17 @@ var Signaliz = class {
409
409
  }
410
410
  async findEmail(params) {
411
411
  const data = await this.client.post("api/v1/find-email", findEmailRequestBody(params));
412
- return normalizeFindEmailResult(data);
412
+ return isCoreProductDryRun(data) ? normalizeCoreProductDryRunResult(data) : normalizeFindEmailResult(data);
413
413
  }
414
414
  async findEmails(params, options) {
415
415
  validateBatchSize(params);
416
+ if (options?.dryRun === true) {
417
+ return this.runCoreProductDryRun(
418
+ "api/v1/find-email",
419
+ params.map(findEmailRequestBody),
420
+ options.idempotencyKey
421
+ );
422
+ }
416
423
  const startedAt = Date.now();
417
424
  const round = await this.runCoreBatchRound(
418
425
  "api/v1/find-email",
@@ -431,9 +438,12 @@ var Signaliz = class {
431
438
  const request = compact({
432
439
  email: email || void 0,
433
440
  verification_run_id: options?.verificationRunId,
434
- skip_cache: options?.skipCache
441
+ skip_cache: options?.skipCache,
442
+ dry_run: options?.dryRun,
443
+ idempotency_key: options?.idempotencyKey
435
444
  });
436
445
  let data = await this.client.post("api/v1/verify-email", request);
446
+ if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
437
447
  if (options?.waitForResult !== false && isPendingSignalResponse(data) && data.verification_run_id) {
438
448
  const maxWaitMs = Math.max(1e3, options?.maxWaitMs ?? 10 * 6e4);
439
449
  const startedAt = Date.now();
@@ -453,6 +463,13 @@ var Signaliz = class {
453
463
  }
454
464
  async verifyEmails(emails, options) {
455
465
  validateBatchSize(emails);
466
+ if (options?.dryRun === true) {
467
+ return this.runCoreProductDryRun(
468
+ "api/v1/verify-email",
469
+ emails.map((email) => ({ email, skip_cache: options.skipCache })),
470
+ options.idempotencyKey
471
+ );
472
+ }
456
473
  const startedAt = Date.now();
457
474
  const round = await this.runCoreBatchRound(
458
475
  "api/v1/verify-email",
@@ -473,6 +490,7 @@ var Signaliz = class {
473
490
  async enrichCompanySignals(params) {
474
491
  const request = companySignalRequestBody(params);
475
492
  let data = await this.client.post("api/v1/company-signals", request);
493
+ if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
476
494
  if (params.waitForResult !== false && data.run_id && isPendingSignalResponse(data)) {
477
495
  const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
478
496
  const startedAt = Date.now();
@@ -503,6 +521,13 @@ var Signaliz = class {
503
521
  }
504
522
  async enrichCompanies(companies, options) {
505
523
  validateBatchSize(companies);
524
+ if (options?.dryRun === true) {
525
+ return this.runCoreProductDryRun(
526
+ "api/v1/company-signals",
527
+ companies.map(companySignalRequestBody),
528
+ options.idempotencyKey
529
+ );
530
+ }
506
531
  const startedAt = Date.now();
507
532
  const results = new Array(companies.length);
508
533
  const initialTasks = companies.map((params, index) => ({
@@ -527,7 +552,11 @@ var Signaliz = class {
527
552
  const params = companies[item.index];
528
553
  const task = taskByIndex.get(item.index);
529
554
  if (!item.success || !item.raw) {
530
- results[item.index] = { index: item.index, success: false, error: item.error || "Company Signal batch request failed" };
555
+ results[item.index] = batchItemFailure(
556
+ item.index,
557
+ item.error || "Company Signal batch request failed",
558
+ item.raw ?? item
559
+ );
531
560
  continue;
532
561
  }
533
562
  if (!isPendingSignalResponse(item.raw) || params.waitForResult === false) {
@@ -576,6 +605,7 @@ var Signaliz = class {
576
605
  validateSignalToCopyParams(params);
577
606
  const request = signalToCopyRequestBody(params);
578
607
  let data = await this.client.post("api/v1/signal-to-copy", request);
608
+ if (isCoreProductDryRun(data)) return normalizeCoreProductDryRunResult(data);
579
609
  if (params.waitForResult !== false && (data.signal_run_id || data.run_id) && isPendingSignalResponse(data)) {
580
610
  const maxWaitMs = Math.max(1e3, params.maxWaitMs ?? 20 * 6e4);
581
611
  const startedAt = Date.now();
@@ -594,6 +624,13 @@ var Signaliz = class {
594
624
  async createSignalCopyBatch(requests, options) {
595
625
  validateBatchSize(requests);
596
626
  requests.forEach(validateSignalToCopyParams);
627
+ if (options?.dryRun === true) {
628
+ return this.runCoreProductDryRun(
629
+ "api/v1/signal-to-copy",
630
+ requests.map(signalToCopyRequestBody),
631
+ options.idempotencyKey
632
+ );
633
+ }
597
634
  const startedAt = Date.now();
598
635
  const deduplicated = dedupeExactBatchItems(requests, (params) => JSON.stringify({
599
636
  request: signalToCopyRequestBody(params),
@@ -620,6 +657,17 @@ var Signaliz = class {
620
657
  results: compactedResults
621
658
  };
622
659
  }
660
+ async runCoreProductDryRun(path2, requests, idempotencyKey) {
661
+ const data = await this.client.post(path2, compact({
662
+ requests,
663
+ dry_run: true,
664
+ idempotency_key: idempotencyKey
665
+ }));
666
+ if (!isCoreProductDryRun(data)) {
667
+ throw new Error(`${path2} dry run returned a live-result contract`);
668
+ }
669
+ return normalizeCoreProductDryRunResult(data);
670
+ }
623
671
  async runSignalCopyBatchChunks(uniqueRequests, options) {
624
672
  const chunks = [];
625
673
  for (let offset = 0; offset < uniqueRequests.length; offset += 25) {
@@ -636,11 +684,11 @@ var Signaliz = class {
636
684
  const chunk = chunks[chunkResult.index];
637
685
  if (!chunkResult.success || !chunkResult.data) {
638
686
  for (let index = 0; index < chunk.requests.length; index += 1) {
639
- uniqueResults[chunk.offset + index] = {
640
- index: chunk.offset + index,
641
- success: false,
642
- error: chunkResult.error || "Signal to Copy batch request failed"
643
- };
687
+ uniqueResults[chunk.offset + index] = batchItemFailure(
688
+ chunk.offset + index,
689
+ chunkResult.error || "Signal to Copy batch request failed",
690
+ chunkResult
691
+ );
644
692
  }
645
693
  continue;
646
694
  }
@@ -662,7 +710,7 @@ var Signaliz = class {
662
710
  const results = new Array(requests.length);
663
711
  for (const row of rows) {
664
712
  const index = Number(row.index);
665
- results[index] = recoverableJobRowFailed(row) ? { index, success: false, error: row.error || row.message || "Signal to Copy failed" } : { index, success: true, data: normalizeSignalToCopyResult(row) };
713
+ results[index] = recoverableJobRowFailed(row) ? batchItemFailure(index, row.error || row.message || "Signal to Copy failed", row) : { index, success: true, data: normalizeSignalToCopyResult(row) };
666
714
  }
667
715
  return results;
668
716
  }
@@ -767,7 +815,11 @@ var Signaliz = class {
767
815
  rateLimited.push({ index: task.index, params: task.params, raw: item });
768
816
  continue;
769
817
  }
770
- results[task.index] = { index: task.index, success: false, error: item.error || item.message || "Signal to Copy failed" };
818
+ results[task.index] = batchItemFailure(
819
+ task.index,
820
+ item.error || item.message || "Signal to Copy failed",
821
+ item
822
+ );
771
823
  continue;
772
824
  }
773
825
  if (!isPendingSignalResponse(item)) {
@@ -872,7 +924,10 @@ var Signaliz = class {
872
924
  uniqueResults[chunk.offset + index] = {
873
925
  index: chunk.tasks[index].index,
874
926
  success: false,
875
- error: chunkResult.error || `${path2} batch request failed`
927
+ error: chunkResult.error || `${path2} batch request failed`,
928
+ errorCode: chunkResult.errorCode,
929
+ retryEligible: chunkResult.retryEligible,
930
+ retryAfterSeconds: chunkResult.retryAfterSeconds
876
931
  };
877
932
  }
878
933
  continue;
@@ -938,6 +993,21 @@ function rowRateLimitDelayMs(items) {
938
993
  });
939
994
  return Math.max(1, Math.min(6e4, Math.max(...delays)));
940
995
  }
996
+ function batchItemFailure(index, error, raw) {
997
+ const errorCode = String(raw?.error_code ?? raw?.code ?? raw?.errorCode ?? "").trim();
998
+ const retryAfterSeconds2 = Number(
999
+ raw?.retry_after_seconds ?? raw?.retry_after ?? raw?.retryAfterSeconds ?? (Number.isFinite(Number(raw?.retry_after_ms)) ? Number(raw?.retry_after_ms) / 1e3 : void 0)
1000
+ );
1001
+ const retryEligible = raw?.retry_eligible ?? raw?.retryEligible;
1002
+ return {
1003
+ index,
1004
+ success: false,
1005
+ error,
1006
+ ...errorCode ? { errorCode } : {},
1007
+ ...typeof retryEligible === "boolean" ? { retryEligible } : {},
1008
+ ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {}
1009
+ };
1010
+ }
941
1011
  function recoverableJobRowFailed(row) {
942
1012
  return row.success === false || ["failed", "skipped"].includes(String(row._status || "").toLowerCase());
943
1013
  }
@@ -956,9 +1026,34 @@ function findEmailRequestBody(params) {
956
1026
  last_name: params.lastName,
957
1027
  linkedin_url: params.linkedinUrl,
958
1028
  company_name: params.companyName,
959
- skip_cache: params.skipCache
1029
+ skip_cache: params.skipCache,
1030
+ dry_run: params.dryRun,
1031
+ idempotency_key: params.idempotencyKey
960
1032
  });
961
1033
  }
1034
+ function isCoreProductDryRun(data) {
1035
+ return data.dry_run === true && data.status === "planned";
1036
+ }
1037
+ function normalizeCoreProductDryRunResult(data) {
1038
+ const estimatedCredits = data.estimated_credits && typeof data.estimated_credits === "object" ? data.estimated_credits : { min: 0, max: 0 };
1039
+ return {
1040
+ success: true,
1041
+ dryRun: true,
1042
+ status: "planned",
1043
+ capability: String(data.capability || ""),
1044
+ inputCount: Number(data.input_count || 0),
1045
+ recoveryReadCount: Number(data.recovery_read_count || 0),
1046
+ plan: data.plan && typeof data.plan === "object" ? data.plan : {},
1047
+ estimate: data.estimate && typeof data.estimate === "object" ? data.estimate : {},
1048
+ estimatedCredits: {
1049
+ min: Number(estimatedCredits.min || 0),
1050
+ max: Number(estimatedCredits.max || 0)
1051
+ },
1052
+ creditsCharged: 0,
1053
+ sideEffects: Array.isArray(data.side_effects) ? data.side_effects : [],
1054
+ raw: data
1055
+ };
1056
+ }
962
1057
  function normalizeFindEmailResult(data) {
963
1058
  const email = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
964
1059
  const found = Boolean(email) && data.found !== false;
@@ -1027,7 +1122,9 @@ function companySignalRequestBody(params) {
1027
1122
  // REST always returns a resumable run instead of holding the connection;
1028
1123
  // waitForResult controls the SDK polling loop.
1029
1124
  wait_for_result: false,
1030
- max_wait_ms: params.maxWaitMs
1125
+ max_wait_ms: params.maxWaitMs,
1126
+ dry_run: params.dryRun,
1127
+ idempotency_key: params.idempotencyKey
1031
1128
  });
1032
1129
  }
1033
1130
  function companySignalRecoverableBatchOptions(companies, tasks) {
@@ -1148,7 +1245,11 @@ function finalizeCoreBatch(total, startedAt, round, normalize, options) {
1148
1245
  const results = new Array(total);
1149
1246
  for (const item of round) {
1150
1247
  if (!item.success || !item.raw) {
1151
- results[item.index] = { index: item.index, success: false, error: item.error || "Signaliz batch item failed" };
1248
+ results[item.index] = batchItemFailure(
1249
+ item.index,
1250
+ item.error || "Signaliz batch item failed",
1251
+ item.raw ?? item
1252
+ );
1152
1253
  continue;
1153
1254
  }
1154
1255
  try {
@@ -1195,7 +1296,9 @@ function signalToCopyRequestBody(params) {
1195
1296
  research_prompt: params.researchPrompt,
1196
1297
  signal_run_id: params.signalRunId,
1197
1298
  skip_cache: params.skipCache,
1198
- enable_deep_search: params.enableDeepSearch
1299
+ enable_deep_search: params.enableDeepSearch,
1300
+ dry_run: params.dryRun,
1301
+ idempotency_key: params.idempotencyKey
1199
1302
  });
1200
1303
  }
1201
1304
  function validateSignalToCopyParams(params) {
@@ -1276,11 +1379,15 @@ async function runBatch(items, execute, options) {
1276
1379
  try {
1277
1380
  results[index] = { index, success: true, data: await execute(items[index]) };
1278
1381
  } catch (error) {
1279
- results[index] = {
1382
+ results[index] = batchItemFailure(
1280
1383
  index,
1281
- success: false,
1282
- error: error instanceof Error ? error.message : String(error)
1283
- };
1384
+ error instanceof Error ? error.message : String(error),
1385
+ error instanceof SignalizError ? {
1386
+ error_code: error.code,
1387
+ retry_eligible: error.isRetryable,
1388
+ retry_after_seconds: error.retryAfter
1389
+ } : void 0
1390
+ );
1284
1391
  }
1285
1392
  }
1286
1393
  };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Signaliz
4
- } from "./chunk-NAJO2CAD.mjs";
4
+ } from "./chunk-KTWDTUCI.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.45",
3
+ "version": "1.0.47",
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",