zupost 0.2.0 → 0.6.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/dist/index.mjs CHANGED
@@ -44,7 +44,7 @@ var __async = (__this, __arguments, generator) => {
44
44
  };
45
45
 
46
46
  // package.json
47
- var version = "0.2.0";
47
+ var version = "0.6.0";
48
48
 
49
49
  // src/errors.ts
50
50
  var ZupostError = class _ZupostError extends Error {
@@ -105,7 +105,7 @@ var HttpClient = class {
105
105
  __privateAdd(this, _baseUrl);
106
106
  __privateSet(this, _baseUrl, baseUrl.replace(/\/$/, ""));
107
107
  __privateSet(this, _defaultHeaders, {
108
- Authorization: `Bearer ${apiKey}`,
108
+ "Authorization": `Bearer ${apiKey}`,
109
109
  "Content-Type": "application/json",
110
110
  "User-Agent": userAgent
111
111
  });
@@ -153,6 +153,16 @@ var HttpClient = class {
153
153
  return this.request(path, "POST", body);
154
154
  });
155
155
  }
156
+ patch(path, body) {
157
+ return __async(this, null, function* () {
158
+ return this.request(path, "PATCH", body);
159
+ });
160
+ }
161
+ delete(path, body) {
162
+ return __async(this, null, function* () {
163
+ return this.request(path, "DELETE", body);
164
+ });
165
+ }
156
166
  };
157
167
  _defaultHeaders = new WeakMap();
158
168
  _baseUrl = new WeakMap();
@@ -268,6 +278,324 @@ var Campaigns = class {
268
278
  }
269
279
  };
270
280
 
