@voyant-travel/utils 0.105.1

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.
Files changed (160) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +42 -0
  3. package/dist/cache.d.ts +123 -0
  4. package/dist/cache.d.ts.map +1 -0
  5. package/dist/cache.js +182 -0
  6. package/dist/cache.js.map +1 -0
  7. package/dist/countries.d.ts +5 -0
  8. package/dist/countries.d.ts.map +1 -0
  9. package/dist/countries.js +251 -0
  10. package/dist/countries.js.map +1 -0
  11. package/dist/csv.d.ts +28 -0
  12. package/dist/csv.d.ts.map +1 -0
  13. package/dist/csv.js +44 -0
  14. package/dist/csv.js.map +1 -0
  15. package/dist/currencies/part-1.d.ts +408 -0
  16. package/dist/currencies/part-1.d.ts.map +1 -0
  17. package/dist/currencies/part-1.js +408 -0
  18. package/dist/currencies/part-1.js.map +1 -0
  19. package/dist/currencies/part-2.d.ts +408 -0
  20. package/dist/currencies/part-2.d.ts.map +1 -0
  21. package/dist/currencies/part-2.js +408 -0
  22. package/dist/currencies/part-2.js.map +1 -0
  23. package/dist/currencies/part-3.d.ts +201 -0
  24. package/dist/currencies/part-3.d.ts.map +1 -0
  25. package/dist/currencies/part-3.js +201 -0
  26. package/dist/currencies/part-3.js.map +1 -0
  27. package/dist/currencies.d.ts +1011 -0
  28. package/dist/currencies.d.ts.map +1 -0
  29. package/dist/currencies.js +9 -0
  30. package/dist/currencies.js.map +1 -0
  31. package/dist/geographic-regions.d.ts +66 -0
  32. package/dist/geographic-regions.d.ts.map +1 -0
  33. package/dist/geographic-regions.js +326 -0
  34. package/dist/geographic-regions.js.map +1 -0
  35. package/dist/index.d.ts +13 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +13 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/kms-aws.d.ts +28 -0
  40. package/dist/kms-aws.d.ts.map +1 -0
  41. package/dist/kms-aws.js +152 -0
  42. package/dist/kms-aws.js.map +1 -0
  43. package/dist/kms-env.d.ts +13 -0
  44. package/dist/kms-env.d.ts.map +1 -0
  45. package/dist/kms-env.js +13 -0
  46. package/dist/kms-env.js.map +1 -0
  47. package/dist/kms-gcp.d.ts +34 -0
  48. package/dist/kms-gcp.d.ts.map +1 -0
  49. package/dist/kms-gcp.js +135 -0
  50. package/dist/kms-gcp.js.map +1 -0
  51. package/dist/kms-local.d.ts +13 -0
  52. package/dist/kms-local.d.ts.map +1 -0
  53. package/dist/kms-local.js +13 -0
  54. package/dist/kms-local.js.map +1 -0
  55. package/dist/kms-symmetric.d.ts +14 -0
  56. package/dist/kms-symmetric.d.ts.map +1 -0
  57. package/dist/kms-symmetric.js +67 -0
  58. package/dist/kms-symmetric.js.map +1 -0
  59. package/dist/kms-voyant-cloud.d.ts +19 -0
  60. package/dist/kms-voyant-cloud.d.ts.map +1 -0
  61. package/dist/kms-voyant-cloud.js +67 -0
  62. package/dist/kms-voyant-cloud.js.map +1 -0
  63. package/dist/kms.d.ts +79 -0
  64. package/dist/kms.d.ts.map +1 -0
  65. package/dist/kms.js +194 -0
  66. package/dist/kms.js.map +1 -0
  67. package/dist/kv.d.ts +10 -0
  68. package/dist/kv.d.ts.map +1 -0
  69. package/dist/kv.js +18 -0
  70. package/dist/kv.js.map +1 -0
  71. package/dist/languages.d.ts +187 -0
  72. package/dist/languages.d.ts.map +1 -0
  73. package/dist/languages.js +187 -0
  74. package/dist/languages.js.map +1 -0
  75. package/dist/localized-countries-regions/en-part-1.d.ts +7 -0
  76. package/dist/localized-countries-regions/en-part-1.d.ts.map +1 -0
  77. package/dist/localized-countries-regions/en-part-1.js +118 -0
  78. package/dist/localized-countries-regions/en-part-1.js.map +1 -0
  79. package/dist/localized-countries-regions/en-part-2.d.ts +7 -0
  80. package/dist/localized-countries-regions/en-part-2.d.ts.map +1 -0
  81. package/dist/localized-countries-regions/en-part-2.js +138 -0
  82. package/dist/localized-countries-regions/en-part-2.js.map +1 -0
  83. package/dist/localized-countries-regions/en-part-3.d.ts +7 -0
  84. package/dist/localized-countries-regions/en-part-3.d.ts.map +1 -0
  85. package/dist/localized-countries-regions/en-part-3.js +101 -0
  86. package/dist/localized-countries-regions/en-part-3.js.map +1 -0
  87. package/dist/localized-countries-regions/ro-part-1.d.ts +7 -0
  88. package/dist/localized-countries-regions/ro-part-1.d.ts.map +1 -0
  89. package/dist/localized-countries-regions/ro-part-1.js +113 -0
  90. package/dist/localized-countries-regions/ro-part-1.js.map +1 -0
  91. package/dist/localized-countries-regions/ro-part-2.d.ts +7 -0
  92. package/dist/localized-countries-regions/ro-part-2.d.ts.map +1 -0
  93. package/dist/localized-countries-regions/ro-part-2.js +113 -0
  94. package/dist/localized-countries-regions/ro-part-2.js.map +1 -0
  95. package/dist/localized-countries-regions/ro-part-3.d.ts +7 -0
  96. package/dist/localized-countries-regions/ro-part-3.d.ts.map +1 -0
  97. package/dist/localized-countries-regions/ro-part-3.js +101 -0
  98. package/dist/localized-countries-regions/ro-part-3.js.map +1 -0
  99. package/dist/localized-countries-regions.d.ts +15 -0
  100. package/dist/localized-countries-regions.d.ts.map +1 -0
  101. package/dist/localized-countries-regions.js +19 -0
  102. package/dist/localized-countries-regions.js.map +1 -0
  103. package/dist/localized-regions.d.ts +33 -0
  104. package/dist/localized-regions.d.ts.map +1 -0
  105. package/dist/localized-regions.js +63 -0
  106. package/dist/localized-regions.js.map +1 -0
  107. package/dist/pdf-renderer.d.ts +9 -0
  108. package/dist/pdf-renderer.d.ts.map +1 -0
  109. package/dist/pdf-renderer.js +139 -0
  110. package/dist/pdf-renderer.js.map +1 -0
  111. package/dist/rate-limits.d.ts +39 -0
  112. package/dist/rate-limits.d.ts.map +1 -0
  113. package/dist/rate-limits.js +86 -0
  114. package/dist/rate-limits.js.map +1 -0
  115. package/dist/resilience.d.ts +81 -0
  116. package/dist/resilience.d.ts.map +1 -0
  117. package/dist/resilience.js +156 -0
  118. package/dist/resilience.js.map +1 -0
  119. package/dist/session-claims.d.ts +80 -0
  120. package/dist/session-claims.d.ts.map +1 -0
  121. package/dist/session-claims.js +205 -0
  122. package/dist/session-claims.js.map +1 -0
  123. package/dist/template-renderer.d.ts +2 -0
  124. package/dist/template-renderer.d.ts.map +1 -0
  125. package/dist/template-renderer.js +2 -0
  126. package/dist/template-renderer.js.map +1 -0
  127. package/dist/temporal.d.ts +2 -0
  128. package/dist/temporal.d.ts.map +1 -0
  129. package/dist/temporal.js +6 -0
  130. package/dist/temporal.js.map +1 -0
  131. package/dist/timezones/part-1.d.ts +9 -0
  132. package/dist/timezones/part-1.d.ts.map +1 -0
  133. package/dist/timezones/part-1.js +344 -0
  134. package/dist/timezones/part-1.js.map +1 -0
  135. package/dist/timezones/part-2.d.ts +9 -0
  136. package/dist/timezones/part-2.d.ts.map +1 -0
  137. package/dist/timezones/part-2.js +296 -0
  138. package/dist/timezones/part-2.js.map +1 -0
  139. package/dist/timezones/part-3.d.ts +9 -0
  140. package/dist/timezones/part-3.d.ts.map +1 -0
  141. package/dist/timezones/part-3.js +268 -0
  142. package/dist/timezones/part-3.js.map +1 -0
  143. package/dist/timezones/part-4.d.ts +9 -0
  144. package/dist/timezones/part-4.d.ts.map +1 -0
  145. package/dist/timezones/part-4.js +245 -0
  146. package/dist/timezones/part-4.js.map +1 -0
  147. package/dist/timezones/part-5.d.ts +9 -0
  148. package/dist/timezones/part-5.d.ts.map +1 -0
  149. package/dist/timezones/part-5.js +76 -0
  150. package/dist/timezones/part-5.js.map +1 -0
  151. package/dist/timezones.d.ts +9 -0
  152. package/dist/timezones.d.ts.map +1 -0
  153. package/dist/timezones.js +13 -0
  154. package/dist/timezones.js.map +1 -0
  155. package/dist/tsconfig.tsbuildinfo +1 -0
  156. package/dist/with-timeout.d.ts +2 -0
  157. package/dist/with-timeout.d.ts.map +1 -0
  158. package/dist/with-timeout.js +15 -0
  159. package/dist/with-timeout.js.map +1 -0
  160. package/package.json +123 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Rate Limit Utilities
