@wokuapp/sdk 0.2.1 → 0.3.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.cjs CHANGED
@@ -1,5 +1,30 @@
1
1
  'use strict';
2
2
 
3
+ // src/resources/media.ts
4
+ var Media = class {
5
+ constructor(client) {
6
+ this.client = client;
7
+ }
8
+ upload(input, opts) {
9
+ const file = new Blob(
10
+ [input.file instanceof Blob ? input.file : new Uint8Array(input.file)],
11
+ {
12
+ type: input.contentType ?? (input.file instanceof Blob ? input.file.type : "")
13
+ }
14
+ );
15
+ const body = new FormData();
16
+ body.append("file", file, input.filename);
17
+ return this.client.request("post", "/v1/woku-media", {
18
+ ...opts,
19
+ body,
20
+ maxRetries: 0
21
+ });
22
+ }
23
+ };
24
+
25
+ // package.json
26
+ var version = "0.3.0";
27
+
3
28
  // src/core/errors.ts
4
29
  var WokuError = class extends Error {
5
30
  constructor(message, options) {
@@ -41,23 +66,95 @@ var WokuAPIError = class _WokuAPIError extends WokuError {
41
66
  const ra = retryAfterSeconds;
42
67
  switch (true) {
43
68
  case status === 400:
44
- return new BadRequestError(status, body, message, requestId, void 0, ra);
69
+ return new BadRequestError(
70
+ status,
71
+ body,
72
+ message,
73
+ requestId,
74
+ void 0,
75
+ ra
76
+ );
45
77
  case status === 401:
46
- return new AuthenticationError(status, body, message, requestId, void 0, ra);
78
+ return new AuthenticationError(
79
+ status,
80
+ body,
81
+ message,
82
+ requestId,
83
+ void 0,
84
+ ra
85
+ );
47
86
  case status === 403:
48
- return new PermissionDeniedError(status, body, message, requestId, void 0, ra);
87
+ return new PermissionDeniedError(
88
+ status,
89
+ body,
90
+ message,
91
+ requestId,
92
+ void 0,
93
+ ra
94
+ );
49
95
  case status === 404:
50
- return new NotFoundError(status, body, message, requestId, void 0, ra);
96
+ return new NotFoundError(
97
+ status,
98
+ body,
99
+ message,
100
+ requestId,
101
+ void 0,
102
+ ra
103
+ );
51
104
  case status === 409:
52
- return new ConflictError(status, body, message, requestId, void 0, ra);
105
+ return new ConflictError(
106
+ status,
107
+ body,
108
+ message,
109
+ requestId,
110
+ void 0,
111
+ ra
112
+ );
113
+ case status === 413:
114
+ return new PayloadTooLargeError(
115
+ status,
116
+ body,
117
+ message,
118
+ requestId,
119
+ void 0,
120
+ ra
121
+ );
53
122
  case status === 422:
54
- return new UnprocessableEntityError(status, body, message, requestId, void 0, ra);
123
+ return new UnprocessableEntityError(
124
+ status,
125
+ body,
126
+ message,
127
+ requestId,
128
+ void 0,
129
+ ra
130
+ );
55
131
  case status === 429:
56
- return new RateLimitError(status, body, message, requestId, void 0, ra);
132
+ return new RateLimitError(
133
+ status,
134
+ body,
135
+ message,
136
+ requestId,
137
+ void 0,
138
+ ra
139
+ );
57
140
  case status >= 500:
58
- return new InternalServerError(status, body, message, requestId, void 0, ra);
141
+ return new InternalServerError(
142
+ status,
143
+ body,
144
+ message,
145
+ requestId,
146
+ void 0,
147
+ ra
148
+ );
59
149
  default:
60
- return new _WokuAPIError(status, body, message, requestId, void 0, ra);
150
+ return new _WokuAPIError(
151
+ status,
152
+ body,
153
+ message,
154
+ requestId,
155
+ void 0,
156
+ ra
157
+ );
61
158
  }
62
159
  }
63
160
  };
@@ -91,6 +188,12 @@ var ConflictError = class extends WokuAPIError {
91
188
  this.name = "ConflictError";
92
189
  }
93
190
  };