281
+ // src/contact/contacts.ts
282
+ function buildQuery(params) {
283
+ const search = new URLSearchParams();
284
+ for (const [key, value] of Object.entries(params)) {
285
+ if (value === void 0) continue;
286
+ if (Array.isArray(value)) {
287
+ for (const v of value) search.append(key, String(v));
288
+ } else {
289
+ search.set(key, String(value));
290
+ }
291
+ }
292
+ const query = search.toString();
293
+ return query ? `?${query}` : "";
294
+ }
295
+ var Contacts = class {
296
+ constructor(http) {
297
+ this.http = http;
298
+ }
299
+ /**
300
+ * Create or upsert a contact by email. Idempotent on (project, email).
301
+ *
302
+ * @example
303
+ * ```typescript
304
+ * const contact = await zupost.contacts.create({
305
+ * email: 'user@example.com',
306
+ * name: 'Jane Doe',
307
+ * marketingConsent: true,
308
+ * marketingConsentSource: 'signup-form',
309
+ * tags: ['vip', 'beta'],
310
+ * });
311
+ * ```
312
+ */
313
+ create(options) {
314
+ return __async(this, null, function* () {
315
+ return this.http.post("/contact", options);
316
+ });
317
+ }
318
+ /**
319
+ * Get a contact by ID.
320
+ */
321
+ get(id) {
322
+ return __async(this, null, function* () {
323
+ return this.http.get(`/contact/${encodeURIComponent(id)}`);
324
+ });
325
+ }
326
+ /**
327
+ * List contacts with optional search, consent and tag filters.
328
+ */
329
+ list() {
330
+ return __async(this, arguments, function* (options = {}) {
331
+ const query = buildQuery({
332
+ skip: options.skip,
333
+ take: options.take,
334
+ search: options.search,
335
+ marketingConsent: options.marketingConsent === void 0 ? void 0 : String(options.marketingConsent),
336
+ tag: options.tag
337
+ });
338
+ return this.http.get(`/contact${query}`);
339
+ });
340
+ }
341
+ /**
342
+ * Update a contact. Pass only the fields that should change.
343
+ *
344
+ * Toggling `marketingConsent` or `trackingConsent` automatically records
345
+ * a corresponding ContactEvent for the audit trail.
346
+ */
347
+ update(id, patch) {
348
+ return __async(this, null, function* () {
349
+ return this.http.patch(`/contact/${encodeURIComponent(id)}`, patch);
350
+ });
351
+ }
352
+ /**
353
+ * GDPR right-to-be-forgotten. Anonymises the contact, sets `forgottenAt`
354
+ * and disables consent. Existing email events are anonymised in a
355
+ * follow-up cascade.
356
+ */
357
+ forget(id) {
358
+ return __async(this, null, function* () {
359
+ return this.http.delete(`/contact/${encodeURIComponent(id)}`);
360
+ });
361
+ }
362
+ /**
363
+ * Get the chronological event timeline for a contact.
364
+ */
365
+ events(_0) {
366
+ return __async(this, arguments, function* (id, options = {}) {
367
+ const query = buildQuery({
368
+ skip: options.skip,
369
+ take: options.take,
370
+ type: options.type
371
+ });
372
+ return this.http.get(`/contact/${encodeURIComponent(id)}/events${query}`);
373
+ });
374
+ }
375
+ /**
376
+ * GDPR data access export. Returns the complete record for a contact
377
+ * including all emails, events, subscriptions and tags.
378
+ */
379
+ export(id) {
380
+ return __async(this, null, function* () {
381
+ return this.http.get(`/contact/${encodeURIComponent(id)}/export`);
382
+ });
383
+ }
384
+ /**
385
+ * Add a tag to a contact (idempotent).
386
+ */
387
+ addTag(id, name) {
388
+ return __async(this, null, function* () {
389
+ return this.http.post(`/contact/${encodeURIComponent(id)}/tags`, { name });
390
+ });
391
+ }
392
+ /**
393
+ * Remove a tag from a contact.
394
+ */
395
+ removeTag(id, name) {
396
+ return __async(this, null, function* () {
397
+ return this.http.delete(
398
+ `/contact/${encodeURIComponent(id)}/tags/${encodeURIComponent(name)}`
399
+ );
400
+ });
401
+ }
402
+ };
403
+
404
+ // src/sequence/sequences.ts
405
+ function buildQuery2(params) {
406
+ const search = new URLSearchParams();
407
+ for (const [key, value] of Object.entries(params)) {
408
+ if (value === void 0) continue;
409
+ search.set(key, String(value));
410
+ }
411
+ const q = search.toString();
412
+ return q ? `?${q}` : "";
413
+ }
414
+ var Sequences = class {
415
+ constructor(http) {
416
+ this.http = http;
417
+ this.runs = {
418
+ list: (_0, ..._1) => __async(this, [_0, ..._1], function* (sequenceId, options = {}) {
419
+ const query = buildQuery2({
420
+ skip: options.skip,
421
+ take: options.take,
422
+ status: options.status,
423
+ contactId: options.contactId
424
+ });
425
+ return this.http.get(
426
+ `/sequence/${encodeURIComponent(sequenceId)}/runs${query}`
427
+ );
428
+ })
429
+ };
430
+ }
431
+ /**
432
+ * Create a new sequence (always starts in DRAFT). Pass `definition` to
433
+ * seed the first version, or omit it to create an empty placeholder you
434
+ * can later push versions into.
435
+ */
436
+ create(options) {
437
+ return __async(this, null, function* () {
438
+ return this.http.post("/sequence", options);
439
+ });
440
+ }
441
+ /**
442
+ * Get a sequence by ID.
443
+ */
444
+ get(id) {
445
+ return __async(this, null, function* () {
446
+ return this.http.get(`/sequence/${encodeURIComponent(id)}`);
447
+ });
448
+ }
449
+ /**
450
+ * List sequences with filters.
451
+ */
452
+ list() {
453
+ return __async(this, arguments, function* (options = {}) {
454
+ const query = buildQuery2({
455
+ skip: options.skip,
456
+ take: options.take,
457
+ type: options.type,
458
+ status: options.status,
459
+ search: options.search
460
+ });
461
+ return this.http.get(`/sequence${query}`);
462
+ });
463
+ }
464
+ /**
465
+ * Delete a sequence and all its versions and runs.
466
+ */
467
+ delete(id) {
468
+ return __async(this, null, function* () {
469
+ return this.http.delete(`/sequence/${encodeURIComponent(id)}`);
470
+ });
471
+ }
472
+ /**
473
+ * Change the lifecycle status: DRAFT, ACTIVE, PAUSED, ARCHIVED.
474
+ */
475
+ setStatus(id, status) {
476
+ return __async(this, null, function* () {
477
+ return this.http.post(`/sequence/${encodeURIComponent(id)}/status`, { status });
478
+ });
479
+ }
480
+ /**
481
+ * Save a new immutable version of the sequence definition. If `activate`
482
+ * is true (default), the new version becomes the active one for future
483
+ * triggered runs. Existing in-flight runs keep their pinned version.
484
+ */
485
+ saveVersion(id, options) {
486
+ return __async(this, null, function* () {
487
+ return this.http.post(`/sequence/${encodeURIComponent(id)}/versions`, options);
488
+ });
489
+ }
490
+ /**
491
+ * Trigger a sequence run for a contact or ad-hoc email. The sequence
492
+ * must be ACTIVE.
493
+ */
494
+ trigger(id, options) {
495
+ return __async(this, null, function* () {
496
+ if (!options.contactId && !options.email) {
497
+ throw new Error("trigger requires contactId or email");
498
+ }
499
+ return this.http.post(`/sequence/${encodeURIComponent(id)}/runs`, options);
500
+ });
501
+ }
502
+ };
503
+
504
+ // src/domain/domains.ts
505
+ var DEFAULT_REGION = "eu-central-1";
506
+ var Domains = class {
507
+ constructor(http) {
508
+ this.http = http;
509
+ }
510
+ /**
511
+ * Register a new sending domain. The response contains the `dnsRecords` you
512
+ * need to add at your DNS provider before calling {@link verify}.
513
+ *
514
+ * @example
515
+ * ```typescript
516
+ * const domain = await zupost.domains.create({ name: 'example.com' });
517
+ * for (const record of domain.dnsRecords) {
518
+ * console.log(record.type, record.name, record.value);
519
+ * }
520
+ * ```
521
+ */
522
+ create(options) {
523
+ return __async(this, null, function* () {
524
+ var _a;
525
+ return this.http.post("/domain", {
526
+ name: options.name,
527
+ region: (_a = options.region) != null ? _a : DEFAULT_REGION
528
+ });
529
+ });
530
+ }
531
+ /**
532
+ * Get a domain by ID, including its current verification status and DNS
533
+ * records.
534
+ */
535
+ get(id) {
536
+ return __async(this, null, function* () {
537
+ return this.http.get(`/domain/${encodeURIComponent(id)}`);
538
+ });
539
+ }
540
+ /**
541
+ * List all domains in the project.
542
+ */
543
+ list() {
544
+ return __async(this, null, function* () {
545
+ return this.http.get("/domain");
546
+ });
547
+ }
548
+ /**
549
+ * Start the verification process for a domain. Add the DNS records returned
550
+ * by {@link create} or {@link get} first; Zupost then checks them and
551
+ * updates the domain's `status`.
552
+ */
553
+ verify(id) {
554
+ return __async(this, null, function* () {
555
+ return this.http.post(
556
+ `/domain/${encodeURIComponent(id)}/verify`,
557
+ void 0
558
+ );
559
+ });
560
+ }
561
+ /**
562
+ * Schedule a domain for deletion.
563
+ */
564
+ delete(id) {
565
+ return __async(this, null, function* () {
566
+ return this.http.delete(`/domain/${encodeURIComponent(id)}`);
567
+ });
568
+ }
569
+ };
570
+
571
+ // src/inbound/inbound.ts
572
+ var Inbound = class {
573
+ constructor(http) {
574
+ this.http = http;
575
+ /**
576
+ * Inbound channels route mail received on a domain to a webhook.
577
+ */
578
+ this.channels = {
579
+ /**
580
+ * Create an inbound channel for a verified domain.
581
+ *
582
+ * @example
583
+ * ```typescript
584
+ * const channel = await zupost.inbound.channels.create({
585
+ * name: 'Support inbox',
586
+ * domainId: 'dom_123',
587
+ * webhookUrl: 'https://example.com/zupost/inbound',
588
+ * webhookSecret: process.env.ZUPOST_INBOUND_SECRET!,
589
+ * });
590
+ * ```
591
+ */
592
+ create: (options) => __async(this, null, function* () {
593
+ return this.http.post("/inbound-channel", options);
594
+ })
595
+ };
596
+ }
597
+ };
598
+
271
599
  // src/zupost.ts
