@wokuapp/sdk 0.4.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/README.md CHANGED
@@ -313,9 +313,132 @@ available if re-enabled later.
313
313
  Existing definitions retain their contract; create a new v2 journey to adopt these
314
314
  rules. Current participations retain their original definition version.
315
315
 
316
+ ### Flows
317
+
318
+ A flow is an ordered sequence of steps, each a woku or an NPS, CSAT or CES tool,
319
+ that a respondent answers one after the other. Flows live outside folders.
320
+
321
+ ```ts
322
+ const flow = await woku.flows.create({
323
+ name: 'Post-purchase',
324
+ steps: [
325
+ { type: 'nps', toolId: 'nps_tool_id' },
326
+ { type: 'csat', toolId: 'csat_tool_id' },
327
+ { type: 'woku', toolId: 'woku_id' },
328
+ ],
329
+ });
330
+ await woku.flows.update(flow._id, {
331
+ steps: [{ type: 'csat', toolId: 'csat_tool_id' }],
332
+ });
333
+ for await (const f of await woku.flows.list())
334
+ console.log(f.name, f.steps.length);
335
+ await woku.flows.delete(flow._id); // its tools and responses stay
336
+ ```
337
+
338
+ Use 1 to 20 steps, each tool once and all of your company. For an `nps` step,
339
+ your company id as `toolId` means the company-level NPS. `flows.get(id)` returns
340
+ the flow data (steps with their tools, wokus and branding).
341
+
342
+ ### Folders and tools
343
+
344
+ One folder tree holds wokus and NPS, CSAT and CES tools together. Create folders,
345
+ find any tool, read figures per type and move things in batches:
346
+
347
+ ```ts
348
+ const folder = await woku.folders.create({ name: 'Post-sale' });
349
+
350
+ // Create a tool already inside a folder.
351
+ await woku.csatTools.create({
352
+ name: 'Delivery',
353
+ question: 'How satisfied are you with your delivery?',
354
+ folderId: folder.id,
355
+ });
356
+
357
+ // One list for every tool type. Iterate across pages with the cursor.
358
+ const page = await woku.tools.list({
359
+ folderId: folder.id,
360
+ types: ['csat', 'nps'],
361
+ });
362
+ for await (const tool of page) {
363
+ console.log(tool.type, tool.name, tool.reviewCount, tool.headline.value);
364
+ }
365
+ const found = await woku.tools.list({ q: 'delivery', scope: 'company' });
366
+ // Tracker chips (`name:value`): a tool matches when it has every one. Company-wide.
367
+ const tagged = await woku.tools.list({ trackers: ['TRR:00482'] });
368
+
369
+ // "See more" for folders: foldersLimit pages the folders of the level (omit it
370
+ // to receive all of them). The tools keep their own cursor.
371
+ const first = await woku.tools.list({ foldersLimit: 12 });
372
+ if (first.hasMoreFolders()) {
373
+ const more = await first.getMoreFolders(); // only the next folders, no tools
374
+ console.log(more.folders.length);
375
+ }
376
+
377
+ // Headline figures per type (native scales, never mixed), company or folder.
378
+ const stats = await woku.tools.stats({ folderId: folder.id });
379
+
380
+ // Move items of mixed types, or send them to the root with folderId: null.
381
+ await woku.tools.move({
382
+ items: [
383
+ { type: 'csat', id: 'csat_tool_id' },
384
+ { type: 'woku', id: 'woku_id' },
385
+ ],
386
+ folderId: folder.id,
387
+ });
388
+ await woku.npsTools.move('nps_tool_id', { folderId: null });
389
+
390
+ // AI report and CSV over every tool of the folder and its subfolders.
391
+ const report = await woku.folders.report(folder.id, { lang: 'en' });
392
+ const csv: string = await woku.folders.csv(folder.id);
393
+ // PDF of that report with a breakdown per type (bytes, e.g. write them to a file).
394
+ const pdf: Uint8Array = await woku.folders.pdf(folder.id, { lang: 'en' });
395
+
396
+ // Per-tool figures for a period against the one before it (7, 30 or 90 days, or
397
+ // 0 for the whole history), with the daily sums and the histogram of scores.
398
+ // Every type keeps its own scale: NPS -100..100, CSAT %, CES and stars 1-5.
399
+ const metrics = await woku.folders.metrics(folder.id, { days: 30 });
400
+ // Or a custom range of UTC days (both inclusive), as the admin custom period.
401
+ const september = await woku.folders.metrics(folder.id, {
402
+ from: '2026-09-01',
403
+ to: '2026-09-30',
404
+ });
405
+ for (const tool of metrics.tools) {
406
+ console.log(
407
+ tool.name,
408
+ tool.current.headline.value,
409
+ tool.previous.headline.value,
410
+ );
411
+ }
412
+
413
+ // WhatsApp numbers alerted when a comment on any tool of the folder is an
414
+ // improvement (wokus, NPS, CSAT and CES). The list replaces the previous one.
415
+ await woku.folders.update(folder.id, {
416
+ whatsappAlertRecipients: [{ whatsappId: '56912345678', name: 'Ana' }],
417
+ });
418
+
419
+ // Deleting a folder never deletes its content: it moves up to the parent.
420
+ await woku.folders.delete(folder.id);
421
+ ```
422
+
423
+ ```ts
424
+ // Keep customer journeys in folders: Viajes > journey > moment.
425
+ const status = await woku.journeyFolders.status(); // { enabled, organizing, pending: { journeys, tools }, ... }
426
+ if (!status.enabled) await woku.journeyFolders.organize(); // 202; rejects with 409 while a pass is running
427
+
428
+ const journey = await woku.journeys.get('journey_id');
429
+ console.log(journey.folderId, journey.momentFolders); // moment key -> folder id
430
+ ```
431
+
432
+ Folders created by woku (journeys, moments, integrations) report their `origin`
433
+ (`'journeys-root' | 'journey' | 'journey-moment' | 'integration'`); your own folders have `origin: null`.
434
+
435
+ `woku.folders` also has `list`, `get`, `update` and `move`. NPS, CSAT and CES
436
+ tools (`npsTools`, `csatTools`, `cesTools`) accept `folderId` on `create`, expose
437
+ `folderId` on every tool and have `move(id, { folderId })`.
438
+
316
439
  ## Pagination