191
+ var PayloadTooLargeError = class extends WokuAPIError {
192
+ constructor(...args) {
193
+ super(...args);
194
+ this.name = "PayloadTooLargeError";
195
+ }
196
+ };
94
197
  var UnprocessableEntityError = class extends WokuAPIError {
95
198
  constructor(...args) {
96
199
  super(...args);
@@ -109,7 +212,7 @@ var InternalServerError = class extends WokuAPIError {
109
212
  this.name = "InternalServerError";
110
213
  }
111
214
  };
112
- var retryAfterFromBody = (body) => body && typeof body === "object" && typeof body.retryAfter === "number" ? body.retryAfter : void 0;
215
+ var retryAfterFromBody = (body) => body && typeof body === "object" && typeof body.retryAfter === "number" && Number.isFinite(body.retryAfter) && body.retryAfter >= 0 ? body.retryAfter : void 0;
113
216
  var codeForStatus = (status) => {
114
217
  const map = {
115
218
  400: "bad_request",
@@ -117,6 +220,7 @@ var codeForStatus = (status) => {
117
220
  403: "permission_denied",
118
221
  404: "not_found",
119
222
  409: "conflict",
223
+ 413: "payload_too_large",
120
224
  422: "unprocessable_entity",
121
225
  429: "rate_limited"
122
226
  };
@@ -127,7 +231,7 @@ var messageFrom = (status, body, requestId) => {
127
231
  if (typeof body === "string" && body.trim()) {
128
232
  detail = body.trim();
129
233
  } else if (body && typeof body === "object") {
130
- const { message } = body;
234
+ const message = body.message && typeof body.message === "object" && !Array.isArray(body.message) ? body.message.message : body.message;
131
235
  if (Array.isArray(message)) detail = message.join(", ");
132
236
  else if (typeof message === "string" && message) detail = message;
133
237
  else if (typeof body.error === "string") detail = body.error;
@@ -156,7 +260,13 @@ var Page = class {
156
260
  if (!this.hasNextPage()) {
157
261
  throw new RangeError("No next page");
158
262
  }
159
- return this.fetchPage(this.page + 1, this.options);
263
+ return this.fetchPage(this.page + 1, this.options).then((next) => {
264
+ if (next.page <= this.page)
265
+ throw new WokuError("Pagination did not advance.", {
266
+ code: "pagination_error"
267
+ });
268
+ return next;
269
+ });
160
270
  }
161
271
  /** Yield every item across all pages, fetching lazily as needed. */
162
272
  async *[Symbol.asyncIterator]() {
@@ -185,13 +295,32 @@ var DEFAULT_MAX_RETRIES = 2;
185
295
  var RETRY_BASE_MS = 500;
186
296
  var RETRY_CAP_MS = 8e3;
187
297
  var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
188
- var SDK_VERSION = "0.1.0";
189
298
  var randomId = () => {
190
299
  const c = globalThis.crypto;
191
300
  if (c?.randomUUID) return c.randomUUID();
192
301
  return `idmp_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 12)}`;
193
302
  };
194
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
303
+ var sleep = (ms, signal) => ms > 2147483647 ? sleep(2147483647, signal).then(() => sleep(ms - 2147483647, signal)) : new Promise((resolve, reject) => {
304
+ if (signal?.aborted) {
305
+ reject(
306
+ new WokuConnectionError("Request aborted", { code: "aborted" })
307
+ );
308
+ return;
309
+ }
310
+ const cleanup = () => signal?.removeEventListener("abort", abort);
311
+ const timer = setTimeout(() => {
312
+ cleanup();
313
+ resolve();
314
+ }, ms);
315
+ const abort = () => {
316
+ clearTimeout(timer);
317
+ cleanup();
318
+ reject(
319
+ new WokuConnectionError("Request aborted", { code: "aborted" })
320
+ );
321
+ };
322
+ signal?.addEventListener("abort", abort, { once: true });
323
+ });
195
324
  var WokuClient = class {
196
325
  constructor(options = {}) {
197
326
  const apiKey = options.apiKey ?? globalThis.process?.env?.WOKU_API_KEY;
@@ -228,10 +357,10 @@ var WokuClient = class {
228
357
  const url = this.buildUrl(path, args.query);
229
358
  const idempotencyKey = args.idempotencyKey ?? (args.idempotent && method.toUpperCase() === "POST" ? randomId() : void 0);
230
359
  const headers = this.buildHeaders(method, { ...args, idempotencyKey });
231
- const bodyText = args.body === void 0 ? void 0 : JSON.stringify(args.body);
360
+ const bodyText = args.body === void 0 ? void 0 : args.body instanceof FormData ? args.body : JSON.stringify(args.body);
232
361
  const maxRetries = args.maxRetries ?? this.maxRetries;
233
362
  const timeout = args.timeout ?? this.timeout;
234
- const retryable = method.toUpperCase() === "GET" || Boolean(headers["X-Woku-Idempotency-Key"]);
363
+ const retryable = method.toUpperCase() === "GET" || method.toUpperCase() === "POST" && args.idempotent === true && Boolean(headers["X-Woku-Idempotency-Key"]);
235
364
  let attempt = 0;
236
365
  for (; ; ) {
237
366
  try {
@@ -252,8 +381,9 @@ var WokuClient = class {
252
381
  requestId,
253
382
  retryAfterSeconds
254
383
  );
384
+ apiError.idempotencyKey = idempotencyKey;
255
385
  if (retryable && attempt < maxRetries && RETRYABLE_STATUS.has(status)) {
256
- await sleep(this.backoff(attempt, apiError));
386
+ await sleep(this.backoff(attempt, apiError), args.signal);
257
387
  attempt += 1;
258
388
  continue;
259
389
  }
@@ -261,9 +391,16 @@ var WokuClient = class {
261
391
  } catch (error) {
262
392
  if (error instanceof WokuAPIError) throw error;
263
393
  const connError = toConnectionError(error);
394
+ connError.idempotencyKey = idempotencyKey;
264
395
  if (connError.code === "aborted") throw connError;
265
396
  if (retryable && attempt < maxRetries) {
266
- await sleep(this.backoff(attempt));
397
+ try {
398
+ await sleep(this.backoff(attempt), args.signal);
399
+ } catch (waitingError) {
400
+ if (waitingError instanceof WokuError)
401
+ waitingError.idempotencyKey = idempotencyKey;
402
+ throw waitingError;
403
+ }
267
404
  attempt += 1;
268
405
  continue;
269
406
  }
@@ -290,11 +427,25 @@ var WokuClient = class {
290
427
  };
291
428
  return new Page(
292
429
  envelope,
293
- (page, pageOpts) => this.getPage(path, { ...base, page }, { ...opts, ...pageOpts }),
430
+ (page, pageOpts) => this.getPage(
431
+ path,
432
+ { ...query, page },
433
+ {
434
+ ...opts,
435
+ ...pageOpts,
436
+ query: { ...opts?.query, ...pageOpts?.query, page }
437
+ }
438
+ ),
294
439
  opts
295
440
  );
296
441
  }
297
442
  buildUrl(path, query) {
443
+ if (/^\s*[a-z][a-z0-9+.-]*:/i.test(path) || path.startsWith("//")) {
444
+ throw new WokuError(
445
+ "Use an API path relative to baseURL, not an external URL.",
446
+ { code: "config_error" }
447
+ );
448
+ }
298
449
  const url = `${this.baseURL}${path.startsWith("/") ? path : `/${path}`}`;
299
450
  if (!query) return url;
300
451
  const search = new URLSearchParams();
@@ -310,22 +461,35 @@ var WokuClient = class {
310
461
  return qs ? `${url}?${qs}` : url;
311
462
  }
312
463
  buildHeaders(method, args) {
313
- const headers = {
464
+ const defaults = {
314
465
  Accept: "application/json",
315
- "User-Agent": `woku-sdk-js/${SDK_VERSION}`,
316
- ...this.defaultHeaders,
317
- ...args.headers
466
+ "User-Agent": `woku-sdk-js/${version}`
318
467
  };
468
+ const merged = /* @__PURE__ */ new Map();
469
+ for (const source of [defaults, this.defaultHeaders, args.headers ?? {}]) {
470
+ for (const [name, value] of Object.entries(source))
471
+ merged.set(name.toLowerCase(), [name, value]);
472
+ }
473
+ merged.set("authorization", ["Authorization", `Bearer ${this.apiKey}`]);
474
+ const headers = Object.fromEntries(merged.values());
319
475
  headers["Authorization"] = `Bearer ${this.apiKey}`;
320
476
  if (args.body !== void 0) {
321
- headers["Content-Type"] = "application/json";
477
+ for (const name of Object.keys(headers))
478
+ if (name.toLowerCase() === "content-type") delete headers[name];
479
+ if (!(args.body instanceof FormData))
480
+ headers["Content-Type"] = "application/json";
322
481
  }
323
482
  if (args.idempotencyKey !== void 0 && method.toUpperCase() === "POST") {
483
+ for (const name of Object.keys(headers))
484
+ if (name.toLowerCase() === "x-woku-idempotency-key")
485
+ delete headers[name];
324
486
  headers["X-Woku-Idempotency-Key"] = args.idempotencyKey;
325
487
  }
326
488
  return headers;
327
489
  }
328
490
  async send(url, method, headers, body, timeout, signal) {
491
+ if (signal?.aborted)
492
+ throw new WokuConnectionError("Request aborted", { code: "aborted" });
329
493
  const controller = new AbortController();
330
494
  let timedOut = false;
331
495
  const timer = setTimeout(() => {
@@ -364,7 +528,8 @@ var WokuClient = class {
364
528
  }
365
529
  backoff(attempt, apiError) {
366
530
  const retryAfter = apiError && typeof apiError.retryAfterSeconds === "number" ? apiError.retryAfterSeconds * 1e3 : void 0;
367
- if (retryAfter !== void 0) return Math.min(retryAfter, RETRY_CAP_MS);
531
+ if (retryAfter !== void 0 && Number.isFinite(retryAfter) && retryAfter >= 0)
532
+ return retryAfter;
368
533
  const ceiling = Math.min(RETRY_CAP_MS, RETRY_BASE_MS * 2 ** attempt);
369
534
  return Math.random() * ceiling;
370
535
  }
@@ -415,7 +580,7 @@ var Trackers = class {
415
580
  get(id, opts) {
416
581
  return this.client.request(
417
582
  "get",
418
- `/v1/external-trackers/${id}`,
583
+ `/v1/external-trackers/${encodeURIComponent(id)}`,
419
584
  opts
420
585
  );
421
586
  }
@@ -423,7 +588,7 @@ var Trackers = class {
423
588
  update(id, body, opts) {
424
589
  return this.client.request(
425
590
  "patch",
426
- `/v1/external-trackers/${id}`,
591
+ `/v1/external-trackers/${encodeURIComponent(id)}`,
427
592
  { ...opts, body }
428
593
  );
429
594
  }
@@ -431,7 +596,7 @@ var Trackers = class {
431
596
  activate(id, opts) {
432
597
  return this.client.request(
433
598
  "patch",
434
- `/v1/external-trackers/${id}/activate`,
599
+ `/v1/external-trackers/${encodeURIComponent(id)}/activate`,
435
600
  opts
436
601
  );
437
602
  }
@@ -439,7 +604,7 @@ var Trackers = class {
439
604
  deactivate(id, opts) {
440
605
  return this.client.request(
441
606
  "patch",
442
- `/v1/external-trackers/${id}/deactivate`,
607
+ `/v1/external-trackers/${encodeURIComponent(id)}/deactivate`,
443
608
  opts
444
609
  );
445
610
  }
@@ -455,7 +620,7 @@ var Trackers = class {
455
620
  listWokuValues(wokuId, opts) {
456
621
  return this.client.request(
457
622
  "get",
458
- `/v1/external-trackers/wokus/${wokuId}`,
623
+ `/v1/external-trackers/wokus/${encodeURIComponent(wokuId)}`,
459
624
  opts
460
625
  );
461
626
  }
@@ -463,15 +628,15 @@ var Trackers = class {
463
628
  assignToWoku(wokuId, body, opts) {
464
629
  return this.client.request(
465
630
  "post",
466
- `/v1/external-trackers/wokus/${wokuId}`,
467
- { ...opts, body, idempotent: true }
631
+ `/v1/external-trackers/wokus/${encodeURIComponent(wokuId)}`,
632
+ { ...opts, body, maxRetries: 0 }
468
633
  );
469
634
  }
470
635
  /** Remove a tracker value from a woku by tracker name. */
471
636
  removeFromWoku(wokuId, trackerName, opts) {
472
637
  return this.client.request(
473
638
  "delete",
474
- `/v1/external-trackers/wokus/${wokuId}/${encodeURIComponent(trackerName)}`,
639
+ `/v1/external-trackers/wokus/${encodeURIComponent(wokuId)}/${encodeURIComponent(trackerName)}`,
475
640
  opts
476
641
  );
477
642
  }
@@ -487,7 +652,7 @@ var Trackers = class {
487
652
  listEntityValues(entityType, id, opts) {
488
653
  return this.client.request(
489
654
  "get",
490
- `/v1/external-trackers/${entityType}/${id}`,
655
+ `/v1/external-trackers/${encodeURIComponent(entityType)}/${encodeURIComponent(id)}`,
491
656
  opts
492
657
  );
493
658
  }
@@ -495,15 +660,15 @@ var Trackers = class {
495
660
  assignToEntity(entityType, id, body, opts) {
496
661
  return this.client.request(
497
662
  "post",
498
- `/v1/external-trackers/${entityType}/${id}`,
499
- { ...opts, body, idempotent: true }
663
+ `/v1/external-trackers/${encodeURIComponent(entityType)}/${encodeURIComponent(id)}`,
664
+ { ...opts, body, maxRetries: 0 }
500
665
  );
501
666
  }
502
667
  /** Remove a tracker value from a VoC entity by tracker name. */
503
668
  removeFromEntity(entityType, id, trackerName, opts) {
504
669
  return this.client.request(
505
670
  "delete",
506
- `/v1/external-trackers/${entityType}/${id}/${encodeURIComponent(trackerName)}`,
671
+ `/v1/external-trackers/${encodeURIComponent(entityType)}/${encodeURIComponent(id)}/${encodeURIComponent(trackerName)}`,
507
672
  opts
508
673
  );
509
674
  }
@@ -525,18 +690,26 @@ var NpsTools = class {
525
690
  });
526
691
  }
527
692
  get(id, opts) {
528
- return this.client.request("get", `/v1/nps-tool/${id}`, opts);
693
+ return this.client.request(
694
+ "get",
695
+ `/v1/nps-tool/${encodeURIComponent(id)}`,
696
+ opts
697
+ );
529
698
  }
530
699
  update(id, body, opts) {
531
- return this.client.request("patch", `/v1/nps-tools/${id}`, {
532
- ...opts,
533
- body
534
- });
700
+ return this.client.request(
701
+ "patch",
702
+ `/v1/nps-tools/${encodeURIComponent(id)}`,
703
+ {
704
+ ...opts,
705
+ body
706
+ }
707
+ );
535
708
  }
536
709
  delete(id, opts) {
537
710
  return this.client.request(
538
711
  "delete",
539
- `/v1/nps-tools/${id}`,
712
+ `/v1/nps-tools/${encodeURIComponent(id)}`,
540
713
  opts
541
714
  );
542
715
  }
@@ -556,18 +729,26 @@ var CsatTools = class {
556
729
  });
557
730
  }
558
731
  get(id, opts) {
559
- return this.client.request("get", `/v1/csat-tool/${id}`, opts);
732
+ return this.client.request(
733
+ "get",
734
+ `/v1/csat-tool/${encodeURIComponent(id)}`,
735
+ opts
736
+ );
560
737
  }
561
738
  update(id, body, opts) {
562
- return this.client.request("patch", `/v1/csat-tools/${id}`, {
563
- ...opts,
564
- body
565
- });
739
+ return this.client.request(
740
+ "patch",
741
+ `/v1/csat-tools/${encodeURIComponent(id)}`,
742
+ {
743
+ ...opts,
744
+ body
745
+ }
746
+ );
566
747
  }
567
748
  delete(id, opts) {
568
749
  return this.client.request(
569
750
  "delete",
570
- `/v1/csat-tools/${id}`,
751
+ `/v1/csat-tools/${encodeURIComponent(id)}`,
571
752
  opts
572
753
  );
573
754
  }
@@ -587,18 +768,26 @@ var CesTools = class {
587
768
  });
588
769
  }
589
770
  get(id, opts) {
590
- return this.client.request("get", `/v1/ces-tool/${id}`, opts);
771
+ return this.client.request(
772
+ "get",
773
+ `/v1/ces-tool/${encodeURIComponent(id)}`,
774
+ opts
775
+ );
591
776
  }
592
777
  update(id, body, opts) {
593
- return this.client.request("patch", `/v1/ces-tools/${id}`, {
594
- ...opts,
595
- body
596
- });
778
+ return this.client.request(
779
+ "patch",
780
+ `/v1/ces-tools/${encodeURIComponent(id)}`,
781
+ {
782
+ ...opts,
783
+ body
784
+ }
785
+ );
597
786
  }
598
787
  delete(id, opts) {
599
788
  return this.client.request(
600
789
  "delete",
601
- `/v1/ces-tools/${id}`,
790
+ `/v1/ces-tools/${encodeURIComponent(id)}`,
602
791
  opts
603
792
  );
604
793
  }
@@ -627,7 +816,11 @@ var Nps = class {
627
816
  }
628
817
  /** Get one NPS response. */
629
818
  getResponse(id, opts) {
630
- return this.client.request("get", `/v1/nps/${id}`, opts);
819
+ return this.client.request(
820
+ "get",
821
+ `/v1/nps/${encodeURIComponent(id)}`,
822
+ opts
823
+ );
631
824
  }
632
825
  };
633
826
  var Csat = class {
@@ -645,7 +838,11 @@ var Csat = class {
645
838
  return this.client.getPage("/v1/csat", params, opts);
646
839
  }
647
840
  getResponse(id, opts) {
648
- return this.client.request("get", `/v1/csat/${id}`, opts);
841
+ return this.client.request(
842
+ "get",
843
+ `/v1/csat/${encodeURIComponent(id)}`,
844
+ opts
845
+ );
649
846
  }
650
847
  };
651
848
  var Ces = class {
@@ -667,7 +864,11 @@ var Ces = class {
667
864
  return this.client.getPage("/v1/ces", params, opts);
668
865
  }
669
866
  getResponse(id, opts) {
670
- return this.client.request("get", `/v1/ces/${id}`, opts);
867
+ return this.client.request(
868
+ "get",
869
+ `/v1/ces/${encodeURIComponent(id)}`,
870
+ opts
871
+ );
671
872
  }
672
873
  };
673
874
 
@@ -683,44 +884,60 @@ var Wokus = class {
683
884
  return this.client.request("post", "/v1/wokus", {
684
885
  ...opts,
685
886
  body,
686
- idempotent: true
887
+ maxRetries: 0
687
888
  });
688
889
  }
689
890
  /** Get one woku with aggregated review stats. */
690
891
  get(id, opts) {
691
- return this.client.request("get", `/v1/wokus/${id}`, opts);
892
+ return this.client.request(
893
+ "get",
894
+ `/v1/wokus/${encodeURIComponent(id)}`,
895
+ opts
896
+ );
692
897
  }
693
898
  update(id, body, opts) {
694
- return this.client.request("patch", `/v1/wokus/${id}`, {
695
- ...opts,
696
- body
697
- });
899
+ return this.client.request(
900
+ "patch",
901
+ `/v1/wokus/${encodeURIComponent(id)}`,
902
+ {
903
+ ...opts,
904
+ body
905
+ }
906
+ );
698
907
  }
699
908
  delete(id, opts) {
700
909
  return this.client.request(
701
910
  "delete",
702
- `/v1/wokus/${id}`,
911
+ `/v1/wokus/${encodeURIComponent(id)}`,
703
912
  opts
704
913
  );
705
914
  }
706
915
  /** Apply the boolean settings idempotently (closed/reviewsDisabled/...). */
707
916
  updateSettings(id, body, opts) {
708
- return this.client.request("patch", `/v1/wokus/${id}/settings`, {
709
- ...opts,
710
- body
711
- });
917
+ return this.client.request(
918
+ "patch",
919
+ `/v1/wokus/${encodeURIComponent(id)}/settings`,
920
+ {
921
+ ...opts,
922
+ body
923
+ }
924
+ );
712
925
  }
713
926
  /** Move the woku into a folder, or to the root with `{ folderId: null }`. */
714
927
  move(id, body, opts) {
715
- return this.client.request("patch", `/v1/wokus/${id}/move`, {
716
- ...opts,
717
- body
718
- });
928
+ return this.client.request(
929
+ "patch",
930
+ `/v1/wokus/${encodeURIComponent(id)}/move`,
931
+ {
932
+ ...opts,
933
+ body
934
+ }
935
+ );
719
936
  }
720
937
  /** List the reviews of a woku (paginated). */
721
938
  listReviews(id, params, opts) {
722
939
  return this.client.getPage(
723
- `/v1/wokus/${id}/reviews`,
940
+ `/v1/wokus/${encodeURIComponent(id)}/reviews`,
724
941
  params,
725
942
  opts
726
943
  );
@@ -729,16 +946,20 @@ var Wokus = class {
729
946
  sendInvitations(id, body, opts) {
730
947
  return this.client.request(
731
948
  "post",
732
- `/v1/wokus/${id}/invitations`,
949
+ `/v1/wokus/${encodeURIComponent(id)}/invitations`,
733
950
  { ...opts, body, idempotent: true }
734
951
  );
735
952
  }
736
953
  /** Share a woku review link by email. */
737
954
  share(id, body, opts) {
738
- return this.client.request("post", `/v1/wokus/${id}/share`, {
739
- ...opts,
740
- body
741
- });
955
+ return this.client.request(
956
+ "post",
957
+ `/v1/wokus/${encodeURIComponent(id)}/share`,
958
+ {
959
+ ...opts,
960
+ body
961
+ }
962
+ );
742
963
  }
743
964
  };
744
965
 
@@ -751,12 +972,16 @@ var Forms = class {
751
972
  return this.client.getPage("/v1/forms", params, opts);
752
973
  }
753
974
  get(id, opts) {
754
- return this.client.request("get", `/v1/forms/${id}`, opts);
975
+ return this.client.request(
976
+ "get",
977
+ `/v1/forms/${encodeURIComponent(id)}`,
978
+ opts
979
+ );
755
980
  }
756
981
  /** List the responses of a form (paginated). */
757
982
  listResponses(id, params, opts) {
758
983
  return this.client.getPage(
759
- `/v1/forms/${id}/responses`,
984
+ `/v1/forms/${encodeURIComponent(id)}/responses`,
760
985
  params,
761
986
  opts
762
987
  );
@@ -765,7 +990,7 @@ var Forms = class {
765
990
  sendInvitations(id, body, opts) {
766
991
  return this.client.request(
767
992
  "post",
768
- `/v1/forms/${id}/invitations`,
993
+ `/v1/forms/${encodeURIComponent(id)}/invitations`,
769
994
  { ...opts, body, idempotent: true }
770
995
  );
771
996
  }
@@ -780,7 +1005,11 @@ var Flows = class {
780
1005
  return this.client.getPage("/v1/flows", params, opts);
781
1006
  }
782
1007
  get(id, opts) {
783
- return this.client.request("get", `/v1/flows/${id}`, opts);
1008
+ return this.client.request(
1009
+ "get",
1010
+ `/v1/flows/${encodeURIComponent(id)}`,
1011
+ opts
1012
+ );
784
1013
  }
785
1014
  };
786
1015
 
@@ -795,7 +1024,7 @@ var ActionPlans = class {
795
1024
  get(id, opts) {
796
1025
  return this.client.request(
797
1026
  "get",
798
- `/v1/action-plans/${id}`,
1027
+ `/v1/action-plans/${encodeURIComponent(id)}`,
799
1028
  opts
800
1029
  );
801
1030
  }
@@ -803,7 +1032,7 @@ var ActionPlans = class {
803
1032
  events(id, opts) {
804
1033
  return this.client.request(
805
1034
  "get",
806
- `/v1/action-plans/${id}/events`,
1035
+ `/v1/action-plans/${encodeURIComponent(id)}/events`,
807
1036
  opts
808
1037
  );
809
1038
  }
@@ -811,7 +1040,7 @@ var ActionPlans = class {
811
1040
  getConversation(id, opts) {
812
1041
  return this.client.request(
813
1042
  "get",
814
- `/v1/action-plans/${id}/conversation`,
1043
+ `/v1/action-plans/${encodeURIComponent(id)}/conversation`,
815
1044
  opts
816
1045
  );
817
1046
  }
@@ -823,35 +1052,35 @@ var ActionPlans = class {
823
1052
  reply(id, text, opts) {
824
1053
  return this.client.request(
825
1054
  "post",
826
- `/v1/action-plans/${id}/conversation`,
1055
+ `/v1/action-plans/${encodeURIComponent(id)}/conversation`,
827
1056
  { ...opts, body: { text, confirm: true } }
828
1057
  );
829
1058
  }
830
1059
  createTask(id, body, opts) {
831
1060
  return this.client.request(
832
1061
  "post",
833
- `/v1/action-plans/${id}/tasks`,
834
- { ...opts, body, idempotent: true }
1062
+ `/v1/action-plans/${encodeURIComponent(id)}/tasks`,
1063
+ { ...opts, body, maxRetries: 0 }
835
1064
  );
836
1065
  }
837
1066
  updateTask(id, taskId, body, opts) {
838
1067
  return this.client.request(
839
1068
  "patch",
840
- `/v1/action-plans/${id}/tasks/${taskId}`,
1069
+ `/v1/action-plans/${encodeURIComponent(id)}/tasks/${encodeURIComponent(taskId)}`,
841
1070
  { ...opts, body }
842
1071
  );
843
1072
  }
844
1073
  reorderTasks(id, body, opts) {
845
1074
  return this.client.request(
846
1075
  "patch",
847
- `/v1/action-plans/${id}/tasks/reorder`,
1076
+ `/v1/action-plans/${encodeURIComponent(id)}/tasks/reorder`,
848
1077
  { ...opts, body }
849
1078
  );
850
1079
  }
851
1080
  deleteTask(id, taskId, opts) {
852
1081
  return this.client.request(
853
1082
  "delete",
854
- `/v1/action-plans/${id}/tasks/${taskId}`,
1083
+ `/v1/action-plans/${encodeURIComponent(id)}/tasks/${encodeURIComponent(taskId)}`,
855
1084
  opts
856
1085
  );
857
1086
  }
@@ -873,7 +1102,7 @@ var ActionPlans = class {
873
1102
  status(id, action, opts) {
874
1103
  return this.client.request(
875
1104
  "post",
876
- `/v1/action-plans/${id}/${action}`,
1105
+ `/v1/action-plans/${encodeURIComponent(id)}/${action}`,
877
1106
  opts
878
1107
  );
879
1108
  }
@@ -892,7 +1121,7 @@ var ActionPlanGroups = class {
892
1121
  get(id, opts) {
893
1122
  return this.client.request(
894
1123
  "get",
895
- `/v1/action-plan-groups/${id}`,
1124
+ `/v1/action-plan-groups/${encodeURIComponent(id)}`,
896
1125
  opts
897
1126
  );
898
1127
  }
@@ -900,27 +1129,27 @@ var ActionPlanGroups = class {
900
1129
  return this.client.request("post", "/v1/action-plan-groups", {
901
1130
  ...opts,
902
1131
  body,
903
- idempotent: true
1132
+ maxRetries: 0
904
1133
  });
905
1134
  }
906
1135
  update(id, body, opts) {
907
1136
  return this.client.request(
908
1137
  "patch",
909
- `/v1/action-plan-groups/${id}`,
1138
+ `/v1/action-plan-groups/${encodeURIComponent(id)}`,
910
1139
  { ...opts, body }
911
1140
  );
912
1141
  }
913
1142
  setEnabled(id, enabled, opts) {
914
1143
  return this.client.request(
915
1144
  "patch",
916
- `/v1/action-plan-groups/${id}/enabled`,
1145
+ `/v1/action-plan-groups/${encodeURIComponent(id)}/enabled`,
917
1146
  { ...opts, body: { enabled } }
918
1147
  );
919
1148
  }
920
1149
  delete(id, opts) {
921
1150
  return this.client.request(
922
1151
  "delete",
923
- `/v1/action-plan-groups/${id}`,
1152
+ `/v1/action-plan-groups/${encodeURIComponent(id)}`,
924
1153
  opts
925
1154
  );
926
1155
  }
@@ -942,13 +1171,21 @@ var Tickets = class {
942
1171
  });
943
1172
  }
944
1173
  get(id, opts) {
945
- return this.client.request("get", `/v1/tickets/${id}`, opts);
1174
+ return this.client.request(
1175
+ "get",
1176
+ `/v1/tickets/${encodeURIComponent(id)}`,
1177
+ opts
1178
+ );
946
1179
  }
947
1180
  update(id, body, opts) {
948
- return this.client.request("patch", `/v1/tickets/${id}`, {
949
- ...opts,
950
- body
951
- });
1181
+ return this.client.request(
1182
+ "patch",
1183
+ `/v1/tickets/${encodeURIComponent(id)}`,
1184
+ {
1185
+ ...opts,
1186
+ body
1187
+ }
1188
+ );
952
1189
  }
953
1190
  };
954
1191
  var TicketDestinations = class {
@@ -965,7 +1202,7 @@ var TicketDestinations = class {
965
1202
  get(id, opts) {
966
1203
  return this.client.request(
967
1204
  "get",
968
- `/v1/ticket-destinations/${id}`,
1205
+ `/v1/ticket-destinations/${encodeURIComponent(id)}`,
969
1206
  opts
970
1207
  );
971
1208
  }
@@ -973,20 +1210,20 @@ var TicketDestinations = class {
973
1210
  return this.client.request("post", "/v1/ticket-destinations", {
974
1211
  ...opts,
975
1212
  body,
976
- idempotent: true
1213
+ maxRetries: 0
977
1214
  });
978
1215
  }
979
1216
  update(id, body, opts) {
980
1217
  return this.client.request(
981
1218
  "patch",
982
- `/v1/ticket-destinations/${id}`,
1219
+ `/v1/ticket-destinations/${encodeURIComponent(id)}`,
983
1220
  { ...opts, body }
984
1221
  );
985
1222
  }
986
1223
  delete(id, opts) {
987
1224
  return this.client.request(
988
1225
  "delete",
989
- `/v1/ticket-destinations/${id}`,
1226
+ `/v1/ticket-destinations/${encodeURIComponent(id)}`,
990
1227
  opts
991
1228
  );
992
1229
  }
@@ -997,7 +1234,7 @@ var TicketDestinations = class {
997
1234
  test(id, opts) {
998
1235
  return this.client.request(
999
1236
  "post",
1000
- `/v1/ticket-destinations/${id}/test`,
1237
+ `/v1/ticket-destinations/${encodeURIComponent(id)}/test`,
1001
1238
  { ...opts, body: { confirm: true } }
1002
1239
  );
1003
1240
  }
@@ -1037,7 +1274,7 @@ var Reports = class {
1037
1274
  npsTool(npsToolId, params, opts) {
1038
1275
  return this.client.request(
1039
1276
  "get",
1040
- `/v1/reports/nps-tool/${npsToolId}`,
1277
+ `/v1/reports/nps-tool/${encodeURIComponent(npsToolId)}`,
1041
1278
  { ...opts, query: { ...params, ...opts?.query } }
1042
1279
  );
1043
1280
  }
@@ -1070,6 +1307,193 @@ var Company = class {
1070
1307
  }
1071
1308
  };
1072
1309
 
1310
+ // src/resources/journeys.ts
1311
+ var Journeys = class {
1312
+ constructor(client) {
1313
+ this.client = client;
1314
+ }
1315
+ /** Read customer entry metadata without starting or preparing an evaluation. */
1316
+ entryInfo(journeyId, opts) {
1317
+ return this.client.request(
1318
+ "get",
1319
+ `/v1/journey-entries/${encodeURIComponent(journeyId)}`,
1320
+ opts
1321
+ );
1322
+ }
1323
+ /** Prepare a first tool. Only its saved response with dispatchToken confirms entry. */
1324
+ prepareEntry(journeyId, body, opts) {
1325
+ return this.client.request(
1326
+ "post",
1327
+ `/v1/journey-entries/${encodeURIComponent(journeyId)}`,
1328
+ { ...opts, body, maxRetries: 0 }
1329
+ );
1330
+ }
1331
+ /** Every journey of your company. */
1332
+ list(opts) {
1333
+ return this.client.request("get", "/v1/journeys", opts);
1334
+ }
1335
+ /** One journey with its moments. */
1336
+ get(journeyId, opts) {
1337
+ return this.client.request(
1338
+ "get",
1339
+ `/v1/journeys/${encodeURIComponent(journeyId)}`,
1340
+ opts
1341
+ );
1342
+ }
1343
+ /**
1344
+ * Create a journey. The response carries `webhookSecret` once and only here:
1345
+ * it signs legacy woku_signature calls. V2 URL tokens and sender secrets
1346
+ * are configured separately per moment. Store it securely.
1347
+ */
1348
+ create(body, opts) {
1349
+ return this.client.request("post", "/v1/journeys", {
1350
+ ...opts,
1351
+ body,
1352
+ idempotent: true
1353
+ });
1354
+ }
1355
+ /**
1356
+ * Rename a journey, switch it on or off, or replace its moments. Replacing
1357
+ * the moments mints a new version; the enrollments already running keep
1358
+ * executing the version they started with.
1359
+ */
1360
+ update(journeyId, body, opts) {
1361
+ return this.client.request(
1362
+ "patch",
1363
+ `/v1/journeys/${encodeURIComponent(journeyId)}`,
1364
+ {
1365
+ ...opts,
1366
+ body
1367
+ }
1368
+ );
1369
+ }
1370
+ /** Cursor page of exact customer cases; pass nextCursor to retrieve another page. */
1371
+ listEnrollments(journeyId, params, opts) {
1372
+ return this.client.request(
1373
+ "get",
1374
+ `/v1/journeys/${encodeURIComponent(journeyId)}/enrollments`,
1375
+ { ...opts, query: { ...params, ...opts?.query } }
1376
+ );
1377
+ }
1378
+ /** Iterate exact cases lazily, preserving options while advancing the server cursor. */
1379
+ async *iterEnrollments(journeyId, params, opts) {
1380
+ let query = { ...params, ...opts?.query };
1381
+ const seen = /* @__PURE__ */ new Set();
1382
+ for (; ; ) {
1383
+ const page = await this.listEnrollments(journeyId, void 0, {
1384
+ ...opts,
1385
+ query
1386
+ });
1387
+ for (const item of page.items) yield item;
1388
+ const next = page.nextCursor;
1389
+ if (!next) return;
1390
+ if (seen.has(next) || next === query.cursor)
1391
+ throw new WokuError("Enrollment pagination did not advance.", {
1392
+ code: "pagination_error"
1393
+ });
1394
+ seen.add(next);
1395
+ query = { ...query, cursor: next };
1396
+ }
1397
+ }
1398
+ /** History and next step for one exact participation. */
1399
+ getEnrollment(journeyId, enrollmentId, opts) {
1400
+ return this.client.request(
1401
+ "get",
1402
+ `/v1/journeys/${encodeURIComponent(journeyId)}/enrollments/${encodeURIComponent(enrollmentId)}`,
1403
+ opts
1404
+ );
1405
+ }
1406
+ /** Cancel future work for this case. Accepted provider sends cannot be recalled. */
1407
+ stopEnrollment(journeyId, enrollmentId, body = {}, opts) {
1408
+ return this.client.request(
1409
+ "post",
1410
+ `/v1/journeys/${encodeURIComponent(journeyId)}/enrollments/${encodeURIComponent(enrollmentId)}/stop`,
1411
+ { ...opts, body, idempotent: true }
1412
+ );
1413
+ }
1414
+ /** Credential readiness is not evidence that an actual webhook has arrived. */
1415
+ connections(journeyId, opts) {
1416
+ return this.client.request(
1417
+ "get",
1418
+ `/v1/journeys/${encodeURIComponent(journeyId)}/connections`,
1419
+ opts
1420
+ );
1421
+ }
1422
+ /** Replaces this moment's URL credential. Store the returned URL in the sender. */
1423
+ async mintMomentUrl(journeyId, stageKey, opts) {
1424
+ const result = await this.client.request(
1425
+ "post",
1426
+ `/v1/journeys/${encodeURIComponent(journeyId)}/moments/${encodeURIComponent(stageKey)}/url-token`,
1427
+ { ...opts, body: {}, idempotent: true }
1428
+ );
1429
+ return {
1430
+ ...result,
1431
+ url: new URL(result.url, this.client.baseURL).toString()
1432
+ };
1433
+ }
1434
+ /** Store the external sender's signing secret encrypted; it is never returned. */
1435
+ setSenderSecret(journeyId, stageKey, senderSecret, opts) {
1436
+ return this.client.request(
1437
+ "post",
1438
+ `/v1/journeys/${encodeURIComponent(journeyId)}/moments/${encodeURIComponent(stageKey)}/sender-secret`,
1439
+ { ...opts, body: { senderSecret }, maxRetries: 0 }
1440
+ );
1441
+ }
1442
+ /** Test saved payload mapping without creating a participation or sending a tool. */
1443
+ previewMoment(journeyId, stageKey, payload, opts) {
1444
+ return this.client.request(
1445
+ "post",
1446
+ `/v1/journeys/${encodeURIComponent(journeyId)}/moments/${encodeURIComponent(stageKey)}/preview`,
1447
+ { ...opts, body: { payload }, maxRetries: 0 }
1448
+ );
1449
+ }
1450
+ /** Delete a journey. What it already started keeps its own history. */
1451
+ delete(journeyId, opts) {
1452
+ return this.client.request(
1453
+ "delete",
1454
+ `/v1/journeys/${encodeURIComponent(journeyId)}`,
1455
+ opts
1456
+ );
1457
+ }
1458
+ /**
1459
+ * Mint a new signing secret. The previous one stays valid until the next
1460
+ * rotation, so senders can be updated without a gap.
1461
+ */
1462
+ rotateWebhookSecret(journeyId, opts) {
1463
+ return this.client.request(
1464
+ "post",
1465
+ `/v1/journeys/${encodeURIComponent(journeyId)}/webhook-secret`,
1466
+ { ...opts, body: {}, maxRetries: 0 }
1467
+ );
1468
+ }
1469
+ /**
1470
+ * Start the journey for one subject. Enrolling the same person for a later
1471
+ * cycle reuses the subject key after the previous cycle completes or stops.
1472
+ */
1473
+ enroll(journeyId, body, opts) {
1474
+ return this.client.request(
1475
+ "post",
1476
+ `/v1/journeys/${encodeURIComponent(journeyId)}/enrollments`,
1477
+ { ...opts, body, idempotent: true }
1478
+ );
1479
+ }
1480
+ /**
1481
+ * Emit one of your own events. Every journey whose moments listen for that
1482
+ * name reacts.
1483
+ */
1484
+ emitEvent(body, opts) {
1485
+ return this.client.request(
1486
+ "post",
1487
+ "/v1/journey-events",
1488
+ {
1489
+ ...opts,
1490
+ body,
1491
+ idempotent: true
1492
+ }
1493
+ );
1494
+ }
1495
+ };
1496
+
1073
1497
  // src/resources/quarantines.ts
1074
1498
  var Quarantines = class {
1075
1499
  constructor(client) {
@@ -1089,6 +1513,7 @@ var Woku = class {
1089
1513
  constructor(options) {
1090
1514
  const opts = typeof options === "string" ? { apiKey: options } : options ?? {};
1091
1515
  this.client = new WokuClient(opts);
1516
+ this.media = new Media(this.client);
1092
1517
  this.trackers = new Trackers(this.client);
1093
1518
  this.npsTools = new NpsTools(this.client);
1094
1519
  this.csatTools = new CsatTools(this.client);
@@ -1106,6 +1531,7 @@ var Woku = class {
1106
1531
  this.dispatches = new Dispatches(this.client);
1107
1532
  this.reports = new Reports(this.client);
1108
1533
  this.company = new Company(this.client);
1534
+ this.journeys = new Journeys(this.client);
1109
1535
  this.quarantines = new Quarantines(this.client);
1110
1536
  }
1111
1537
  };
@@ -1114,8 +1540,11 @@ exports.AuthenticationError = AuthenticationError;
1114
1540
  exports.BadRequestError = BadRequestError;
1115
1541
  exports.ConflictError = ConflictError;
1116
1542
  exports.InternalServerError = InternalServerError;
1543
+ exports.Journeys = Journeys;
1544
+ exports.Media = Media;
1117
1545
  exports.NotFoundError = NotFoundError;
1118
1546
  exports.Page = Page;
1547
+ exports.PayloadTooLargeError = PayloadTooLargeError;
1119
1548
  exports.PermissionDeniedError = PermissionDeniedError;
1120
1549
  exports.RateLimitError = RateLimitError;
1121
1550
  exports.UnprocessableEntityError = UnprocessableEntityError;