272
600
  var _client;
273
601
  var Zupost = class {
@@ -281,10 +609,54 @@ var Zupost = class {
281
609
  __privateSet(this, _client, new HttpClient(apiKey, baseUrl, `node-sdk@${version}`));
282
610
  this.emails = new Emails(__privateGet(this, _client));
283
611
  this.campaigns = new Campaigns(__privateGet(this, _client));
612
+ this.contacts = new Contacts(__privateGet(this, _client));
613
+ this.sequences = new Sequences(__privateGet(this, _client));
614
+ this.domains = new Domains(__privateGet(this, _client));
615
+ this.inbound = new Inbound(__privateGet(this, _client));
284
616
  }
285
617
  };
286
618
  _client = new WeakMap();
619
+
620
+ // src/sequence/define.ts
621
+ function defineSequence(definition) {
622
+ const issues = [];
623
+ const stepKeys = Object.keys(definition.steps);
624
+ if (!definition.steps[definition.entry]) {
625
+ issues.push(`entry "${String(definition.entry)}" is not a defined step`);
626
+ }
627
+ if (stepKeys.length === 0) issues.push("steps must contain at least one step");
628
+ for (const key of stepKeys) {
629
+ const step = definition.steps[key];
630
+ if (step.key !== key) issues.push(`step.key "${step.key}" does not match object key "${key}"`);
631
+ const checkRef = (ref, where) => {
632
+ if (ref && !definition.steps[ref]) {
633
+ issues.push(`step "${key}" ${where} points at unknown step "${ref}"`);
634
+ }
635
+ };
636
+ switch (step.type) {
637
+ case "send_email":
638
+ case "wait":
639
+ case "tag_add":
640
+ case "tag_remove":
641
+ case "update_contact":
642
+ case "webhook":
643
+ checkRef(step.next, "next");
644
+ break;
645
+ case "branch":
646
+ checkRef(step.thenNext, "thenNext");
647
+ checkRef(step.elseNext, "elseNext");
648
+ break;
649
+ case "end":
650
+ break;
651
+ }
652
+ }
653
+ if (issues.length > 0) {
654
+ throw new Error(`Invalid sequence definition: ${issues.join("; ")}`);
655
+ }
656
+ return definition;
657
+ }
287
658
  export {
288
659
  Zupost,
289
- ZupostError
660
+ ZupostError,
661
+ defineSequence
290
662
  };