317
440
 
318
- Paginated resource methods return a `Page` (journey enrollments use the cursor envelope described above). Iterate every item across pages, or walk pages:
441
+ Paginated resource methods return a `Page` (journey enrollments and `tools.list` use a cursor envelope). Iterate every item across pages, or walk pages:
319
442
 
320
443
  ```ts
321
444
  for await (const ticket of await woku.tickets.list({ severity: 'high' })) {
@@ -375,8 +498,8 @@ await woku.npsTools.create(body, { idempotencyKey: 'my-key' });
375
498
 
376
499
  `trackers`, `npsTools` / `csatTools` / `cesTools`, `nps` / `csat` / `ces`,
377
500
  `wokus`, `forms`, `flows`, `actionPlans`, `actionPlanGroups`, `tickets`,
378
- `ticketDestinations`, `dispatches`, `reports`, `company`, `quarantines`,
379
- `journeys`.
501
+ `ticketDestinations`, `dispatches`, `reports`, `company`, `journeys`,
502
+ `folders`, `tools`, `journeyFolders`.
380
503
 
381
504
  ## License
382
505
 
package/dist/index.cjs CHANGED
@@ -38,7 +38,7 @@ var FollowUps = class {
38
38
  };
39
39
 
40
40
  // package.json
41
- var version = "0.4.0";
41
+ var version = "0.6.0";
42
42
 
43
43
  // src/core/errors.ts
44
44
  var WokuError = class extends Error {
@@ -385,7 +385,8 @@ var WokuClient = class {
385
385
  headers,
386
386
  bodyText,
387
387
  timeout,
388
- args.signal
388
+ args.signal,
389
+ args.binary
389
390
  );
390
391
  if (status >= 200 && status < 300) {
391
392
  return body;
@@ -502,7 +503,7 @@ var WokuClient = class {
502
503
  }
503
504
  return headers;
504
505
  }
505
- async send(url, method, headers, body, timeout, signal) {
506
+ async send(url, method, headers, body, timeout, signal, binary) {
506
507
  if (signal?.aborted)
507
508
  throw new WokuConnectionError("Request aborted", { code: "aborted" });
508
509
  const controller = new AbortController();
@@ -523,6 +524,20 @@ var WokuClient = class {
523
524
  body,
524
525
  signal: controller.signal
525
526
  });
527
+ if (binary && response.status >= 200 && response.status < 300) {
528
+ if (!response.arrayBuffer) {
529
+ throw new WokuError(
530
+ "The fetch implementation cannot read binary responses.",
531
+ { code: "config_error" }
532
+ );
533
+ }
534
+ return {
535
+ status: response.status,
536
+ body: new Uint8Array(await response.arrayBuffer()),
537
+ requestId: requestIdFrom(response.headers),
538
+ retryAfterSeconds: retryAfterFromHeaders(response.headers)
539
+ };
540
+ }
526
541
  const text = await response.text();
527
542
  return {
528
543
  status: response.status,
@@ -721,6 +736,14 @@ var NpsTools = class {
721
736
  }
722
737
  );
723
738
  }
739
+ /** Move the tool into a folder, or to the root with `folderId: null`. */
740
+ move(id, body, opts) {
741
+ return this.client.request(
742
+ "patch",
743
+ `/v1/nps-tools/${encodeURIComponent(id)}/move`,
744
+ { ...opts, body }
745
+ );
746
+ }
724
747
  delete(id, opts) {
725
748
  return this.client.request(
726
749
  "delete",
@@ -760,6 +783,14 @@ var CsatTools = class {
760
783
  }
761
784
  );
762
785
  }
786
+ /** Move the tool into a folder, or to the root with `folderId: null`. */
787
+ move(id, body, opts) {
788
+ return this.client.request(
789
+ "patch",
790
+ `/v1/csat-tools/${encodeURIComponent(id)}/move`,
791
+ { ...opts, body }
792
+ );
793
+ }
763
794
  delete(id, opts) {
764
795
  return this.client.request(
765
796
  "delete",
@@ -799,6 +830,14 @@ var CesTools = class {
799
830
  }
800
831
  );
801
832
  }
833
+ /** Move the tool into a folder, or to the root with `folderId: null`. */
834
+ move(id, body, opts) {
835
+ return this.client.request(
836
+ "patch",
837
+ `/v1/ces-tools/${encodeURIComponent(id)}/move`,
838
+ { ...opts, body }
839
+ );
840
+ }
802
841
  delete(id, opts) {
803
842
  return this.client.request(
804
843
  "delete",
@@ -1016,9 +1055,11 @@ var Flows = class {
1016
1055
  constructor(client) {
1017
1056
  this.client = client;
1018
1057
  }
1058
+ /** List the flows of the company, with their ordered `steps`. */
1019
1059
  list(params, opts) {
1020
1060
  return this.client.getPage("/v1/flows", params, opts);
1021
1061
  }
1062
+ /** The data of a flow: ordered steps with their tools, wokus and branding. */
1022
1063
  get(id, opts) {
1023
1064
  return this.client.request(
1024
1065
  "get",
@@ -1026,6 +1067,33 @@ var Flows = class {
1026
1067
  opts
1027
1068
  );
1028
1069
  }
1070
+ /**
1071
+ * Create a flow from 1 to 20 ordered steps (each tool once, all of your
1072
+ * company). Flows live outside folders.
1073
+ */
1074
+ create(body, opts) {
1075
+ return this.client.request("post", "/v1/flows", {
1076
+ ...opts,
1077
+ body,
1078
+ idempotent: true
1079
+ });
1080
+ }
1081
+ /** Rename a flow or replace its full ordered list of steps. */
1082
+ update(id, body, opts) {
1083
+ return this.client.request(
1084
+ "patch",
1085
+ `/v1/flows/${encodeURIComponent(id)}`,
1086
+ { ...opts, body }
1087
+ );
1088
+ }
1089
+ /** Delete a flow. Its tools and their responses stay. */
1090
+ delete(id, opts) {
1091
+ return this.client.request(
1092
+ "delete",
1093
+ `/v1/flows/${encodeURIComponent(id)}`,
1094
+ opts
1095
+ );
1096
+ }
1029
1097
  };
1030
1098
 
1031
1099
  // src/resources/action-plans.ts
@@ -1509,18 +1577,239 @@ var Journeys = class {
1509
1577
  }
1510
1578
  };
1511
1579
 
1512
- // src/resources/quarantines.ts
1513
- var Quarantines = class {
1580
+ // src/resources/folders.ts
1581
+ var Folders = class {
1514
1582
  constructor(client) {
1515
1583
  this.client = client;
1516
1584
  }
1517
- /** Check whether a contact is quarantined. */
1518
- check(params, opts) {
1519
- return this.client.request("get", "/v1/quarantines/check", {
1585
+ /** Every folder of the company, or the direct subfolders of `parentFolderId`. */
1586
+ list(params, opts) {
1587
+ return this.client.request("get", "/v1/folders", {
1520
1588
  ...opts,
1521
1589
  query: { ...params, ...opts?.query }
1522
1590
  });
1523
1591
  }
1592
+ get(id, opts) {
1593
+ return this.client.request(
1594
+ "get",
1595
+ `/v1/folders/${encodeURIComponent(id)}`,
1596
+ opts
1597
+ );
1598
+ }
1599
+ create(body, opts) {
1600
+ return this.client.request("post", "/v1/folders", {
1601
+ ...opts,
1602
+ body,
1603
+ idempotent: true
1604
+ });
1605
+ }
1606
+ /**
1607
+ * Rename or recolor a folder (`color: null` clears the color) and/or replace
1608
+ * its WhatsApp alert recipients (`whatsappAlertRecipients`, `[]` clears them).
1609
+ */
1610
+ update(id, body, opts) {
1611
+ return this.client.request(
1612
+ "patch",
1613
+ `/v1/folders/${encodeURIComponent(id)}`,
1614
+ { ...opts, body }
1615
+ );
1616
+ }
1617
+ /**
1618
+ * Move a folder under another folder, or to the root with
1619
+ * `parentFolderId: null`. A folder cannot move into its own subtree.
1620
+ */
1621
+ move(id, body, opts) {
1622
+ return this.client.request(
1623
+ "patch",
1624
+ `/v1/folders/${encodeURIComponent(id)}/move`,
1625
+ { ...opts, body }
1626
+ );
1627
+ }
1628
+ /**
1629
+ * Delete a folder. Nothing inside is deleted: subfolders, wokus and tools
1630
+ * move up to the parent folder (or the root).
1631
+ */
1632
+ delete(id, opts) {
1633
+ return this.client.request(
1634
+ "delete",
1635
+ `/v1/folders/${encodeURIComponent(id)}`,
1636
+ opts
1637
+ );
1638
+ }
1639
+ /**
1640
+ * AI report over every woku, NPS, CSAT and CES tool of the folder and its
1641
+ * subfolders. Scales differ per type, so there is no rating distribution.
1642
+ */
1643
+ report(id, params, opts) {
1644
+ return this.client.request(
1645
+ "get",
1646
+ `/v1/folders/${encodeURIComponent(id)}/report`,
1647
+ { ...opts, query: { ...params, ...opts?.query } }
1648
+ );
1649
+ }
1650
+ /**
1651
+ * Per-tool figures of the woku, NPS, CSAT and CES tools of the folder and its
1652
+ * subfolders for a period against the one before it, with the daily sums and
1653
+ * the histogram of scores. `days` is 7, 30 (default) or 90, or 0 for the whole
1654
+ * history (every `previous` window is then empty). The period is the last
1655
+ * `days` calendar days in UTC, today included. Or a custom range with `from`
1656
+ * and `to` (`YYYY-MM-DD` UTC days, both inclusive, up to 731 days), which
1657
+ * replaces `days`. Scales are never mixed across types.
1658
+ */
1659
+ metrics(id, params, opts) {
1660
+ return this.client.request(
1661
+ "get",
1662
+ `/v1/folders/${encodeURIComponent(id)}/metrics`,
1663
+ { ...opts, query: { ...params, ...opts?.query } }
1664
+ );
1665
+ }
1666
+ /** CSV text with one row per response of every tool of the folder subtree. */
1667
+ csv(id, opts) {
1668
+ return this.client.request(
1669
+ "get",
1670
+ `/v1/folders/${encodeURIComponent(id)}/csv`,
1671
+ opts
1672
+ );
1673
+ }
1674
+ /**
1675
+ * PDF of the folder report (every woku, NPS, CSAT and CES tool of the folder
1676
+ * and its subfolders) with a breakdown per type on each native scale.
1677
+ * Returns the file bytes: write them to disk or send them as is.
1678
+ */
1679
+ pdf(id, params, opts) {
1680
+ return this.client.request(
1681
+ "get",
1682
+ `/v1/folders/${encodeURIComponent(id)}/pdf`,
1683
+ {
1684
+ ...opts,
1685
+ headers: { Accept: "application/pdf", ...opts?.headers },
1686
+ query: { ...params, ...opts?.query },
1687
+ binary: true
1688
+ }
1689
+ );
1690
+ }
1691
+ };
1692
+
1693
+ // src/resources/tools.ts
1694
+ var ToolsPage = class {
1695
+ constructor(response, fetchNext, fetchMoreFolders = () => Promise.reject(new RangeError("No more folders"))) {
1696
+ this.fetchNext = fetchNext;
1697
+ this.fetchMoreFolders = fetchMoreFolders;
1698
+ this.folders = response.folders;
1699
+ this.tools = response.tools;
1700
+ this.nextCursor = response.nextCursor;
1701
+ this.foldersNextCursor = response.foldersNextCursor ?? null;
1702
+ this.facets = response.facets;
1703
+ }
1704
+ hasMoreFolders() {
1705
+ return this.foldersNextCursor !== null;
1706
+ }
1707
+ /** The next page of folders ("see more"): a page with those folders and no tools. */
1708
+ getMoreFolders() {
1709
+ if (this.foldersNextCursor === null)
1710
+ throw new RangeError("No more folders");
1711
+ return this.fetchMoreFolders(this.foldersNextCursor);
1712
+ }
1713
+ hasNextPage() {
1714
+ return this.nextCursor !== null;
1715
+ }
1716
+ getNextPage() {
1717
+ if (this.nextCursor === null) throw new RangeError("No next page");
1718
+ return this.fetchNext(this.nextCursor);
1719
+ }
1720
+ async *iterPages() {
1721
+ yield this;
1722
+ if (this.hasNextPage()) yield* (await this.getNextPage()).iterPages();
1723
+ }
1724
+ async *[Symbol.asyncIterator]() {
1725
+ for await (const page of this.iterPages()) {
1726
+ for (const tool of page.tools) yield tool;
1727
+ }
1728
+ }
1729
+ };
1730
+ var Tools = class {
1731
+ constructor(client) {
1732
+ this.client = client;
1733
+ }
1734
+ /**
1735
+ * List one folder level (or search the company). Folders and tools of every
1736
+ * type come together; the first page also carries the folders and facets.
1737
+ */
1738
+ async list(params, opts) {
1739
+ const fetchPage = async (cursor, foldersCursor) => {
1740
+ const response = await this.client.request(
1741
+ "get",
1742
+ "/v1/tools",
1743
+ {
1744
+ ...opts,
1745
+ query: {
1746
+ ...params,
1747
+ types: params?.types?.join(","),
1748
+ ...cursor ? { cursor } : {},
1749
+ ...foldersCursor ? { foldersCursor } : {},
1750
+ ...opts?.query
1751
+ }
1752
+ }
1753
+ );
1754
+ return new ToolsPage(
1755
+ response,
1756
+ fetchPage,
1757
+ (next) => fetchPage(void 0, next)
1758
+ );
1759
+ };
1760
+ return fetchPage();
1761
+ }
1762
+ /**
1763
+ * Headline figures per type (woku stars, NPS score, CSAT satisfied %, CES
1764
+ * mean) for the company or one folder and its subfolders, in a period.
1765
+ * A type that fails reports `{ error: true }`; the others stay valid.
1766
+ */
1767
+ stats(params, opts) {
1768
+ return this.client.request("get", "/v1/tools/stats", {
1769
+ ...opts,
1770
+ query: { ...params, ...opts?.query }
1771
+ });
1772
+ }
1773
+ /**
1774
+ * Move up to 200 items of mixed types (`woku`, `nps`, `csat`, `ces`,
1775
+ * `folder`) into a folder, or to the root with `folderId: null`. Idempotent;
1776
+ * nothing moves if one item or the destination is not found.
1777
+ */
1778
+ move(body, opts) {
1779
+ return this.client.request("post", "/v1/tools/move", {
1780
+ ...opts,
1781
+ body
1782
+ });
1783
+ }
1784
+ };
1785
+
1786
+ // src/resources/journey-folders.ts
1787
+ var JourneyFolders = class {
1788
+ constructor(client) {
1789
+ this.client = client;
1790
+ }
1791
+ /** Whether the organization is on, running, and how much is still without a folder. */
1792
+ status(opts) {
1793
+ return this.client.request(
1794
+ "get",
1795
+ "/v1/journey-folders",
1796
+ opts
1797
+ );
1798
+ }
1799
+ /**
1800
+ * Switch the organization on and run the one-off pass in the background: tools
1801
+ * of existing journeys without a folder move into their moment folder, and
1802
+ * tools journeys create from now on are placed there. Tools that already have
1803
+ * a folder are never touched. Rejects with a conflict (409) while a pass is
1804
+ * already running; poll {@link status} for progress.
1805
+ */
1806
+ organize(opts) {
1807
+ return this.client.request(
1808
+ "post",
1809
+ "/v1/journey-folders/organize",
1810
+ opts
1811
+ );
1812
+ }
1524
1813
  };
1525
1814
 
1526
1815
  // src/woku.ts
@@ -1548,15 +1837,19 @@ var Woku = class {
1548
1837
  this.reports = new Reports(this.client);
1549
1838
  this.company = new Company(this.client);
1550
1839
  this.journeys = new Journeys(this.client);
1551
- this.quarantines = new Quarantines(this.client);
1840
+ this.folders = new Folders(this.client);
1841
+ this.tools = new Tools(this.client);
1842
+ this.journeyFolders = new JourneyFolders(this.client);
1552
1843
  }
1553
1844
  };
1554
1845
 
1555
1846
  exports.AuthenticationError = AuthenticationError;
1556
1847
  exports.BadRequestError = BadRequestError;
1557
1848
  exports.ConflictError = ConflictError;
1849
+ exports.Folders = Folders;
1558
1850
  exports.FollowUps = FollowUps;
1559
1851
  exports.InternalServerError = InternalServerError;
1852
+ exports.JourneyFolders = JourneyFolders;
1560
1853
  exports.Journeys = Journeys;
1561
1854
  exports.Media = Media;
1562
1855
  exports.NotFoundError = NotFoundError;
@@ -1564,6 +1857,8 @@ exports.Page = Page;
1564
1857
  exports.PayloadTooLargeError = PayloadTooLargeError;
1565
1858
  exports.PermissionDeniedError = PermissionDeniedError;
1566
1859
  exports.RateLimitError = RateLimitError;
1860
+ exports.Tools = Tools;
1861
+ exports.ToolsPage = ToolsPage;
1567
1862
  exports.UnprocessableEntityError = UnprocessableEntityError;
1568
1863
  exports.Woku = Woku;
1569
1864
  exports.WokuAPIError = WokuAPIError;