hanzio 1.2.0 → 2.1.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.
Files changed (155) hide show
  1. package/README.md +786 -239
  2. package/dist/api-wrapper/builders.d.ts +4 -3
  3. package/dist/api-wrapper/create-client.d.ts +1 -1
  4. package/dist/api-wrapper/describe.d.ts +16 -0
  5. package/dist/api-wrapper/endpoint.d.ts +18 -0
  6. package/dist/api-wrapper/errors.d.ts +39 -2
  7. package/dist/api-wrapper/index.d.ts +5 -0
  8. package/dist/api-wrapper/otel.d.ts +44 -0
  9. package/dist/api-wrapper/paginate.d.ts +12 -0
  10. package/dist/api-wrapper/redaction.d.ts +2 -0
  11. package/dist/api-wrapper/shared.d.ts +16 -2
  12. package/dist/api-wrapper/testing.d.ts +23 -0
  13. package/dist/api-wrapper/transport.d.ts +4 -5
  14. package/dist/api-wrapper/types.d.ts +103 -18
  15. package/dist/array/index.d.ts +50 -4
  16. package/dist/cache/createCache.d.ts +31 -0
  17. package/dist/cache/index.d.ts +19 -2
  18. package/dist/color/contrast.d.ts +40 -0
  19. package/dist/color/index.d.ts +2 -0
  20. package/dist/{math → color}/metricsSeriesColors.d.ts +14 -7
  21. package/dist/cool-console-log/index.d.ts +34 -5
  22. package/dist/crypto/encoding.d.ts +18 -0
  23. package/dist/crypto/hash.d.ts +33 -0
  24. package/dist/crypto/index.d.ts +4 -0
  25. package/dist/crypto/sealer.d.ts +25 -0
  26. package/dist/crypto/webhook.d.ts +20 -0
  27. package/dist/date/index.d.ts +46 -2
  28. package/dist/emitter/index.d.ts +21 -0
  29. package/dist/error/index.d.ts +12 -0
  30. package/dist/format/index.d.ts +102 -0
  31. package/dist/function/index.d.ts +60 -0
  32. package/dist/guard/index.d.ts +20 -0
  33. package/dist/i18n/index.d.ts +37 -0
  34. package/dist/index-0zvtcbzy.js +224 -0
  35. package/dist/index-8r2vdzjg.js +87 -0
  36. package/dist/index-hy7kbb99.js +157 -0
  37. package/dist/index-hyt6c56h.js +290 -0
  38. package/dist/{src/secrets/infisical.js → index-j1ybktbr.js} +500 -538
  39. package/dist/index-k9yx6bne.js +89 -0
  40. package/dist/index-kj1phgrx.js +169 -0
  41. package/dist/index-ktynw3vs.js +37 -0
  42. package/dist/index-mnkcfyx8.js +78 -0
  43. package/dist/{p-queue/index.js → index-pwt0dz63.js} +7 -11
  44. package/dist/index-rp5pqghz.js +157 -0
  45. package/dist/index-w882zry3.js +29 -0
  46. package/dist/index.d.ts +7 -7
  47. package/dist/jwt/index.d.ts +50 -2
  48. package/dist/math/index.d.ts +2 -2
  49. package/dist/math/linearRegressionTrend.d.ts +14 -1
  50. package/dist/math/scale.d.ts +8 -0
  51. package/dist/object/index.d.ts +19 -0
  52. package/dist/p-queue/index.d.ts +1 -1
  53. package/dist/promise/index.d.ts +97 -2
  54. package/dist/secrets/core.d.ts +7 -0
  55. package/dist/secrets/index.d.ts +3 -1
  56. package/dist/secrets/public-config.d.ts +7 -0
  57. package/dist/secrets/vite.d.ts +1 -0
  58. package/dist/secrets/worker.d.ts +1 -0
  59. package/dist/sitemap/index.d.ts +48 -6
  60. package/dist/src/api-wrapper/index.js +60 -1130
  61. package/dist/src/api-wrapper/testing.js +20 -0
  62. package/dist/{cool-console-log → src/cool-console-log}/index.js +80 -33
  63. package/dist/src/crypto/index.js +195 -0
  64. package/dist/src/format/index.js +211 -0
  65. package/dist/src/i18n/index.js +36 -0
  66. package/dist/src/index.js +739 -3145
  67. package/dist/src/jwt/index.js +138 -92
  68. package/dist/src/p-queue/index.js +9 -437
  69. package/dist/src/secrets/index.js +50 -1428
  70. package/dist/src/secrets/vite.js +10 -39
  71. package/dist/src/secrets/worker.js +14 -273
  72. package/dist/src/sitemap/index.js +183 -511
  73. package/dist/src/storage/index.js +119 -0
  74. package/dist/src/zod/index.js +340 -167
  75. package/dist/state/index.d.ts +21 -0
  76. package/dist/storage/index.d.ts +58 -0
  77. package/dist/string/index.d.ts +75 -2
  78. package/dist/typedswitch/index.d.ts +31 -24
  79. package/dist/types/index.d.ts +11 -0
  80. package/dist/url/index.d.ts +50 -1
  81. package/dist/zod/index.d.ts +32 -3
  82. package/dist/zod/introspect.d.ts +19 -0
  83. package/dist/zod/jsonToZodSchemaMapper.d.ts +3 -1
  84. package/dist/zod/zodMinMaxFilter.d.ts +22 -3
  85. package/dist/zod/zodToTypeString.d.ts +8 -3
  86. package/package.json +78 -7
  87. package/src/api-wrapper/actions-impl.ts +11 -72
  88. package/src/api-wrapper/builders.ts +32 -25
  89. package/src/api-wrapper/create-client.ts +171 -41
  90. package/src/api-wrapper/describe.ts +157 -0
  91. package/src/api-wrapper/endpoint.ts +26 -0
  92. package/src/api-wrapper/error-reporting.ts +7 -1
  93. package/src/api-wrapper/errors.ts +107 -2
  94. package/src/api-wrapper/headers.ts +11 -6
  95. package/src/api-wrapper/index.ts +5 -0
  96. package/src/api-wrapper/otel.ts +139 -0
  97. package/src/api-wrapper/paginate.ts +28 -0
  98. package/src/api-wrapper/redaction.ts +42 -0
  99. package/src/api-wrapper/shared.ts +21 -2
  100. package/src/api-wrapper/testing.ts +48 -0
  101. package/src/api-wrapper/transport.ts +41 -43
  102. package/src/api-wrapper/types.ts +185 -18
  103. package/src/array/index.ts +195 -42
  104. package/src/cache/createCache.ts +157 -0
  105. package/src/cache/index.ts +132 -147
  106. package/src/color/contrast.ts +163 -0
  107. package/src/color/index.ts +22 -0
  108. package/src/{math → color}/metricsSeriesColors.ts +47 -49
  109. package/src/cool-console-log/index.ts +128 -41
  110. package/src/crypto/encoding.ts +83 -0
  111. package/src/crypto/hash.ts +170 -0
  112. package/src/crypto/index.ts +26 -0
  113. package/src/crypto/sealer.ts +168 -0
  114. package/src/crypto/webhook.ts +86 -0
  115. package/src/date/index.ts +109 -34
  116. package/src/emitter/index.ts +82 -0
  117. package/src/error/index.ts +63 -0
  118. package/src/format/index.ts +389 -0
  119. package/src/function/index.ts +201 -0
  120. package/src/guard/index.ts +80 -0
  121. package/src/i18n/index.ts +125 -0
  122. package/src/index.ts +11 -7
  123. package/src/jwt/index.ts +199 -125
  124. package/src/math/index.ts +5 -10
  125. package/src/math/linearRegressionTrend.ts +33 -8
  126. package/src/math/scale.ts +17 -0
  127. package/src/object/index.ts +99 -0
  128. package/src/p-queue/index.ts +2 -0
  129. package/src/promise/index.ts +304 -9
  130. package/src/secrets/core.ts +72 -16
  131. package/src/secrets/index.ts +16 -4
  132. package/src/secrets/public-config.ts +47 -0
  133. package/src/secrets/vite.ts +2 -0
  134. package/src/secrets/worker.ts +2 -0
  135. package/src/sitemap/index.ts +293 -130
  136. package/src/state/index.ts +133 -81
  137. package/src/storage/index.ts +187 -0
  138. package/src/string/index.ts +261 -23
  139. package/src/typedswitch/index.ts +58 -38
  140. package/src/types/index.ts +14 -0
  141. package/src/url/index.ts +146 -1
  142. package/src/zod/index.ts +105 -9
  143. package/src/zod/introspect.ts +103 -0
  144. package/src/zod/jsonToZodSchemaMapper.ts +44 -21
  145. package/src/zod/zodMinMaxFilter.ts +37 -13
  146. package/src/zod/zodToTypeString.ts +184 -181
  147. package/dist/api-wrapper/index.js +0 -250
  148. package/dist/gzip/index.d.ts +0 -2
  149. package/dist/gzip/index.js +0 -16
  150. package/dist/index.js +0 -1823
  151. package/dist/jwt/index.js +0 -19
  152. package/dist/sitemap/index.js +0 -552
  153. package/dist/src/gzip/index.js +0 -16
  154. package/dist/src/secrets/loaders.js +0 -1529
  155. package/dist/zod/index.js +0 -210
package/dist/src/index.js CHANGED
@@ -1,3 +1,89 @@
1
+ import {
2
+ chunkArray2,
3
+ findArrayDifferenceByKey2,
4
+ getUniqueValues2,
5
+ compact2,
6
+ getUniqueValuesByKey2,
7
+ groupBy2,
8
+ countBy2,
9
+ keyBy2,
10
+ partition2,
11
+ pickItemsInArray2,
12
+ toArray2,
13
+ sumBy2,
14
+ sortBy2,
15
+ range2
16
+ } from "../index-hy7kbb99.js";
17
+ import {
18
+ createCache2
19
+ } from "../index-k9yx6bne.js";
20
+ import {
21
+ ok2,
22
+ err2,
23
+ isSuccess2,
24
+ isFailure2,
25
+ unwrapResult2,
26
+ backgroundPromise2,
27
+ backgroundPromiseSync2,
28
+ promiseTimeout2,
29
+ tryCatch2,
30
+ PromiseTimeoutError2,
31
+ withTimeout2,
32
+ exponentialBackoff2,
33
+ retry2,
34
+ pMap2,
35
+ startTimer2,
36
+ timed2,
37
+ anySignal2,
38
+ timeoutSignal2,
39
+ raceAbort2,
40
+ createLatestGuard2
41
+ } from "../index-0zvtcbzy.js";
42
+ import {
43
+ InvariantError2,
44
+ getErrorMessage2,
45
+ toError2,
46
+ must2,
47
+ invariant2,
48
+ isRecord2,
49
+ isPlainObject2,
50
+ isDefined2,
51
+ isTruthy2,
52
+ isString2,
53
+ isNumber2,
54
+ isBoolean2,
55
+ isFunction2,
56
+ isOneOf2,
57
+ hasKey2,
58
+ isNonEmpty2,
59
+ nonEmpty2
60
+ } from "../index-8r2vdzjg.js";
61
+ import {
62
+ extractNumber2,
63
+ generateId2,
64
+ sha256Id2,
65
+ slugify2,
66
+ stableStringify2,
67
+ truncate2,
68
+ normalizeWhitespace2,
69
+ escapeRegExp2,
70
+ foldForSearch2,
71
+ matchesSearch2,
72
+ initials2,
73
+ safeJsonParse2
74
+ } from "../index-kj1phgrx.js";
75
+ import {
76
+ buildQueryString2,
77
+ replacePathParams2,
78
+ buildUrl2,
79
+ normalizeDomain2,
80
+ getDomainFaviconUrl2,
81
+ trimTrailingSlash2,
82
+ ensureLeadingSlash2,
83
+ buildMailto2,
84
+ parseRetryAfter2
85
+ } from "../index-mnkcfyx8.js";
86
+
1
87
  // src/typedswitch/index.ts