package/package.json CHANGED
@@ -1,62 +1,61 @@
1
1
  {
2
- "name": "zupost",
3
- "version": "0.2.0",
4
- "description": "Node.js SDK for Zupost",
5
- "engines": {
6
- "node": ">=20"
7
- },
8
- "license": "MIT",
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/zupost/node-sdk.git"
12
- },
13
- "scripts": {
14
- "build": "tsup src/index.ts --format esm,cjs --dts",
15
- "test": "vitest run",
16
- "test:watch": "vitest",
17
- "typecheck": "tsc --noEmit",
18
- "lint": "prettier --check .",
19
- "lint:fix": "prettier --write .",
20
- "prepublishOnly": "pnpm run build"
21
- },
22
- "main": "./dist/index.js",
23
- "module": "./dist/index.mjs",
24
- "types": "./dist/index.d.ts",
25
- "files": [
26
- "dist/**"
27
- ],
28
- "exports": {
29
- ".": {
30
- "import": {
31
- "types": "./dist/index.d.ts",
32
- "default": "./dist/index.mjs"
33
- },
34
- "require": {
35
- "types": "./dist/index.d.ts",
36
- "default": "./dist/index.js"
37
- }
38
- }
39
- },
40
- "devDependencies": {
41
- "@react-email/render": "^2.0.7",
42
- "@types/node": "^24.12.2",
43
- "@types/react": "^19.2.14",
44
- "mathisch": "^0.4.1",
45
- "prettier": "^3.8.3",
46
- "tsup": "^8.5.1",
47
- "typescript": "^5.9.3",
48
- "vitest": "^4.1.5"
49
- },
50
- "peerDependencies": {
51
- "@react-email/render": "^2.0.0",
52
- "react": "^18 || ^19"
53
- },
54
- "peerDependenciesMeta": {
55
- "@react-email/render": {
56
- "optional": true
57
- },
58
- "react": {
59
- "optional": true
60
- }
61
- }
62
- }
2
+ "name": "zupost",
3
+ "version": "0.6.0",
4
+ "description": "Node.js SDK for Zupost",
5
+ "engines": {
6
+ "node": ">=20"
7
+ },
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/zupost/node-sdk.git"
12
+ },
13
+ "main": "./dist/index.js",
14
+ "module": "./dist/index.mjs",
15
+ "types": "./dist/index.d.ts",
16
+ "files": [
17
+ "dist/**"
18
+ ],
19
+ "exports": {
20
+ ".": {
21
+ "import": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.mjs"
24
+ },
25
+ "require": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ }
29
+ }
30
+ },
31
+ "devDependencies": {
32
+ "@react-email/render": "^2.0.8",
33
+ "@types/node": "^24.12.4",
34
+ "@types/react": "^19.2.16",
35
+ "mathisch": "^0.4.1",
36
+ "prettier": "^3.8.3",
37
+ "tsup": "^8.5.1",
38
+ "typescript": "^5.9.3",
39
+ "vitest": "^4.1.8"
40
+ },
41
+ "peerDependencies": {
42
+ "@react-email/render": "^2.0.0",
43
+ "react": "^18 || ^19"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "@react-email/render": {
47
+ "optional": true
48
+ },
49
+ "react": {
50
+ "optional": true
51
+ }
52
+ },
53
+ "scripts": {
54
+ "build": "tsup src/index.ts --format esm,cjs --dts",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest",
57
+ "typecheck": "tsc --noEmit",
58
+ "lint": "prettier --check .",
59
+ "lint:fix": "prettier --write ."
60
+ }
61
+ }