@tratto/email 1.1.1 → 1.2.0

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
@@ -252,9 +252,24 @@ await tratto.campaigns.send(id, { scheduledAt: new Date('2025-07-01T09:00:00Z')
252
252
  const stats = await tratto.campaigns.getStats(id);
253
253
  console.log(stats.rates.openRate);
254
254
 
255
+ // Cancel a scheduled send: back to draft, scheduledAt cleared
256
+ await tratto.campaigns.unschedule(id);
257
+
258
+ // 409 if it cannot: the send already started, the campaign is in another
259
+ // status, or it already sent a test wave to part of the list and is waiting
260
+ // for the bounce rate. Those emails cannot be recalled, so pause it instead.
261
+
255
262
  // Pause
256
263
  await tratto.campaigns.pause(id);
257
264
 
265
+ // Why a campaign is paused: 'quota_exceeded', 'schedule_missed',
266
+ // 'bounce_rate' (it stopped itself on too many permanent bounces),
267
+ // or null when it was paused by hand.
268
+ const campaign = await tratto.campaigns.get(id);
269
+ if (campaign.pausedReason === 'bounce_rate') {
270
+ console.log('Stopped: too many bounces. Clean the list before resuming.');
271
+ }
272
+
258
273
  // Test send
259
274
  const { emailId } = await tratto.campaigns.testSend(id, 'me@example.com');
260
275
  ```
@@ -442,6 +457,7 @@ import type {
442
457
  Contact,
443
458
  Audience,
444
459
  Campaign,
460
+ CampaignPausedReason,
445
461
  CampaignStatsDetail,
446
462
  Template,
447
463
  Webhook,
@@ -462,14 +478,23 @@ See the [`examples/`](examples/) folder:
462
478
 
463
479
  | File | Description |
464
480
  |---|---|
465
- | [`send-email.ts`](examples/send-email.ts) | Send transactional emails (HTML, template, with idempotency) |
481
+ | [`send-email.ts`](examples/send-email.ts) | Send transactional emails (HTML, template, with idempotency), read the timeline |
466
482
  | [`contacts.ts`](examples/contacts.ts) | Contact management and CSV bulk import |
467
- | [`campaign.ts`](examples/campaign.ts) | Create, configure, and send a marketing campaign |
483
+ | [`audiences.ts`](examples/audiences.ts) | Rule-based audiences, adding contacts to one |
484
+ | [`campaign.ts`](examples/campaign.ts) | Create, configure, send, unschedule and pause a marketing campaign |
485
+ | [`templates.ts`](examples/templates.ts) | Template life cycle: create, edit, versions, test send, delete |
468
486
  | [`analytics.ts`](examples/analytics.ts) | Fetch delivery metrics and daily timeseries |
469
487
  | [`webhook.ts`](examples/webhook.ts) | Register a webhook and inspect delivery history |
488
+ | [`domains.ts`](examples/domains.ts) | Add a sending domain, print its DNS records, verify it |
489
+ | [`flows.ts`](examples/flows.ts) | Automation flows (needs an API key with the `*` permission) |
490
+ | [`workspace.ts`](examples/workspace.ts) | Workspace settings, per-send-type senders, members |
470
491
  | [`nextjs.ts`](examples/nextjs.ts) | Next.js App Router route handler sending a welcome email |
471
492
  | [`express.ts`](examples/express.ts) | Express route sending a password-reset email |
472
493
  | [`fastify.ts`](examples/fastify.ts) | Fastify route sending an order-confirmation email via template |
494
+ | [`smoke.ts`](examples/smoke.ts) | Runnable round-trip against a real API in test mode ([how to run](CONTRIBUTING.md#manual-smoke-test)) |
495
+
496
+ Every example is type-checked and linted with the rest of the repo, so a
497
+ renamed method breaks the build instead of a user's integration.
473
498
 
474
499
  Run any example with [tsx](https://github.com/privatenumber/tsx):
475
500
 
@@ -477,6 +502,9 @@ Run any example with [tsx](https://github.com/privatenumber/tsx):
477
502
  TRATTO_API_KEY=tratto_live_... npx tsx examples/send-email.ts
478
503
  ```
479
504
 
505
+ The examples are written the way you would use the SDK, against real
506
+ resources: read them before you run them.
507
+
480
508
  ---
481
509
 
482
510
  ## Contributing
package/dist/index.d.mts CHANGED
@@ -154,6 +154,15 @@ interface AddContactsToAudienceResult {
154
154
  notFound: number;
155
155
  }
156
156
  type CampaignStatus = 'draft' | 'sending' | 'scheduled' | 'paused' | 'completed';
157
+ /**
158
+ * Why a paused campaign is paused. `null` for a manual pause, or for a
159
+ * campaign that was never paused.
160
+ *
161
+ * - `quota_exceeded` — the dispatcher hit the monthly plan cap mid-send.
162
+ * - `schedule_missed` — the scheduled send window passed without dispatch.
163
+ * - `bounce_rate` — too many permanent bounces, the campaign stopped itself.
164
+ */
165
+ type CampaignPausedReason = 'quota_exceeded' | 'schedule_missed' | 'bounce_rate';
157
166
  interface CampaignStats {
158
167
  total: number;
159
168
  sent: number;
@@ -166,6 +175,8 @@ interface Campaign {
166
175
  id: string;
167
176
  name: string;
168
177
  status: CampaignStatus;
178
+ /** Set whenever `status` is `paused`, `null` otherwise. */
179
+ pausedReason: CampaignPausedReason | null;
169
180
  templateId: string;
170
181
  audienceId: string;
171
182
  fromName: string;
@@ -373,6 +384,29 @@ interface ListFlowsParams {
373
384
  type WorkspacePlan = 'free' | 'starter' | 'growth';
374
385
  type WorkspaceMemberRole = 'owner' | 'admin' | 'member';
375
386
  type WorkspaceLocale = 'it' | 'en';
387
+ type SendType = 'marketing' | 'automation' | 'transactional';
388
+ /**
389
+ * Sender for one send type. `fromEmail` must be on a domain already verified
390
+ * for the workspace; `replyTo` is a response header, not the envelope sender,
391
+ * so it needs no verified domain.
392
+ */
393
+ interface Sender {
394
+ fromEmail: string;
395
+ fromName: string;
396
+ replyTo?: string | null;
397
+ }
398
+ /**
399
+ * Default sender per send type (API >= 0.5.7).
400
+ *
401
+ * `null` on a type means it is not configured and **inherits** the
402
+ * workspace-wide `defaultFromEmail`/`defaultFromName` — it is never a reason
403
+ * for a send to be refused.
404
+ *
405
+ * Which type applies where: `marketing` for campaigns and template test sends,
406
+ * `automation` for the emails a flow sends (resolved when the flow is
407
+ * activated), `transactional` for API sends that carry no `from` of their own.
408
+ */
409
+ type Senders = Record<SendType, Sender | null>;
376
410
  interface Workspace {
377
411
  id: string;
378
412
  name: string;
@@ -383,6 +417,11 @@ interface Workspace {
383
417
  /** Workspace default sender, used when a send does not specify one. */
384
418
  defaultFromName: string | null;
385
419
  defaultFromEmail: string | null;
420
+ /**
421
+ * Per-send-type senders. Optional: an API older than 0.5.7 does not send
422
+ * the field at all, so read it defensively rather than assuming three keys.
423
+ */
424
+ senders?: Partial<Senders>;
386
425
  /**
387
426
  * Tenant-hosted unsubscribe/preference page. When set, {{unsubscribe_url}}
388
427
  * resolves here instead of the Tratto-hosted page.
@@ -418,6 +457,13 @@ interface UpdateWorkspaceParams {
418
457
  locale?: WorkspaceLocale;
419
458
  defaultFromName?: string;
420
459
  defaultFromEmail?: string;
460
+ /**
461
+ * Partial write: sending only `marketing` leaves the other two untouched,
462
+ * and `{ marketing: null }` puts that type back to inheriting the
463
+ * workspace-wide default. Both halves of a sender travel together — a
464
+ * `fromEmail` without a `fromName` is refused.
465
+ */
466
+ senders?: Partial<Record<SendType, Sender | null>>;
421
467
  /** Set to null to clear and fall back to the Tratto-hosted page. */
422
468
  customUnsubscribeUrl?: string | null;
423
469
  /**
@@ -486,6 +532,21 @@ declare class CampaignsResource extends BaseResource {
486
532
  send(id: string, params?: SendCampaignParams): Promise<{
487
533
  status: string;
488
534
  }>;
535
+ /**
536
+ * Cancel a scheduled send: the campaign goes back to `draft` and its
537
+ * `scheduledAt` is cleared.
538
+ *
539
+ * Only a campaign still waiting for its date can be unscheduled. Anything
540
+ * else answers 409: a send already running, a campaign in any other status,
541
+ * or one that has already sent a test wave to part of its list and is
542
+ * waiting for the bounce rate before sending the rest. In those cases the
543
+ * emails already out cannot be recalled, so `pause()` is the way to stop it.
544
+ */
545
+ unschedule(id: string): Promise<{
546
+ id: string;
547
+ status: 'draft';
548
+ scheduledAt: null;
549
+ }>;
489
550
  pause(id: string): Promise<{
490
551
  status: string;
491
552
  }>;
@@ -580,4 +641,4 @@ declare class Tratto {
580
641
  constructor(apiKey: string, options?: TrattoOptions);
581
642
  }
582
643
 
583
- export { type AddContactsToAudienceResult, type AnalyticsPeriod, type AnalyticsSummary, type Audience, type AudienceRule, type AudienceRuleOperator, type Campaign, type CampaignStats, type CampaignStatsDetail, type CampaignStatus, type Contact, type ContactStatus, type CreateAudienceParams, type CreateCampaignParams, type CreateContactParams, type CreateFlowParams, type CreateTemplateParams, type CreateWebhookParams, type Domain, type DomainRecord, type DomainStatus, type DomainSummary, type EmailDetail, type EmailEvent, type EmailSummary, type Flow, type FlowStatus, type FlowStep, type FlowStepType, type FlowTrigger, type FlowTriggerType, type ImportJobStatus, type InviteMemberParams, type ListAudiencesParams, type ListCampaignsParams, type ListContactsParams, type ListDomainsParams, type ListEmailsParams, type ListFlowsParams, type ListTemplatesParams, type ListWebhookDeliveriesParams, type PaginatedResponse, type Pagination, type SendCampaignParams, type SendEmailParams, type Template, type TemplateStatus, type TemplateSummary, type TemplateVersion, type TemplateVersionSummary, type TimeseriesPoint, Tratto, TrattoError, type TrattoOptions, type UpdateContactParams, type UpdateFlowParams, type UpdateMemberParams, type UpdateTemplateParams, type UpdateWorkspaceParams, type UpdateWorkspacePreferencesParams, type Webhook, type WebhookDelivery, type WebhookDeliveryStatus, type WebhookEventType, type WebhookStatus, type Workspace, type WorkspaceLocale, type WorkspaceMember, type WorkspaceMemberRole, type WorkspacePlan, type WorkspacePreferences };
644
+ export { type AddContactsToAudienceResult, type AnalyticsPeriod, type AnalyticsSummary, type Audience, type AudienceRule, type AudienceRuleOperator, type Campaign, type CampaignPausedReason, type CampaignStats, type CampaignStatsDetail, type CampaignStatus, type Contact, type ContactStatus, type CreateAudienceParams, type CreateCampaignParams, type CreateContactParams, type CreateFlowParams, type CreateTemplateParams, type CreateWebhookParams, type Domain, type DomainRecord, type DomainStatus, type DomainSummary, type EmailDetail, type EmailEvent, type EmailSummary, type Flow, type FlowStatus, type FlowStep, type FlowStepType, type FlowTrigger, type FlowTriggerType, type ImportJobStatus, type InviteMemberParams, type ListAudiencesParams, type ListCampaignsParams, type ListContactsParams, type ListDomainsParams, type ListEmailsParams, type ListFlowsParams, type ListTemplatesParams, type ListWebhookDeliveriesParams, type PaginatedResponse, type Pagination, type SendCampaignParams, type SendEmailParams, type SendType, type Sender, type Senders, type Template, type TemplateStatus, type TemplateSummary, type TemplateVersion, type TemplateVersionSummary, type TimeseriesPoint, Tratto, TrattoError, type TrattoOptions, type UpdateContactParams, type UpdateFlowParams, type UpdateMemberParams, type UpdateTemplateParams, type UpdateWorkspaceParams, type UpdateWorkspacePreferencesParams, type Webhook, type WebhookDelivery, type WebhookDeliveryStatus, type WebhookEventType, type WebhookStatus, type Workspace, type WorkspaceLocale, type WorkspaceMember, type WorkspaceMemberRole, type WorkspacePlan, type WorkspacePreferences };
package/dist/index.d.ts CHANGED
@@ -154,6 +154,15 @@ interface AddContactsToAudienceResult {
154
154
  notFound: number;
155
155
  }
156
156
  type CampaignStatus = 'draft' | 'sending' | 'scheduled' | 'paused' | 'completed';
157
+ /**
158
+ * Why a paused campaign is paused. `null` for a manual pause, or for a
159
+ * campaign that was never paused.
160
+ *
161
+ * - `quota_exceeded` — the dispatcher hit the monthly plan cap mid-send.
162
+ * - `schedule_missed` — the scheduled send window passed without dispatch.
163
+ * - `bounce_rate` — too many permanent bounces, the campaign stopped itself.
164
+ */
165
+ type CampaignPausedReason = 'quota_exceeded' | 'schedule_missed' | 'bounce_rate';
157
166
  interface CampaignStats {
158
167
  total: number;
159
168
  sent: number;
@@ -166,6 +175,8 @@ interface Campaign {
166
175
  id: string;
167
176
  name: string;
168
177
  status: CampaignStatus;
178
+ /** Set whenever `status` is `paused`, `null` otherwise. */
179
+ pausedReason: CampaignPausedReason | null;
169
180
  templateId: string;
170
181
  audienceId: string;
171
182
  fromName: string;
@@ -373,6 +384,29 @@ interface ListFlowsParams {
373
384
  type WorkspacePlan = 'free' | 'starter' | 'growth';
374
385
  type WorkspaceMemberRole = 'owner' | 'admin' | 'member';
375
386
  type WorkspaceLocale = 'it' | 'en';
387
+ type SendType = 'marketing' | 'automation' | 'transactional';
388
+ /**
389
+ * Sender for one send type. `fromEmail` must be on a domain already verified
390
+ * for the workspace; `replyTo` is a response header, not the envelope sender,
391
+ * so it needs no verified domain.
392
+ */
393
+ interface Sender {
394
+ fromEmail: string;
395
+ fromName: string;
396
+ replyTo?: string | null;
397
+ }
398
+ /**
399
+ * Default sender per send type (API >= 0.5.7).
400
+ *
401
+ * `null` on a type means it is not configured and **inherits** the
402
+ * workspace-wide `defaultFromEmail`/`defaultFromName` — it is never a reason
403
+ * for a send to be refused.
404
+ *
405
+ * Which type applies where: `marketing` for campaigns and template test sends,
406
+ * `automation` for the emails a flow sends (resolved when the flow is
407
+ * activated), `transactional` for API sends that carry no `from` of their own.
408
+ */
409
+ type Senders = Record<SendType, Sender | null>;
376
410
  interface Workspace {
377
411
  id: string;
378
412
  name: string;
@@ -383,6 +417,11 @@ interface Workspace {
383
417
  /** Workspace default sender, used when a send does not specify one. */
384
418
  defaultFromName: string | null;
385
419
  defaultFromEmail: string | null;
420
+ /**
421
+ * Per-send-type senders. Optional: an API older than 0.5.7 does not send
422
+ * the field at all, so read it defensively rather than assuming three keys.
423
+ */
424
+ senders?: Partial<Senders>;
386
425
  /**
387
426
  * Tenant-hosted unsubscribe/preference page. When set, {{unsubscribe_url}}
388
427
  * resolves here instead of the Tratto-hosted page.
@@ -418,6 +457,13 @@ interface UpdateWorkspaceParams {
418
457
  locale?: WorkspaceLocale;
419
458
  defaultFromName?: string;
420
459
  defaultFromEmail?: string;
460
+ /**
461
+ * Partial write: sending only `marketing` leaves the other two untouched,
462
+ * and `{ marketing: null }` puts that type back to inheriting the
463
+ * workspace-wide default. Both halves of a sender travel together — a
464
+ * `fromEmail` without a `fromName` is refused.
465
+ */
466
+ senders?: Partial<Record<SendType, Sender | null>>;
421
467
  /** Set to null to clear and fall back to the Tratto-hosted page. */
422
468
  customUnsubscribeUrl?: string | null;
423
469
  /**
@@ -486,6 +532,21 @@ declare class CampaignsResource extends BaseResource {
486
532
  send(id: string, params?: SendCampaignParams): Promise<{
487
533
  status: string;
488
534
  }>;
535
+ /**
536
+ * Cancel a scheduled send: the campaign goes back to `draft` and its
537
+ * `scheduledAt` is cleared.
538
+ *
539
+ * Only a campaign still waiting for its date can be unscheduled. Anything
540
+ * else answers 409: a send already running, a campaign in any other status,
541
+ * or one that has already sent a test wave to part of its list and is
542
+ * waiting for the bounce rate before sending the rest. In those cases the
543
+ * emails already out cannot be recalled, so `pause()` is the way to stop it.
544
+ */
545
+ unschedule(id: string): Promise<{
546
+ id: string;
547
+ status: 'draft';
548
+ scheduledAt: null;
549
+ }>;
489
550
  pause(id: string): Promise<{
490
551
  status: string;
491
552
  }>;
@@ -580,4 +641,4 @@ declare class Tratto {
580
641
  constructor(apiKey: string, options?: TrattoOptions);
581
642
  }
582
643
 
583
- export { type AddContactsToAudienceResult, type AnalyticsPeriod, type AnalyticsSummary, type Audience, type AudienceRule, type AudienceRuleOperator, type Campaign, type CampaignStats, type CampaignStatsDetail, type CampaignStatus, type Contact, type ContactStatus, type CreateAudienceParams, type CreateCampaignParams, type CreateContactParams, type CreateFlowParams, type CreateTemplateParams, type CreateWebhookParams, type Domain, type DomainRecord, type DomainStatus, type DomainSummary, type EmailDetail, type EmailEvent, type EmailSummary, type Flow, type FlowStatus, type FlowStep, type FlowStepType, type FlowTrigger, type FlowTriggerType, type ImportJobStatus, type InviteMemberParams, type ListAudiencesParams, type ListCampaignsParams, type ListContactsParams, type ListDomainsParams, type ListEmailsParams, type ListFlowsParams, type ListTemplatesParams, type ListWebhookDeliveriesParams, type PaginatedResponse, type Pagination, type SendCampaignParams, type SendEmailParams, type Template, type TemplateStatus, type TemplateSummary, type TemplateVersion, type TemplateVersionSummary, type TimeseriesPoint, Tratto, TrattoError, type TrattoOptions, type UpdateContactParams, type UpdateFlowParams, type UpdateMemberParams, type UpdateTemplateParams, type UpdateWorkspaceParams, type UpdateWorkspacePreferencesParams, type Webhook, type WebhookDelivery, type WebhookDeliveryStatus, type WebhookEventType, type WebhookStatus, type Workspace, type WorkspaceLocale, type WorkspaceMember, type WorkspaceMemberRole, type WorkspacePlan, type WorkspacePreferences };
644
+ export { type AddContactsToAudienceResult, type AnalyticsPeriod, type AnalyticsSummary, type Audience, type AudienceRule, type AudienceRuleOperator, type Campaign, type CampaignPausedReason, type CampaignStats, type CampaignStatsDetail, type CampaignStatus, type Contact, type ContactStatus, type CreateAudienceParams, type CreateCampaignParams, type CreateContactParams, type CreateFlowParams, type CreateTemplateParams, type CreateWebhookParams, type Domain, type DomainRecord, type DomainStatus, type DomainSummary, type EmailDetail, type EmailEvent, type EmailSummary, type Flow, type FlowStatus, type FlowStep, type FlowStepType, type FlowTrigger, type FlowTriggerType, type ImportJobStatus, type InviteMemberParams, type ListAudiencesParams, type ListCampaignsParams, type ListContactsParams, type ListDomainsParams, type ListEmailsParams, type ListFlowsParams, type ListTemplatesParams, type ListWebhookDeliveriesParams, type PaginatedResponse, type Pagination, type SendCampaignParams, type SendEmailParams, type SendType, type Sender, type Senders, type Template, type TemplateStatus, type TemplateSummary, type TemplateVersion, type TemplateVersionSummary, type TimeseriesPoint, Tratto, TrattoError, type TrattoOptions, type UpdateContactParams, type UpdateFlowParams, type UpdateMemberParams, type UpdateTemplateParams, type UpdateWorkspaceParams, type UpdateWorkspacePreferencesParams, type Webhook, type WebhookDelivery, type WebhookDeliveryStatus, type WebhookEventType, type WebhookStatus, type Workspace, type WorkspaceLocale, type WorkspaceMember, type WorkspaceMemberRole, type WorkspacePlan, type WorkspacePreferences };
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ var TrattoError = class extends Error {
37
37
  };
38
38
 
39
39
  // package.json
40
- var version = "1.1.1";
40
+ var version = "1.2.0";
41
41
 
42
42
  // src/resources/base.ts
43
43
  var BaseResource = class {
@@ -192,6 +192,23 @@ var CampaignsResource = class extends BaseResource {
192
192
  }
193
193
  return this.fetchData("POST", `/v1/campaigns/${id}/send`, { body });
194
194
  }
195
+ /**
196
+ * Cancel a scheduled send: the campaign goes back to `draft` and its
197
+ * `scheduledAt` is cleared.
198
+ *
199
+ * Only a campaign still waiting for its date can be unscheduled. Anything
200
+ * else answers 409: a send already running, a campaign in any other status,
201
+ * or one that has already sent a test wave to part of its list and is
202
+ * waiting for the bounce rate before sending the rest. In those cases the
203
+ * emails already out cannot be recalled, so `pause()` is the way to stop it.
204
+ */
205
+ unschedule(id) {
206
+ return this.fetchData(
207
+ "POST",
208
+ `/v1/campaigns/${id}/unschedule`,
209
+ { body: {} }
210
+ );
211
+ }
195
212
  pause(id) {
196
213
  return this.fetchData("POST", `/v1/campaigns/${id}/pause`, { body: {} });
197
214
  }
package/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ var TrattoError = class extends Error {
10
10
  };
11
11
 
12
12
  // package.json
13
- var version = "1.1.1";
13
+ var version = "1.2.0";
14
14
 
15
15
  // src/resources/base.ts
16
16
  var BaseResource = class {
@@ -165,6 +165,23 @@ var CampaignsResource = class extends BaseResource {
165
165
  }
166
166
  return this.fetchData("POST", `/v1/campaigns/${id}/send`, { body });
167
167
  }
168
+ /**
169
+ * Cancel a scheduled send: the campaign goes back to `draft` and its
170
+ * `scheduledAt` is cleared.
171
+ *
172
+ * Only a campaign still waiting for its date can be unscheduled. Anything
173
+ * else answers 409: a send already running, a campaign in any other status,
174
+ * or one that has already sent a test wave to part of its list and is
175
+ * waiting for the bounce rate before sending the rest. In those cases the
176
+ * emails already out cannot be recalled, so `pause()` is the way to stop it.
177
+ */
178
+ unschedule(id) {
179
+ return this.fetchData(
180
+ "POST",
181
+ `/v1/campaigns/${id}/unschedule`,
182
+ { body: {} }
183
+ );
184
+ }
168
185
  pause(id) {
169
186
  return this.fetchData("POST", `/v1/campaigns/${id}/pause`, { body: {} });
170
187
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tratto/email",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Tratto Node.js SDK \u2014 send transactional and marketing email",
5
5
  "author": "Tratto <hello@tratto.email>",
6
6
  "license": "MIT",
@@ -29,7 +29,7 @@
29
29
  "scripts": {
30
30
  "build": "tsup src/index.ts --format cjs,esm --dts --clean",
31
31
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
32
- "lint": "eslint src --ext .ts",
32
+ "lint": "eslint src examples --ext .ts",
33
33
  "typecheck": "tsc --noEmit",
34
34
  "test": "vitest run",
35
35
  "test:watch": "vitest",
@@ -49,5 +49,8 @@
49
49
  },
50
50
  "engines": {
51
51
  "node": ">=18"
52
+ },
53
+ "volta": {
54
+ "node": "24.21.0"
52
55
  }
53
56
  }