2
88
  function typedSwitch(value, keyOrCases, casesOrDefault, defaultCase) {
3
89
  let key;
@@ -14,10 +100,10 @@ function typedSwitch(value, keyOrCases, casesOrDefault, defaultCase) {
14
100
  }
15
101
  discriminantKey = keyOrCases;
16
102
  const discriminantValue = value[discriminantKey];
17
- if (typeof discriminantValue !== "string") {
18
- throw new Error(`Invalid discriminant value for key "${discriminantKey}": expected string, received ${typeof discriminantValue}`);
103
+ if (typeof discriminantValue !== "string" && typeof discriminantValue !== "number" && typeof discriminantValue !== "boolean") {
104
+ throw new Error(`Invalid discriminant value for key "${discriminantKey}": expected string, number or boolean, received ${typeof discriminantValue}`);
19
105
  }
20
- key = discriminantValue;
106
+ key = String(discriminantValue);
21
107
  cases = casesOrDefault;
22
108
  defaultHandler = defaultCase;
23
109
  }
@@ -33,184 +119,8 @@ function typedSwitch(value, keyOrCases, casesOrDefault, defaultCase) {
33
119
  const contextSuffix = discriminantKey ? ` (discriminant key: "${discriminantKey}")` : "";
34
120
  throw new Error(`Unhandled case: ${key}${contextSuffix}.${availableCasesSuffix}`);
35
121
  }
36
- // src/array/index.ts
37
- function chunkArray(array, chunkSize) {
38
- if (!Number.isInteger(chunkSize) || chunkSize <= 0) {
39
- throw new Error("Chunk size must be a positive integer");
40
- }
41
- const chunks = [];
42
- for (let index = 0;index < array.length; index += chunkSize) {
43
- chunks.push(array.slice(index, index + chunkSize));
44
- }
45
- return chunks;
46
- }
47
- function findArrayDifferenceByKey(current, next, key) {
48
- const currentByKey = new Map(current.map((item) => [String(item[key]), item]));
49
- const itemsToDelete = [...current];
50
- const newItems = [];
51
- const upsertItems = [];
52
- for (const item of next) {
53
- const keyValue = String(item[key]);
54
- if (!currentByKey.has(keyValue)) {
55
- newItems.push(item);
56
- continue;
57
- }
58
- upsertItems.push(item);
59
- const deleteIndex = itemsToDelete.findIndex((currentItem) => String(currentItem[key]) === keyValue);
60
- if (deleteIndex >= 0) {
61
- itemsToDelete.splice(deleteIndex, 1);
62
- }
63
- }
64
- return {
65
- new: newItems,
66
- upsert: upsertItems,
67
- delete: itemsToDelete
68
- };
69
- }
70
- function getUniqueValues(array) {
71
- const seen = new Map;
72
- for (const element of array) {
73
- const key = JSON.stringify(element);
74
- if (!seen.has(key)) {
75
- seen.set(key, element);
76
- }
77
- }
78
- return Array.from(seen.values());
79
- }
80
- function compact(array) {
81
- return array.filter((item) => item != null);
82
- }
83
- function getUniqueValuesByKey(array, key) {
84
- const seen = new Set;
85
- const result = [];
86
- for (const item of array) {
87
- const value = item[key];
88
- if (seen.has(value))
89
- continue;
90
- seen.add(value);
91
- result.push(item);
92
- }
93
- return result;
94
- }
95
- function groupBy(array, keyOrFn) {
96
- return array.reduce((result, item) => {
97
- const groupKey = typeof keyOrFn === "function" ? keyOrFn(item) : item[keyOrFn];
98
- result[groupKey] ??= [];
99
- result[groupKey].push(item);
100
- return result;
101
- }, {});
102
- }
103
- function keyBy(array, keyOrFn) {
104
- const result = {};
105
- for (const item of array) {
106
- const recordKey = typeof keyOrFn === "function" ? keyOrFn(item) : item[keyOrFn];
107
- result[recordKey] = item;
108
- }
109
- return result;
110
- }
111
- function partition(array, predicate) {
112
- const matched = [];
113
- const unmatched = [];
114
- array.forEach((item, index) => {
115
- if (predicate(item, index)) {
116
- matched.push(item);
117
- } else {
118
- unmatched.push(item);
119
- }
120
- });
121
- return [matched, unmatched];
122
- }
123
- function pickItemsInArray(array, key, values) {
124
- return array.filter((item) => values.includes(item[key]));
125
- }
126
- // src/string/index.ts
127
- function extractNumber(value) {
128
- if (!value?.trim())
129
- return;
130
- const cleanedValue = value.replace(/[^\d.,-]/g, "");
131
- if (!cleanedValue)
132
- return;
133
- const parsedNumber = Number.parseFloat(cleanedValue);
134
- return Number.isNaN(parsedNumber) ? undefined : parsedNumber;
135
- }
136
- function generateId(input) {
137
- const data = normalizeIdInput(input);
138
- let hash = 0;
139
- for (let index = 0;index < data.length; index++) {
140
- const char = data.charCodeAt(index);
141
- hash = (hash << 5) - hash + char;
142
- hash |= 0;
143
- }
144
- return Math.abs(hash).toString(16).padStart(8, "0");
145
- }
146
- function slugify(value) {
147
- const source = "àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìłḿñńǹňôöòóœøōõṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·/_,:;";
148
- const replacement = "aaaaaaaaaacccddeeeeeeeegghiiiiiilmnnnnooooooooprrsssssttuuuuuuuuuwxyyzzz------";
149
- const specialCharacterPattern = new RegExp(source.split("").join("|"), "g");
150
- return value.toString().toLowerCase().replace(/\s+/g, "-").replace(specialCharacterPattern, (character) => replacement.charAt(source.indexOf(character))).replace(/&/g, "-and-").replace(/[^\w-]+/g, "").replace(/--+/g, "-").replace(/^-+/, "").replace(/-+$/, "");
151
- }
152
- function normalizeIdInput(input) {
153
- if (input === undefined)
154
- return "undefined";
155
- if (input === null)
156
- return "null";
157
- if (typeof input === "string") {
158
- return decodeURIComponent(input).split("&").sort().join("&");
159
- }
160
- if (typeof input === "number" || typeof input === "boolean") {
161
- return input.toString();
162
- }
163
- return JSON.stringify(sortJsonValue(input));
164
- }
165
- function sortJsonValue(value) {
166
- if (value === undefined)
167
- return null;
168
- if (Array.isArray(value)) {
169
- return value.map((item) => sortJsonValue(item));
170
- }
171
- if (typeof value === "object" && value !== null) {
172
- return Object.fromEntries(Object.entries(value).filter(([, itemValue]) => itemValue !== undefined).sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)).map(([key, itemValue]) => [key, sortJsonValue(itemValue)]));
173
- }
174
- return value;
175
- }
176
- // src/promise/index.ts
177
- function isSuccess(result) {
178
- return result.error === null;
179
- }
180
- function isFailure(result) {
181
- return result.error !== null;
182
- }
183
- function unwrapResult(result) {
184
- if (isSuccess(result))
185
- return result.data;
186
- throw result.error;
187
- }
188
- function backgroundPromise(promiseFactory, options = {}) {
189
- const run = () => {
190
- promiseFactory().then(() => options.onSuccess?.()).catch((error) => options.onError?.(error));
191
- };
192
- if (options.scheduler) {
193
- options.scheduler(run);
194
- return;
195
- }
196
- run();
197
- }
198
- function backgroundPromiseSync(promiseFactory, options = {}) {
199
- backgroundPromise(promiseFactory, {
200
- ...options,
201
- scheduler: options.scheduler ?? queueMicrotask
202
- });
203
- }
204
- function promiseTimeout(ms) {
205
- return new Promise((resolve) => setTimeout(resolve, ms));
206
- }
207
- async function tryCatch(promise) {
208
- try {
209
- const data = await promise;
210
- return { data, error: null };
211
- } catch (error) {
212
- return { data: null, error };
213
- }
122
+ function assertNever(value, message) {
123
+ throw new Error(message ?? `Unexpected value: ${String(value)}`);
214
124
  }
215
125
  // src/cache/index.ts
216
126
  function cacheFunction(options) {
@@ -223,7 +133,8 @@ function cacheFunction(options) {
223
133
  cacheKeyFn,
224
134
  onBackgroundRefreshError,
225
135
  store,
226
- onStoreError
136
+ onStoreError,
137
+ maxEntries
227
138
  } = options;
228
139
  const externalStore = store;
229
140
  const cache = new Map;
@@ -255,143 +166,116 @@ function cacheFunction(options) {
255
166
  };
256
167
  const buildKey = (args) => {
257
168
  const keyArgs = cacheKeyFn ? cacheKeyFn(...args) : cacheKeyArgs ? cacheKeyArgs.map((i) => args[i]) : args;
258
- return `${name}:${JSON.stringify(keyArgs)}`;
169
+ return `${name}:${stableStringify2(keyArgs)}`;
259
170
  };
260
- const saveEntry = (key, value) => {
261
- cache.set(key, { value, timestamp: Date.now() });
262
- ensureCleanup();
171
+ const isFresh = (entry) => Date.now() - entry.timestamp < cacheTimeMs;
172
+ const servable = (entry) => entry && (refreshInBackground || isFresh(entry)) ? entry : undefined;
173
+ const touch = (key, entry) => {
174
+ cache.delete(key);
175
+ cache.set(key, entry);
176
+ if (maxEntries !== undefined && cache.size > maxEntries) {
177
+ cache.delete(cache.keys().next().value);
178
+ }
263
179
  };
264
- const fetchAndPopulate = (key, args) => {
265
- const result = fn(...args);
266
- if (!isPromiseLike(result)) {
267
- saveEntry(key, result);
268
- return result;
180
+ const guardStore = async (operation) => {
181
+ try {
182
+ return await operation();
183
+ } catch (error) {
184
+ onStoreError?.(error, { name });
185
+ return;
269
186
  }
270
- const promise = result.then((value) => {
271
- saveEntry(key, value);
272
- pendingPromises.delete(key);
273
- return value;
274
- }).catch((error) => {
275
- pendingPromises.delete(key);
276
- throw error;
277
- });
278
- pendingPromises.set(key, promise);
279
- return promise;
280
187
  };
281
- const startBackgroundRefresh = (key, args, staleValue) => {
188
+ const save = (key, value) => {
189
+ const entry = { value, timestamp: Date.now() };
190
+ if (externalStore) {
191
+ return guardStore(() => externalStore.set(key, entry, entryLifetimeMs));
192
+ }
193
+ touch(key, entry);
194
+ ensureCleanup();
195
+ };
196
+ const run = (key, args, stale) => {
282
197
  const result = fn(...args);
283
198
  if (!isPromiseLike(result)) {
284
- saveEntry(key, result);
285
- return;
199
+ save(key, result);
200
+ return result;
286
201
  }
287
- const promise = result.then((value) => {
288
- saveEntry(key, value);
289
- pendingPromises.delete(key);
202
+ const promise = Promise.resolve(result).then(async (value) => {
203
+ if (pendingPromises.get(key) === promise) {
204
+ await save(key, value);
205
+ }
290
206
  return value;
291
- }).catch((error) => {
292
- pendingPromises.delete(key);
207
+ }, (error) => {
208
+ if (!stale)
209
+ throw error;
293
210
  onBackgroundRefreshError?.(error, { name });
294
- return staleValue;
211
+ return stale.value;
212
+ }).finally(() => {
213
+ if (pendingPromises.get(key) === promise) {
214
+ pendingPromises.delete(key);
215
+ }
295
216
  });
296
217
  pendingPromises.set(key, promise);
218
+ return promise;
297
219
  };
298
- const saveToStore = async (cacheStore, key, value) => {
299
- try {
300
- await cacheStore.set(key, { value, timestamp: Date.now() }, entryLifetimeMs);
301
- } catch (error) {
302
- onStoreError?.(error, { name });
220
+ const serve = (key, args, entry) => {
221
+ if (!isFresh(entry) && !pendingPromises.has(key)) {
222
+ run(key, args, entry);
303
223
  }
224
+ return entry.value;
304
225
  };
305
- const callWithStore = (cacheStore, args) => {
306
- const key = buildKey(args);
226
+ const callWithStore = (cacheStore, key, args) => {
307
227
  const pending = pendingPromises.get(key);
308
228
  if (pending && !refreshInBackground) {
309
229
  return pending;
310
230
  }
311
231
  return (async () => {
312
- let entry;
313
- try {
314
- entry = await cacheStore.get(key);
315
- } catch (error) {
316
- onStoreError?.(error, { name });
232
+ const entry = await guardStore(() => cacheStore.get(key));
233
+ const cached = servable(entry);
234
+ if (cached) {
235
+ return serve(key, args, cached);
317
236
  }
318
237
  if (entry) {
319
- const isFresh = Date.now() - entry.timestamp < cacheTimeMs;
320
- if (isFresh) {
321
- return entry.value;
322
- }
323
- if (refreshInBackground) {
324
- if (!pendingPromises.has(key)) {
325
- const staleValue = entry.value;
326
- const refresh = fn(...args).then(async (value) => {
327
- await saveToStore(cacheStore, key, value);
328
- pendingPromises.delete(key);
329
- return value;
330
- }).catch((error) => {
331
- pendingPromises.delete(key);
332
- onBackgroundRefreshError?.(error, { name });
333
- return staleValue;
334
- });
335
- pendingPromises.set(key, refresh);
336
- }
337
- return entry.value;
338
- }
339
- try {
340
- await cacheStore.delete(key);
341
- } catch (error) {
342
- onStoreError?.(error, { name });
343
- }
344
- }
345
- const existing = pendingPromises.get(key);
346
- if (existing) {
347
- return existing;
238
+ await guardStore(() => cacheStore.delete(key));
348
239
  }
349
- const promise = fn(...args).then(async (value) => {
350
- await saveToStore(cacheStore, key, value);
351
- pendingPromises.delete(key);
352
- return value;
353
- }).catch((error) => {
354
- pendingPromises.delete(key);
355
- throw error;
356
- });
357
- pendingPromises.set(key, promise);
358
- return promise;
240
+ return pendingPromises.get(key) ?? run(key, args);
359
241
  })();
360
242
  };
361
243
  const wrappedFn = (...args) => {
244
+ const key = buildKey(args);
362
245
  if (externalStore) {
363
- return callWithStore(externalStore, args);
246
+ return callWithStore(externalStore, key, args);
364
247
  }
365
- const key = buildKey(args);
366
- const cached = cache.get(key);
248
+ const cached = servable(cache.get(key));
367
249
  if (cached) {
368
- const isFresh = Date.now() - cached.timestamp < cacheTimeMs;
369
- if (isFresh) {
370
- return cached.value;
371
- }
372
- if (refreshInBackground) {
373
- if (!pendingPromises.has(key)) {
374
- startBackgroundRefresh(key, args, cached.value);
375
- }
376
- return cached.value;
377
- }
378
- cache.delete(key);
379
- }
380
- const pending = pendingPromises.get(key);
381
- if (pending) {
382
- return pending;
250
+ touch(key, cached);
251
+ return serve(key, args, cached);
383
252
  }
384
- return fetchAndPopulate(key, args);
253
+ cache.delete(key);
254
+ return pendingPromises.get(key) ?? run(key, args);
385
255
  };
386
256
  wrappedFn.clearCache = () => {
387
257
  cache.clear();
388
258
  pendingPromises.clear();
389
259
  stopCleanup();
390
- if (externalStore?.clear) {
391
- externalStore.clear().catch((error) => {
392
- onStoreError?.(error, { name });
393
- });
260
+ const clearStore = externalStore?.clear?.bind(externalStore);
261
+ if (clearStore) {
262
+ guardStore(clearStore);
263
+ }
264
+ };
265
+ wrappedFn.invalidate = (...args) => {
266
+ const key = buildKey(args);
267
+ cache.delete(key);
268
+ pendingPromises.delete(key);
269
+ if (externalStore) {
270
+ guardStore(() => externalStore.delete(key));
271
+ }
272
+ };
273
+ wrappedFn.peek = (...args) => {
274
+ const key = buildKey(args);
275
+ if (externalStore) {
276
+ return guardStore(() => externalStore.get(key)).then((entry) => servable(entry)?.value);
394
277
  }
278
+ return servable(cache.get(key))?.value;
395
279
  };
396
280
  return wrappedFn;
397
281
  }
@@ -413,84 +297,54 @@ class SimpleStateMachine {
413
297
  }
414
298
  async getAvailableActions() {
415
299
  const actionsMap = this.config.stateActionMap[this.getCurrentStatus()] || {};
416
- const result = {};
417
- for (const [action, targetState] of Object.entries(actionsMap)) {
418
- if (!targetState)
419
- continue;
420
- const validationResult = await this.validateTransition(targetState, { action });
421
- result[action] = validationResult;
422
- }
423
- return result;
300
+ const entries = Object.entries(actionsMap).filter((entry) => !!entry[1]);
301
+ const results = await Promise.all(entries.map(([action, targetState]) => this.validateTransition(targetState, { action })));
302
+ return Object.fromEntries(entries.map(([action], i) => [action, results[i]]));
424
303
  }
425
304
  async getPossibleTransitions() {
426
305
  const possibleTransitions = this.config.stateTransitions[this.getCurrentStatus()] || [];
427
306
  const actionsMap = this.config.stateActionMap[this.getCurrentStatus()] || {};
428
- const result = {};
429
- for (const targetState of possibleTransitions) {
307
+ const results = await Promise.all(possibleTransitions.map(async (targetState) => {
430
308
  const actionsToTarget = Object.entries(actionsMap).filter(([, t]) => t === targetState);
431
309
  if (actionsToTarget.length === 0) {
432
- result[targetState] = await this.validateTransition(targetState);
433
- continue;
434
- }
435
- let lastError = true;
436
- let anyOk = false;
437
- for (const [actionKey] of actionsToTarget) {
438
- const r = await this.validateTransition(targetState, {
439
- action: actionKey
440
- });
441
- if (r === true) {
442
- anyOk = true;
443
- break;
444
- }
445
- lastError = r;
310
+ return this.validateTransition(targetState);
446
311
  }
447
- result[targetState] = anyOk ? true : lastError;
448
- }
449
- return result;
312
+ const rs = await Promise.all(actionsToTarget.map(([action]) => this.validateTransition(targetState, { action })));
313
+ return rs.includes(true) ? true : rs[rs.length - 1];
314
+ }));
315
+ return Object.fromEntries(possibleTransitions.map((targetState, i) => [targetState, results[i]]));
450
316
  }
451
- async executeAction(action, data) {
317
+ async canExecute(action) {
452
318
  const actionsMap = this.config.stateActionMap[this.getCurrentStatus()];
453
319
  if (!actionsMap) {
454
- return {
455
- success: false,
456
- error: `No actions defined for state '${this.getCurrentStatus()}'`
457
- };
320
+ return `No actions defined for state '${this.getCurrentStatus()}'`;
458
321
  }
459
322
  const targetState = actionsMap[action];
460
323
  if (!targetState) {
461
- return {
462
- success: false,
463
- error: `Action '${action}' not allowed in state '${this.getCurrentStatus()}'`
464
- };
324
+ return `Action '${action}' not allowed in state '${this.getCurrentStatus()}'`;
465
325
  }
466
- const validationResult = await this.validateTransition(targetState, {
467
- action
468
- });
469
- if (validationResult === true) {
470
- if (this.config.actions?.[action]) {
471
- try {
472
- const result = await this.config.actions[action](this.item, data);
473
- return {
474
- success: true,
475
- newState: targetState,
476
- result
477
- };
478
- } catch (error) {
479
- return {
480
- success: false,
481
- error: error instanceof Error ? error.message : String(error)
482
- };
483
- }
326
+ return this.validateTransition(targetState, { action });
327
+ }
328
+ async executeAction(action, data) {
329
+ const fromState = this.getCurrentStatus();
330
+ const validationResult = await this.canExecute(action);
331
+ if (validationResult !== true) {
332
+ return { success: false, error: validationResult };
333
+ }
334
+ const targetState = this.config.stateActionMap[fromState][action];
335
+ try {
336
+ const result = await this.config.actions?.[action]?.(this.item, data);
337
+ await this.config.onTransition?.(this.item, fromState, targetState, action);
338
+ if (this.config.applyTransition) {
339
+ this.item[this.stateKey] = targetState;
484
340
  }
341
+ return { success: true, newState: targetState, result };
342
+ } catch (error) {
485
343
  return {
486
- success: true,
487
- newState: targetState
344
+ success: false,
345
+ error: error instanceof Error ? error.message : String(error)
488
346
  };
489
347
  }
490
- return {
491
- success: false,
492
- error: validationResult
493
- };
494
348
  }
495
349
  async validateTransition(toState, options) {
496
350
  const action = options?.action;
@@ -508,7 +362,14 @@ class SimpleStateMachine {
508
362
  }
509
363
  const stateValidator = this.config.validators?.[toState];
510
364
  if (stateValidator) {
511
- return await stateValidator(this.item, this.getCurrentStatus(), toState, action);
365
+ const stateResult = await stateValidator(this.item, this.getCurrentStatus(), toState, action);
366
+ if (stateResult !== true) {
367
+ return stateResult;
368
+ }
369
+ }
370
+ const actionValidator = action && this.config.actionValidators?.[action];
371
+ if (actionValidator) {
372
+ return await actionValidator(this.item, this.getCurrentStatus(), toState, action);
512
373
  }
513
374
  return true;
514
375
  }
@@ -516,17 +377,23 @@ class SimpleStateMachine {
516
377
  return Object.keys(this.config.stateTransitions);
517
378
  }
518
379
  getInitialState() {
519
- return this.getStates()[0];
380
+ return this.config.initialState ?? this.getStates()[0];
520
381
  }
521
382
  }
383
+ function createStateMachine(config) {
384
+ return {
385
+ config,
386
+ for: (item, stateKey) => new SimpleStateMachine(config, item, stateKey)
387
+ };
388
+ }
522
389
  // src/math/linearRegressionTrend.ts
523
- function linearRegressionTrend(values) {
390
+ function linearRegression(values) {
524
391
  const n = values.length;
525
392
  if (n < 2)
526
- return [];
393
+ return null;
527
394
  for (const v of values) {
528
395
  if (!Number.isFinite(v))
529
- return [];
396
+ return null;
530
397
  }
531
398
  let sumX = 0;
532
399
  let sumY = 0;
@@ -542,24 +409,155 @@ function linearRegressionTrend(values) {
542
409
  }
543
410
  const denom = n * sumX2 - sumX * sumX;
544
411
  if (denom === 0)
545
- return [];
412
+ return null;
546
413
  const slope = (n * sumXY - sumX * sumY) / denom;
547
414
  const intercept = (sumY - slope * sumX) / n;
548
- const out = [];
415
+ const mean = sumY / n;
416
+ const fitted = [];
417
+ let ssRes = 0;
418
+ let ssTot = 0;
549
419
  for (let i = 0;i < n; i++) {
550
- out.push(intercept + slope * i);
420
+ const y = values[i];
421
+ const f = intercept + slope * i;
422
+ fitted.push(f);
423
+ ssRes += (y - f) ** 2;
424
+ ssTot += (y - mean) ** 2;
551
425
  }
552
- return out;
426
+ return { slope, intercept, r2: ssTot === 0 ? 1 : 1 - ssRes / ssTot, fitted };
427
+ }
428
+ function linearRegressionTrend(values) {
429
+ return linearRegression(values)?.fitted ?? [];
430
+ }
431
+ // src/math/scale.ts
432
+ function clamp(value, min, max) {
433
+ return Math.min(max, Math.max(min, value));
434
+ }
435
+ function lerp(start, end, t) {
436
+ return start + (end - start) * t;
437
+ }
438
+ function normalize(value, min, max) {
439
+ if (max <= min)
440
+ return 0;
441
+ return clamp((value - min) / (max - min), 0, 1);
442
+ }
443
+ // src/color/contrast.ts
444
+ function colorFunctionArgs(color) {
445
+ const match = /^(rgb|hsl)a?\(([^()]*)\)$/i.exec(color.trim());
446
+ const args = match?.[2]?.trim().split(/[\s,/]+/);
447
+ if (!match || !args || args.length !== 3 && args.length !== 4) {
448
+ return;
449
+ }
450
+ return { name: match[1].toLowerCase(), args };
451
+ }
452
+ function parseChannel(raw, percentOf) {
453
+ const isPercent = raw.endsWith("%");
454
+ const text = isPercent ? raw.slice(0, -1) : raw;
455
+ const n = text === "" ? Number.NaN : Number(text);
456
+ if (!Number.isFinite(n))
457
+ return;
458
+ return isPercent ? n / 100 * percentOf : n;
459
+ }
460
+ function hslToRgb(h, s, l) {
461
+ const a = s * Math.min(l, 1 - l);
462
+ const f = (n) => {
463
+ const k = (n + h / 30) % 12;
464
+ return (l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1))) * 255;
465
+ };
466
+ return [f(0), f(8), f(4)];
467
+ }
468
+ function parseColor(color) {
469
+ const trimmed = color.trim();
470
+ const hex = /^#([\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/i.exec(trimmed)?.[1];
471
+ if (hex) {
472
+ const full = hex.length <= 4 ? hex.replace(/./g, "$&$&") : hex;
473
+ const [r = 0, g = 0, b = 0, a = 255] = [0, 2, 4, 6].map((i) => i < full.length ? Number.parseInt(full.slice(i, i + 2), 16) : undefined);
474
+ return { r, g, b, a: a / 255 };
475
+ }
476
+ const fn = colorFunctionArgs(trimmed);
477
+ if (!fn)
478
+ return;
479
+ const [x = "", y = "", z = "", alpha = "1"] = fn.args;
480
+ const isRgb = fn.name === "rgb";
481
+ const c1 = parseChannel(isRgb ? x : x.replace(/deg$/i, ""), isRgb ? 255 : 360);
482
+ const c2 = parseChannel(y, isRgb ? 255 : 100);
483
+ const c3 = parseChannel(z, isRgb ? 255 : 100);
484
+ const a = parseChannel(alpha, 1);
485
+ if (c1 === undefined || c2 === undefined || c3 === undefined)
486
+ return;
487
+ if (a === undefined)
488
+ return;
489
+ const [r, g, b] = isRgb ? [c1, c2, c3] : hslToRgb((c1 % 360 + 360) % 360, clamp(c2 / 100, 0, 1), clamp(c3 / 100, 0, 1));
490
+ const channel = (v) => Math.round(clamp(v, 0, 255));
491
+ return { r: channel(r), g: channel(g), b: channel(b), a: clamp(a, 0, 1) };
492
+ }
493
+ function toRgba(color) {
494
+ if (typeof color !== "string")
495
+ return color;
496
+ const parsed = parseColor(color);
497
+ if (!parsed)
498
+ throw new TypeError(`Unsupported color: ${color}`);
499
+ return parsed;
500
+ }
501
+ function toHex(color, options = {}) {
502
+ const { r, g, b, a } = toRgba(color);
503
+ const channels = options.alpha ? [r, g, b, a * 255] : [r, g, b];
504
+ return `#${channels.map((v) => Math.round(clamp(v, 0, 255)).toString(16).padStart(2, "0")).join("")}`;
505
+ }
506
+ function toRgbString(color) {
507
+ const { r, g, b, a } = toRgba(color);
508
+ const [rr, gg, bb] = [r, g, b].map((v) => Math.round(clamp(v, 0, 255)));
509
+ return a < 1 ? `rgba(${rr}, ${gg}, ${bb}, ${a})` : `rgb(${rr}, ${gg}, ${bb})`;
510
+ }
511
+ function relativeLuminance(color) {
512
+ const { r, g, b } = toRgba(color);
513
+ const [lr, lg, lb] = [r, g, b].map((v) => {
514
+ const c = clamp(v, 0, 255) / 255;
515
+ return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
516
+ });
517
+ return 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;
518
+ }
519
+ function contrastRatio(a, b) {
520
+ const la = relativeLuminance(a);
521
+ const lb = relativeLuminance(b);
522
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
523
+ }
524
+ function contrastText(background, options = {}) {
525
+ const { light = "#ffffff", dark = "#000000" } = options;
526
+ return contrastRatio(background, light) >= contrastRatio(background, dark) ? light : dark;
527
+ }
528
+ function mix(a, b, weight) {
529
+ const t = clamp(weight, 0, 1);
530
+ const at = (x, y) => x + (y - x) * t;
531
+ return {
532
+ r: at(a.r, b.r),
533
+ g: at(a.g, b.g),
534
+ b: at(a.b, b.b),
535
+ a: at(a.a, b.a)
536
+ };
553
537
  }
554
- // src/math/metricsSeriesColors.ts
538
+ var hexOf = (color) => toHex(color, { alpha: color.a < 1 });
539
+ function mixColors(a, b, weight) {
540
+ return hexOf(mix(toRgba(a), toRgba(b), weight));
541
+ }
542
+ function shade(color, amount) {
543
+ const base = toRgba(color);
544
+ const v = amount < 0 ? 0 : 255;
545
+ return hexOf(mix(base, { r: v, g: v, b: v, a: base.a }, Math.abs(amount)));
546
+ }
547
+ // src/color/metricsSeriesColors.ts
555
548
  var METRICS_SERIES_PALETTE_SIZE = 10;
556
- function hashStringToColorIndex(str) {
549
+ function hashStringToColorIndex(str, paletteSize = METRICS_SERIES_PALETTE_SIZE) {
557
550
  let hash = 2166136261;
558
551
  for (let i = 0;i < str.length; i++) {
559
552
  hash ^= str.charCodeAt(i);
560
553
  hash = Math.imul(hash, 16777619);
561
554
  }
562
- return Math.abs(hash) % METRICS_SERIES_PALETTE_SIZE;
555
+ return Math.abs(hash) % paletteSize;
556
+ }
557
+ function pickFromPalette(seed, palette) {
558
+ if (palette.length === 0)
559
+ throw new RangeError("palette is empty");
560
+ return palette[hashStringToColorIndex(seed, palette.length)];
563
561
  }
564
562
  function circularHueDistance(a, b, paletteSize) {
565
563
  const diff = Math.abs(a - b);
@@ -586,21 +584,20 @@ function seriesColorKey(label, key) {
586
584
  return fromLabel;
587
585
  return key.trim() || key;
588
586
  }
589
- function resolveSeriesColorMap(keys) {
587
+ function resolveSeriesColorMap(keys, paletteSize = METRICS_SERIES_PALETTE_SIZE) {
590
588
  const result = new Map;
591
589
  const uniqueKeys = [...new Set(keys)];
592
590
  if (uniqueKeys.length === 0)
593
591
  return result;
594
- const paletteSize = METRICS_SERIES_PALETTE_SIZE;
595
592
  if (uniqueKeys.length === 1) {
596
593
  const only = uniqueKeys[0];
597
- result.set(only, hashStringToColorIndex(only));
594
+ result.set(only, hashStringToColorIndex(only, paletteSize));
598
595
  return result;
599
596
  }
600
597
  const usedIndices = new Set;
601
598
  const entries = uniqueKeys.map((key) => ({
602
599
  key,
603
- preferred: hashStringToColorIndex(key)
600
+ preferred: hashStringToColorIndex(key, paletteSize)
604
601
  }));
605
602
  for (const entry of entries) {
606
603
  if (!usedIndices.has(entry.preferred)) {
@@ -622,35 +619,25 @@ function resolveSeriesColorMap(keys) {
622
619
  }
623
620
  return result;
624
621
  }
625
- function createResolveColorsByKeys(keys, getSeriesColor, getSeriesHoverColor) {
626
- const colorMap = resolveSeriesColorMap(keys);
622
+ function createResolveColorsByKeys(keys, getSeriesColor, getSeriesHoverColor, paletteSize = METRICS_SERIES_PALETTE_SIZE) {
623
+ const colorMap = resolveSeriesColorMap(keys, paletteSize);
624
+ const indexOf = (key) => colorMap.get(key) ?? hashStringToColorIndex(key, paletteSize);
627
625
  return {
628
- getColor: (key) => getSeriesColor(colorMap.get(key) ?? hashStringToColorIndex(key)),
629
- getHoverColor: (key) => getSeriesHoverColor(colorMap.get(key) ?? hashStringToColorIndex(key))
626
+ getColor: (key) => getSeriesColor(indexOf(key)),
627
+ getHoverColor: (key) => getSeriesHoverColor(indexOf(key))
630
628
  };
631
629
  }
632
- function clamp(value, min, max) {
633
- return Math.min(max, Math.max(min, value));
634
- }
635
630
  function withAlpha(color, alpha) {
636
631
  const trimmed = color.trim();
637
- if (trimmed.startsWith("#")) {
638
- const hex = trimmed.slice(1);
639
- const normalized = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
640
- if (normalized.length === 6) {
641
- const r = Number.parseInt(normalized.slice(0, 2), 16);
642
- const g = Number.parseInt(normalized.slice(2, 4), 16);
643
- const b = Number.parseInt(normalized.slice(4, 6), 16);
644
- return `rgba(${r}, ${g}, ${b}, ${alpha})`;
645
- }
646
- }
647
- if (trimmed.startsWith("rgb(")) {
648
- return trimmed.replace("rgb(", "rgba(").replace(")", `, ${alpha})`);
632
+ const rgba = parseColor(trimmed);
633
+ const fn = rgba && colorFunctionArgs(trimmed);
634
+ if (fn?.name === "hsl") {
635
+ const [h, s, l] = fn.args;
636
+ return `hsla(${h}, ${s}, ${l}, ${alpha})`;
649
637
  }
650
- if (trimmed.startsWith("rgba(")) {
651
- return trimmed.replace(/,\s*[\d.]+\)$/, `, ${alpha})`);
652
- }
653
- return trimmed;
638
+ if (rgba)
639
+ return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${alpha})`;
640
+ return `color-mix(in srgb, ${trimmed} ${alpha * 100}%, transparent)`;
654
641
  }
655
642
  function getHeatmapCellStyle(params) {
656
643
  const {
@@ -673,2833 +660,440 @@ function getHeatmapCellStyle(params) {
673
660
  backgroundColor: withAlpha(neutralColor ?? baseColor, 0.32)
674
661
  };
675
662
  }
676
- const normalized = clamp((value - min) / (max - min), 0, 1);
677
- const alpha = minAlpha + (maxAlpha - minAlpha) * normalized;
678
663
  return {
679
- backgroundColor: withAlpha(baseColor, alpha)
664
+ backgroundColor: withAlpha(baseColor, lerp(minAlpha, maxAlpha, normalize(value, min, max)))
680
665
  };
681
666
  }
682
667
  // src/date/index.ts
683
- function getPreviousReportPeriod(periodType, referenceDate = new Date) {
684
- const month = referenceDate.getMonth();
685
- const year = referenceDate.getFullYear();
686
- switch (periodType) {
687
- case "MONTHLY": {
688
- const prevMonth = month === 0 ? 11 : month - 1;
689
- const prevYear = month === 0 ? year - 1 : year;
690
- return {
691
- start: new Date(prevYear, prevMonth, 1),
692
- end: new Date(year, month, 0)
693
- };
694
- }
695
- case "QUARTERLY": {
696
- const qStart = Math.floor(month / 3) * 3;
697
- const prevQStart = qStart === 0 ? 9 : qStart - 3;
698
- const prevQYear = qStart === 0 ? year - 1 : year;
699
- return {
700
- start: new Date(prevQYear, prevQStart, 1),
701
- end: new Date(prevQYear, prevQStart + 3, 0)
702
- };
703
- }
704
- case "YEARLY": {
705
- return {
706
- start: new Date(year - 1, 0, 1),
707
- end: new Date(year - 1, 11, 31)
708
- };
709
- }
668
+ var MONTHS_PER_PERIOD = { MONTHLY: 1, QUARTERLY: 3, YEARLY: 12 };
669
+ function ymd(date, utc = false) {
670
+ return utc ? [date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()] : [date.getFullYear(), date.getMonth(), date.getDate()];
671
+ }
672
+ function startOfDay(y, m, d, utc = false) {
673
+ return utc ? new Date(Date.UTC(y, m, d)) : new Date(y, m, d);
674
+ }
675
+ function addPeriod(date, type, n, options = {}) {
676
+ const { utc = false } = options;
677
+ let [y, m, d] = ymd(date, utc);
678
+ if (type === "DAILY" || type === "WEEKLY") {
679
+ d += n * (type === "WEEKLY" ? 7 : 1);
680
+ } else {
681
+ m += n * MONTHS_PER_PERIOD[type];
682
+ d = Math.min(d, new Date(Date.UTC(y, m + 1, 0)).getUTCDate());
710
683
  }
684
+ const out = new Date(date);
685
+ if (utc)
686
+ out.setUTCFullYear(y, m, d);
687
+ else
688
+ out.setFullYear(y, m, d);
689
+ return out;
690
+ }
691
+ function getPeriod(type, referenceDate = new Date, offset = 0, options = {}) {
692
+ const { utc = false, weekStartsOn = 1 } = options;
693
+ const [y, m, d] = ymd(referenceDate, utc);
694
+ const weekday = utc ? referenceDate.getUTCDay() : referenceDate.getDay();
695
+ const anchor = {
696
+ DAILY: () => startOfDay(y, m, d, utc),
697
+ WEEKLY: () => startOfDay(y, m, d - (weekday - weekStartsOn + 7) % 7, utc),
698
+ MONTHLY: () => startOfDay(y, m, 1, utc),
699
+ QUARTERLY: () => startOfDay(y, m - m % 3, 1, utc),
700
+ YEARLY: () => startOfDay(y, 0, 1, utc)
701
+ }[type]();
702
+ const [ey, em, ed] = ymd(addPeriod(anchor, type, offset + 1, { utc }), utc);
703
+ return {
704
+ start: addPeriod(anchor, type, offset, { utc }),
705
+ end: startOfDay(ey, em, ed - 1, utc)
706
+ };
707
+ }
708
+ function getPreviousReportPeriod(periodType, referenceDate = new Date) {
709
+ return getPeriod(periodType, referenceDate, -1);
711
710
  }
712
- function formatPeriodLabel(start, end, includeDay = false) {
711
+ function formatPeriodLabel(start, end, options = false) {
712
+ const {
713
+ includeDay = false,
714
+ locale = "en-GB",
715
+ separator = " — ",
716
+ collapse = false,
717
+ timeZone
718
+ } = typeof options === "boolean" ? { includeDay: options } : options;
713
719
  const s = start instanceof Date ? start : new Date(start);
714
720
  const e = end instanceof Date ? end : new Date(end);
715
- const opts = {
721
+ const format = new Intl.DateTimeFormat(locale, {
716
722
  year: "numeric",
717
723
  month: "long",
718
- ...includeDay && { day: "numeric" }
719
- };
720
- return `${s.toLocaleDateString("en-GB", opts)} — ${e.toLocaleDateString("en-GB", opts)}`;
721
- }
722
- // src/url/index.ts
723
- function getDomainFaviconUrl(domain, size = 128) {
724
- return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=${size}`;
725
- }
726
- // src/zod/index.ts
727
- import { z as z3 } from "zod";
728
-
729
- // src/zod/jsonToZodSchemaMapper.ts
730
- import { z } from "zod";
731
- function jsonToZodSchemaMapper(jsonObject, zodSchema, config) {
732
- const result = {};
733
- const shape = zodSchema instanceof z.ZodObject ? zodSchema.shape : {};
734
- for (const key of Object.keys(shape)) {
735
- const fieldConfig = config[key];
736
- let value = jsonObject[key];
737
- if (fieldConfig) {
738
- value = fieldConfig(jsonObject);
739
- }
740
- if (shape[key] instanceof z.ZodObject && value && typeof value === "object") {
741
- result[key] = jsonToZodSchemaMapper(value, shape[key], config[key] || {});
742
- } else {
743
- result[key] = value;
744
- }
745
- }
746
- return zodSchema.parse(result);
747
- }
748
- // src/zod/zodMinMaxFilter.ts
749
- import { z as z2 } from "zod";
750
- var zodMinMaxFilter = z2.object({
751
- min: z2.number().optional(),
752
- max: z2.number().optional()
753
- });
754
- function toGteLteFilter(filter) {
755
- if (!filter)
756
- return;
757
- const hasMin = filter.min !== undefined;
758
- const hasMax = filter.max !== undefined;
759
- if (!hasMin && !hasMax)
760
- return;
761
- return {
762
- ...hasMin && { gte: filter.min },
763
- ...hasMax && { lte: filter.max }
764
- };
765
- }
766
- // src/zod/zodToTypeString.ts
767
- function getDefProp(def, prop) {
768
- return def?.[prop];
724
+ ...includeDay && { day: "numeric" },
725
+ timeZone
726
+ });
727
+ return collapse ? format.formatRange(s, e) : `${format.format(s)}${separator}${format.format(e)}`;
769
728
  }
770
- function zodToTypeString(schema, indent = 0) {
771
- const pad = " ".repeat(indent);
772
- const innerPad = " ".repeat(indent + 1);
773
- const def = schema._zod?.def ?? schema._def;
774
- if (!def)
775
- return "unknown";
776
- const schemaType = getDefProp(def, "type") ?? getDefProp(def, "typeName");
777
- switch (schemaType) {
778
- case "object": {
779
- const shape = getDefProp(def, "shape");
780
- if (!shape || Object.keys(shape).length === 0)
781
- return "{}";
782
- const fields = Object.entries(shape).map(([key, value]) => {
783
- const valueType = getDefProp(value._zod?.def ?? value._def, "type");
784
- const isOptional = valueType === "optional";
785
- const innerSchema = isOptional ? getDefProp(value._zod?.def ?? value._def, "innerType") ?? value : value;
786
- const typeStr = zodToTypeString(innerSchema, indent + 1);
787
- return `${innerPad}${key}${isOptional ? "?" : ""}: ${typeStr}`;
788
- });
789
- return `{
790
- ${fields.join(`
791
- `)}
792
- ${pad}}`;
793
- }
794
- case "array": {
795
- const itemType = getDefProp(def, "element") ?? getDefProp(def, "type");
796
- return itemType ? `${zodToTypeString(itemType, indent)}[]` : "unknown[]";
797
- }
798
- case "string":
799
- return "string";
800
- case "number":
801
- return "number";
802
- case "boolean":
803
- return "boolean";
804
- case "date":
805
- return "Date";
806
- case "null":
807
- return "null";
808
- case "undefined":
809
- return "undefined";
810
- case "any":
811
- return "any";
812
- case "unknown":
813
- return "unknown";
814
- case "record": {
815
- const valueType = getDefProp(def, "valueType");
816
- return valueType ? `Record<string, ${zodToTypeString(valueType, indent)}>` : "Record<string, unknown>";
817
- }
818
- case "enum": {
819
- const values = getDefProp(def, "values");
820
- return values ? values.map((v) => `"${v}"`).join(" | ") : "string";
821
- }
822
- case "literal": {
823
- const value = getDefProp(def, "value");
824
- return typeof value === "string" ? `"${value}"` : String(value);
825
- }
826
- case "union": {
827
- const options = getDefProp(def, "options");
828
- return options ? options.map((o) => zodToTypeString(o, indent)).join(" | ") : "unknown";
829
- }
830
- case "optional": {
831
- const inner = getDefProp(def, "innerType");
832
- return inner ? `${zodToTypeString(inner, indent)} | undefined` : "unknown";
833
- }
834
- case "nullable": {
835
- const inner = getDefProp(def, "innerType");
836
- return inner ? `${zodToTypeString(inner, indent)} | null` : "unknown";
837
- }
838
- case "default": {
839
- const inner = getDefProp(def, "innerType");
840
- return inner ? zodToTypeString(inner, indent) : "unknown";
841
- }
842
- case "ZodObject":
843
- case "ZodArray":
844
- case "ZodString":
845
- case "ZodNumber":
846
- case "ZodBoolean":
847
- case "ZodDate":
848
- case "ZodNull":
849
- case "ZodUndefined":
850
- case "ZodAny":
851
- case "ZodUnknown":
852
- case "ZodRecord":
853
- case "ZodEnum":
854
- case "ZodLiteral":
855
- case "ZodUnion":
856
- case "ZodOptional":
857
- case "ZodNullable":
858
- case "ZodDefault":
859
- return zodToTypeString(schema, indent);
860
- default:
861
- return "unknown";
862
- }
729
+ // src/object/index.ts
730
+ function typedKeys(object) {
731
+ return Object.keys(object);
863
732
  }
864
- function zodToExample(schema) {
865
- const def = schema._zod?.def ?? schema._def;
866
- if (!def)
867
- return null;
868
- const schemaType = getDefProp(def, "type") ?? getDefProp(def, "typeName");
869
- switch (schemaType) {
870
- case "object": {
871
- const shape = getDefProp(def, "shape");
872
- if (!shape)
873
- return {};
874
- const result = {};
875
- for (const [key, value] of Object.entries(shape)) {
876
- const valueType = getDefProp(value._zod?.def ?? value._def, "type");
877
- const isOptional = valueType === "optional";
878
- if (!isOptional) {
879
- result[key] = zodToExample(value);
880
- }
881
- }
882
- return result;
883
- }
884
- case "array": {
885
- const itemType = getDefProp(def, "element") ?? getDefProp(def, "type");
886
- return itemType ? [zodToExample(itemType)] : [];
887
- }
888
- case "string":
889
- return "";
890
- case "number":
891
- return 0;
892
- case "boolean":
893
- return false;
894
- case "enum": {
895
- const values = getDefProp(def, "values");
896
- return values?.[0] ?? "";
897
- }
898
- case "literal":
899
- return getDefProp(def, "value");
900
- case "optional":
901
- case "default": {
902
- const inner = getDefProp(def, "innerType");
903
- return inner ? zodToExample(inner) : null;
904
- }
905
- case "record":
906
- return {};
907
- default:
908
- return null;
909
- }
733
+ function typedEntries(object) {
734
+ return Object.entries(object);
910
735
  }
911
-
912
- // src/zod/index.ts
913
- function createZId(typeName) {
914
- return z3.string().min(1, `${typeName} ID is required`).brand();
736
+ function typedFromEntries(entries) {
737
+ return Object.fromEntries(entries);
915
738
  }
916
- function zSingleOrArray(schema) {
917
- return z3.union([z3.array(schema), schema]);
739
+ function recordFromKeys(keys, fn) {
740
+ return typedFromEntries(keys.map((key) => [key, fn(key)]));
918
741
  }
919
- function normalizeSingleOrArray(value) {
920
- if (value == null)
921
- return [];
922
- if (Array.isArray(value))
923
- return [...value];
924
- return [value];
742
+ function mapValues(object, fn) {
743
+ return typedFromEntries(typedEntries(object).map(([key, value]) => [key, fn(value, key)]));
925
744
  }
926
- // src/api-wrapper/headers.ts
927
- var CANONICAL_HEADER_NAMES = {
928
- "content-type": "Content-Type",
929
- authorization: "Authorization",
930
- accept: "Accept",
931
- "user-agent": "User-Agent",
932
- "x-request-id": "X-Request-Id",
933
- "x-correlation-id": "X-Correlation-Id"
934
- };
935
- function normalizeHeaders(headers) {
745
+ function pick(object, keys) {
936
746
  const result = {};
937
- const lowerToCanonical = new Map;
938
- for (const [key, value] of Object.entries(headers)) {
939
- const lower = key.toLowerCase();
940
- const canonical = CANONICAL_HEADER_NAMES[lower] ?? key;
941
- const existing = lowerToCanonical.get(lower);
942
- if (existing && existing !== canonical)
943
- delete result[existing];
944
- lowerToCanonical.set(lower, canonical);
945
- result[canonical] = value;
747
+ for (const key of keys) {
748
+ if (key in object)
749
+ result[key] = object[key];
946
750
  }
947
751
  return result;
948
752
  }
949
- function getHeader(headers, name) {
950
- const lower = name.toLowerCase();
951
- const canonical = CANONICAL_HEADER_NAMES[lower];
952
- if (canonical !== undefined) {
953
- if (headers[canonical] !== undefined)
954
- return headers[canonical];
955
- }
956
- for (const [k, v] of Object.entries(headers)) {
957
- if (k.toLowerCase() === lower)
958
- return v;
959
- }
960
- return;
753
+ function omit(object, keys) {
754
+ const result = { ...object };
755
+ for (const key of keys)
756
+ delete result[key];
757
+ return result;
961
758
  }
962
- function hasJsonContentType(headers) {
963
- const ct = getHeader(headers, "content-type") ?? "";
964
- return ct.includes("application/json");
759
+ var UNSAFE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
760
+ function toPath(path) {
761
+ if (typeof path !== "string")
762
+ return path;
763
+ return path === "" ? [] : path.split(".");
965
764
  }
966
- function buildFetchBody(body, headers, configError) {
967
- if (body === undefined || body === null)
968
- return;
969
- if (body instanceof FormData || body instanceof Blob)
970
- return body;
971
- if (hasJsonContentType(headers))
972
- return JSON.stringify(body);
973
- if (typeof body === "object" && (Object.getPrototypeOf(body) === Object.prototype || Object.getPrototypeOf(body) === null) && configError) {
974
- throw configError("Request body is a plain object but Content-Type is not application/json");
765
+ function getAtPath(object, path) {
766
+ let current = object;
767
+ for (const key of toPath(path)) {
768
+ if (current === null || typeof current !== "object")
769
+ return;
770
+ current = current[key];
975
771
  }
976
- return body;
977
- }
978
- function getHeadersAsObject(headers) {
979
- const result = {};
980
- headers.forEach((value, key) => {
981
- result[key] = value;
982
- });
983
- return result;
984
- }
985
-
986
- // src/api-wrapper/zod-issues.ts
987
- import { ZodError } from "zod";
988
- function formatZodIssues(error, rawData) {
989
- return error.issues.map((issue) => {
990
- const pathArray = [...issue.path];
991
- const path = formatIssuePath(pathArray);
992
- const value = getValueAtPath(rawData, pathArray);
993
- const valuePreview = value === undefined ? undefined : previewValue(value);
994
- const raw = issue;
995
- const expected = typeof raw.expected === "string" ? raw.expected : undefined;
996
- const received = typeof raw.received === "string" ? raw.received : value !== undefined ? typeofVerbose(value) : undefined;
997
- const known = new Set([
998
- "code",
999
- "path",
1000
- "message",
1001
- "expected",
1002
- "received",
1003
- "fatal"
1004
- ]);
1005
- const extra = {};
1006
- for (const [k, v] of Object.entries(raw)) {
1007
- if (!known.has(k) && v !== undefined)
1008
- extra[k] = v;
1009
- }
1010
- return {
1011
- path,
1012
- pathArray,
1013
- code: issue.code,
1014
- message: issue.message,
1015
- expected,
1016
- received,
1017
- value,
1018
- valuePreview,
1019
- extra: Object.keys(extra).length > 0 ? extra : undefined
1020
- };
1021
- });
772
+ return current;
1022
773
  }
1023
- function renderIssueSummary(issues) {
1024
- if (issues.length === 0)
1025
- return "(no issues reported)";
1026
- return issues.map((issue, idx) => {
1027
- const parts = [];
1028
- parts.push(` [${idx + 1}] ${issue.path}: ${issue.message}`);
1029
- if (issue.expected || issue.received) {
1030
- const exp = issue.expected ? `expected ${issue.expected}` : "";
1031
- const rec = issue.received ? `received ${issue.received}` : "";
1032
- parts.push(` (${[exp, rec].filter(Boolean).join(", ")})`);
1033
- }
1034
- if (issue.valuePreview !== undefined) {
1035
- parts.push(` value: ${issue.valuePreview}`);
1036
- }
1037
- if (issue.extra) {
1038
- for (const [k, v] of Object.entries(issue.extra)) {
1039
- parts.push(` ${k}: ${previewValue(v, 120)}`);
1040
- }
774
+ function setAtPath(object, path, value) {
775
+ const keys = toPath(path);
776
+ if (keys.length === 0)
777
+ throw new Error("Path must not be empty");
778
+ for (const key of keys) {
779
+ if (UNSAFE_KEYS.has(String(key))) {
780
+ throw new Error(`Unsafe path segment: ${key}`);
1041
781
  }
1042
- return parts.join(`
1043
- `);
1044
- }).join(`
1045
- `);
1046
- }
1047
- function formatIssuePath(pathArray) {
1048
- if (pathArray.length === 0)
1049
- return "<root>";
1050
- let out = "";
1051
- for (const segment of pathArray) {
1052
- if (typeof segment === "number") {
1053
- out += `[${segment}]`;
1054
- } else if (/^[A-Za-z_$][\w$]*$/.test(segment)) {
1055
- out += out === "" ? segment : `.${segment}`;
1056
- } else {
1057
- out += `[${JSON.stringify(segment)}]`;
782
+ }
783
+ let current = object;
784
+ for (let index = 0;index < keys.length - 1; index++) {
785
+ const key = keys[index];
786
+ let next = current[key];
787
+ if (next === null || typeof next !== "object") {
788
+ next = /^\d+$/.test(String(keys[index + 1])) ? [] : {};
789
+ current[key] = next;
1058
790
  }
791
+ current = next;
1059
792
  }
1060
- return out;
1061
- }
1062
- function getValueAtPath(data, pathArray) {
1063
- let current = data;
1064
- for (const segment of pathArray) {
1065
- if (current === null || current === undefined)
1066
- return;
1067
- if (typeof current !== "object")
1068
- return;
1069
- current = current[segment];
1070
- }
1071
- return current;
1072
- }
1073
- function typeofVerbose(value) {
1074
- if (value === null)
1075
- return "null";
1076
- if (Array.isArray(value))
1077
- return "array";
1078
- return typeof value;
1079
- }
1080
- function previewValue(value, maxLen = 200) {
1081
- let str;
1082
- try {
1083
- if (value === undefined)
1084
- str = "undefined";
1085
- else if (typeof value === "string")
1086
- str = JSON.stringify(value);
1087
- else
1088
- str = JSON.stringify(value);
1089
- } catch {
1090
- str = String(value);
1091
- }
1092
- if (str === undefined)
1093
- str = String(value);
1094
- if (str.length > maxLen)
1095
- str = `${str.slice(0, maxLen)}…`;
1096
- return str;
1097
- }
1098
- function makeJsonParseZodError(error, text) {
1099
- const message = error instanceof Error ? `Response body was not valid JSON: ${error.message}` : "Response body was not valid JSON";
1100
- const preview = text.length > 200 ? `${text.slice(0, 200)}…` : text;
1101
- return new ZodError([
1102
- {
1103
- code: "custom",
1104
- path: [],
1105
- message: `${message} (body preview: ${JSON.stringify(preview)})`,
1106
- input: text
1107
- }
1108
- ]);
1109
- }
1110
- function tryParseJson(body) {
1111
- if (!body)
1112
- return;
1113
- try {
1114
- return JSON.parse(body);
1115
- } catch {
1116
- return;
1117
- }
1118
- }
1119
-
1120
- // src/api-wrapper/errors.ts
1121
- var MAX_BODY_PREVIEW = 500;
1122
-
1123
- class ApiError extends Error {
1124
- context;
1125
- cause;
1126
- constructor(message, context, options) {
1127
- super(message);
1128
- this.name = "ApiError";
1129
- this.context = context;
1130
- this.cause = options?.cause;
1131
- }
1132
- toJSON() {
1133
- return {
1134
- name: this.name,
1135
- message: this.message,
1136
- context: this.context
1137
- };
1138
- }
1139
- }
1140
-
1141
- class HttpResponseError extends ApiError {
1142
- status;
1143
- body;
1144
- bodyJson;
1145
- responseHeaders;
1146
- requestId;
1147
- constructor(status, body, responseHeaders, context) {
1148
- const preview = body.length > MAX_BODY_PREVIEW ? `${body.slice(0, MAX_BODY_PREVIEW)}…` : body;
1149
- super(`HTTP ${status} ${context.method} ${context.url} - ${preview}`, context);
1150
- this.name = "HttpResponseError";
1151
- this.status = status;
1152
- this.body = body;
1153
- this.responseHeaders = responseHeaders;
1154
- this.requestId = getHeader(responseHeaders, "x-request-id") ?? getHeader(responseHeaders, "x-correlation-id");
1155
- this.bodyJson = tryParseJson(body);
1156
- }
1157
- toJSON() {
1158
- return {
1159
- ...super.toJSON(),
1160
- status: this.status,
1161
- body: this.body,
1162
- bodyJson: this.bodyJson,
1163
- requestId: this.requestId,
1164
- responseHeaders: this.responseHeaders
1165
- };
1166
- }
1167
- }
1168
-
1169
- class ResponseValidationError extends ApiError {
1170
- zodError;
1171
- rawResponse;
1172
- issues;
1173
- validationIssues;
1174
- constructor(zodError, rawResponse, context) {
1175
- const issues = formatZodIssues(zodError, rawResponse);
1176
- const summary = renderIssueSummary(issues);
1177
- super(`Response validation failed (${context.method} ${context.url}):
1178
- ${summary}`, context, { cause: zodError });
1179
- this.name = "ResponseValidationError";
1180
- this.zodError = zodError;
1181
- this.rawResponse = rawResponse;
1182
- this.issues = issues;
1183
- this.validationIssues = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
1184
- }
1185
- toJSON() {
1186
- return {
1187
- ...super.toJSON(),
1188
- issues: this.issues,
1189
- validationIssues: this.validationIssues,
1190
- rawResponsePreview: previewValue(this.rawResponse, 1000)
1191
- };
1192
- }
1193
- }
1194
-
1195
- class RequestValidationError extends ApiError {
1196
- zodError;
1197
- target;
1198
- rawInput;
1199
- issues;
1200
- validationIssues;
1201
- constructor(zodError, target, rawInput, context) {
1202
- const issues = formatZodIssues(zodError, rawInput);
1203
- const summary = renderIssueSummary(issues);
1204
- super(`Request ${target} validation failed (${context.method} ${context.url}):
1205
- ${summary}`, context, { cause: zodError });
1206
- this.name = "RequestValidationError";
1207
- this.zodError = zodError;
1208
- this.target = target;
1209
- this.rawInput = rawInput;
1210
- this.issues = issues;
1211
- this.validationIssues = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
1212
- }
1213
- toJSON() {
1214
- return {
1215
- ...super.toJSON(),
1216
- target: this.target,
1217
- issues: this.issues,
1218
- validationIssues: this.validationIssues
1219
- };
1220
- }
1221
- }
1222
-
1223
- class RequestTimeoutError extends ApiError {
1224
- timeoutMs;
1225
- constructor(timeoutMs, context) {
1226
- super(`Request timed out after ${timeoutMs}ms: ${context.method} ${context.url}`, context);
1227
- this.name = "RequestTimeoutError";
1228
- this.timeoutMs = timeoutMs;
1229
- }
1230
- }
1231
-
1232
- class NetworkError extends ApiError {
1233
- constructor(message, context, cause) {
1234
- super(message, context, { cause });
1235
- this.name = "NetworkError";
1236
- }
1237
- }
1238
-
1239
- class RequestAbortedError extends ApiError {
1240
- constructor(context, cause) {
1241
- super(`Request aborted: ${context.method} ${context.url}`, context, {
1242
- cause
1243
- });
1244
- this.name = "RequestAbortedError";
1245
- }
1246
- }
1247
-
1248
- class ConfigError extends ApiError {
1249
- constructor(message, context) {
1250
- super(message, context);
1251
- this.name = "ConfigError";
1252
- }
1253
- }
1254
-
1255
- class ActionError extends ApiError {
1256
- constructor(message, context, cause) {
1257
- super(message, context, { cause });
1258
- this.name = "ActionError";
1259
- }
1260
- }
1261
- var isApiError = (e) => e instanceof ApiError;
1262
- var isHttpResponseError = (e) => e instanceof HttpResponseError;
1263
- var isResponseValidationError = (e) => e instanceof ResponseValidationError;
1264
- var isRequestValidationError = (e) => e instanceof RequestValidationError;
1265
- var isRequestTimeoutError = (e) => e instanceof RequestTimeoutError;
1266
- var isNetworkError = (e) => e instanceof NetworkError;
1267
- var isRequestAbortedError = (e) => e instanceof RequestAbortedError;
1268
- var isConfigError = (e) => e instanceof ConfigError;
1269
- var isActionError = (e) => e instanceof ActionError;
1270
- function isNonRetryableApiError(error) {
1271
- return error instanceof HttpResponseError || error instanceof ResponseValidationError || error instanceof RequestValidationError || error instanceof ConfigError || error instanceof RequestAbortedError;
1272
- }
1273
- // src/api-wrapper/builders.ts
1274
- import { ZodError as ZodError2 } from "zod";
1275
- function formatQueryString(queryParams, doNotEncode) {
1276
- if (Object.keys(queryParams).length === 0)
1277
- return "";
1278
- return doNotEncode ? Object.entries(queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(queryParams).toString();
1279
- }
1280
- function buildUrl(options) {
1281
- const {
1282
- endpoint,
1283
- params,
1284
- queryParams,
1285
- baseApiUrls,
1286
- defaultBaseApiUrl,
1287
- url,
1288
- doNotEncodeQueryParams,
1289
- configError
1290
- } = options;
1291
- let finalUrl;
1292
- if (url) {
1293
- finalUrl = replacePathParams(url, params, configError);
1294
- } else {
1295
- const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
1296
- if (!defaultBaseUrl) {
1297
- throw configError("At least one base API URL is required");
1298
- }
1299
- const baseUrlKey = endpoint.baseApiUrl ?? defaultBaseUrl;
1300
- const configBaseUrl = baseApiUrls[baseUrlKey];
1301
- if (!configBaseUrl) {
1302
- throw configError(`Unknown base API URL: ${baseUrlKey}`);
1303
- }
1304
- const baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
1305
- const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
1306
- finalUrl = `${baseUrl}${replacePathParams(path, params, configError)}`;
1307
- }
1308
- const queryString = formatQueryString(queryParams, doNotEncodeQueryParams);
1309
- return {
1310
- finalUrl,
1311
- fullUrl: queryString ? `${finalUrl}?${queryString}` : finalUrl
1312
- };
1313
- }
1314
- function buildPathParams(endpoint, params, ctx) {
1315
- if (!endpoint.reqParamsSchema)
1316
- return params;
1317
- try {
1318
- return endpoint.reqParamsSchema.parse(params);
1319
- } catch (error) {
1320
- if (error instanceof ZodError2) {
1321
- throw new RequestValidationError(error, "params", params, ctx());
1322
- }
1323
- throw error;
1324
- }
1325
- }
1326
- function buildQueryParams(endpoint, query, ctx) {
1327
- const defaultParams = endpoint.reqDefaultQueryParams ?? {};
1328
- let parsedQuery;
1329
- if (endpoint.reqQuerySchema) {
1330
- try {
1331
- parsedQuery = endpoint.reqQuerySchema.parse(query);
1332
- } catch (error) {
1333
- if (error instanceof ZodError2) {
1334
- throw new RequestValidationError(error, "query", query, ctx());
1335
- }
1336
- throw error;
1337
- }
1338
- } else {
1339
- parsedQuery = query ?? {};
1340
- }
1341
- const merged = { ...defaultParams, ...parsedQuery };
1342
- return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
1343
- }
1344
- function buildRequestBody(endpoint, body, ctx) {
1345
- if (!endpoint.reqBodySchema)
1346
- return body;
1347
- let parsedBody;
1348
- try {
1349
- parsedBody = endpoint.reqBodySchema.parse(body);
1350
- } catch (error) {
1351
- if (error instanceof ZodError2) {
1352
- throw new RequestValidationError(error, "body", body, ctx());
1353
- }
1354
- throw error;
1355
- }
1356
- if (endpoint.reqBodyFormat !== "form-data") {
1357
- return parsedBody;
1358
- }
1359
- const formData = new FormData;
1360
- for (const [key, value] of Object.entries(parsedBody)) {
1361
- if (value == null)
1362
- continue;
1363
- formData.set(key, value instanceof Blob ? value : String(value));
1364
- }
1365
- return formData;
1366
- }
1367
- function buildHeaders(endpoint, headers, defaultHeaders, ctx) {
1368
- const contentType = endpoint.reqBodyFormat === "json" ? "application/json" : undefined;
1369
- let parsedHeaders;
1370
- if (endpoint.reqHeadersSchema) {
1371
- try {
1372
- parsedHeaders = endpoint.reqHeadersSchema.parse(headers);
1373
- } catch (error) {
1374
- if (error instanceof ZodError2) {
1375
- throw new RequestValidationError(error, "headers", headers, ctx());
1376
- }
1377
- throw error;
1378
- }
1379
- } else {
1380
- parsedHeaders = headers ?? {};
1381
- }
1382
- return normalizeHeaders({
1383
- ...defaultHeaders,
1384
- ...contentType && { "Content-Type": contentType },
1385
- ...endpoint.defaultHeaders,
1386
- ...parsedHeaders
1387
- });
1388
- }
1389
- function replacePathParams(path, params, configError) {
1390
- return path.replace(/:(\w+)/g, (_, key) => {
1391
- const value = params?.[key];
1392
- if (value === undefined) {
1393
- throw configError(`Missing required path parameter: ${key}`);
1394
- }
1395
- return encodeURIComponent(String(value));
1396
- });
1397
- }
1398
-
1399
- // src/api-wrapper/actions-impl.ts
1400
- function createActionCache() {
1401
- const store = new Map;
1402
- const isExpired = (entry) => entry.expiresAt !== undefined && entry.expiresAt <= Date.now();
1403
- const cache = async (key, fn, options) => {
1404
- const existing = store.get(key);
1405
- if (existing && !isExpired(existing)) {
1406
- if (existing.inflight)
1407
- return existing.inflight;
1408
- if (existing.hasValue)
1409
- return existing.value;
1410
- }
1411
- const promise = (async () => fn())();
1412
- const entry = {
1413
- hasValue: false,
1414
- inflight: promise,
1415
- expiresAt: options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined
1416
- };
1417
- store.set(key, entry);
1418
- try {
1419
- const value = await promise;
1420
- entry.value = value;
1421
- entry.hasValue = true;
1422
- entry.inflight = undefined;
1423
- return value;
1424
- } catch (error) {
1425
- store.delete(key);
1426
- throw error;
1427
- }
1428
- };
1429
- cache.get = (key) => {
1430
- const entry = store.get(key);
1431
- if (!entry || !entry.hasValue)
1432
- return;
1433
- if (isExpired(entry)) {
1434
- store.delete(key);
1435
- return;
1436
- }
1437
- return entry.value;
1438
- };
1439
- cache.set = (key, value, options) => {
1440
- store.set(key, {
1441
- value,
1442
- hasValue: true,
1443
- expiresAt: options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined
1444
- });
1445
- };
1446
- cache.invalidate = (key) => {
1447
- store.delete(key);
1448
- };
1449
- cache.clear = () => {
1450
- store.clear();
1451
- };
1452
- return cache;
1453
- }
1454
- function makeDefineAction() {
1455
- function defineAction(...args) {
1456
- if (args.length === 0) {
1457
- return (def) => ({
1458
- ...def,
1459
- noRuntimeInput: false
1460
- });
1461
- }
1462
- const def = args[0];
1463
- return {
1464
- ...def,
1465
- noRuntimeInput: true
1466
- };
1467
- }
1468
- return defineAction;
1469
- }
1470
- var defineAction = makeDefineAction();
1471
-
1472
- // src/api-wrapper/redaction.ts
1473
- var SENSITIVE_HEADER_KEYS = new Set([
1474
- "authorization",
1475
- "cookie",
1476
- "set-cookie",
1477
- "x-api-key",
1478
- "x-auth-token"
1479
- ]);
1480
- var SENSITIVE_BODY_KEYS = new Set([
1481
- "password",
1482
- "token",
1483
- "accesstoken",
1484
- "access_token",
1485
- "refreshtoken",
1486
- "refresh_token",
1487
- "secret",
1488
- "client_secret",
1489
- "clientsecret",
1490
- "apikey",
1491
- "api_key"
1492
- ]);
1493
- function redactHeaders(headers) {
1494
- if (!headers)
1495
- return;
1496
- const out = {};
1497
- for (const [k, v] of Object.entries(headers)) {
1498
- if (SENSITIVE_HEADER_KEYS.has(k.toLowerCase())) {
1499
- out[k] = "[REDACTED]";
1500
- } else {
1501
- out[k] = v;
1502
- }
1503
- }
1504
- return out;
1505
- }
1506
- function redactBody(body) {
1507
- if (body === null || body === undefined)
1508
- return body;
1509
- if (typeof body !== "object" || Array.isArray(body)) {
1510
- return body;
1511
- }
1512
- const rec = body;
1513
- const out = { ...rec };
1514
- for (const key of Object.keys(out)) {
1515
- if (SENSITIVE_BODY_KEYS.has(key.toLowerCase())) {
1516
- out[key] = "[REDACTED]";
1517
- }
1518
- }
1519
- return out;
1520
- }
1521
- function defaultRedactContext(ctx) {
1522
- return {
1523
- ...ctx,
1524
- requestHeaders: redactHeaders(ctx.requestHeaders),
1525
- requestBody: redactBody(ctx.requestBody),
1526
- meta: ctx.meta
1527
- };
1528
- }
1529
-
1530
- // src/api-wrapper/error-reporting.ts
1531
- function applyErrorContextRedaction(ctx, config) {
1532
- const base = defaultRedactContext(ctx);
1533
- return config.redact ? config.redact(base) : base;
1534
- }
1535
- async function reportApiError(err, config) {
1536
- err.context = applyErrorContextRedaction(err.context, config);
1537
- try {
1538
- await config.onError?.(err);
1539
- } catch {}
1540
- config.logger?.error?.(err);
1541
- return err;
1542
- }
1543
-
1544
- // src/api-wrapper/size.ts
1545
- function calculateSizeInMb(data) {
1546
- let bytes;
1547
- if (data === undefined || data === null) {
1548
- bytes = 0;
1549
- } else if (data instanceof Blob) {
1550
- bytes = data.size;
1551
- } else if (typeof FormData !== "undefined" && data instanceof FormData) {
1552
- bytes = 0;
1553
- data.forEach((value) => {
1554
- if (value instanceof Blob)
1555
- bytes += value.size;
1556
- else
1557
- bytes += new TextEncoder().encode(String(value)).length;
1558
- });
1559
- } else if (typeof data === "string") {
1560
- bytes = new TextEncoder().encode(data).length;
1561
- } else {
1562
- bytes = new TextEncoder().encode(JSON.stringify(data) ?? "").length;
1563
- }
1564
- return bytes / (1024 * 1024);
1565
- }
1566
-
1567
- // src/api-wrapper/transport.ts
1568
- import { ZodError as ZodError3 } from "zod";
1569
- async function validateResponse(schema, data, ctx) {
1570
- try {
1571
- return await schema.parseAsync(data);
1572
- } catch (error) {
1573
- if (error instanceof ZodError3) {
1574
- throw new ResponseValidationError(error, data, ctx());
1575
- }
1576
- throw error;
1577
- }
1578
- }
1579
- async function httpRequest(config, ctx) {
1580
- const timeoutController = new AbortController;
1581
- const timeout = config.timeoutMs === undefined ? undefined : setTimeout(() => timeoutController.abort(), config.timeoutMs);
1582
- const signal = mergeSignals(timeoutController.signal, config.userSignal);
1583
- try {
1584
- return await config.fetchFn(config.fullUrl, {
1585
- method: config.method,
1586
- headers: config.headers,
1587
- body: buildFetchBody(config.body, config.headers, (msg) => new ConfigError(msg, ctx())),
1588
- signal
1589
- });
1590
- } catch (error) {
1591
- if (isDomAbortError(error)) {
1592
- if (config.userSignal?.aborted) {
1593
- throw new RequestAbortedError(ctx(), error);
1594
- }
1595
- if (timeoutController.signal.aborted && config.timeoutMs !== undefined) {
1596
- throw new RequestTimeoutError(config.timeoutMs, ctx());
1597
- }
1598
- throw new RequestAbortedError(ctx(), error);
1599
- }
1600
- if (error instanceof ConfigError)
1601
- throw error;
1602
- throw new NetworkError(error instanceof Error ? error.message : String(error), ctx(), error);
1603
- } finally {
1604
- if (timeout)
1605
- clearTimeout(timeout);
1606
- }
1607
- }
1608
- function isDomAbortError(error) {
1609
- return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
1610
- }
1611
- function mergeSignals(a, b) {
1612
- if (!b)
1613
- return a;
1614
- const anyFn = AbortSignal.any;
1615
- if (typeof anyFn === "function")
1616
- return anyFn([a, b]);
1617
- const controller = new AbortController;
1618
- const onAbort = () => controller.abort();
1619
- if (a.aborted)
1620
- controller.abort();
1621
- else
1622
- a.addEventListener("abort", onAbort, { once: true });
1623
- if (b.aborted)
1624
- controller.abort();
1625
- else
1626
- b.addEventListener("abort", onAbort, { once: true });
1627
- return controller.signal;
1628
- }
1629
- async function readErrorResponse(response) {
1630
- const body = await response.text();
1631
- return {
1632
- body,
1633
- headers: getHeadersAsObject(response.headers),
1634
- status: response.status
1635
- };
1636
- }
1637
- function getRetryDelay(params) {
1638
- const maxDelay = params.maxRetryDelayMs ?? 30000;
1639
- const { retryDelay, attempt, response } = params;
1640
- if (response && (response.status === 429 || response.status === 503)) {
1641
- const retryAfter = response.headers.get("retry-after");
1642
- if (retryAfter) {
1643
- const seconds = Number(retryAfter);
1644
- if (Number.isFinite(seconds)) {
1645
- return Math.min(Math.max(0, seconds * 1000), maxDelay);
1646
- }
1647
- const dateMs = Date.parse(retryAfter);
1648
- if (!Number.isNaN(dateMs)) {
1649
- return Math.min(Math.max(0, dateMs - Date.now()), maxDelay);
1650
- }
1651
- }
1652
- }
1653
- const delayMs = typeof retryDelay === "function" ? retryDelay(attempt) : retryDelay;
1654
- return Math.min(delayMs, maxDelay);
1655
- }
1656
- function shouldAttemptRetryOnError(error) {
1657
- return !isNonRetryableApiError(error);
1658
- }
1659
- function shouldAttemptRetryOnHttp(retryCtx) {
1660
- const { error, response } = retryCtx;
1661
- if (error)
1662
- return shouldAttemptRetryOnError(error);
1663
- return response ? response.status >= 500 || response.status === 429 : false;
1664
- }
1665
- async function makeRequestWithRetry(args) {
1666
- const {
1667
- config,
1668
- maxRetries,
1669
- retryDelay,
1670
- maxRetryDelayMs,
1671
- validateFn,
1672
- shouldRetry,
1673
- logger,
1674
- ctx,
1675
- endpointName,
1676
- method,
1677
- fullUrl,
1678
- meta,
1679
- onResponse,
1680
- onRetry
1681
- } = args;
1682
- let retryCount = 0;
1683
- while (true) {
1684
- let response;
1685
- try {
1686
- logger?.debug?.("API request", config);
1687
- response = await httpRequest(config, (over) => ctx({ attempt: retryCount, ...over }));
1688
- await onResponse?.({
1689
- endpoint: endpointName,
1690
- method,
1691
- url: fullUrl,
1692
- response: response.clone(),
1693
- attempt: retryCount,
1694
- meta
1695
- });
1696
- if (response.status >= 400) {
1697
- const retryCtx = {
1698
- response,
1699
- retryCount,
1700
- maxRetries,
1701
- endpoint: endpointName,
1702
- method,
1703
- url: fullUrl
1704
- };
1705
- if (retryCount < maxRetries && shouldRetry(retryCtx)) {
1706
- const delayMs = getRetryDelay({
1707
- retryDelay,
1708
- attempt: retryCount,
1709
- response,
1710
- maxRetryDelayMs
1711
- });
1712
- await onRetry?.({
1713
- ...retryCtx,
1714
- delayMs,
1715
- nextAttempt: retryCount + 1
1716
- });
1717
- retryCount++;
1718
- await delay(delayMs);
1719
- continue;
1720
- }
1721
- const { body, headers: responseHeaders } = await readErrorResponse(response);
1722
- throw new HttpResponseError(response.status, body, responseHeaders, ctx({ attempt: retryCount }));
1723
- }
1724
- return {
1725
- validatedData: await validateFn(response, retryCount),
1726
- retryCount,
1727
- httpStatus: response.status
1728
- };
1729
- } catch (error) {
1730
- if (isNonRetryableApiError(error)) {
1731
- throw error;
1732
- }
1733
- const retryCtx = {
1734
- error,
1735
- retryCount,
1736
- maxRetries,
1737
- endpoint: endpointName,
1738
- method,
1739
- url: fullUrl
1740
- };
1741
- if (retryCount >= maxRetries || !shouldRetry(retryCtx)) {
1742
- throw error;
1743
- }
1744
- const delayMs = getRetryDelay({
1745
- retryDelay,
1746
- attempt: retryCount,
1747
- response: undefined,
1748
- maxRetryDelayMs
1749
- });
1750
- await onRetry?.({
1751
- ...retryCtx,
1752
- delayMs,
1753
- nextAttempt: retryCount + 1
1754
- });
1755
- retryCount++;
1756
- await delay(delayMs);
1757
- }
1758
- }
1759
- }
1760
- async function validateAndTransformResponse(endpoint, response, ctx) {
1761
- const responseHeaders = getHeadersAsObject(response.headers);
1762
- const text = await response.text();
1763
- const responseSizeBytes = new TextEncoder().encode(text).length;
1764
- let responseData;
1765
- if (hasJsonContentType(responseHeaders)) {
1766
- try {
1767
- responseData = text === "" ? undefined : JSON.parse(text);
1768
- } catch (error) {
1769
- throw new ResponseValidationError(makeJsonParseZodError(error, text), text, ctx());
1770
- }
1771
- } else {
1772
- responseData = text;
1773
- }
1774
- const transformedData = endpoint.resFormatter ? endpoint.resFormatter(responseData, responseHeaders) : responseData;
1775
- const data = await validateResponse(endpoint.resSchema, transformedData, ctx);
1776
- return { data, responseSizeBytes };
1777
- }
1778
- function defaultShouldRetry(ctx) {
1779
- if (ctx.error)
1780
- return shouldAttemptRetryOnError(ctx.error);
1781
- return shouldAttemptRetryOnHttp(ctx);
1782
- }
1783
- function delay(ms) {
1784
- return new Promise((resolve) => setTimeout(resolve, ms));
1785
- }
1786
-
1787
- // src/api-wrapper/create-client.ts
1788
- function isActionInvokeOptionsOnly(value) {
1789
- if (value === null || value === undefined)
1790
- return false;
1791
- if (typeof value !== "object" || Array.isArray(value))
1792
- return false;
1793
- return Object.keys(value).every((k) => k === "signal" || k === "meta");
1794
- }
1795
- function resolveActionInvocation(action, actionName, first, second) {
1796
- if (!action.noRuntimeInput) {
1797
- return { input: first, options: second ?? {} };
1798
- }
1799
- if (first === undefined || first === null) {
1800
- return { input: undefined, options: second ?? {} };
1801
- }
1802
- if (second !== undefined) {
1803
- return { input: undefined, options: second };
1804
- }
1805
- if (isActionInvokeOptionsOnly(first)) {
1806
- return { input: undefined, options: first };
1807
- }
1808
- throw new ConfigError(`Action "${actionName}" does not accept input; pass only optional { signal, meta }`, {
1809
- endpoint: actionName,
1810
- method: "GET",
1811
- url: "",
1812
- attempt: 0,
1813
- maxRetries: 0,
1814
- elapsedMs: 0
1815
- });
1816
- }
1817
- function createApiClientImpl(apiConfig) {
1818
- let actions = {};
1819
- const cache = createActionCache();
1820
- const invokeEndpoint = async (endpointKey, input = {}) => {
1821
- const endpoint = apiConfig.endpoints[endpointKey];
1822
- const rawInput = input;
1823
- const endpointName = apiConfig.name ? `${apiConfig.name}.${String(endpointKey)}` : String(endpointKey);
1824
- const startedAt = Date.now();
1825
- const maxRetries = input.retries ?? apiConfig.retries ?? 3;
1826
- const baseContext = {
1827
- endpoint: endpointName,
1828
- method: endpoint?.method ?? "GET",
1829
- url: "",
1830
- attempt: 0,
1831
- maxRetries,
1832
- elapsedMs: 0,
1833
- meta: input.meta
1834
- };
1835
- const elapsed = () => Date.now() - startedAt;
1836
- const ctx = (over = {}) => ({
1837
- ...baseContext,
1838
- elapsedMs: elapsed(),
1839
- ...over
1840
- });
1841
- if (!endpoint) {
1842
- throw await reportApiError(new ConfigError(`Unknown API endpoint: ${String(endpointKey)}`, ctx()), apiConfig);
1843
- }
1844
- baseContext.method = endpoint.method;
1845
- let pathParams;
1846
- let queryParams;
1847
- let body;
1848
- let headers;
1849
- try {
1850
- pathParams = buildPathParams(endpoint, rawInput.reqParams, ctx);
1851
- queryParams = buildQueryParams(endpoint, rawInput.reqQuery, ctx);
1852
- body = buildRequestBody(endpoint, rawInput.reqBody, ctx);
1853
- headers = buildHeaders(endpoint, rawInput.reqHeaders, apiConfig.defaultHeaders ?? {}, ctx);
1854
- } catch (e) {
1855
- if (e instanceof ApiError)
1856
- throw await reportApiError(e, apiConfig);
1857
- throw e;
1858
- }
1859
- let fullUrl;
1860
- try {
1861
- const built = buildUrl({
1862
- endpoint,
1863
- params: pathParams,
1864
- queryParams,
1865
- baseApiUrls: apiConfig.baseApiUrls,
1866
- defaultBaseApiUrl: apiConfig.defaultBaseApiUrl,
1867
- url: input.url,
1868
- doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
1869
- configError: (msg) => new ConfigError(msg, ctx())
1870
- });
1871
- fullUrl = built.fullUrl;
1872
- } catch (e) {
1873
- if (e instanceof ApiError)
1874
- throw await reportApiError(e, apiConfig);
1875
- throw e;
1876
- }
1877
- baseContext.url = fullUrl;
1878
- baseContext.requestHeaders = headers;
1879
- baseContext.requestBody = body;
1880
- if (apiConfig.onRequest) {
1881
- try {
1882
- const result = await apiConfig.onRequest({
1883
- endpoint: endpointName,
1884
- method: endpoint.method,
1885
- url: fullUrl,
1886
- headers,
1887
- body,
1888
- meta: input.meta
1889
- });
1890
- if (result?.headers)
1891
- headers = result.headers;
1892
- if (result && "body" in result)
1893
- body = result.body;
1894
- } catch (e) {
1895
- if (e instanceof ApiError)
1896
- throw await reportApiError(e, apiConfig);
1897
- throw e;
1898
- }
1899
- }
1900
- const config = {
1901
- method: endpoint.method,
1902
- fullUrl,
1903
- body,
1904
- headers,
1905
- timeoutMs: input.timeoutMs ?? apiConfig.timeoutMs,
1906
- fetchFn: input.fetch ?? apiConfig.fetch ?? fetch,
1907
- userSignal: input.signal
1908
- };
1909
- try {
1910
- const result = await makeRequestWithRetry({
1911
- config,
1912
- maxRetries,
1913
- retryDelay: apiConfig.retryDelayMs ?? 300,
1914
- maxRetryDelayMs: apiConfig.maxRetryDelayMs,
1915
- validateFn: (response, attempt) => validateAndTransformResponse(endpoint, response, (over) => ctx({ attempt, ...over })),
1916
- shouldRetry: apiConfig.shouldRetry ?? defaultShouldRetry,
1917
- logger: apiConfig.logger,
1918
- ctx,
1919
- endpointName,
1920
- method: endpoint.method,
1921
- fullUrl,
1922
- meta: input.meta,
1923
- onResponse: apiConfig.onResponse,
1924
- onRetry: apiConfig.onRetry
1925
- });
1926
- const responseTimeMs = elapsed();
1927
- const responseSizeMb = result.validatedData.responseSizeBytes / (1024 * 1024);
1928
- const requestBodySizeMb = calculateSizeInMb(config.body);
1929
- return {
1930
- data: result.validatedData.data,
1931
- requestBodySizeMb,
1932
- responseSizeMb,
1933
- responseTimeMs,
1934
- httpStatus: result.httpStatus,
1935
- retryCount: result.retryCount
1936
- };
1937
- } catch (error) {
1938
- if (error instanceof ApiError)
1939
- throw await reportApiError(error, apiConfig);
1940
- throw error;
1941
- }
1942
- };
1943
- async function invokeAction(name, rawInput, options = {}) {
1944
- const action = actions[name];
1945
- const inputForHandler = action.noRuntimeInput ? undefined : rawInput;
1946
- const actionName = apiConfig.name ? `${apiConfig.name}.${name}` : name;
1947
- const startedAt = Date.now();
1948
- const baseContext = {
1949
- endpoint: actionName,
1950
- method: "GET",
1951
- url: "",
1952
- attempt: 0,
1953
- maxRetries: 0,
1954
- elapsedMs: 0,
1955
- meta: options.meta,
1956
- requestBody: action.noRuntimeInput ? undefined : rawInput
1957
- };
1958
- const ctx = (over = {}) => ({
1959
- ...baseContext,
1960
- elapsedMs: Date.now() - startedAt,
1961
- ...over
1962
- });
1963
- apiConfig.logger?.debug?.("Action start", { action: actionName });
1964
- try {
1965
- const result = await action.handler({
1966
- input: inputForHandler,
1967
- api: proxy,
1968
- signal: options.signal,
1969
- meta: options.meta,
1970
- logger: apiConfig.logger,
1971
- cache
1972
- });
1973
- apiConfig.logger?.debug?.("Action end", {
1974
- action: actionName,
1975
- elapsedMs: Date.now() - startedAt
1976
- });
1977
- return result;
1978
- } catch (error) {
1979
- if (error instanceof ApiError) {
1980
- throw await reportApiError(error, apiConfig);
1981
- }
1982
- const wrapped = new ActionError(`Action ${actionName} failed: ${error instanceof Error ? error.message : String(error)}`, ctx(), error);
1983
- throw await reportApiError(wrapped, apiConfig);
1984
- }
1985
- }
1986
- async function dispatchAction(name, first, second) {
1987
- const action = actions[name];
1988
- const { input, options } = resolveActionInvocation(action, name, first, second);
1989
- return invokeAction(name, input, options);
1990
- }
1991
- const request = async (key, input, options) => {
1992
- if (key in actions) {
1993
- return dispatchAction(key, input, options);
1994
- }
1995
- return invokeEndpoint(key, input ?? {});
1996
- };
1997
- const client = { request, cache };
1998
- const proxy = new Proxy(client, {
1999
- get(target, prop, receiver) {
2000
- if (prop in target || typeof prop !== "string") {
2001
- return Reflect.get(target, prop, receiver);
2002
- }
2003
- if (prop in apiConfig.endpoints) {
2004
- return (input) => invokeEndpoint(prop, input ?? {});
2005
- }
2006
- if (prop in actions) {
2007
- return (first, second) => dispatchAction(prop, first, second);
2008
- }
2009
- return;
2010
- }
2011
- });
2012
- const rawActions = apiConfig.actions;
2013
- if (typeof rawActions === "function") {
2014
- actions = rawActions({
2015
- api: proxy,
2016
- defineAction: makeDefineAction()
2017
- });
2018
- } else if (rawActions) {
2019
- actions = rawActions;
2020
- }
2021
- const collisions = Object.keys(actions).filter((key) => (key in apiConfig.endpoints));
2022
- if (collisions.length > 0) {
2023
- throw new ConfigError(`Action name(s) collide with endpoint name(s): ${collisions.join(", ")}`, {
2024
- endpoint: collisions[0],
2025
- method: "GET",
2026
- url: "",
2027
- attempt: 0,
2028
- maxRetries: 0,
2029
- elapsedMs: 0
2030
- });
2031
- }
2032
- return proxy;
2033
- }
2034
- var createApiClient = (apiConfig) => createApiClientImpl(apiConfig);
2035
- // src/p-queue/index.ts
2036
- class TimeoutError extends Error {
2037
- constructor(message) {
2038
- super(message);
2039
- this.name = "TimeoutError";
2040
- }
2041
- }
2042
-
2043
- class PriorityQueue {
2044
- #queue = [];
2045
- enqueue(run, options = {}) {
2046
- const element = {
2047
- priority: options.priority ?? 0,
2048
- id: options.id,
2049
- run
2050
- };
2051
- const index = this.#queue.findIndex((item) => item.priority < element.priority);
2052
- if (index === -1) {
2053
- this.#queue.push(element);
2054
- return;
2055
- }
2056
- this.#queue.splice(index, 0, element);
2057
- }
2058
- dequeue() {
2059
- return this.#queue.shift()?.run;
2060
- }
2061
- filter(options) {
2062
- return this.#queue.filter((item) => Object.entries(options).every(([key, value]) => item[key] === value)).map((item) => item.run);
2063
- }
2064
- setPriority(id, priority) {
2065
- const index = this.#queue.findIndex((item) => item.id === id);
2066
- if (index === -1) {
2067
- throw new ReferenceError(`No promise function with the id "${id}" exists in the queue.`);
2068
- }
2069
- const [item] = this.#queue.splice(index, 1);
2070
- this.enqueue(item.run, { ...item, priority });
2071
- }
2072
- remove(id) {
2073
- const index = this.#queue.findIndex((item) => item.id === id);
2074
- if (index !== -1) {
2075
- this.#queue.splice(index, 1);
2076
- }
2077
- }
2078
- get size() {
2079
- return this.#queue.length;
2080
- }
2081
- }
2082
-
2083
- class PQueue {
2084
- #listeners = new Map;
2085
- #queue;
2086
- #queueClass;
2087
- #pending = 0;
2088
- #concurrency;
2089
- #isPaused;
2090
- #intervalCap;
2091
- #interval;
2092
- #carryoverIntervalCount;
2093
- #strict;
2094
- #intervalCount = 0;
2095
- #intervalEnd = 0;
2096
- #intervalTimer;
2097
- #strictTimestamps = [];
2098
- #rateLimited = false;
2099
- #idCounter = 1n;
2100
- #runningTasks = new Map;
2101
- timeout;
2102
- constructor(options = {}) {
2103
- const {
2104
- concurrency = Number.POSITIVE_INFINITY,
2105
- autoStart = true,
2106
- queueClass = PriorityQueue,
2107
- intervalCap = Number.POSITIVE_INFINITY,
2108
- interval = 0,
2109
- carryoverIntervalCount,
2110
- carryoverConcurrencyCount,
2111
- strict = false,
2112
- timeout
2113
- } = options;
2114
- if (!(typeof concurrency === "number" && concurrency >= 1)) {
2115
- throw new TypeError("Expected `concurrency` to be a number from 1 and up");
2116
- }
2117
- if (!(typeof intervalCap === "number" && intervalCap >= 1)) {
2118
- throw new TypeError("Expected `intervalCap` to be a number from 1 and up");
2119
- }
2120
- if (!(typeof interval === "number" && Number.isFinite(interval) && interval >= 0)) {
2121
- throw new TypeError("Expected `interval` to be a finite number >= 0");
2122
- }
2123
- if (strict && interval === 0) {
2124
- throw new TypeError("The `strict` option requires a non-zero `interval`");
2125
- }
2126
- if (strict && intervalCap === Number.POSITIVE_INFINITY) {
2127
- throw new TypeError("The `strict` option requires a finite `intervalCap`");
2128
- }
2129
- if (timeout !== undefined && !(Number.isFinite(timeout) && timeout > 0)) {
2130
- throw new TypeError("Expected `timeout` to be a positive finite number");
2131
- }
2132
- this.#queueClass = queueClass;
2133
- this.#queue = new queueClass;
2134
- this.#concurrency = concurrency;
2135
- this.#isPaused = !autoStart;
2136
- this.#intervalCap = intervalCap;
2137
- this.#interval = interval;
2138
- this.#carryoverIntervalCount = carryoverIntervalCount ?? carryoverConcurrencyCount ?? false;
2139
- this.#strict = strict;
2140
- this.timeout = timeout;
2141
- }
2142
- on(event, listener) {
2143
- const listeners = this.#listeners.get(event) ?? new Set;
2144
- listeners.add(listener);
2145
- this.#listeners.set(event, listeners);
2146
- return this;
2147
- }
2148
- off(event, listener) {
2149
- this.#listeners.get(event)?.delete(listener);
2150
- return this;
2151
- }
2152
- emit(event, ...arguments_) {
2153
- const listeners = this.#listeners.get(event);
2154
- if (!listeners || listeners.size === 0) {
2155
- return false;
2156
- }
2157
- for (const listener of [...listeners]) {
2158
- listener(...arguments_);
2159
- }
2160
- return true;
2161
- }
2162
- get concurrency() {
2163
- return this.#concurrency;
2164
- }
2165
- set concurrency(newConcurrency) {
2166
- if (!(typeof newConcurrency === "number" && newConcurrency >= 1)) {
2167
- throw new TypeError("Expected `concurrency` to be a number from 1 and up");
2168
- }
2169
- this.#concurrency = newConcurrency;
2170
- this.#processQueue();
2171
- }
2172
- get size() {
2173
- return this.#queue.size;
2174
- }
2175
- sizeBy(options) {
2176
- return this.#queue.filter(options).length;
2177
- }
2178
- get pending() {
2179
- return this.#pending;
2180
- }
2181
- get isPaused() {
2182
- return this.#isPaused;
2183
- }
2184
- get isRateLimited() {
2185
- this.#updateRateLimitState();
2186
- return this.#rateLimited;
2187
- }
2188
- get isSaturated() {
2189
- return this.#pending >= this.#concurrency && this.#queue.size > 0 || this.isRateLimited && this.#queue.size > 0;
2190
- }
2191
- get runningTasks() {
2192
- return [...this.#runningTasks.values()].map((task) => ({ ...task }));
2193
- }
2194
- setPriority(id, priority) {
2195
- if (typeof priority !== "number" || !Number.isFinite(priority)) {
2196
- throw new TypeError("Expected `priority` to be a finite number");
2197
- }
2198
- this.#queue.setPriority(id, priority);
2199
- }
2200
- async add(function_, options = {}) {
2201
- const taskOptions = {
2202
- timeout: this.timeout,
2203
- ...options,
2204
- id: options.id ?? (this.#idCounter++).toString()
2205
- };
2206
- return new Promise((resolve, reject) => {
2207
- const taskSymbol = Symbol(taskOptions.id);
2208
- let didRun = false;
2209
- let queuedAbortHandler;
2210
- let runningAbortHandler;
2211
- const cleanupQueuedAbort = () => {
2212
- if (queuedAbortHandler) {
2213
- taskOptions.signal?.removeEventListener("abort", queuedAbortHandler);
2214
- queuedAbortHandler = undefined;
2215
- }
2216
- };
2217
- const run = async () => {
2218
- didRun = true;
2219
- cleanupQueuedAbort();
2220
- this.#pending++;
2221
- this.#runningTasks.set(taskSymbol, {
2222
- id: taskOptions.id,
2223
- priority: taskOptions.priority ?? 0,
2224
- startTime: Date.now(),
2225
- timeout: taskOptions.timeout
2226
- });
2227
- try {
2228
- taskOptions.signal?.throwIfAborted();
2229
- const operation = Promise.resolve(function_({ signal: taskOptions.signal }));
2230
- const result = await this.#withTimeoutAndAbort(operation, taskOptions, (listener) => {
2231
- runningAbortHandler = listener;
2232
- });
2233
- resolve(result);
2234
- this.emit("completed", result);
2235
- } catch (error) {
2236
- reject(error);
2237
- this.emit("error", error);
2238
- } finally {
2239
- if (runningAbortHandler) {
2240
- taskOptions.signal?.removeEventListener("abort", runningAbortHandler);
2241
- }
2242
- this.#runningTasks.delete(taskSymbol);
2243
- queueMicrotask(() => {
2244
- this.#next();
2245
- });
2246
- }
2247
- };
2248
- if (taskOptions.signal?.aborted) {
2249
- reject(taskOptions.signal.reason);
2250
- return;
2251
- }
2252
- if (taskOptions.signal) {
2253
- queuedAbortHandler = () => {
2254
- if (didRun) {
2255
- return;
2256
- }
2257
- this.#queue.remove?.(taskOptions.id);
2258
- reject(taskOptions.signal?.reason);
2259
- this.emit("next");
2260
- this.#tryToStartAnother();
2261
- };
2262
- taskOptions.signal.addEventListener("abort", queuedAbortHandler, {
2263
- once: true
2264
- });
2265
- }
2266
- this.#queue.enqueue(run, taskOptions);
2267
- this.emit("add");
2268
- this.#processQueue();
2269
- });
2270
- }
2271
- async addAll(functions, options) {
2272
- return Promise.all(functions.map(async (function_) => this.add(function_, options)));
2273
- }
2274
- start() {
2275
- if (!this.#isPaused) {
2276
- return this;
2277
- }
2278
- this.#isPaused = false;
2279
- this.#processQueue();
2280
- return this;
2281
- }
2282
- pause() {
2283
- this.#isPaused = true;
2284
- }
2285
- clear() {
2286
- this.#queue = new this.#queueClass;
2287
- this.#clearIntervalTimer();
2288
- this.#updateRateLimitState();
2289
- this.emit("empty");
2290
- if (this.#pending === 0) {
2291
- this.emit("idle");
2292
- }
2293
- this.emit("next");
2294
- }
2295
- async onEmpty() {
2296
- if (this.#queue.size === 0) {
2297
- return;
2298
- }
2299
- await this.#onEvent("empty");
2300
- }
2301
- async onIdle() {
2302
- if (this.#queue.size === 0 && this.#pending === 0) {
2303
- return;
2304
- }
2305
- await this.#onEvent("idle");
2306
- }
2307
- async onPendingZero() {
2308
- if (this.#pending === 0) {
2309
- return;
2310
- }
2311
- await this.#onEvent("pendingZero");
2312
- }
2313
- async onRateLimit() {
2314
- if (this.isRateLimited) {
2315
- return;
2316
- }
2317
- await this.#onEvent("rateLimit");
2318
- }
2319
- async onRateLimitCleared() {
2320
- if (!this.isRateLimited) {
2321
- return;
2322
- }
2323
- await this.#onEvent("rateLimitCleared");
2324
- }
2325
- onError() {
2326
- return new Promise((_resolve, reject) => {
2327
- const listener = (error) => {
2328
- this.off("error", listener);
2329
- reject(error);
2330
- };
2331
- this.on("error", listener);
2332
- });
2333
- }
2334
- async onSizeLessThan(limit) {
2335
- if (this.#queue.size < limit) {
2336
- return;
2337
- }
2338
- await this.#onEvent("next", () => this.#queue.size < limit);
2339
- }
2340
- async#onEvent(event, filter) {
2341
- return new Promise((resolve) => {
2342
- const listener = () => {
2343
- if (filter && !filter()) {
2344
- return;
2345
- }
2346
- this.off(event, listener);
2347
- resolve();
2348
- };
2349
- this.on(event, listener);
2350
- });
2351
- }
2352
- async#withTimeoutAndAbort(operation, options, setAbortListener) {
2353
- const races = [operation];
2354
- let timeoutId;
2355
- if (options.timeout) {
2356
- races.push(new Promise((_resolve, reject) => {
2357
- timeoutId = setTimeout(() => {
2358
- reject(new TimeoutError(`Task timed out after ${options.timeout}ms`));
2359
- }, options.timeout);
2360
- }));
2361
- }
2362
- if (options.signal) {
2363
- races.push(new Promise((_resolve, reject) => {
2364
- const listener = () => {
2365
- reject(options.signal?.reason);
2366
- };
2367
- setAbortListener(listener);
2368
- options.signal?.addEventListener("abort", listener, { once: true });
2369
- }));
2370
- }
2371
- try {
2372
- return await Promise.race(races);
2373
- } finally {
2374
- if (timeoutId) {
2375
- clearTimeout(timeoutId);
2376
- }
2377
- }
2378
- }
2379
- #next() {
2380
- this.#pending--;
2381
- if (this.#pending === 0) {
2382
- this.emit("pendingZero");
2383
- }
2384
- this.#processQueue();
2385
- this.emit("next");
2386
- }
2387
- #processQueue() {
2388
- while (this.#tryToStartAnother()) {}
2389
- this.#updateRateLimitState();
2390
- }
2391
- #tryToStartAnother() {
2392
- if (this.#queue.size === 0) {
2393
- this.#clearIntervalTimer();
2394
- this.emit("empty");
2395
- if (this.#pending === 0) {
2396
- this.emit("idle");
2397
- }
2398
- return false;
2399
- }
2400
- if (this.#isPaused || this.#pending >= this.#concurrency || !this.#doesIntervalAllowAnother()) {
2401
- return false;
2402
- }
2403
- const job = this.#queue.dequeue();
2404
- if (!job) {
2405
- return false;
2406
- }
2407
- this.#consumeIntervalSlot();
2408
- this.emit("active");
2409
- job();
2410
- return true;
2411
- }
2412
- #doesIntervalAllowAnother() {
2413
- if (this.#intervalCap === Number.POSITIVE_INFINITY || this.#interval === 0) {
2414
- return true;
2415
- }
2416
- const now = Date.now();
2417
- if (this.#strict) {
2418
- this.#strictTimestamps = this.#strictTimestamps.filter((timestamp) => now - timestamp < this.#interval);
2419
- if (this.#strictTimestamps.length < this.#intervalCap) {
2420
- return true;
2421
- }
2422
- this.#scheduleIntervalResume(this.#interval - (now - this.#strictTimestamps[0]));
2423
- return false;
2424
- }
2425
- if (this.#intervalEnd === 0 || now >= this.#intervalEnd) {
2426
- this.#intervalCount = this.#carryoverIntervalCount ? this.#pending : 0;
2427
- this.#intervalEnd = now + this.#interval;
2428
- }
2429
- if (this.#intervalCount < this.#intervalCap) {
2430
- return true;
2431
- }
2432
- this.#scheduleIntervalResume(this.#intervalEnd - now);
2433
- return false;
2434
- }
2435
- #consumeIntervalSlot() {
2436
- if (this.#intervalCap === Number.POSITIVE_INFINITY || this.#interval === 0) {
2437
- return;
2438
- }
2439
- if (this.#strict) {
2440
- this.#strictTimestamps.push(Date.now());
2441
- return;
2442
- }
2443
- this.#intervalCount++;
2444
- }
2445
- #scheduleIntervalResume(delay) {
2446
- if (this.#intervalTimer) {
2447
- return;
2448
- }
2449
- this.#intervalTimer = setTimeout(() => {
2450
- this.#intervalTimer = undefined;
2451
- this.#processQueue();
2452
- }, Math.max(0, delay));
2453
- }
2454
- #clearIntervalTimer() {
2455
- if (this.#intervalTimer) {
2456
- clearTimeout(this.#intervalTimer);
2457
- this.#intervalTimer = undefined;
2458
- }
2459
- }
2460
- #updateRateLimitState() {
2461
- const previous = this.#rateLimited;
2462
- const isRateLimited = this.#queue.size > 0 && this.#intervalCap !== Number.POSITIVE_INFINITY && this.#interval > 0 && !this.#doesIntervalAllowAnother();
2463
- this.#rateLimited = isRateLimited;
2464
- if (previous !== isRateLimited) {
2465
- this.emit(isRateLimited ? "rateLimit" : "rateLimitCleared");
2466
- }
2467
- }
2468
- }
2469
- // src/cool-console-log/index.ts
2470
- var terminalColors = {
2471
- reset: "\x1B[0m",
2472
- bold: "\x1B[1m",
2473
- dim: "\x1B[2m",
2474
- italic: "\x1B[3m",
2475
- underline: "\x1B[4m",
2476
- black: "\x1B[30m",
2477
- red: "\x1B[31m",
2478
- green: "\x1B[32m",
2479
- yellow: "\x1B[33m",
2480
- blue: "\x1B[34m",
2481
- magenta: "\x1B[35m",
2482
- cyan: "\x1B[36m",
2483
- white: "\x1B[37m",
2484
- gray: "\x1B[90m",
2485
- brightRed: "\x1B[91m",
2486
- brightGreen: "\x1B[92m",
2487
- brightYellow: "\x1B[93m",
2488
- brightBlue: "\x1B[94m",
2489
- brightMagenta: "\x1B[95m",
2490
- brightCyan: "\x1B[96m",
2491
- brightWhite: "\x1B[97m",
2492
- bgBlack: "\x1B[40m",
2493
- bgRed: "\x1B[41m",
2494
- bgGreen: "\x1B[42m",
2495
- bgYellow: "\x1B[43m",
2496
- bgBlue: "\x1B[44m",
2497
- bgMagenta: "\x1B[45m",
2498
- bgCyan: "\x1B[46m",
2499
- bgWhite: "\x1B[47m"
2500
- };
2501
- var levelColors = {
2502
- debug: { badge: terminalColors.gray, text: terminalColors.dim },
2503
- info: { badge: terminalColors.cyan, text: terminalColors.reset },
2504
- warn: { badge: terminalColors.yellow, text: terminalColors.yellow },
2505
- error: { badge: terminalColors.red, text: terminalColors.brightRed }
2506
- };
2507
- function formatAttributes(attrs) {
2508
- if (!attrs || Object.keys(attrs).length === 0)
2509
- return "";
2510
- const formatted = Object.entries(attrs).map(([k, v]) => `${terminalColors.dim}${k}=${terminalColors.reset}${v}`).join(" ");
2511
- return ` ${formatted}`;
2512
- }
2513
- function createCoolLogger(options = {}) {
2514
- const { colorsInDevOnly = true, onLog } = options;
2515
- const isDev = true;
2516
- const useColors = colorsInDevOnly ? isDev : true;
2517
- const createLogMethod = (level) => (message, attributes) => {
2518
- onLog?.(level, message, attributes);
2519
- if (level === "debug" && !isDev)
2520
- return;
2521
- if (useColors) {
2522
- const { badge, text } = levelColors[level];
2523
- const label = level.toUpperCase().padEnd(5);
2524
- const attrStr = formatAttributes(attributes);
2525
- console.log(`${badge}[${label}]${terminalColors.reset} ${text}${message}${terminalColors.reset}${attrStr}`);
2526
- } else {
2527
- console[level](`[${level.toUpperCase()}]`, message, attributes ?? "");
2528
- }
2529
- };
2530
- return {
2531
- debug: createLogMethod("debug"),
2532
- info: createLogMethod("info"),
2533
- warn: createLogMethod("warn"),
2534
- error: createLogMethod("error")
2535
- };
2536
- }
2537
- function logOperationSummary(operationName, durationMs, success, options = {}) {
2538
- const { devOnly = true, error } = options;
2539
- const isDev = true;
2540
- if (devOnly && !isDev)
2541
- return;
2542
- const c = terminalColors;
2543
- const statusIcon = success ? `${c.brightGreen}✓${c.reset}` : `${c.brightRed}✗${c.reset}`;
2544
- const statusColor = success ? c.green : c.red;
2545
- const durationStr = `${c.dim}${durationMs.toFixed(1)}ms${c.reset}`;
2546
- const errorMessage = error ? ` ${c.dim}·${c.reset} ${c.red}${error instanceof Error ? error.message : String(error)}${c.reset}` : "";
2547
- console.log(`${statusColor}${c.bold}${operationName}${c.reset} ${statusIcon} ${c.dim}·${c.reset} ${durationStr}${errorMessage}`);
2548
- }
2549
- function colorize(text, color, ...modifiers) {
2550
- const isDev = true;
2551
- if (!isDev)
2552
- return text;
2553
- const colorCodes = [
2554
- terminalColors[color],
2555
- ...modifiers.map((m) => terminalColors[m])
2556
- ].join("");
2557
- return `${colorCodes}${text}${terminalColors.reset}`;
2558
- }
2559
- function logBanner(text, color = "cyan", options = {}) {
2560
- const { devOnly = true } = options;
2561
- const isDev = true;
2562
- if (devOnly && !isDev) {
2563
- console.log(`=== ${text} ===`);
2564
- return;
2565
- }
2566
- const c = terminalColors;
2567
- const padding = 2;
2568
- const line = "═".repeat(text.length + padding * 2);
2569
- console.log(`${c[color]}╔${line}╗${c.reset}`);
2570
- console.log(`${c[color]}║${" ".repeat(padding)}${c.bold}${text}${c.reset}${c[color]}${" ".repeat(padding)}║${c.reset}`);
2571
- console.log(`${c[color]}╚${line}╝${c.reset}`);
2572
- }
2573
- // src/jwt/index.ts
2574
- var BYTE_CHUNK_SIZE = 32768;
2575
- var textEncoder = new TextEncoder;
2576
- var textDecoder = new TextDecoder;
2577
- async function jwtSign(payload, secret) {
2578
- const header = encodeJson({ alg: "HS256" });
2579
- const body = encodeJson({
2580
- ...payload,
2581
- iat: Math.floor(Date.now() / 1000)
2582
- });
2583
- const data = `${header}.${body}`;
2584
- const signature = await sign(data, secret);
2585
- return `${data}.${encodeBase64Url(signature)}`;
2586
- }
2587
- async function jwtVerify(token, secret) {
2588
- try {
2589
- const parts = token.split(".");
2590
- if (parts.length !== 3)
2591
- return null;
2592
- const [encodedHeader, encodedPayload, encodedSignature] = parts;
2593
- const header = decodeJson(encodedHeader);
2594
- if (!isJwtHeader(header))
2595
- return null;
2596
- const data = `${encodedHeader}.${encodedPayload}`;
2597
- const signature = decodeBase64Url(encodedSignature);
2598
- const isValid = await verify(data, signature, secret);
2599
- if (!isValid)
2600
- return null;
2601
- const payload = decodeJson(encodedPayload);
2602
- if (!isJwtPayload(payload) || !isTimeValid(payload))
2603
- return null;
2604
- return payload;
2605
- } catch (_error) {
2606
- return null;
2607
- }
2608
- }
2609
- async function sign(data, secret) {
2610
- const key = await getKey(secret, ["sign"]);
2611
- return await getSubtleCrypto().sign("HMAC", key, encodeText(data));
2612
- }
2613
- async function verify(data, signature, secret) {
2614
- const key = await getKey(secret, ["verify"]);
2615
- return await getSubtleCrypto().verify("HMAC", key, signature, encodeText(data));
2616
- }
2617
- async function getKey(secret, usages) {
2618
- return await getSubtleCrypto().importKey("raw", encodeText(secret), {
2619
- name: "HMAC",
2620
- hash: "SHA-256"
2621
- }, false, usages);
2622
- }
2623
- function encodeJson(value) {
2624
- return encodeBase64Url(encodeText(JSON.stringify(value)));
2625
- }
2626
- function decodeJson(value) {
2627
- return JSON.parse(textDecoder.decode(decodeBase64Url(value)));
2628
- }
2629
- function encodeBase64Url(value) {
2630
- const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value;
2631
- return btoa(bytesToBinaryString(bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
2632
- }
2633
- function decodeBase64Url(value) {
2634
- const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
2635
- const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
2636
- return binaryStringToBytes(atob(padded)).buffer;
2637
- }
2638
- function encodeText(value) {
2639
- return toArrayBuffer(textEncoder.encode(value));
2640
- }
2641
- function toArrayBuffer(bytes) {
2642
- return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
2643
- }
2644
- function bytesToBinaryString(bytes) {
2645
- let binary = "";
2646
- for (let offset = 0;offset < bytes.length; offset += BYTE_CHUNK_SIZE) {
2647
- binary += String.fromCharCode(...bytes.subarray(offset, offset + BYTE_CHUNK_SIZE));
2648
- }
2649
- return binary;
2650
- }
2651
- function binaryStringToBytes(binary) {
2652
- const bytes = new Uint8Array(binary.length);
2653
- for (let index = 0;index < binary.length; index += 1) {
2654
- bytes[index] = binary.charCodeAt(index);
2655
- }
2656
- return bytes;
2657
- }
2658
- function getSubtleCrypto() {
2659
- const subtle = globalThis.crypto?.subtle;
2660
- if (!subtle) {
2661
- throw new Error("Web Crypto is not available in this runtime");
2662
- }
2663
- return subtle;
2664
- }
2665
- function isJwtHeader(value) {
2666
- return typeof value === "object" && value !== null && "alg" in value && value.alg === "HS256";
2667
- }
2668
- function isJwtPayload(value) {
2669
- return typeof value === "object" && value !== null && !Array.isArray(value);
2670
- }
2671
- function isTimeValid(payload) {
2672
- const now = Math.floor(Date.now() / 1000);
2673
- if (typeof payload.exp === "number" && now >= payload.exp)
2674
- return false;
2675
- if (typeof payload.nbf === "number" && now < payload.nbf)
2676
- return false;
2677
- return true;
793
+ current[keys.at(-1)] = value;
2678
794
  }
2679
- // src/sitemap/index.ts
2680
- var createTimeoutController = (timeout) => {
2681
- const controller = new AbortController;
2682
- setTimeout(() => controller.abort(), timeout);
2683
- return controller;
2684
- };
2685
- var discoverSitemaps = async (domain) => {
2686
- const normalizedDomain = domain.replace(/\/$/, "");
2687
- const commonLocations = [
2688
- "/sitemap.xml",
2689
- "/sitemap_index.xml",
2690
- "/sitemap/",
2691
- "/sitemaps.xml",
2692
- "/sitemap/sitemap.xml"
2693
- ];
2694
- const sitemaps = new Set;
2695
- const requestOptions = {
2696
- headers: {
2697
- "User-Agent": "SitemapCrawler/1.0"
2698
- },
2699
- signal: createTimeoutController(5000).signal
2700
- };
2701
- try {
2702
- const robotsTxt = await fetch(`${normalizedDomain}/robots.txt`, {
2703
- ...requestOptions
2704
- });
2705
- if (robotsTxt.ok) {
2706
- const text = await robotsTxt.text();
2707
- const sitemapMatches = text.match(/Sitemap: (.*)/gi);
2708
- if (sitemapMatches) {
2709
- sitemapMatches.forEach((match) => {
2710
- const url = match.replace("Sitemap:", "").trim();
2711
- sitemaps.add(url);
2712
- });
2713
- }
2714
- if (sitemaps.size > 0) {
2715
- return [...sitemaps];
2716
- }
2717
- }
2718
- } catch (error) {
2719
- console.warn("Could not fetch robots.txt:", error instanceof Error ? error.message : "Unknown error");
2720
- }
2721
- for (const location of commonLocations) {
2722
- try {
2723
- const response = await fetch(`${normalizedDomain}${location}`, {
2724
- ...requestOptions,
2725
- method: "HEAD"
2726
- });
2727
- if (response.ok) {
2728
- return [`${normalizedDomain}${location}`];
2729
- }
2730
- } catch (_error) {}
2731
- }
2732
- return [];
2733
- };
2734
- var getDomainSitemap = async (domain, options = {}) => {
2735
- const {
2736
- filterIndexes,
2737
- concurrency = 10,
2738
- maxDepth = 5,
2739
- timeout = 1e4
2740
- } = options;
2741
- let normalizedDomain = domain.replace(/\/$/, "");
2742
- if (!normalizedDomain.startsWith("http")) {
2743
- normalizedDomain = `https://${normalizedDomain}`;
2744
- }
2745
- const sitemapUrls = await discoverSitemaps(normalizedDomain);
2746
- if (sitemapUrls.length === 0) {
2747
- throw new Error("No sitemaps found for the domain");
2748
- }
2749
- const queue = new PQueue({ concurrency });
2750
- const result = {
2751
- links: new Set,
2752
- visited: new Set
2753
- };
2754
- const locRegex = /<loc>([^<]+)<\/loc>/g;
2755
- const isXmlSitemap = (url) => url.endsWith(".xml") || url.toLowerCase().includes("sitemap");
2756
- const processSitemap = async (sitemapUrl, depth = 0) => {
2757
- if (depth > maxDepth || result.visited.has(sitemapUrl)) {
2758
- return;
2759
- }
2760
- result.visited.add(sitemapUrl);
2761
- try {
2762
- const controller = createTimeoutController(timeout);
2763
- const response = await fetch(sitemapUrl, {
2764
- headers: {
2765
- "User-Agent": "SitemapCrawler/1.0"
2766
- },
2767
- signal: controller.signal
2768
- });
2769
- if (!response.ok)
2770
- throw new Error(`HTTP error! status: ${response.status}`);
2771
- const data = await response.text();
2772
- const matches = [...data.matchAll(locRegex)].map((match) => match[1].trim()).filter((link) => {
2773
- if (filterIndexes && isXmlSitemap(link)) {
2774
- return link.includes(filterIndexes);
2775
- }
2776
- return true;
2777
- });
2778
- for (const link of matches) {
2779
- if (isXmlSitemap(link)) {
2780
- queue.add(() => processSitemap(link, depth + 1));
2781
- } else {
2782
- result.links.add(link);
2783
- }
2784
- }
2785
- } catch (error) {
2786
- console.warn(`Failed to process sitemap ${sitemapUrl}:`, error instanceof Error ? error.message : "Unknown error");
2787
- }
795
+ // src/function/index.ts
796
+ function debounce(fn, waitMs, options = {}) {
797
+ let timer;
798
+ let maxTimer;
799
+ let lastArgs;
800
+ const cancel = () => {
801
+ clearTimeout(timer);
802
+ clearTimeout(maxTimer);
803
+ timer = maxTimer = lastArgs = undefined;
2788
804
  };
2789
- await Promise.all(sitemapUrls.map((sitemapUrl) => queue.add(() => processSitemap(sitemapUrl))));
2790
- await queue.onIdle();
2791
- return [...result.links];
2792
- };
2793
- // src/secrets/errors.ts
2794
- class SecretLoadError extends Error {
2795
- code;
2796
- operation;
2797
- status;
2798
- constructor(code, message, details = {}) {
2799
- super(message);
2800
- this.name = "SecretLoadError";
2801
- this.code = code;
2802
- this.operation = details.operation;
2803
- this.status = details.status;
2804
- }
2805
- }
2806
-
2807
- // src/secrets/deadline.ts
2808
- async function withSecretDeadline(options, operation) {
2809
- const timeoutMs = options.timeoutMs ?? 30000;
2810
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) {
2811
- throw new SecretLoadError("CONFIGURATION", "Secret timeoutMs must be a positive finite timer duration.");
2812
- }
2813
- const controller = new AbortController;
2814
- const cancel = () => controller.abort(new SecretLoadError("ABORTED", "Secret loading was cancelled."));
2815
- if (options.signal?.aborted) {
805
+ const flush = () => {
806
+ const args = lastArgs;
2816
807
  cancel();
2817
- } else {
2818
- options.signal?.addEventListener("abort", cancel, { once: true });
2819
- }
2820
- const timer = setTimeout(() => controller.abort(new SecretLoadError("TIMEOUT", `Secret loading timed out after ${timeoutMs}ms.`)), timeoutMs);
2821
- let rejectOnAbort = () => {};
2822
- try {
2823
- const aborted = new Promise((_, reject) => {
2824
- rejectOnAbort = () => reject(controller.signal.reason);
2825
- controller.signal.addEventListener("abort", rejectOnAbort, { once: true });
2826
- });
2827
- if (controller.signal.aborted) {
2828
- throw controller.signal.reason;
2829
- }
2830
- return await Promise.race([operation(controller.signal), aborted]);
2831
- } finally {
2832
- clearTimeout(timer);
2833
- options.signal?.removeEventListener("abort", cancel);
2834
- controller.signal.removeEventListener("abort", rejectOnAbort);
2835
- }
2836
- }
2837
-
2838
- // src/secrets/environment.ts
2839
- function getProcessEnvironment() {
2840
- return typeof process === "undefined" ? undefined : process.env;
2841
- }
2842
- function resolveSecretEnvironment(option, env) {
2843
- const explicit = typeof option === "function" ? option() : option;
2844
- if (typeof explicit === "object" && explicit !== null) {
2845
- if (typeof explicit.slug !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(explicit.slug)) {
2846
- throw new SecretLoadError("CONFIGURATION", "Custom secret environment requires a valid slug.");
2847
- }
2848
- return explicit.slug;
2849
- }
2850
- const selected = explicit ?? env?.SECRETS_ENV;
2851
- if (selected !== undefined) {
2852
- if (selected === "production" || selected === "prod") {
2853
- return "prod";
2854
- }
2855
- if (selected === "development" || selected === "dev") {
2856
- return "dev";
2857
- }
2858
- if (selected === "staging") {
2859
- return "staging";
2860
- }
2861
- throw new SecretLoadError("CONFIGURATION", "Invalid secret environment. Use dev, staging, prod, or an explicit { slug } option.");
2862
- }
2863
- if (env?.NODE_ENV === "production" || env?.NODE_ENV === "prod") {
2864
- return "prod";
2865
- }
2866
- if (env?.NODE_ENV === "staging") {
2867
- return "staging";
2868
- }
2869
- return "dev";
2870
- }
2871
-
2872
- // src/secrets/core.ts
2873
- var registryKey = Symbol.for("hanzio.secrets.environmentWrites.v1");
2874
- var shared = globalThis;
2875
- var environmentWrites = shared[registryKey] ?? new WeakMap;
2876
- shared[registryKey] = environmentWrites;
2877
- var defaultLogger = {
2878
- info: (message, attrs) => console.log(`${message} environment=${attrs.environment} count=${attrs.count} source=${attrs.source} durationMs=${attrs.durationMs}`, { sources: attrs.sources }),
2879
- error: (message, attrs) => console.error(message, attrs)
2880
- };
2881
- function readOverrides(keys, env) {
2882
- if (!env) {
2883
- return {};
2884
- }
2885
- const writes = environmentWrites.get(env);
2886
- return Object.fromEntries(keys.flatMap((key) => {
2887
- const value = Object.hasOwn(env, key) ? env[key] : undefined;
2888
- if (writes?.has(key)) {
2889
- if (writes.get(key) === value) {
2890
- return [];
2891
- }
2892
- writes.delete(key);
2893
- }
2894
- return value ? [[key, value]] : [];
2895
- }));
2896
- }
2897
- async function defineSecretSet(keys, options, environmentVariables) {
2898
- const keySet = [...new Set(keys)];
2899
- const environment = resolveSecretEnvironment(options.environment, environmentVariables);
2900
- const logger = options.logger === false ? undefined : options.logger ?? defaultLogger;
2901
- let cached;
2902
- let parsed;
2903
- let sources;
2904
- let inFlight;
2905
- const log = (level, message, attributes) => {
2906
- try {
2907
- logger?.[level](message, attributes);
2908
- } catch {}
808
+ if (args)
809
+ fn(...args);
2909
810
  };
2910
- const loadSnapshot = async (request, signal) => {
2911
- const local = readOverrides(keySet, environmentVariables);
2912
- const missing = keySet.filter((key) => !Object.hasOwn(local, key));
2913
- let loaderSource = options.loader.source ?? "custom loader";
2914
- let loaded = {};
2915
- if (missing.length) {
2916
- try {
2917
- loaded = await options.loader({
2918
- keys: missing,
2919
- environment,
2920
- signal,
2921
- timeoutMs: request.timeoutMs ?? options.timeoutMs ?? 30000,
2922
- reportSource: (source) => {
2923
- loaderSource = source;
2924
- }
2925
- });
2926
- } catch (error) {
2927
- if (error instanceof SecretLoadError) {
2928
- throw error;
2929
- }
2930
- throw new SecretLoadError("LOADER", "Secret loader failed.");
2931
- }
2932
- }
2933
- const overrides = readOverrides(keySet, environmentVariables);
2934
- const next = Object.fromEntries(keySet.map((key) => {
2935
- if (Object.hasOwn(overrides, key)) {
2936
- return [key, overrides[key]];
2937
- }
2938
- if (Object.hasOwn(loaded, key)) {
2939
- return [key, loaded[key]];
2940
- }
2941
- return [key, undefined];
2942
- }));
2943
- const missingKeys = keySet.filter((key) => typeof next[key] !== "string" || !next[key]);
2944
- if (missingKeys.length) {
2945
- throw new SecretLoadError("MISSING_SECRETS", `Missing secrets: ${missingKeys.join(", ")}`);
2946
- }
2947
- let nextParsed;
2948
- try {
2949
- nextParsed = options.schema ? await options.schema.parseAsync({ ...next }) : { ...next };
2950
- nextParsed = structuredClone(nextParsed);
2951
- } catch {
2952
- throw new SecretLoadError("VALIDATION", "Secret schema validation failed. Check the configured schema; values are omitted.");
2953
- }
2954
- signal.throwIfAborted();
2955
- const nextSources = Object.fromEntries(keySet.map((key) => [
2956
- key,
2957
- Object.hasOwn(overrides, key) ? "process.env" : loaderSource
2958
- ]));
2959
- if (options.writeToProcessEnv !== false && environmentVariables) {
2960
- let writes = environmentWrites.get(environmentVariables);
2961
- if (!writes) {
2962
- writes = new Map;
2963
- environmentWrites.set(environmentVariables, writes);
2964
- }
2965
- for (const key of keySet) {
2966
- if (Object.hasOwn(overrides, key)) {
2967
- continue;
2968
- }
2969
- environmentVariables[key] = next[key];
2970
- writes.set(key, next[key]);
2971
- }
811
+ const debounced = (...args) => {
812
+ const idle = timer === undefined;
813
+ clearTimeout(timer);
814
+ timer = setTimeout(flush, waitMs);
815
+ if (options.maxWaitMs !== undefined && maxTimer === undefined) {
816
+ maxTimer = setTimeout(flush, options.maxWaitMs);
2972
817
  }
2973
- cached = next;
2974
- parsed = nextParsed;
2975
- sources = nextSources;
818
+ if (idle && options.leading)
819
+ fn(...args);
820
+ else
821
+ lastArgs = args;
2976
822
  };
2977
- const load = (request = {}) => {
2978
- if (inFlight) {
2979
- return inFlight;
2980
- }
2981
- inFlight = (async () => {
2982
- const start = Date.now();
2983
- try {
2984
- await withSecretDeadline({
2985
- timeoutMs: request.timeoutMs ?? options.timeoutMs,
2986
- signal: request.signal ?? options.signal
2987
- }, (signal) => loadSnapshot(request, signal));
2988
- const sourceCounts = new Map;
2989
- for (const key of keySet) {
2990
- const source = sources[key];
2991
- sourceCounts.set(source, (sourceCounts.get(source) ?? 0) + 1);
2992
- }
2993
- const counts = Object.fromEntries(sourceCounts);
2994
- log("info", "Loaded secrets", {
2995
- environment,
2996
- count: keySet.length,
2997
- durationMs: Date.now() - start,
2998
- source: Object.keys(counts).join(" + ") || "none",
2999
- sources: counts
3000
- });
3001
- } catch (error) {
3002
- const safe = error instanceof SecretLoadError ? error : new SecretLoadError("LOADER", "Secret loading failed.");
3003
- log("error", "Secret loading failed", {
3004
- environment,
3005
- count: keySet.length,
3006
- durationMs: Date.now() - start,
3007
- code: safe.code
3008
- });
3009
- throw safe;
3010
- }
3011
- })().finally(() => {
3012
- inFlight = undefined;
3013
- });
3014
- return inFlight;
823
+ return Object.assign(debounced, {
824
+ cancel,
825
+ flush,
826
+ pending: () => lastArgs !== undefined
827
+ });
828
+ }
829
+ function keyedDebounce(fn, waitMs) {
830
+ const byKey = new Map;
831
+ const each = (key, action) => {
832
+ const targets = key === undefined ? [...byKey.values()] : [byKey.get(key)];
833
+ for (const debounced of targets)
834
+ if (debounced)
835
+ action(debounced);
3015
836
  };
3016
- const secretSet = {
3017
- secret(key) {
3018
- if (!Object.hasOwn(cached, key)) {
3019
- throw new SecretLoadError("MISSING_SECRETS", `Secret ${key} not found`);
837
+ return {
838
+ call(key, ...args) {
839
+ let debounced = byKey.get(key);
840
+ if (!debounced) {
841
+ debounced = debounce((...latest) => {
842
+ byKey.delete(key);
843
+ fn(key, ...latest);
844
+ }, waitMs);
845
+ byKey.set(key, debounced);
3020
846
  }
3021
- return cached[key];
847
+ debounced(...args);
3022
848
  },
3023
- secrets: () => ({ ...cached }),
3024
- parsed: () => structuredClone(parsed),
3025
- sources: () => ({ ...sources }),
3026
- async reload(request) {
3027
- await load(request);
3028
- return secretSet;
849
+ cancel(key) {
850
+ each(key, (debounced) => debounced.cancel());
851
+ if (key === undefined)
852
+ byKey.clear();
853
+ else
854
+ byKey.delete(key);
855
+ },
856
+ flush(key) {
857
+ each(key, (debounced) => debounced.flush());
3029
858
  }
3030
859
  };
3031
- await load();
3032
- return secretSet;
3033
860
  }
3034
-
3035
- // src/secrets/infisical.ts
3036
- import { z as z5 } from "zod";
3037
-
3038
- // src/secrets/infisical-cli.ts
3039
- async function readInfisicalCliToken(options) {
3040
- const { execFile } = await import("node:child_process").catch(() => {
3041
- throw new SecretLoadError("CLI", "Infisical CLI fallback requires a runtime with subprocess support. Provide Infisical client credentials or a custom secret loader.");
3042
- });
3043
- options.signal.throwIfAborted();
3044
- const env = {
3045
- ...getProcessEnvironment(),
3046
- INFISICAL_DISABLE_UPDATE_CHECK: "true"
861
+ function throttle(fn, intervalMs, options = {}) {
862
+ const { trailing = true } = options;
863
+ let timer;
864
+ let trailingArgs;
865
+ const invoke = (args) => {
866
+ timer = setTimeout(() => {
867
+ timer = undefined;
868
+ const next = trailingArgs;
869
+ trailingArgs = undefined;
870
+ if (next)
871
+ invoke(next);
872
+ }, intervalMs);
873
+ fn(...args);
3047
874
  };
3048
- delete env.INFISICAL_TOKEN;
3049
- delete env.INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN;
3050
- delete env.TOKEN;
3051
- try {
3052
- const stdout = await new Promise((resolve, reject) => {
3053
- const child = execFile("infisical", [
3054
- "user",
3055
- "get",
3056
- "token",
3057
- "--plain",
3058
- `--domain=${options.siteUrl}`,
3059
- "--silent"
3060
- ], {
3061
- env,
3062
- encoding: "utf8",
3063
- shell: false,
3064
- timeout: options.timeoutMs ?? 30000,
3065
- signal: options.signal,
3066
- killSignal: "SIGKILL",
3067
- maxBuffer: 10485760,
3068
- windowsHide: true
3069
- }, (error, output) => {
3070
- if (error) {
3071
- reject(error);
3072
- } else {
3073
- resolve(output);
3074
- }
3075
- });
3076
- child.stdin?.end();
3077
- });
3078
- return stdout.trim();
3079
- } catch (error) {
3080
- if (options.signal.aborted) {
3081
- throw options.signal.reason;
3082
- }
3083
- const failure = error;
3084
- if (failure?.code === "ENOENT") {
3085
- throw new SecretLoadError("CLI", "Infisical CLI not found. Install infisical and ensure it is on PATH, or provide Infisical client credentials.");
3086
- }
3087
- if (failure?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
3088
- throw new SecretLoadError("CLI", "Infisical CLI output exceeded the 10 MiB limit.");
3089
- }
3090
- if (failure?.killed) {
3091
- throw new SecretLoadError("TIMEOUT", `Infisical CLI timed out after ${(options.timeoutMs ?? 30000) / 1000} seconds or was terminated.`);
3092
- }
3093
- throw new SecretLoadError("CLI", `Infisical CLI session token failed. Run infisical login --domain=${options.siteUrl} and verify access to the configured organization, project, and environment.`);
3094
- }
3095
- }
3096
-
3097
- // src/secrets/infisical-config.ts
3098
- function validateInfisicalScope(options) {
3099
- validateInfisicalOrganizationId(options.organizationId);
3100
- let url;
3101
- try {
3102
- url = new URL(options.siteUrl);
3103
- } catch {
3104
- throw new SecretLoadError("CONFIGURATION", "Invalid Infisical siteUrl.");
3105
- }
3106
- if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
3107
- throw new SecretLoadError("CONFIGURATION", "Infisical siteUrl must be an HTTP(S) URL without credentials, query, or fragment.");
3108
- }
3109
- if (!options.projectId.trim() || !options.environment.trim() || !(options.secretPath ?? "/").startsWith("/")) {
3110
- throw new SecretLoadError("CONFIGURATION", "Infisical requires a projectId, environment, and absolute secretPath.");
3111
- }
3112
- }
3113
- function validateInfisicalOrganizationId(organizationId) {
3114
- if (typeof organizationId !== "string" || !organizationId.trim() || /\s/.test(organizationId)) {
3115
- throw new SecretLoadError("CONFIGURATION", "Infisical organizationId is required and must be a nonempty ID without whitespace.");
3116
- }
3117
- }
3118
-
3119
- // src/secrets/infisical-session.ts
3120
- import { z as z4 } from "zod";
3121
- var sessionClaimsSchema = z4.object({
3122
- organizationId: z4.string().optional(),
3123
- subOrganizationId: z4.string().optional(),
3124
- exp: z4.number().finite().optional()
3125
- });
3126
- function parseInfisicalSessionToken(token) {
3127
- let claims;
3128
- try {
3129
- if (!/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token)) {
3130
- throw new Error("Invalid token format");
3131
- }
3132
- const payload = token.split(".")[1];
3133
- const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
3134
- claims = sessionClaimsSchema.parse(JSON.parse(json));
3135
- } catch {
3136
- throw new SecretLoadError("AUTHENTICATION", "Infisical returned an invalid user session token. Run infisical login for the configured domain.");
3137
- }
3138
- if (claims.exp !== undefined && claims.exp <= Date.now() / 1000) {
3139
- throw new SecretLoadError("AUTHENTICATION", "Infisical user session has expired. Run infisical login for the configured domain.");
3140
- }
3141
- return { organizationId: claims.subOrganizationId ?? claims.organizationId };
3142
- }
3143
-
3144
- // src/secrets/infisical.ts
3145
- var DEFAULT_INFISICAL_SITE_URL = "https://eu.infisical.com";
3146
- var defaultFetch = globalThis.fetch;
3147
- var infisicalAuthResponseSchema = z5.object({ accessToken: z5.string() });
3148
- var infisicalSecretSchema = z5.object({
3149
- secretKey: z5.string(),
3150
- secretValue: z5.string()
3151
- });
3152
- var infisicalSecretsResponseSchema = z5.object({
3153
- secrets: z5.array(infisicalSecretSchema)
3154
- });
3155
- async function fetchInfisicalSecrets(options) {
3156
- validateInfisicalScope(options);
3157
- return withSecretDeadline(options, async (signal) => {
3158
- let operation = "login";
3159
- try {
3160
- const infisical = createInfisicalApiClient(options.siteUrl);
3161
- const { data: auth } = await infisical.request("login", {
3162
- signal,
3163
- reqBody: {
3164
- clientId: options.clientId,
3165
- clientSecret: options.clientSecret
3166
- }
3167
- });
3168
- signal.throwIfAborted();
3169
- operation = "listSecrets";
3170
- return await listInfisicalSecrets(infisical, auth.accessToken, {
3171
- ...options,
3172
- signal
3173
- });
3174
- } catch (error) {
3175
- throwInfisicalHttpError(error, operation, signal);
875
+ const throttled = (...args) => {
876
+ if (timer === undefined)
877
+ invoke(args);
878
+ else if (trailing)
879
+ trailingArgs = args;
880
+ };
881
+ return Object.assign(throttled, {
882
+ cancel: () => {
883
+ clearTimeout(timer);
884
+ timer = trailingArgs = undefined;
3176
885
  }
3177
886
  });
3178
887
  }
3179
- async function loadInfisicalSecrets(options) {
3180
- validateInfisicalScope(options);
3181
- const env = getProcessEnvironment();
3182
- const clientId = env?.[options.clientIdEnvKey];
3183
- const clientSecret = env?.[options.clientSecretEnvKey];
3184
- const mode = options.auth ?? "auto";
3185
- if (!["auto", "http", "cli"].includes(mode)) {
3186
- throw new SecretLoadError("CONFIGURATION", "Invalid Infisical auth mode.");
3187
- }
3188
- const useHttp = mode === "http" || mode === "auto" && Boolean(clientId && clientSecret);
3189
- if (useHttp) {
3190
- if (!clientId || !clientSecret) {
3191
- throw new SecretLoadError("AUTHENTICATION", "Infisical HTTP authentication requires both client credentials.");
888
+ function once(fn) {
889
+ let called = false;
890
+ let result;
891
+ return (...args) => {
892
+ if (!called) {
893
+ called = true;
894
+ result = fn(...args);
3192
895
  }
3193
- options.reportSource?.("Infisical HTTP");
3194
- return fetchInfisicalSecrets({ ...options, clientId, clientSecret });
3195
- }
3196
- options.reportSource?.(`Infisical CLI session (organization=${options.organizationId} project=${options.projectId} environment=${options.environment})`);
3197
- return withSecretDeadline(options, (signal) => fetchInfisicalSessionSecrets({ ...options, signal }));
896
+ return result;
897
+ };
3198
898
  }
3199
- async function fetchInfisicalSessionSecrets(options) {
3200
- const { signal } = options;
3201
- let token = await readInfisicalCliToken(options);
3202
- signal.throwIfAborted();
3203
- const session = parseInfisicalSessionToken(token);
3204
- const infisical = createInfisicalApiClient(options.siteUrl);
3205
- let operation = "selectOrganization";
3206
- try {
3207
- if (session.organizationId !== options.organizationId) {
3208
- const { data } = await infisical.request("selectOrganization", {
3209
- signal,
3210
- reqHeaders: {
3211
- Authorization: `Bearer ${token}`,
3212
- "User-Agent": "hanzio/secrets"
3213
- },
3214
- reqBody: { organizationId: options.organizationId }
3215
- });
3216
- signal.throwIfAborted();
3217
- if (data.isMfaEnabled) {
3218
- throw new SecretLoadError("AUTHENTICATION", "Infisical requires MFA for this organization. Complete infisical login for the target organization and configured domain before loading secrets.", { operation });
3219
- }
3220
- const scopedSession = parseInfisicalSessionToken(data.token);
3221
- if (scopedSession.organizationId !== options.organizationId) {
3222
- throw new SecretLoadError("AUTHENTICATION", "Infisical returned a session for a different organization.", { operation });
899
+ function createHeartbeat(options) {
900
+ const {
901
+ now = Date.now,
902
+ setTimeout: schedule = (callback, ms) => setTimeout(callback, ms),
903
+ clearTimeout: unschedule = (timer) => clearTimeout(timer)
904
+ } = options;
905
+ let active = false;
906
+ let lastSentAt = 0;
907
+ let idleTimer;
908
+ const stop = () => {
909
+ if (idleTimer !== undefined)
910
+ unschedule(idleTimer);
911
+ idleTimer = undefined;
912
+ if (!active)
913
+ return;
914
+ active = false;
915
+ options.onStop?.();
916
+ };
917
+ return {
918
+ activity() {
919
+ const time = now();
920
+ if (!active || time - lastSentAt >= options.intervalMs) {
921
+ active = true;
922
+ lastSentAt = time;
923
+ options.send();
3223
924
  }
3224
- token = data.token;
3225
- }
3226
- signal.throwIfAborted();
3227
- operation = "listSecrets";
3228
- return await listInfisicalSecrets(infisical, token, options);
3229
- } catch (error) {
3230
- throwInfisicalHttpError(error, operation, signal);
3231
- }
3232
- }
3233
- async function listInfisicalSecrets(infisical, token, options) {
3234
- const { data } = await infisical.request("listSecrets", {
3235
- signal: options.signal,
3236
- reqHeaders: { Authorization: `Bearer ${token}` },
3237
- reqQuery: {
3238
- environment: options.environment,
3239
- projectId: options.projectId,
3240
- secretPath: options.secretPath ?? "/",
3241
- viewSecretValue: true,
3242
- expandSecretReferences: true,
3243
- includeImports: true
3244
- }
3245
- });
3246
- const wantedKeys = new Set(options.keys);
3247
- return Object.fromEntries(data.secrets.filter((secret) => wantedKeys.has(secret.secretKey)).map((secret) => [secret.secretKey, secret.secretValue]));
3248
- }
3249
- function throwInfisicalHttpError(error, operation, signal) {
3250
- if (signal.aborted) {
3251
- throw signal.reason;
3252
- }
3253
- if (error instanceof SecretLoadError) {
3254
- throw error;
3255
- }
3256
- const status = error instanceof HttpResponseError ? error.status : undefined;
3257
- const guidance = operation === "selectOrganization" && (status === 401 || status === 403) ? " Run infisical login for the target organization and configured domain, then verify your access." : "";
3258
- throw new SecretLoadError("HTTP", `Infisical HTTP ${operation} failed${status ? ` (status ${status})` : ""}.${guidance}`, { operation, status });
3259
- }
3260
- function createInfisicalApiClient(siteUrl) {
3261
- return createApiClient({
3262
- name: "infisical",
3263
- baseApiUrls: {
3264
- infisical: siteUrl
925
+ if (idleTimer !== undefined)
926
+ unschedule(idleTimer);
927
+ idleTimer = schedule(stop, options.idleMs);
3265
928
  },
3266
- defaultBaseApiUrl: "infisical",
3267
- fetch: getFetch(),
3268
- retries: 1,
3269
- endpoints: {
3270
- selectOrganization: {
3271
- method: "POST",
3272
- path: "/api/v3/auth/select-organization",
3273
- reqHeadersSchema: z5.object({
3274
- Authorization: z5.string(),
3275
- "User-Agent": z5.string()
3276
- }),
3277
- reqBodySchema: z5.object({ organizationId: z5.string() }),
3278
- resSchema: z5.object({
3279
- token: z5.string().min(1),
3280
- isMfaEnabled: z5.boolean().optional()
3281
- }),
3282
- reqBodyFormat: "json"
3283
- },
3284
- login: {
3285
- method: "POST",
3286
- path: "/api/v1/auth/universal-auth/login",
3287
- reqBodySchema: z5.object({
3288
- clientId: z5.string(),
3289
- clientSecret: z5.string()
3290
- }),
3291
- resSchema: infisicalAuthResponseSchema,
3292
- reqBodyFormat: "json"
3293
- },
3294
- listSecrets: {
3295
- method: "GET",
3296
- path: "/api/v4/secrets",
3297
- reqHeadersSchema: z5.object({
3298
- Authorization: z5.string()
3299
- }),
3300
- reqQuerySchema: z5.object({
3301
- projectId: z5.string(),
3302
- environment: z5.string(),
3303
- secretPath: z5.string(),
3304
- viewSecretValue: z5.boolean(),
3305
- expandSecretReferences: z5.boolean(),
3306
- includeImports: z5.boolean()
3307
- }),
3308
- resSchema: infisicalSecretsResponseSchema
3309
- }
3310
- }
3311
- });
3312
- }
3313
- function getFetch() {
3314
- const fetchFn = globalThis.fetch ?? defaultFetch;
3315
- return (input, init) => {
3316
- init?.signal?.throwIfAborted();
3317
- return fetchFn(input, init);
929
+ stop
3318
930
  };
3319
931
  }
3320
- // src/secrets/worker-loader.ts
3321
- function cloudflareWorkerEnvLoader(workerEnv) {
3322
- const loader = async (ctx) => {
3323
- const out = Object.create(null);
3324
- for (const key of ctx.keys) {
3325
- const str = workerBindingToString(Object.hasOwn(workerEnv, key) ? workerEnv[key] : undefined);
3326
- if (str !== undefined) {
3327
- out[key] = str;
3328
- }
3329
- }
3330
- return out;
932
+ // src/emitter/index.ts
933
+ function createEmitter() {
934
+ const listeners = new Map;
935
+ const add = (event, listener, once) => {
936
+ const map = listeners.get(event) ?? new Map;
937
+ listeners.set(event, map);
938
+ map.set(listener, once);
939
+ return () => {
940
+ map.delete(listener);
941
+ };
3331
942
  };
3332
- return Object.assign(loader, { source: "Cloudflare Worker bindings" });
3333
- }
3334
- function workerBindingToString(value) {
3335
- if (value === undefined || value === null) {
3336
- return;
3337
- }
3338
- if (typeof value === "string") {
3339
- return value === "" ? undefined : value;
3340
- }
3341
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
3342
- return String(value);
3343
- }
3344
- if (typeof value === "object") {
3345
- return JSON.stringify(value);
3346
- }
3347
- return String(value);
3348
- }
3349
-
3350
- // src/secrets/loaders.ts
3351
- function infisicalLoader(config) {
3352
- validateInfisicalOrganizationId(config.organizationId);
3353
- const loader = async (ctx) => fetchInfisicalSecrets({
3354
- keys: ctx.keys,
3355
- projectId: config.projectId,
3356
- organizationId: config.organizationId,
3357
- environment: ctx.environment,
3358
- siteUrl: config.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
3359
- clientId: config.clientId,
3360
- clientSecret: config.clientSecret,
3361
- secretPath: config.secretPath,
3362
- timeoutMs: config.timeoutMs ?? ctx.timeoutMs,
3363
- signal: ctx.signal
3364
- });
3365
- return Object.assign(loader, { source: "Infisical HTTP" });
3366
- }
3367
- async function processEnvLoader(_ctx) {
3368
- return {};
3369
- }
3370
- Object.assign(processEnvLoader, { source: "process.env" });
3371
- // src/secrets/vite.ts
3372
- function getViteDefine(secretSet, options = {}) {
3373
- const values = secretSet.secrets();
3374
- const keys = options.publicKeys ?? Object.keys(values);
3375
- if (!options.publicKeys && keys.some((key) => !key.startsWith("VITE_"))) {
3376
- throw new SecretLoadError("CONFIGURATION", "Vite secret sets must contain only VITE_ keys, or provide an explicit publicKeys list.");
3377
- }
3378
- for (const key of keys) {
3379
- if (!Object.hasOwn(values, key) || !/^[A-Za-z_$][\w$]*$/.test(key)) {
3380
- throw new SecretLoadError("CONFIGURATION", "Vite publicKeys must reference configured keys with valid JavaScript identifiers.");
3381
- }
3382
- if (["MODE", "BASE_URL", "PROD", "DEV", "SSR"].includes(key)) {
3383
- throw new SecretLoadError("CONFIGURATION", "Vite built-in environment constants cannot be overridden by secrets.");
3384
- }
3385
- }
3386
- return Object.fromEntries(keys.map((key) => [`import.meta.env.${key}`, JSON.stringify(values[key])]));
3387
- }
3388
- function viteSecretSetPlugin(secretSet, options = {}) {
3389
943
  return {
3390
- name: "hanzio-secret-set",
3391
- config: () => ({ define: getViteDefine(secretSet, options) })
944
+ on: (event, listener) => add(event, listener, false),
945
+ once: (event, listener) => add(event, listener, true),
946
+ off(event, listener) {
947
+ listeners.get(event)?.delete(listener);
948
+ },
949
+ emit(event, ...[payload]) {
950
+ const map = listeners.get(event);
951
+ if (!map)
952
+ return;
953
+ const errors = [];
954
+ for (const [listener, once] of [...map]) {
955
+ if (!map.has(listener))
956
+ continue;
957
+ if (once)
958
+ map.delete(listener);
959
+ try {
960
+ listener(payload);
961
+ } catch (error) {
962
+ errors.push(error);
963
+ }
964
+ }
965
+ if (errors.length === 1)
966
+ throw errors[0];
967
+ if (errors.length > 1) {
968
+ throw new AggregateError(errors, `${errors.length} listeners threw for "${String(event)}"`);
969
+ }
970
+ },
971
+ clear(event) {
972
+ if (event === undefined)
973
+ listeners.clear();
974
+ else
975
+ listeners.delete(event);
976
+ },
977
+ listenerCount: (event) => listeners.get(event)?.size ?? 0
3392
978
  };
3393
979
  }
3394
-
3395
- // src/secrets/index.ts
3396
- function getSecretEnvironment() {
3397
- return resolveSecretEnvironment(undefined, getProcessEnvironment());
3398
- }
3399
- async function defineSecretSet2(keys, options) {
3400
- let loader;
3401
- if (!options.loader) {
3402
- validateInfisicalOrganizationId(options.organizationId);
3403
- loader = createDefaultInfisicalLoader(options);
3404
- } else {
3405
- loader = options.loader;
3406
- }
3407
- return defineSecretSet(keys, { ...options, loader }, getProcessEnvironment());
3408
- }
3409
- function createDefaultInfisicalLoader(options) {
3410
- return (context) => loadInfisicalSecrets({
3411
- ...context,
3412
- projectId: options.projectId,
3413
- organizationId: options.organizationId,
3414
- siteUrl: options.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
3415
- secretPath: options.secretPath,
3416
- auth: options.auth,
3417
- clientIdEnvKey: options.clientIdEnvKey ?? "INFISICAL_CLIENT_ID",
3418
- clientSecretEnvKey: options.clientSecretEnvKey ?? "INFISICAL_CLIENT_SECRET"
3419
- });
3420
- }
3421
980
  export {
3422
- ActionError,
3423
- ApiError,
3424
- ConfigError,
3425
- DEFAULT_INFISICAL_SITE_URL,
3426
- HttpResponseError,
981
+ InvariantError2 as InvariantError,
3427
982
  METRICS_SERIES_PALETTE_SIZE,
3428
- NetworkError,
3429
- PriorityQueue,
3430
- RequestAbortedError,
3431
- RequestTimeoutError,
3432
- RequestValidationError,
3433
- ResponseValidationError,
3434
- SecretLoadError,
983
+ PromiseTimeoutError2 as PromiseTimeoutError,
3435
984
  SimpleStateMachine,
3436
- TimeoutError,
3437
- backgroundPromise,
3438
- backgroundPromiseSync,
985
+ addPeriod,
986
+ anySignal2 as anySignal,
987
+ assertNever,
988
+ backgroundPromise2 as backgroundPromise,
989
+ backgroundPromiseSync2 as backgroundPromiseSync,
990
+ buildMailto2 as buildMailto,
991
+ buildQueryString2 as buildQueryString,
992
+ buildUrl2 as buildUrl,
3439
993
  cacheFunction,
3440
- chunkArray,
3441
- cloudflareWorkerEnvLoader,
3442
- colorize,
3443
- compact,
3444
- createApiClient,
3445
- createCoolLogger,
994
+ chunkArray2 as chunkArray,
995
+ clamp,
996
+ compact2 as compact,
997
+ contrastRatio,
998
+ contrastText,
999
+ countBy2 as countBy,
1000
+ createCache2 as createCache,
1001
+ createEmitter,
1002
+ createHeartbeat,
1003
+ createLatestGuard2 as createLatestGuard,
3446
1004
  createResolveColorsByKeys,
3447
- createZId,
3448
- defineAction,
3449
- defineSecretSet2 as defineSecretSet,
3450
- extractNumber,
3451
- fetchInfisicalSecrets,
3452
- findArrayDifferenceByKey,
1005
+ createStateMachine,
1006
+ debounce,
1007
+ ensureLeadingSlash2 as ensureLeadingSlash,
1008
+ err2 as err,
1009
+ escapeRegExp2 as escapeRegExp,
1010
+ exponentialBackoff2 as exponentialBackoff,
1011
+ extractNumber2 as extractNumber,
1012
+ findArrayDifferenceByKey2 as findArrayDifferenceByKey,
1013
+ foldForSearch2 as foldForSearch,
3453
1014
  formatPeriodLabel,
3454
- generateId,
3455
- getDomainFaviconUrl,
3456
- getDomainSitemap,
1015
+ generateId2 as generateId,
1016
+ getAtPath,
1017
+ getDomainFaviconUrl2 as getDomainFaviconUrl,
1018
+ getErrorMessage2 as getErrorMessage,
3457
1019
  getHeatmapCellStyle,
1020
+ getPeriod,
3458
1021
  getPreviousReportPeriod,
3459
- getSecretEnvironment,
3460
- getUniqueValues,
3461
- getUniqueValuesByKey,
3462
- getViteDefine,
3463
- groupBy,
1022
+ getUniqueValues2 as getUniqueValues,
1023
+ getUniqueValuesByKey2 as getUniqueValuesByKey,
1024
+ groupBy2 as groupBy,
1025
+ hasKey2 as hasKey,
3464
1026
  hashStringToColorIndex,
3465
- infisicalLoader,
3466
- isActionError,
3467
- isApiError,
3468
- isConfigError,
3469
- isFailure,
3470
- isHttpResponseError,
3471
- isNetworkError,
3472
- isNonRetryableApiError,
3473
- isRequestAbortedError,
3474
- isRequestTimeoutError,
3475
- isRequestValidationError,
3476
- isResponseValidationError,
3477
- isSuccess,
3478
- jsonToZodSchemaMapper,
3479
- jwtSign,
3480
- jwtVerify,
3481
- keyBy,
1027
+ initials2 as initials,
1028
+ invariant2 as invariant,
1029
+ isBoolean2 as isBoolean,
1030
+ isDefined2 as isDefined,
1031
+ isFailure2 as isFailure,
1032
+ isFunction2 as isFunction,
1033
+ isNonEmpty2 as isNonEmpty,
1034
+ isNumber2 as isNumber,
1035
+ isOneOf2 as isOneOf,
1036
+ isPlainObject2 as isPlainObject,
1037
+ isRecord2 as isRecord,
1038
+ isString2 as isString,
1039
+ isSuccess2 as isSuccess,
1040
+ isTruthy2 as isTruthy,
1041
+ keyBy2 as keyBy,
1042
+ keyedDebounce,
1043
+ lerp,
1044
+ linearRegression,
3482
1045
  linearRegressionTrend,
3483
- loadInfisicalSecrets,
3484
- logBanner,
3485
- logOperationSummary,
3486
- normalizeSingleOrArray,
3487
- partition,
3488
- pickItemsInArray,
3489
- processEnvLoader,
3490
- promiseTimeout,
1046
+ mapValues,
1047
+ matchesSearch2 as matchesSearch,
1048
+ mixColors,
1049
+ must2 as must,
1050
+ nonEmpty2 as nonEmpty,
1051
+ normalize,
1052
+ normalizeDomain2 as normalizeDomain,
1053
+ normalizeWhitespace2 as normalizeWhitespace,
1054
+ ok2 as ok,
1055
+ omit,
1056
+ once,
1057
+ pMap2 as pMap,
1058
+ parseColor,
1059
+ parseRetryAfter2 as parseRetryAfter,
1060
+ partition2 as partition,
1061
+ pick,
1062
+ pickFromPalette,
1063
+ pickItemsInArray2 as pickItemsInArray,
1064
+ promiseTimeout2 as promiseTimeout,
1065
+ raceAbort2 as raceAbort,
1066
+ range2 as range,
1067
+ recordFromKeys,
1068
+ relativeLuminance,
1069
+ replacePathParams2 as replacePathParams,
3491
1070
  resolveSeriesColorMap,
1071
+ retry2 as retry,
1072
+ safeJsonParse2 as safeJsonParse,
3492
1073
  seriesColorKey,
3493
- slugify,
3494
- terminalColors,
3495
- toGteLteFilter,
3496
- tryCatch,
1074
+ setAtPath,
1075
+ sha256Id2 as sha256Id,
1076
+ shade,
1077
+ slugify2 as slugify,
1078
+ sortBy2 as sortBy,
1079
+ stableStringify2 as stableStringify,
1080
+ startTimer2 as startTimer,
1081
+ sumBy2 as sumBy,
1082
+ throttle,
1083
+ timed2 as timed,
1084
+ timeoutSignal2 as timeoutSignal,
1085
+ toArray2 as toArray,
1086
+ toError2 as toError,
1087
+ toHex,
1088
+ toRgbString,
1089
+ trimTrailingSlash2 as trimTrailingSlash,
1090
+ truncate2 as truncate,
1091
+ tryCatch2 as tryCatch,
1092
+ typedEntries,
1093
+ typedFromEntries,
1094
+ typedKeys,
3497
1095
  typedSwitch,
3498
- unwrapResult,
3499
- viteSecretSetPlugin,
1096
+ unwrapResult2 as unwrapResult,
3500
1097
  withAlpha,
3501
- zSingleOrArray,
3502
- zodMinMaxFilter,
3503
- zodToExample,
3504
- zodToTypeString
1098
+ withTimeout2 as withTimeout
3505
1099
  };