3
+ *
4
+ * Maps subscription plans to API rate limits
5
+ */
6
+ /**
7
+ * Default rate limits by plan tier
8
+ *
9
+ * These limits apply to API key authentication
10
+ * Based on subscription tier or plan key
11
+ */
12
+ export const RATE_LIMITS_BY_PLAN = {
13
+ // Free/Starter tier
14
+ free: {
15
+ rps: 5,
16
+ rpm: 300,
17
+ burst: 10,
18
+ },
19
+ starter: {
20
+ rps: 5,
21
+ rpm: 300,
22
+ burst: 10,
23
+ },
24
+ // Professional tier
25
+ professional: {
26
+ rps: 50,
27
+ rpm: 3000,
28
+ burst: 100,
29
+ },
30
+ pro: {
31
+ rps: 50,
32
+ rpm: 3000,
33
+ burst: 100,
34
+ },
35
+ // Business tier
36
+ business: {
37
+ rps: 100,
38
+ rpm: 6000,
39
+ burst: 200,
40
+ },
41
+ // Enterprise tier
42
+ enterprise: {
43
+ rps: 500,
44
+ rpm: 30000,
45
+ burst: 1000,
46
+ },
47
+ // Default fallback
48
+ default: {
49
+ rps: 5,
50
+ rpm: 300,
51
+ burst: 10,
52
+ },
53
+ };
54
+ /**
55
+ * Get rate limits for a given plan key
56
+ *
57
+ * @param planKey - The plan key (e.g., "starter", "professional", "enterprise")
58
+ * @returns Rate limit configuration
59
+ */
60
+ export function getRateLimitsForPlan(planKey) {
61
+ if (!planKey)
62
+ return RATE_LIMITS_BY_PLAN.default;
63
+ const normalized = planKey.toLowerCase().trim();
64
+ return RATE_LIMITS_BY_PLAN[normalized] || RATE_LIMITS_BY_PLAN.default;
65
+ }
66
+ /**
67
+ * Check if a plan has enterprise-level access
68
+ *
69
+ * @param planKey - The plan key
70
+ * @returns True if enterprise plan
71
+ */
72
+ export function isEnterprisePlan(planKey) {
73
+ if (!planKey)
74
+ return false;
75
+ return planKey.toLowerCase().includes("enterprise");
76
+ }
77
+ /**
78
+ * Get human-readable rate limit description
79
+ *
80
+ * @param limits - Rate limit configuration
81
+ * @returns Formatted string
82
+ */
83
+ export function describeRateLimits(limits) {
84
+ return `${limits.rpm} requests/minute, ${limits.rps} requests/second`;
85
+ }
86
+ //# sourceMappingURL=rate-limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limits.js","sourceRoot":"","sources":["../src/rate-limits.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAoC;IAClE,oBAAoB;IACpB,IAAI,EAAE;QACJ,GAAG,EAAE,CAAC;QACN,GAAG,EAAE,GAAG;QACR,KAAK,EAAE,EAAE;KACV;IACD,OAAO,EAAE;QACP,GAAG,EAAE,CAAC;QACN,GAAG,EAAE,GAAG;QACR,KAAK,EAAE,EAAE;KACV;IAED,oBAAoB;IACpB,YAAY,EAAE;QACZ,GAAG,EAAE,EAAE;QACP,GAAG,EAAE,IAAI;QACT,KAAK,EAAE,GAAG;KACX;IACD,GAAG,EAAE;QACH,GAAG,EAAE,EAAE;QACP,GAAG,EAAE,IAAI;QACT,KAAK,EAAE,GAAG;KACX;IAED,gBAAgB;IAChB,QAAQ,EAAE;QACR,GAAG,EAAE,GAAG;QACR,GAAG,EAAE,IAAI;QACT,KAAK,EAAE,GAAG;KACX;IAED,kBAAkB;IAClB,UAAU,EAAE;QACV,GAAG,EAAE,GAAG;QACR,GAAG,EAAE,KAAK;QACV,KAAK,EAAE,IAAI;KACZ;IAED,mBAAmB;IACnB,OAAO,EAAE;QACP,GAAG,EAAE,CAAC;QACN,GAAG,EAAE,GAAG;QACR,KAAK,EAAE,EAAE;KACV;CACF,CAAA;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAkC;IACrE,IAAI,CAAC,OAAO;QAAE,OAAO,mBAAmB,CAAC,OAAQ,CAAA;IAEjD,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAA;IAC/C,OAAO,mBAAmB,CAAC,UAAU,CAAC,IAAI,mBAAmB,CAAC,OAAQ,CAAA;AACxE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAkC;IACjE,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAC1B,OAAO,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;AACrD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAuB;IACxD,OAAO,GAAG,MAAM,CAAC,GAAG,qBAAqB,MAAM,CAAC,GAAG,kBAAkB,CAAA;AACvE,CAAC"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Outbound-HTTP resilience primitives (RFC voyant#1687 Phase 3.3).
3
+ *
4
+ * Every outbound call from a Worker burns request CPU/subrequest budget
5
+ * (now platform-enforced per plan tier), so a slow third-party must
6
+ * fail fast, a flaky one must retry with jitter instead of hammering,
7
+ * and a down one must trip a breaker instead of cascading. These are
8
+ * the defaults the plugin clients (e-invoicing, CMS sync, payments)
9
+ * and channel push wrap their fetches with — consumers stop
10
+ * hand-rolling them.
11
+ */
12
+ export interface RetryOptions {
13
+ /** Max attempts INCLUDING the first. Default 3. */
14
+ attempts?: number;
15
+ /** Base backoff before the 2nd attempt; doubles per retry. Default 250ms. */
16
+ baseDelayMs?: number;
17
+ /** Backoff ceiling. Default 4s. */
18
+ maxDelayMs?: number;
19
+ /**
20
+ * Which failures retry. Defaults to network errors, timeouts, 429 and
21
+ * 5xx responses — for requests considered idempotent (see
22
+ * `retryNonIdempotent` on {@link ResilientFetchOptions}).
23
+ */
24
+ retryOn?: (result: {
25
+ response?: Response;
26
+ error?: unknown;
27
+ }) => boolean;
28
+ }
29
+ export interface CircuitBreakerOptions {
30
+ /** Consecutive failures before the circuit opens. Default 5. */
31
+ failureThreshold?: number;
32
+ /** How long an open circuit rejects immediately. Default 30s. */
33
+ openMs?: number;
34
+ }
35
+ /**
36
+ * Minimal consecutive-failure circuit breaker. Half-open after
37
+ * `openMs`: the next call probes; success closes, failure re-opens.
38
+ * State is per-isolate (Workers have many isolates — the breaker bounds
39
+ * each isolate's contribution to a hammering herd rather than providing
40
+ * a global cutoff, which is the right scope for edge runtimes).
41
+ */
42
+ export interface CircuitBreaker {
43
+ /** Throws {@link CircuitOpenError} when open. */
44
+ assertClosed(): void;
45
+ recordSuccess(): void;
46
+ recordFailure(): void;
47
+ readonly state: "closed" | "open" | "half-open";
48
+ }
49
+ export declare class CircuitOpenError extends Error {
50
+ readonly retryAfterMs: number;
51
+ constructor(retryAfterMs: number);
52
+ }
53
+ export declare function createCircuitBreaker(options?: CircuitBreakerOptions): CircuitBreaker;
54
+ export interface ResilientFetchOptions {
55
+ /** Per-attempt timeout. Default 10s. */
56
+ timeoutMs?: number;
57
+ /** Retry policy. `false` disables retries. */
58
+ retry?: RetryOptions | false;
59
+ /**
60
+ * Retries default to idempotent methods only (GET/HEAD/PUT/DELETE).
61
+ * POSTs against APIs with their own idempotency keys (most payment /
62
+ * e-invoicing providers) can opt in.
63
+ */
64
+ retryNonIdempotent?: boolean;
65
+ /** Optional breaker — share one per upstream service. */
66
+ breaker?: CircuitBreaker;
67
+ /** Injection point for tests. Defaults to global fetch. */
68
+ fetchImpl?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
69
+ }
70
+ /**
71
+ * `fetch` with a per-attempt timeout, capped exponential retries with
72
+ * full jitter, and an optional circuit breaker.
73
+ *
74
+ * Defaults: 10s timeout, 3 attempts on network errors/timeouts/429/5xx —
75
+ * but only for idempotent methods unless `retryNonIdempotent` is set.
76
+ * 4xx responses (other than 429) are returned, never retried. Breaker
77
+ * failures count once per attempt; a `CircuitOpenError` is thrown
78
+ * without touching the network.
79
+ */
80
+ export declare function resilientFetch(input: string | URL | Request, init?: RequestInit, options?: ResilientFetchOptions): Promise<Response>;
81
+ //# sourceMappingURL=resilience.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilience.d.ts","sourceRoot":"","sources":["../src/resilience.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,YAAY;IAC3B,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mCAAmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAA;CACxE;AAED,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,YAAY,IAAI,IAAI,CAAA;IACpB,aAAa,IAAI,IAAI,CAAA;IACrB,aAAa,IAAI,IAAI,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAA;CAChD;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;gBACjB,YAAY,EAAE,MAAM;CAKjC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,cAAc,CA0CxF;AAED,MAAM,WAAW,qBAAqB;IACpC,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,YAAY,GAAG,KAAK,CAAA;IAC5B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,yDAAyD;IACzD,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CACrF;AAwBD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAC7B,IAAI,GAAE,WAAgB,EACtB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,QAAQ,CAAC,CAgEnB"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Outbound-HTTP resilience primitives (RFC voyant#1687 Phase 3.3).
3
+ *
4
+ * Every outbound call from a Worker burns request CPU/subrequest budget
5
+ * (now platform-enforced per plan tier), so a slow third-party must
6
+ * fail fast, a flaky one must retry with jitter instead of hammering,
7
+ * and a down one must trip a breaker instead of cascading. These are
8
+ * the defaults the plugin clients (e-invoicing, CMS sync, payments)
9
+ * and channel push wrap their fetches with — consumers stop
10
+ * hand-rolling them.
11
+ */
12
+ export class CircuitOpenError extends Error {
13
+ retryAfterMs;
14
+ constructor(retryAfterMs) {
15
+ super(`circuit open; retry in ~${Math.ceil(retryAfterMs / 1000)}s`);
16
+ this.name = "CircuitOpenError";
17
+ this.retryAfterMs = retryAfterMs;
18
+ }
19
+ }
20
+ export function createCircuitBreaker(options = {}) {
21
+ const failureThreshold = options.failureThreshold ?? 5;
22
+ const openMs = options.openMs ?? 30_000;
23
+ let consecutiveFailures = 0;
24
+ let openedAt = null;
25
+ let probing = false;
26
+ function currentState() {
27
+ if (openedAt === null)
28
+ return "closed";
29
+ if (Date.now() - openedAt >= openMs)
30
+ return "half-open";
31
+ return "open";
32
+ }
33
+ return {
34
+ get state() {
35
+ return currentState();
36
+ },
37
+ assertClosed() {
38
+ const state = currentState();
39
+ if (state === "closed")
40
+ return;
41
+ if (state === "half-open" && !probing) {
42
+ // One probe through; everyone else keeps getting rejected until
43
+ // the probe settles.
44
+ probing = true;
45
+ return;
46
+ }
47
+ const elapsed = Date.now() - (openedAt ?? 0);
48
+ throw new CircuitOpenError(Math.max(0, openMs - elapsed));
49
+ },
50
+ recordSuccess() {
51
+ consecutiveFailures = 0;
52
+ openedAt = null;
53
+ probing = false;
54
+ },
55
+ recordFailure() {
56
+ consecutiveFailures += 1;
57
+ probing = false;
58
+ if (consecutiveFailures >= failureThreshold) {
59
+ openedAt = Date.now();
60
+ }
61
+ },
62
+ };
63
+ }
64
+ const IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
65
+ function defaultRetryOn(result) {
66
+ if (result.error)
67
+ return !(result.error instanceof CircuitOpenError);
68
+ if (!result.response)
69
+ return false;
70
+ return result.response.status === 429 || result.response.status >= 500;
71
+ }
72
+ function backoffDelay(attempt, base, cap) {
73
+ const exp = Math.min(base * 2 ** (attempt - 1), cap);
74
+ // Full jitter — desynchronizes retry herds across isolates.
75
+ return Math.round(exp * (0.5 + Math.random() * 0.5));
76
+ }
77
+ function sleep(ms) {
78
+ return new Promise((resolve) => setTimeout(resolve, ms));
79
+ }
80
+ function defaultFetch(input, init) {
81
+ return fetch(input, init);
82
+ }
83
+ /**
84
+ * `fetch` with a per-attempt timeout, capped exponential retries with
85
+ * full jitter, and an optional circuit breaker.
86
+ *
87
+ * Defaults: 10s timeout, 3 attempts on network errors/timeouts/429/5xx —
88
+ * but only for idempotent methods unless `retryNonIdempotent` is set.
89
+ * 4xx responses (other than 429) are returned, never retried. Breaker
90
+ * failures count once per attempt; a `CircuitOpenError` is thrown
91
+ * without touching the network.
92
+ */
93
+ export async function resilientFetch(input, init = {}, options = {}) {
94
+ const fetchImpl = options.fetchImpl ?? defaultFetch;
95
+ const timeoutMs = options.timeoutMs ?? 10_000;
96
+ const method = (init.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
97
+ const retriesAllowed = options.retry !== false && (IDEMPOTENT_METHODS.has(method) || options.retryNonIdempotent);
98
+ const retry = options.retry === false ? undefined : options.retry;
99
+ const attempts = retriesAllowed ? (retry?.attempts ?? 3) : 1;
100
+ const retryOn = retry?.retryOn ?? defaultRetryOn;
101
+ const baseDelayMs = retry?.baseDelayMs ?? 250;
102
+ const maxDelayMs = retry?.maxDelayMs ?? 4_000;
103
+ let lastError;
104
+ for (let attempt = 1; attempt <= attempts; attempt++) {
105
+ options.breaker?.assertClosed();
106
+ const controller = new AbortController();
107
+ const timer = setTimeout(() => controller.abort(new Error(`request timed out after ${timeoutMs}ms`)), timeoutMs);
108
+ // Compose with a caller-supplied signal when present.
109
+ if (init.signal) {
110
+ const upstream = init.signal;
111
+ if (upstream.aborted)
112
+ controller.abort(upstream.reason);
113
+ else
114
+ upstream.addEventListener("abort", () => controller.abort(upstream.reason));
115
+ }
116
+ try {
117
+ // Cast: CF's lib types Request with its own generics; the runtime
118
+ // value is the same structural Request either way.
119
+ const requestInput = (input instanceof Request ? input.clone() : input);
120
+ const response = await fetchImpl(requestInput, { ...init, signal: controller.signal });
121
+ if (response.ok || !retryOn({ response })) {
122
+ if (response.ok)
123
+ options.breaker?.recordSuccess();
124
+ else if (response.status >= 500 || response.status === 429) {
125
+ options.breaker?.recordFailure();
126
+ }
127
+ else {
128
+ // 4xx = upstream is alive and judging our request; not a
129
+ // service-health signal either way.
130
+ options.breaker?.recordSuccess();
131
+ }
132
+ return response;
133
+ }
134
+ options.breaker?.recordFailure();
135
+ lastError = new Error(`upstream responded ${response.status}`);
136
+ // Drain/cancel so the runtime doesn't hold the body across retries.
137
+ await response.body?.cancel().catch(() => { });
138
+ }
139
+ catch (error) {
140
+ if (error instanceof CircuitOpenError)
141
+ throw error;
142
+ options.breaker?.recordFailure();
143
+ lastError = error;
144
+ if (!retryOn({ error }))
145
+ throw error;
146
+ }
147
+ finally {
148
+ clearTimeout(timer);
149
+ }
150
+ if (attempt < attempts) {
151
+ await sleep(backoffDelay(attempt, baseDelayMs, maxDelayMs));
152
+ }
153
+ }
154
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
155
+ }
156
+ //# sourceMappingURL=resilience.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilience.js","sourceRoot":"","sources":["../src/resilience.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Session Claims Cookie Utilities
3
+ *
4
+ * Implements signed session claims to reduce API calls in middleware.
5
+ * Claims contain minimal session info (userId, sessionId hash) that can be
6
+ * verified locally without database lookup.
7
+ *
8
+ * Security:
9
+ * - HMAC-SHA256 signing prevents tampering
10
+ * - 5-minute expiration ensures quick revocation
11
+ * - HttpOnly, Secure, SameSite cookies
12
+ * - Context-separated signing keys (HKDF-SHA256): the deployment secret
13
+ * (`SESSION_CLAIMS_SECRET`) is never used directly as an HMAC key.
14
+ * `signSessionClaims`/`verifySessionClaims` derive a dedicated key under
15
+ * the `voyant:session-claims:v1` context, so a leak of a token-signing
16
+ * key (or any other derived key) does not compromise sibling contexts
17
+ * (e.g. the cloud-admin state cookie) or the raw Better Auth secret.
18
+ *
19
+ * Compatible with environments that expose the standard Web Crypto API,
20
+ * including Node.js, browsers, and Cloudflare Workers.
21
+ */
22
+ export interface SessionClaims {
23
+ userId: string;
24
+ sessionId: string;
25
+ iat: number;
26
+ exp: number;
27
+ }
28
+ /**
29
+ * HKDF context label under which session-claims bearer tokens are signed.
30
+ * Bumping the version suffix invalidates all outstanding tokens.
31
+ */
32
+ export declare const SESSION_CLAIMS_KEY_CONTEXT = "voyant:session-claims:v1";
33
+ /**
34
+ * HKDF context label for the Voyant Cloud admin-auth state-cookie HMAC key.
35
+ * Consumers (e.g. the operator auth handler) derive this at the boundary and
36
+ * pass the derived key down as `cookieSecret`.
37
+ */
38
+ export declare const CLOUD_STATE_COOKIE_KEY_CONTEXT = "voyant:cloud-state-cookie:v1";
39
+ /**
40
+ * Derive a context-separated subkey from a root secret via HKDF-SHA256
41
+ * (Web Crypto, Workers-compatible).
42
+ *
43
+ * The same root secret yields independent keys per context label, so a
44
+ * compromise of one derived key cannot be replayed against another context
45
+ * or against anything still using the root secret (e.g. Better Auth).
46
+ *
47
+ * @param secret - Root secret (e.g. `SESSION_CLAIMS_SECRET`)
48
+ * @param context - Context label, e.g. `"voyant:session-claims:v1"`
49
+ * @returns base64url-encoded 256-bit key (43 chars — satisfies >=32-char
50
+ * secret checks downstream)
51
+ */
52
+ export declare function deriveContextKey(secret: string, context: string): Promise<string>;
53
+ /**
54
+ * Sign session claims and return as JWT-like token
55
+ *
56
+ * Format: base64url(header).base64url(payload).base64url(signature)
57
+ *
58
+ * The HMAC key is NOT the raw `secret`: it is derived via
59
+ * `deriveContextKey(secret, SESSION_CLAIMS_KEY_CONTEXT)` so the root secret
60
+ * is never used directly as a token-signing key (context separation).
61
+ *
62
+ * @param userId - User ID from verified session
63
+ * @param sessionId - Full session ID (will be hashed)
64
+ * @param secret - Root secret; the signing key is derived from it
65
+ * @returns Signed token string
66
+ */
67
+ export declare function signSessionClaims(userId: string, sessionId: string, secret: string): Promise<string>;
68
+ /**
69
+ * Verify and decode session claims token
70
+ *
71
+ * Verification uses the same context-derived key as `signSessionClaims`
72
+ * (`SESSION_CLAIMS_KEY_CONTEXT`), so tokens signed with the raw secret
73
+ * (pre context-separation) are rejected.
74
+ *
75
+ * @param token - Signed token from cookie
76
+ * @param secret - Root secret; the verification key is derived from it
77
+ * @returns Decoded claims if valid, null if invalid/expired
78
+ */
79
+ export declare function verifySessionClaims(token: string, secret: string): Promise<SessionClaims | null>;
80
+ //# sourceMappingURL=session-claims.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-claims.d.ts","sourceRoot":"","sources":["../src/session-claims.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAUH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ;AAID;;;GAGG;AACH,eAAO,MAAM,0BAA0B,6BAA6B,CAAA;AAEpE;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,iCAAiC,CAAA;AAiB5E;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYjF;AAmDD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAwBjB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CA6C/B"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Session Claims Cookie Utilities
3
+ *
4
+ * Implements signed session claims to reduce API calls in middleware.
5
+ * Claims contain minimal session info (userId, sessionId hash) that can be
6
+ * verified locally without database lookup.
7
+ *
8
+ * Security:
9
+ * - HMAC-SHA256 signing prevents tampering
10
+ * - 5-minute expiration ensures quick revocation
11
+ * - HttpOnly, Secure, SameSite cookies
12
+ * - Context-separated signing keys (HKDF-SHA256): the deployment secret
13
+ * (`SESSION_CLAIMS_SECRET`) is never used directly as an HMAC key.
14
+ * `signSessionClaims`/`verifySessionClaims` derive a dedicated key under
15
+ * the `voyant:session-claims:v1` context, so a leak of a token-signing
16
+ * key (or any other derived key) does not compromise sibling contexts
17
+ * (e.g. the cloud-admin state cookie) or the raw Better Auth secret.
18
+ *
19
+ * Compatible with environments that expose the standard Web Crypto API,
20
+ * including Node.js, browsers, and Cloudflare Workers.
21
+ */
22
+ function getWebCrypto() {
23
+ if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
24
+ return globalThis.crypto;
25
+ }
26
+ throw new Error("No crypto implementation available");
27
+ }
28
+ const CLAIMS_EXPIRY_SECONDS = 5 * 60; // 5 minutes
29
+ /**
30
+ * HKDF context label under which session-claims bearer tokens are signed.
31
+ * Bumping the version suffix invalidates all outstanding tokens.
32
+ */
33
+ export const SESSION_CLAIMS_KEY_CONTEXT = "voyant:session-claims:v1";
34
+ /**
35
+ * HKDF context label for the Voyant Cloud admin-auth state-cookie HMAC key.
36
+ * Consumers (e.g. the operator auth handler) derive this at the boundary and
37
+ * pass the derived key down as `cookieSecret`.
38
+ */
39
+ export const CLOUD_STATE_COOKIE_KEY_CONTEXT = "voyant:cloud-state-cookie:v1";
40
+ /**
41
+ * Fixed, public HKDF salt. Domain separation comes from the `info` (context)
42
+ * parameter; the salt only needs to be a stable application-wide constant.
43
+ */
44
+ const HKDF_SALT = "voyant:hkdf:salt:v1";
45
+ const derivedKeyCache = new Map();
46
+ function base64UrlEncode(bytes) {
47
+ return btoa(String.fromCharCode(...bytes))
48
+ .replace(/\+/g, "-")
49
+ .replace(/\//g, "_")
50
+ .replace(/=/g, "");
51
+ }
52
+ /**
53
+ * Derive a context-separated subkey from a root secret via HKDF-SHA256
54
+ * (Web Crypto, Workers-compatible).
55
+ *
56
+ * The same root secret yields independent keys per context label, so a
57
+ * compromise of one derived key cannot be replayed against another context
58
+ * or against anything still using the root secret (e.g. Better Auth).
59
+ *
60
+ * @param secret - Root secret (e.g. `SESSION_CLAIMS_SECRET`)
61
+ * @param context - Context label, e.g. `"voyant:session-claims:v1"`
62
+ * @returns base64url-encoded 256-bit key (43 chars — satisfies >=32-char
63
+ * secret checks downstream)
64
+ */
65
+ export function deriveContextKey(secret, context) {
66
+ const cacheKey = `${context}\u0000${secret}`;
67
+ let cached = derivedKeyCache.get(cacheKey);
68
+ if (!cached) {
69
+ cached = deriveContextKeyUncached(secret, context).catch((error) => {
70
+ // Don't cache failures (e.g. transient crypto unavailability).
71
+ derivedKeyCache.delete(cacheKey);
72
+ throw error;
73
+ });
74
+ derivedKeyCache.set(cacheKey, cached);
75
+ }
76
+ return cached;
77
+ }
78
+ async function deriveContextKeyUncached(secret, context) {
79
+ const webCrypto = getWebCrypto();
80
+ const encoder = new TextEncoder();
81
+ const keyMaterial = await webCrypto.subtle.importKey("raw", encoder.encode(secret), "HKDF", false, ["deriveBits"]);
82
+ const bits = await webCrypto.subtle.deriveBits({
83
+ name: "HKDF",
84
+ hash: "SHA-256",
85
+ salt: encoder.encode(HKDF_SALT),
86
+ info: encoder.encode(context),
87
+ }, keyMaterial, 256);
88
+ return base64UrlEncode(new Uint8Array(bits));
89
+ }
90
+ async function hmacBase64Url(message, key) {
91
+ const webCrypto = getWebCrypto();
92
+ const encoder = new TextEncoder();
93
+ const cryptoKey = await webCrypto.subtle.importKey("raw", encoder.encode(key), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
94
+ const sigBuffer = await webCrypto.subtle.sign("HMAC", cryptoKey, encoder.encode(message));
95
+ return base64UrlEncode(new Uint8Array(sigBuffer));
96
+ }
97
+ /**
98
+ * Create a short identifier from session ID for inclusion in claims
99
+ * Uses first 16 chars of base64url-encoded SHA-256 hash
100
+ */
101
+ async function hashSessionId(sessionId) {
102
+ const webCrypto = getWebCrypto();
103
+ const encoder = new TextEncoder();
104
+ const data = encoder.encode(sessionId);
105
+ const hashBuffer = await webCrypto.subtle.digest("SHA-256", data);
106
+ return base64UrlEncode(new Uint8Array(hashBuffer)).slice(0, 16);
107
+ }
108
+ /**
109
+ * Sign session claims and return as JWT-like token
110
+ *
111
+ * Format: base64url(header).base64url(payload).base64url(signature)
112
+ *
113
+ * The HMAC key is NOT the raw `secret`: it is derived via
114
+ * `deriveContextKey(secret, SESSION_CLAIMS_KEY_CONTEXT)` so the root secret
115
+ * is never used directly as a token-signing key (context separation).
116
+ *
117
+ * @param userId - User ID from verified session
118
+ * @param sessionId - Full session ID (will be hashed)
119
+ * @param secret - Root secret; the signing key is derived from it
120
+ * @returns Signed token string
121
+ */
122
+ export async function signSessionClaims(userId, sessionId, secret) {
123
+ const now = Math.floor(Date.now() / 1000);
124
+ const exp = now + CLAIMS_EXPIRY_SECONDS;
125
+ const sessionIdHash = await hashSessionId(sessionId);
126
+ const claims = {
127
+ userId,
128
+ sessionId: sessionIdHash,
129
+ iat: now,
130
+ exp,
131
+ };
132
+ // Encode payload (works in both environments)
133
+ const header = { alg: "HS256", typ: "JWT" };
134
+ const encoder = new TextEncoder();
135
+ const headerB64 = base64UrlEncode(encoder.encode(JSON.stringify(header)));
136
+ const payloadB64 = base64UrlEncode(encoder.encode(JSON.stringify(claims)));
137
+ // Create signature with the context-derived key
138
+ const message = `${headerB64}.${payloadB64}`;
139
+ const signingKey = await deriveContextKey(secret, SESSION_CLAIMS_KEY_CONTEXT);
140
+ const signature = await hmacBase64Url(message, signingKey);
141
+ return `${headerB64}.${payloadB64}.${signature}`;
142
+ }
143
+ /**
144
+ * Verify and decode session claims token
145
+ *
146
+ * Verification uses the same context-derived key as `signSessionClaims`
147
+ * (`SESSION_CLAIMS_KEY_CONTEXT`), so tokens signed with the raw secret
148
+ * (pre context-separation) are rejected.
149
+ *
150
+ * @param token - Signed token from cookie
151
+ * @param secret - Root secret; the verification key is derived from it
152
+ * @returns Decoded claims if valid, null if invalid/expired
153
+ */
154
+ export async function verifySessionClaims(token, secret) {
155
+ try {
156
+ const parts = token.split(".");
157
+ if (parts.length !== 3) {
158
+ return null;
159
+ }
160
+ const [headerB64, payloadB64, signature] = parts;
161
+ // Ensure all parts are defined
162
+ if (!headerB64 || !payloadB64 || !signature) {
163
+ return null;
164
+ }
165
+ // Verify signature with the context-derived key
166
+ const message = `${headerB64}.${payloadB64}`;
167
+ const signingKey = await deriveContextKey(secret, SESSION_CLAIMS_KEY_CONTEXT);
168
+ const expectedSig = await hmacBase64Url(message, signingKey);
169
+ // Constant-time comparison
170
+ if (!constantTimeEqual(signature, expectedSig)) {
171
+ return null;
172
+ }
173
+ // Decode payload (works in both environments)
174
+ // base64url decode: replace - with +, _ with /, then decode
175
+ const payloadJson = atob(payloadB64.replace(/-/g, "+").replace(/_/g, "/"));
176
+ const payload = JSON.parse(payloadJson);
177
+ // Check expiration
178
+ const now = Math.floor(Date.now() / 1000);
179
+ if (payload.exp < now) {
180
+ return null;
181
+ }
182
+ // Validate structure
183
+ if (!payload.userId || !payload.sessionId || !payload.iat || !payload.exp) {
184
+ return null;
185
+ }
186
+ return payload;
187
+ }
188
+ catch (_error) {
189
+ // Invalid token format or JSON parse error
190
+ return null;
191
+ }
192
+ }
193
+ /**
194
+ * Constant-time string comparison to prevent timing attacks
195
+ */
196
+ function constantTimeEqual(a, b) {
197
+ if (a.length !== b.length)
198
+ return false;
199
+ let result = 0;
200
+ for (let i = 0; i < a.length; i++) {
201
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
202
+ }
203
+ return result === 0;
204
+ }
205
+ //# sourceMappingURL=session-claims.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-claims.js","sourceRoot":"","sources":["../src/session-claims.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export * from "@voyant-travel/templating";
2
+ //# sourceMappingURL=template-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-renderer.d.ts","sourceRoot":"","sources":["../src/template-renderer.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from "@voyant-travel/templating";
2
+ //# sourceMappingURL=template-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-renderer.js","sourceRoot":"","sources":["../src/template-renderer.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAA"}
@@ -0,0 +1,2 @@
1
+ export declare function toIsoDateUtc(date: Date): string;
2
+ //# sourceMappingURL=temporal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temporal.d.ts","sourceRoot":"","sources":["../src/temporal.ts"],"names":[],"mappings":"AACA,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAG/C"}