hanzio 1.0.10 → 1.2.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 (71) hide show
  1. package/README.md +70 -4
  2. package/dist/api-wrapper/actions-impl.d.ts +4 -0
  3. package/dist/api-wrapper/builders.d.ts +23 -0
  4. package/dist/api-wrapper/create-client.d.ts +6 -0
  5. package/dist/api-wrapper/error-reporting.d.ts +7 -0
  6. package/dist/api-wrapper/errors.d.ts +64 -0
  7. package/dist/api-wrapper/headers.d.ts +10 -0
  8. package/dist/api-wrapper/index.d.ts +5 -84
  9. package/dist/api-wrapper/redaction.d.ts +2 -0
  10. package/dist/api-wrapper/shared.d.ts +16 -0
  11. package/dist/api-wrapper/size.d.ts +1 -0
  12. package/dist/api-wrapper/test-helpers.d.ts +6 -0
  13. package/dist/api-wrapper/transport.d.ts +55 -0
  14. package/dist/api-wrapper/types.d.ts +154 -0
  15. package/dist/api-wrapper/zod-issues.d.ts +17 -0
  16. package/dist/cache/index.d.ts +26 -0
  17. package/dist/secrets/core.d.ts +47 -0
  18. package/dist/secrets/deadline.d.ts +6 -0
  19. package/dist/secrets/environment.d.ts +9 -0
  20. package/dist/secrets/errors.d.ts +11 -0
  21. package/dist/secrets/index.d.ts +14 -23
  22. package/dist/secrets/infisical-cli.d.ts +6 -0
  23. package/dist/secrets/infisical-config.d.ts +17 -0
  24. package/dist/secrets/infisical-session.d.ts +4 -0
  25. package/dist/secrets/infisical.d.ts +14 -2
  26. package/dist/secrets/loaders.d.ts +9 -0
  27. package/dist/secrets/vite.d.ts +7 -3
  28. package/dist/secrets/worker-loader.d.ts +9 -0
  29. package/dist/secrets/worker.d.ts +3 -0
  30. package/dist/src/api-wrapper/index.js +1059 -177
  31. package/dist/src/index.js +2807 -1397
  32. package/dist/src/jwt/index.js +2 -2
  33. package/dist/src/p-queue/index.js +3 -3
  34. package/dist/src/secrets/index.js +1610 -407
  35. package/dist/src/secrets/infisical.js +1352 -214
  36. package/dist/src/secrets/loaders.js +1529 -0
  37. package/dist/src/secrets/vite.js +33 -11
  38. package/dist/src/secrets/worker.js +275 -0
  39. package/dist/src/sitemap/index.js +1 -1
  40. package/dist/src/zod/index.js +7 -7
  41. package/dist/typedswitch/index.d.ts +9 -5
  42. package/package.json +12 -3
  43. package/src/api-wrapper/actions-impl.ts +100 -0
  44. package/src/api-wrapper/builders.ts +200 -0
  45. package/src/api-wrapper/create-client.ts +393 -0
  46. package/src/api-wrapper/error-reporting.ts +31 -0
  47. package/src/api-wrapper/errors.ts +231 -0
  48. package/src/api-wrapper/headers.ts +79 -0
  49. package/src/api-wrapper/index.ts +5 -492
  50. package/src/api-wrapper/redaction.ts +62 -0
  51. package/src/api-wrapper/shared.ts +25 -0
  52. package/src/api-wrapper/size.ts +19 -0
  53. package/src/api-wrapper/test-helpers.ts +25 -0
  54. package/src/api-wrapper/transport.ts +350 -0
  55. package/src/api-wrapper/types.ts +259 -0
  56. package/src/api-wrapper/zod-issues.ts +157 -0
  57. package/src/cache/index.ts +228 -61
  58. package/src/secrets/core.ts +348 -0
  59. package/src/secrets/deadline.ts +69 -0
  60. package/src/secrets/environment.ts +69 -0
  61. package/src/secrets/errors.ts +29 -0
  62. package/src/secrets/index.ts +81 -140
  63. package/src/secrets/infisical-cli.ts +99 -0
  64. package/src/secrets/infisical-config.ts +70 -0
  65. package/src/secrets/infisical-session.ts +42 -0
  66. package/src/secrets/infisical.ts +219 -35
  67. package/src/secrets/loaders.ts +43 -0
  68. package/src/secrets/vite.ts +44 -15
  69. package/src/secrets/worker-loader.ts +56 -0
  70. package/src/secrets/worker.ts +4 -0
  71. package/src/typedswitch/index.ts +80 -69
package/dist/src/index.js CHANGED
@@ -1,3 +1,728 @@
1
+ // src/typedswitch/index.ts
2
+ function typedSwitch(value, keyOrCases, casesOrDefault, defaultCase) {
3
+ let key;
4
+ let cases;
5
+ let defaultHandler;
6
+ let discriminantKey;
7
+ if (typeof value === "string") {
8
+ key = value;
9
+ cases = keyOrCases;
10
+ defaultHandler = casesOrDefault;
11
+ } else {
12
+ if (typeof keyOrCases !== "string") {
13
+ throw new Error("Invalid typedSwitch call: object mode requires a string discriminant key");
14
+ }
15
+ discriminantKey = keyOrCases;
16
+ const discriminantValue = value[discriminantKey];
17
+ if (typeof discriminantValue !== "string") {
18
+ throw new Error(`Invalid discriminant value for key "${discriminantKey}": expected string, received ${typeof discriminantValue}`);
19
+ }
20
+ key = discriminantValue;
21
+ cases = casesOrDefault;
22
+ defaultHandler = defaultCase;
23
+ }
24
+ const handler = Object.hasOwn(cases, key) ? cases[key] : undefined;
25
+ if (handler) {
26
+ return handler(value);
27
+ }
28
+ if (defaultHandler) {
29
+ return defaultHandler(value);
30
+ }
31
+ const availableCases = Object.keys(cases).join(", ");
32
+ const availableCasesSuffix = availableCases.length > 0 ? ` Available cases: ${availableCases}` : " Available cases: (none)";
33
+ const contextSuffix = discriminantKey ? ` (discriminant key: "${discriminantKey}")` : "";
34
+ throw new Error(`Unhandled case: ${key}${contextSuffix}.${availableCasesSuffix}`);
35
+ }
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
+ }
214
+ }
215
+ // src/cache/index.ts
216
+ function cacheFunction(options) {
217
+ const {
218
+ name,
219
+ fn,
220
+ cacheTimeMs = 10 * 60 * 1000,
221
+ refreshInBackground = false,
222
+ cacheKeyArgs,
223
+ cacheKeyFn,
224
+ onBackgroundRefreshError,
225
+ store,
226
+ onStoreError
227
+ } = options;
228
+ const externalStore = store;
229
+ const cache = new Map;
230
+ const pendingPromises = new Map;
231
+ const entryLifetimeMs = refreshInBackground ? cacheTimeMs * 2 : cacheTimeMs;
232
+ let cleanupInterval;
233
+ const stopCleanup = () => {
234
+ if (cleanupInterval !== undefined) {
235
+ clearInterval(cleanupInterval);
236
+ cleanupInterval = undefined;
237
+ }
238
+ };
239
+ const ensureCleanup = () => {
240
+ if (cleanupInterval !== undefined) {
241
+ return;
242
+ }
243
+ cleanupInterval = setInterval(() => {
244
+ const now = Date.now();
245
+ for (const [key, entry] of cache.entries()) {
246
+ if (now - entry.timestamp >= entryLifetimeMs) {
247
+ cache.delete(key);
248
+ }
249
+ }
250
+ if (cache.size === 0) {
251
+ stopCleanup();
252
+ }
253
+ }, Math.min(cacheTimeMs / 2, 5 * 60 * 1000));
254
+ cleanupInterval.unref?.();
255
+ };
256
+ const buildKey = (args) => {
257
+ const keyArgs = cacheKeyFn ? cacheKeyFn(...args) : cacheKeyArgs ? cacheKeyArgs.map((i) => args[i]) : args;
258
+ return `${name}:${JSON.stringify(keyArgs)}`;
259
+ };
260
+ const saveEntry = (key, value) => {
261
+ cache.set(key, { value, timestamp: Date.now() });
262
+ ensureCleanup();
263
+ };
264
+ const fetchAndPopulate = (key, args) => {
265
+ const result = fn(...args);
266
+ if (!isPromiseLike(result)) {
267
+ saveEntry(key, result);
268
+ return result;
269
+ }
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
+ };
281
+ const startBackgroundRefresh = (key, args, staleValue) => {
282
+ const result = fn(...args);
283
+ if (!isPromiseLike(result)) {
284
+ saveEntry(key, result);
285
+ return;
286
+ }
287
+ const promise = result.then((value) => {
288
+ saveEntry(key, value);
289
+ pendingPromises.delete(key);
290
+ return value;
291
+ }).catch((error) => {
292
+ pendingPromises.delete(key);
293
+ onBackgroundRefreshError?.(error, { name });
294
+ return staleValue;
295
+ });
296
+ pendingPromises.set(key, promise);
297
+ };
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 });
303
+ }
304
+ };
305
+ const callWithStore = (cacheStore, args) => {
306
+ const key = buildKey(args);
307
+ const pending = pendingPromises.get(key);
308
+ if (pending && !refreshInBackground) {
309
+ return pending;
310
+ }
311
+ return (async () => {
312
+ let entry;
313
+ try {
314
+ entry = await cacheStore.get(key);
315
+ } catch (error) {
316
+ onStoreError?.(error, { name });
317
+ }
318
+ 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;
348
+ }
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;
359
+ })();
360
+ };
361
+ const wrappedFn = (...args) => {
362
+ if (externalStore) {
363
+ return callWithStore(externalStore, args);
364
+ }
365
+ const key = buildKey(args);
366
+ const cached = cache.get(key);
367
+ 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;
383
+ }
384
+ return fetchAndPopulate(key, args);
385
+ };
386
+ wrappedFn.clearCache = () => {
387
+ cache.clear();
388
+ pendingPromises.clear();
389
+ stopCleanup();
390
+ if (externalStore?.clear) {
391
+ externalStore.clear().catch((error) => {
392
+ onStoreError?.(error, { name });
393
+ });
394
+ }
395
+ };
396
+ return wrappedFn;
397
+ }
398
+ function isPromiseLike(value) {
399
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
400
+ }
401
+ // src/state/index.ts
402
+ class SimpleStateMachine {
403
+ config;
404
+ item;
405
+ stateKey;
406
+ constructor(config, item, stateKey) {
407
+ this.config = config;
408
+ this.item = item;
409
+ this.stateKey = stateKey;
410
+ }
411
+ getCurrentStatus() {
412
+ return this.item[this.stateKey];
413
+ }
414
+ async getAvailableActions() {
415
+ 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;
424
+ }
425
+ async getPossibleTransitions() {
426
+ const possibleTransitions = this.config.stateTransitions[this.getCurrentStatus()] || [];
427
+ const actionsMap = this.config.stateActionMap[this.getCurrentStatus()] || {};
428
+ const result = {};
429
+ for (const targetState of possibleTransitions) {
430
+ const actionsToTarget = Object.entries(actionsMap).filter(([, t]) => t === targetState);
431
+ 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;
446
+ }
447
+ result[targetState] = anyOk ? true : lastError;
448
+ }
449
+ return result;
450
+ }
451
+ async executeAction(action, data) {
452
+ const actionsMap = this.config.stateActionMap[this.getCurrentStatus()];
453
+ if (!actionsMap) {
454
+ return {
455
+ success: false,
456
+ error: `No actions defined for state '${this.getCurrentStatus()}'`
457
+ };
458
+ }
459
+ const targetState = actionsMap[action];
460
+ if (!targetState) {
461
+ return {
462
+ success: false,
463
+ error: `Action '${action}' not allowed in state '${this.getCurrentStatus()}'`
464
+ };
465
+ }
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
+ }
484
+ }
485
+ return {
486
+ success: true,
487
+ newState: targetState
488
+ };
489
+ }
490
+ return {
491
+ success: false,
492
+ error: validationResult
493
+ };
494
+ }
495
+ async validateTransition(toState, options) {
496
+ const action = options?.action;
497
+ if (this.getCurrentStatus() === toState)
498
+ return true;
499
+ const allowedTransitions = this.config.stateTransitions[this.getCurrentStatus()] || [];
500
+ if (!allowedTransitions.includes(toState)) {
501
+ return `Invalid transition from '${this.getCurrentStatus()}' to '${toState}'`;
502
+ }
503
+ if (this.config.globalValidator) {
504
+ const globalResult = await this.config.globalValidator(this.item, this.getCurrentStatus(), toState, action);
505
+ if (globalResult !== true) {
506
+ return globalResult;
507
+ }
508
+ }
509
+ const stateValidator = this.config.validators?.[toState];
510
+ if (stateValidator) {
511
+ return await stateValidator(this.item, this.getCurrentStatus(), toState, action);
512
+ }
513
+ return true;
514
+ }
515
+ getStates() {
516
+ return Object.keys(this.config.stateTransitions);
517
+ }
518
+ getInitialState() {
519
+ return this.getStates()[0];
520
+ }
521
+ }
522
+ // src/math/linearRegressionTrend.ts
523
+ function linearRegressionTrend(values) {
524
+ const n = values.length;
525
+ if (n < 2)
526
+ return [];
527
+ for (const v of values) {
528
+ if (!Number.isFinite(v))
529
+ return [];
530
+ }
531
+ let sumX = 0;
532
+ let sumY = 0;
533
+ let sumXY = 0;
534
+ let sumX2 = 0;
535
+ for (let i = 0;i < n; i++) {
536
+ const x = i;
537
+ const y = values[i];
538
+ sumX += x;
539
+ sumY += y;
540
+ sumXY += x * y;
541
+ sumX2 += x * x;
542
+ }
543
+ const denom = n * sumX2 - sumX * sumX;
544
+ if (denom === 0)
545
+ return [];
546
+ const slope = (n * sumXY - sumX * sumY) / denom;
547
+ const intercept = (sumY - slope * sumX) / n;
548
+ const out = [];
549
+ for (let i = 0;i < n; i++) {
550
+ out.push(intercept + slope * i);
551
+ }
552
+ return out;
553
+ }
554
+ // src/math/metricsSeriesColors.ts
555
+ var METRICS_SERIES_PALETTE_SIZE = 10;
556
+ function hashStringToColorIndex(str) {
557
+ let hash = 2166136261;
558
+ for (let i = 0;i < str.length; i++) {
559
+ hash ^= str.charCodeAt(i);
560
+ hash = Math.imul(hash, 16777619);
561
+ }
562
+ return Math.abs(hash) % METRICS_SERIES_PALETTE_SIZE;
563
+ }
564
+ function circularHueDistance(a, b, paletteSize) {
565
+ const diff = Math.abs(a - b);
566
+ return Math.min(diff, paletteSize - diff);
567
+ }
568
+ function pickHueMaxMinDistance(candidates, used, paletteSize) {
569
+ let best = candidates[0] ?? 0;
570
+ let bestScore = -1;
571
+ for (const cand of candidates) {
572
+ let minD = paletteSize;
573
+ for (const u of used) {
574
+ minD = Math.min(minD, circularHueDistance(cand, u, paletteSize));
575
+ }
576
+ if (minD > bestScore || minD === bestScore && cand < best) {
577
+ bestScore = minD;
578
+ best = cand;
579
+ }
580
+ }
581
+ return best;
582
+ }
583
+ function seriesColorKey(label, key) {
584
+ const fromLabel = label?.trim();
585
+ if (fromLabel)
586
+ return fromLabel;
587
+ return key.trim() || key;
588
+ }
589
+ function resolveSeriesColorMap(keys) {
590
+ const result = new Map;
591
+ const uniqueKeys = [...new Set(keys)];
592
+ if (uniqueKeys.length === 0)
593
+ return result;
594
+ const paletteSize = METRICS_SERIES_PALETTE_SIZE;
595
+ if (uniqueKeys.length === 1) {
596
+ const only = uniqueKeys[0];
597
+ result.set(only, hashStringToColorIndex(only));
598
+ return result;
599
+ }
600
+ const usedIndices = new Set;
601
+ const entries = uniqueKeys.map((key) => ({
602
+ key,
603
+ preferred: hashStringToColorIndex(key)
604
+ }));
605
+ for (const entry of entries) {
606
+ if (!usedIndices.has(entry.preferred)) {
607
+ result.set(entry.key, entry.preferred);
608
+ usedIndices.add(entry.preferred);
609
+ }
610
+ }
611
+ const unassigned = entries.filter((e) => !result.has(e.key));
612
+ for (const entry of unassigned) {
613
+ const unused = [];
614
+ for (let i = 0;i < paletteSize; i++) {
615
+ if (!usedIndices.has(i))
616
+ unused.push(i);
617
+ }
618
+ const candidates = unused.length > 0 ? unused : Array.from({ length: paletteSize }, (_, i) => i);
619
+ const idx = pickHueMaxMinDistance(candidates, usedIndices, paletteSize);
620
+ result.set(entry.key, idx);
621
+ usedIndices.add(idx);
622
+ }
623
+ return result;
624
+ }
625
+ function createResolveColorsByKeys(keys, getSeriesColor, getSeriesHoverColor) {
626
+ const colorMap = resolveSeriesColorMap(keys);
627
+ return {
628
+ getColor: (key) => getSeriesColor(colorMap.get(key) ?? hashStringToColorIndex(key)),
629
+ getHoverColor: (key) => getSeriesHoverColor(colorMap.get(key) ?? hashStringToColorIndex(key))
630
+ };
631
+ }
632
+ function clamp(value, min, max) {
633
+ return Math.min(max, Math.max(min, value));
634
+ }
635
+ function withAlpha(color, alpha) {
636
+ 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})`);
649
+ }
650
+ if (trimmed.startsWith("rgba(")) {
651
+ return trimmed.replace(/,\s*[\d.]+\)$/, `, ${alpha})`);
652
+ }
653
+ return trimmed;
654
+ }
655
+ function getHeatmapCellStyle(params) {
656
+ const {
657
+ value,
658
+ min,
659
+ max,
660
+ baseColor,
661
+ neutralColor,
662
+ minAlpha = 0.08,
663
+ maxAlpha = 0.78
664
+ } = params;
665
+ if (value === null || value === undefined || Number.isNaN(value)) {
666
+ return {};
667
+ }
668
+ if (!Number.isFinite(min) || !Number.isFinite(max)) {
669
+ return {};
670
+ }
671
+ if (max <= min) {
672
+ return {
673
+ backgroundColor: withAlpha(neutralColor ?? baseColor, 0.32)
674
+ };
675
+ }
676
+ const normalized = clamp((value - min) / (max - min), 0, 1);
677
+ const alpha = minAlpha + (maxAlpha - minAlpha) * normalized;
678
+ return {
679
+ backgroundColor: withAlpha(baseColor, alpha)
680
+ };
681
+ }
682
+ // 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
+ }
710
+ }
711
+ }
712
+ function formatPeriodLabel(start, end, includeDay = false) {
713
+ const s = start instanceof Date ? start : new Date(start);
714
+ const e = end instanceof Date ? end : new Date(end);
715
+ const opts = {
716
+ year: "numeric",
717
+ 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
+ }
1
726
  // src/zod/index.ts
2
727
  import { z as z3 } from "zod";
3
728
 
@@ -198,252 +923,1115 @@ function normalizeSingleOrArray(value) {
198
923
  return [...value];
199
924
  return [value];
200
925
  }
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) {
936
+ 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;
946
+ }
947
+ return result;
948
+ }
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;
961
+ }
962
+ function hasJsonContentType(headers) {
963
+ const ct = getHeader(headers, "content-type") ?? "";
964
+ return ct.includes("application/json");
965
+ }
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");
975
+ }
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
+ }
201
985
 
202
- // src/api-wrapper/index.ts
986
+ // src/api-wrapper/zod-issues.ts
203
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
+ });
1022
+ }
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
+ }
1041
+ }
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)}]`;
1058
+ }
1059
+ }
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
+ }
204
1168
 
205
- class ResponseValidationError extends Error {
1169
+ class ResponseValidationError extends ApiError {
206
1170
  zodError;
207
1171
  rawResponse;
1172
+ issues;
208
1173
  validationIssues;
209
- constructor(zodError, rawResponse) {
210
- const issues = zodError.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
211
- super(`Response validation failed: ${issues}`);
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 });
212
1179
  this.name = "ResponseValidationError";
213
1180
  this.zodError = zodError;
214
1181
  this.rawResponse = rawResponse;
215
- this.validationIssues = issues;
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
+ };
216
1192
  }
217
1193
  }
218
1194
 
219
- class HttpResponseError extends Error {
220
- status;
221
- body;
222
- constructor(status, body) {
223
- super(`HTTP error: ${status} - ${body}`);
224
- this.name = "HttpResponseError";
225
- this.status = status;
226
- this.body = body;
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
+ };
227
1220
  }
228
1221
  }
229
- function createApiClient(apiConfig) {
230
- const request = async (endpointKey, input = {}) => {
231
- const endpoint = apiConfig.endpoints[endpointKey];
232
- if (!endpoint) {
233
- throw new Error(`Unknown API endpoint: ${String(endpointKey)}`);
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");
234
1298
  }
235
- const { finalUrl, fullUrl } = buildUrl(endpoint, input.reqParams, input.reqQuery, apiConfig.baseApiUrls, apiConfig.defaultBaseApiUrl, input.url);
236
- const endpointName = apiConfig.name ? `${apiConfig.name}.${String(endpointKey)}` : String(endpointKey);
237
- const startedAt = Date.now();
238
- let retryCount = 0;
239
- let httpStatus = 0;
240
- apiConfig.onRequestStart?.({ endpoint: endpointName, url: fullUrl });
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) {
241
1330
  try {
242
- const config = {
243
- method: endpoint.method,
244
- url: finalUrl,
245
- queryParams: buildQueryParams(endpoint, input.reqQuery),
246
- body: buildRequestBody(endpoint, input.reqBody),
247
- headers: buildHeaders(endpoint, input.reqHeaders, apiConfig.defaultHeaders ?? {}),
248
- doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
249
- timeoutMs: apiConfig.timeoutMs,
250
- fetchFn: apiConfig.fetch ?? fetch
251
- };
252
- const result = await makeRequestWithRetry(config, apiConfig.retries ?? 3, apiConfig.retryDelayMs ?? 300, (response) => validateAndTransformResponse(endpoint, response), apiConfig.shouldRetry ?? defaultShouldRetry, apiConfig.logger);
253
- retryCount = result.retryCount;
254
- httpStatus = result.httpStatus;
255
- const responseTimeMs = Date.now() - startedAt;
256
- const responseSizeMb = calculateSizeInMb(result.validatedData);
257
- const requestSizeMb = calculateSizeInMb({
258
- method: config.method,
259
- url: fullUrl,
260
- body: config.body,
261
- headers: config.headers
262
- });
263
- apiConfig.onRequestEnd?.({
264
- endpoint: endpointName,
265
- url: fullUrl,
266
- status: httpStatus,
267
- retryCount,
268
- responseTimeMs
269
- });
270
- return {
271
- data: result.validatedData,
272
- requestSizeMb,
273
- responseSizeMb,
274
- requestSizeTotalMb: requestSizeMb + responseSizeMb,
275
- responseTimeMs,
276
- httpStatus,
277
- retryCount
278
- };
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;
279
1424
  } catch (error) {
280
- apiConfig.logger?.error?.(error);
1425
+ store.delete(key);
281
1426
  throw error;
282
1427
  }
283
1428
  };
284
- return { request };
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
+ };
285
1636
  }
286
- var createApiWrapper = createApiClient;
287
- async function validateResponse(schema, data) {
288
- try {
289
- return await schema.parseAsync(data);
290
- } catch (error) {
291
- if (error instanceof ZodError) {
292
- throw new ResponseValidationError(error, data);
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
+ }
293
1651
  }
294
- throw error;
295
1652
  }
1653
+ const delayMs = typeof retryDelay === "function" ? retryDelay(attempt) : retryDelay;
1654
+ return Math.min(delayMs, maxDelay);
296
1655
  }
297
- async function httpRequest(config) {
298
- const queryString = config.doNotEncodeQueryParams ? Object.entries(config.queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(config.queryParams).toString();
299
- const fullUrl = queryString ? `${config.url}?${queryString}` : config.url;
300
- const abortController = new AbortController;
301
- const timeout = config.timeoutMs === undefined ? undefined : setTimeout(() => abortController.abort(), config.timeoutMs);
302
- try {
303
- return await config.fetchFn(fullUrl, {
304
- method: config.method,
305
- headers: config.headers,
306
- body: buildFetchBody(config.body, config.headers),
307
- signal: abortController.signal
308
- });
309
- } finally {
310
- if (timeout)
311
- clearTimeout(timeout);
312
- }
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;
313
1664
  }
314
- async function makeRequestWithRetry(config, maxRetries, retryDelayMs, validateFn, shouldRetry, logger) {
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;
315
1682
  let retryCount = 0;
316
1683
  while (true) {
1684
+ let response;
317
1685
  try {
318
1686
  logger?.debug?.("API request", config);
319
- const response = await httpRequest(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
+ });
320
1696
  if (response.status >= 400) {
321
- if (retryCount < maxRetries && shouldRetry({ response, retryCount, maxRetries })) {
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
+ });
322
1717
  retryCount++;
323
- await delay(retryDelayMs);
1718
+ await delay(delayMs);
324
1719
  continue;
325
1720
  }
326
- throw new HttpResponseError(response.status, await response.text());
1721
+ const { body, headers: responseHeaders } = await readErrorResponse(response);
1722
+ throw new HttpResponseError(response.status, body, responseHeaders, ctx({ attempt: retryCount }));
327
1723
  }
328
1724
  return {
329
- validatedData: await validateFn(response),
1725
+ validatedData: await validateFn(response, retryCount),
330
1726
  retryCount,
331
1727
  httpStatus: response.status
332
1728
  };
333
1729
  } catch (error) {
334
- if (error instanceof HttpResponseError)
1730
+ if (isNonRetryableApiError(error)) {
335
1731
  throw error;
336
- if (retryCount >= maxRetries || !shouldRetry({ error, retryCount, maxRetries })) {
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)) {
337
1742
  throw error;
338
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
+ });
339
1755
  retryCount++;
340
- await delay(retryDelayMs);
1756
+ await delay(delayMs);
341
1757
  }
342
1758
  }
343
1759
  }
344
- async function validateAndTransformResponse(endpoint, response) {
1760
+ async function validateAndTransformResponse(endpoint, response, ctx) {
345
1761
  const responseHeaders = getHeadersAsObject(response.headers);
346
- const contentType = responseHeaders["content-type"] ?? "";
347
- const responseData = contentType.includes("application/json") ? await response.json() : await response.text();
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
+ }
348
1774
  const transformedData = endpoint.resFormatter ? endpoint.resFormatter(responseData, responseHeaders) : responseData;
349
- return validateResponse(endpoint.resSchema, transformedData);
1775
+ const data = await validateResponse(endpoint.resSchema, transformedData, ctx);
1776
+ return { data, responseSizeBytes };
350
1777
  }
351
- function buildUrl(endpoint, params, reqQuery, baseApiUrls, defaultBaseApiUrl, url) {
352
- if (url) {
353
- const finalUrl2 = replacePathParams(url, params);
354
- const queryString2 = new URLSearchParams(buildQueryParams(endpoint, reqQuery)).toString();
355
- return {
356
- finalUrl: finalUrl2,
357
- fullUrl: queryString2 ? `${finalUrl2}?${queryString2}` : finalUrl2
358
- };
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 ?? {} };
359
1798
  }
360
- const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
361
- if (!defaultBaseUrl) {
362
- throw new Error("At least one base API URL is required");
1799
+ if (first === undefined || first === null) {
1800
+ return { input: undefined, options: second ?? {} };
363
1801
  }
364
- const baseUrlKey = endpoint.baseApiUrl ?? defaultBaseUrl;
365
- const configBaseUrl = baseApiUrls[baseUrlKey];
366
- if (!configBaseUrl) {
367
- throw new Error(`Unknown base API URL: ${baseUrlKey}`);
1802
+ if (second !== undefined) {
1803
+ return { input: undefined, options: second };
368
1804
  }
369
- const baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
370
- const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
371
- const finalUrl = replacePathParams(`${baseUrl}${path}`, params);
372
- const queryString = new URLSearchParams(buildQueryParams(endpoint, reqQuery)).toString();
373
- return {
374
- finalUrl,
375
- fullUrl: queryString ? `${finalUrl}?${queryString}` : finalUrl
376
- };
377
- }
378
- function buildQueryParams(endpoint, query) {
379
- const defaultParams = endpoint.reqDefaultQueryParams ?? {};
380
- const parsedQuery = endpoint.reqQuerySchema ? endpoint.reqQuerySchema.parse(query) : query ?? {};
381
- const merged = { ...defaultParams, ...parsedQuery };
382
- return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
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
+ });
383
1816
  }
384
- function buildRequestBody(endpoint, body) {
385
- if (!endpoint.reqBodySchema)
386
- return body;
387
- const parsedBody = endpoint.reqBodySchema.parse(body);
388
- if (endpoint.reqBodyFormat !== "form-data") {
389
- return parsedBody;
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
+ }
390
1985
  }
391
- const formData = new FormData;
392
- for (const [key, value] of Object.entries(parsedBody)) {
393
- if (value == null)
394
- continue;
395
- formData.set(key, value instanceof Blob ? value : String(value));
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);
396
1990
  }
397
- return formData;
398
- }
399
- function buildHeaders(endpoint, headers, defaultHeaders) {
400
- const contentType = endpoint.reqBodyFormat === "json" ? "application/json" : undefined;
401
- const parsedHeaders = endpoint.reqHeadersSchema ? endpoint.reqHeadersSchema.parse(headers) : headers ?? {};
402
- return {
403
- ...defaultHeaders,
404
- ...contentType && { "Content-Type": contentType },
405
- ...endpoint.defaultHeaders,
406
- ...parsedHeaders
1991
+ const request = async (key, input, options) => {
1992
+ if (key in actions) {
1993
+ return dispatchAction(key, input, options);
1994
+ }
1995
+ return invokeEndpoint(key, input ?? {});
407
1996
  };
408
- }
409
- function buildFetchBody(body, headers) {
410
- if (body === undefined || body === null)
411
- return;
412
- if (body instanceof FormData || body instanceof Blob)
413
- return body;
414
- if (headers["Content-Type"] === "application/json")
415
- return JSON.stringify(body);
416
- return body;
417
- }
418
- function replacePathParams(path, params) {
419
- return path.replace(/:(\w+)/g, (_, key) => {
420
- const value = params?.[key];
421
- if (value === undefined) {
422
- throw new Error(`Missing required path parameter: ${key}`);
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;
423
2010
  }
424
- return encodeURIComponent(String(value));
425
- });
426
- }
427
- function getHeadersAsObject(headers) {
428
- const result = {};
429
- headers.forEach((value, key) => {
430
- result[key] = value;
431
2011
  });
432
- return result;
433
- }
434
- function calculateSizeInMb(data) {
435
- const sizeInBytes = new TextEncoder().encode(JSON.stringify(data)).length;
436
- return sizeInBytes / (1024 * 1024);
437
- }
438
- function defaultShouldRetry({ error, response }) {
439
- if (error)
440
- return true;
441
- return response ? response.status >= 500 : false;
442
- }
443
- function delay(ms) {
444
- return new Promise((resolve) => setTimeout(resolve, ms));
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;
445
2033
  }
446
-
2034
+ var createApiClient = (apiConfig) => createApiClientImpl(apiConfig);
447
2035
  // src/p-queue/index.ts
448
2036
  class TimeoutError extends Error {
449
2037
  constructor(message) {
@@ -474,7 +2062,7 @@ class PriorityQueue {
474
2062
  return this.#queue.filter((item) => Object.entries(options).every(([key, value]) => item[key] === value)).map((item) => item.run);
475
2063
  }
476
2064
  setPriority(id, priority) {
477
- const index = this.#queue.findIndex((item2) => item2.id === id);
2065
+ const index = this.#queue.findIndex((item) => item.id === id);
478
2066
  if (index === -1) {
479
2067
  throw new ReferenceError(`No promise function with the id "${id}" exists in the queue.`);
480
2068
  }
@@ -664,551 +2252,220 @@ class PQueue {
664
2252
  if (taskOptions.signal) {
665
2253
  queuedAbortHandler = () => {
666
2254
  if (didRun) {
667
- return;
668
- }
669
- this.#queue.remove?.(taskOptions.id);
670
- reject(taskOptions.signal?.reason);
671
- this.emit("next");
672
- this.#tryToStartAnother();
673
- };
674
- taskOptions.signal.addEventListener("abort", queuedAbortHandler, {
675
- once: true
676
- });
677
- }
678
- this.#queue.enqueue(run, taskOptions);
679
- this.emit("add");
680
- this.#processQueue();
681
- });
682
- }
683
- async addAll(functions, options) {
684
- return Promise.all(functions.map(async (function_) => this.add(function_, options)));
685
- }
686
- start() {
687
- if (!this.#isPaused) {
688
- return this;
689
- }
690
- this.#isPaused = false;
691
- this.#processQueue();
692
- return this;
693
- }
694
- pause() {
695
- this.#isPaused = true;
696
- }
697
- clear() {
698
- this.#queue = new this.#queueClass;
699
- this.#clearIntervalTimer();
700
- this.#updateRateLimitState();
701
- this.emit("empty");
702
- if (this.#pending === 0) {
703
- this.emit("idle");
704
- }
705
- this.emit("next");
706
- }
707
- async onEmpty() {
708
- if (this.#queue.size === 0) {
709
- return;
710
- }
711
- await this.#onEvent("empty");
712
- }
713
- async onIdle() {
714
- if (this.#queue.size === 0 && this.#pending === 0) {
715
- return;
716
- }
717
- await this.#onEvent("idle");
718
- }
719
- async onPendingZero() {
720
- if (this.#pending === 0) {
721
- return;
722
- }
723
- await this.#onEvent("pendingZero");
724
- }
725
- async onRateLimit() {
726
- if (this.isRateLimited) {
727
- return;
728
- }
729
- await this.#onEvent("rateLimit");
730
- }
731
- async onRateLimitCleared() {
732
- if (!this.isRateLimited) {
733
- return;
734
- }
735
- await this.#onEvent("rateLimitCleared");
736
- }
737
- onError() {
738
- return new Promise((_resolve, reject) => {
739
- const listener = (error) => {
740
- this.off("error", listener);
741
- reject(error);
742
- };
743
- this.on("error", listener);
744
- });
745
- }
746
- async onSizeLessThan(limit) {
747
- if (this.#queue.size < limit) {
748
- return;
749
- }
750
- await this.#onEvent("next", () => this.#queue.size < limit);
751
- }
752
- async#onEvent(event, filter) {
753
- return new Promise((resolve) => {
754
- const listener = () => {
755
- if (filter && !filter()) {
756
- return;
757
- }
758
- this.off(event, listener);
759
- resolve();
760
- };
761
- this.on(event, listener);
762
- });
763
- }
764
- async#withTimeoutAndAbort(operation, options, setAbortListener) {
765
- const races = [operation];
766
- let timeoutId;
767
- if (options.timeout) {
768
- races.push(new Promise((_resolve, reject) => {
769
- timeoutId = setTimeout(() => {
770
- reject(new TimeoutError(`Task timed out after ${options.timeout}ms`));
771
- }, options.timeout);
772
- }));
773
- }
774
- if (options.signal) {
775
- races.push(new Promise((_resolve, reject) => {
776
- const listener = () => {
777
- reject(options.signal?.reason);
2255
+ return;
2256
+ }
2257
+ this.#queue.remove?.(taskOptions.id);
2258
+ reject(taskOptions.signal?.reason);
2259
+ this.emit("next");
2260
+ this.#tryToStartAnother();
778
2261
  };
779
- setAbortListener(listener);
780
- options.signal?.addEventListener("abort", listener, { once: true });
781
- }));
782
- }
783
- try {
784
- return await Promise.race(races);
785
- } finally {
786
- if (timeoutId) {
787
- clearTimeout(timeoutId);
2262
+ taskOptions.signal.addEventListener("abort", queuedAbortHandler, {
2263
+ once: true
2264
+ });
788
2265
  }
789
- }
2266
+ this.#queue.enqueue(run, taskOptions);
2267
+ this.emit("add");
2268
+ this.#processQueue();
2269
+ });
790
2270
  }
791
- #next() {
792
- this.#pending--;
793
- if (this.#pending === 0) {
794
- this.emit("pendingZero");
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;
795
2277
  }
2278
+ this.#isPaused = false;
796
2279
  this.#processQueue();
797
- this.emit("next");
798
- }
799
- #processQueue() {
800
- while (this.#tryToStartAnother()) {}
801
- this.#updateRateLimitState();
2280
+ return this;
802
2281
  }
803
- #tryToStartAnother() {
804
- if (this.#queue.size === 0) {
805
- this.#clearIntervalTimer();
806
- this.emit("empty");
807
- if (this.#pending === 0) {
808
- this.emit("idle");
809
- }
810
- return false;
811
- }
812
- if (this.#isPaused || this.#pending >= this.#concurrency || !this.#doesIntervalAllowAnother()) {
813
- return false;
814
- }
815
- const job = this.#queue.dequeue();
816
- if (!job) {
817
- return false;
818
- }
819
- this.#consumeIntervalSlot();
820
- this.emit("active");
821
- job();
822
- return true;
2282
+ pause() {
2283
+ this.#isPaused = true;
823
2284
  }
824
- #doesIntervalAllowAnother() {
825
- if (this.#intervalCap === Number.POSITIVE_INFINITY || this.#interval === 0) {
826
- return true;
827
- }
828
- const now = Date.now();
829
- if (this.#strict) {
830
- this.#strictTimestamps = this.#strictTimestamps.filter((timestamp) => now - timestamp < this.#interval);
831
- if (this.#strictTimestamps.length < this.#intervalCap) {
832
- return true;
833
- }
834
- this.#scheduleIntervalResume(this.#interval - (now - this.#strictTimestamps[0]));
835
- return false;
836
- }
837
- if (this.#intervalEnd === 0 || now >= this.#intervalEnd) {
838
- this.#intervalCount = this.#carryoverIntervalCount ? this.#pending : 0;
839
- this.#intervalEnd = now + this.#interval;
840
- }
841
- if (this.#intervalCount < this.#intervalCap) {
842
- return true;
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");
843
2292
  }
844
- this.#scheduleIntervalResume(this.#intervalEnd - now);
845
- return false;
2293
+ this.emit("next");
846
2294
  }
847
- #consumeIntervalSlot() {
848
- if (this.#intervalCap === Number.POSITIVE_INFINITY || this.#interval === 0) {
849
- return;
850
- }
851
- if (this.#strict) {
852
- this.#strictTimestamps.push(Date.now());
2295
+ async onEmpty() {
2296
+ if (this.#queue.size === 0) {
853
2297
  return;
854
2298
  }
855
- this.#intervalCount++;
2299
+ await this.#onEvent("empty");
856
2300
  }
857
- #scheduleIntervalResume(delay2) {
858
- if (this.#intervalTimer) {
2301
+ async onIdle() {
2302
+ if (this.#queue.size === 0 && this.#pending === 0) {
859
2303
  return;
860
2304
  }
861
- this.#intervalTimer = setTimeout(() => {
862
- this.#intervalTimer = undefined;
863
- this.#processQueue();
864
- }, Math.max(0, delay2));
865
- }
866
- #clearIntervalTimer() {
867
- if (this.#intervalTimer) {
868
- clearTimeout(this.#intervalTimer);
869
- this.#intervalTimer = undefined;
870
- }
871
- }
872
- #updateRateLimitState() {
873
- const previous = this.#rateLimited;
874
- const isRateLimited = this.#queue.size > 0 && this.#intervalCap !== Number.POSITIVE_INFINITY && this.#interval > 0 && !this.#doesIntervalAllowAnother();
875
- this.#rateLimited = isRateLimited;
876
- if (previous !== isRateLimited) {
877
- this.emit(isRateLimited ? "rateLimit" : "rateLimitCleared");
878
- }
879
- }
880
- }
881
-
882
- // src/jwt/index.ts
883
- var BYTE_CHUNK_SIZE = 32768;
884
- var textEncoder = new TextEncoder;
885
- var textDecoder = new TextDecoder;
886
- async function jwtSign(payload, secret) {
887
- const header = encodeJson({ alg: "HS256" });
888
- const body = encodeJson({
889
- ...payload,
890
- iat: Math.floor(Date.now() / 1000)
891
- });
892
- const data = `${header}.${body}`;
893
- const signature = await sign(data, secret);
894
- return `${data}.${encodeBase64Url(signature)}`;
895
- }
896
- async function jwtVerify(token, secret) {
897
- try {
898
- const parts = token.split(".");
899
- if (parts.length !== 3)
900
- return null;
901
- const [encodedHeader, encodedPayload, encodedSignature] = parts;
902
- const header = decodeJson(encodedHeader);
903
- if (!isJwtHeader(header))
904
- return null;
905
- const data = `${encodedHeader}.${encodedPayload}`;
906
- const signature = decodeBase64Url(encodedSignature);
907
- const isValid = await verify(data, signature, secret);
908
- if (!isValid)
909
- return null;
910
- const payload = decodeJson(encodedPayload);
911
- if (!isJwtPayload(payload) || !isTimeValid(payload))
912
- return null;
913
- return payload;
914
- } catch (_error) {
915
- return null;
916
- }
917
- }
918
- async function sign(data, secret) {
919
- const key = await getKey(secret, ["sign"]);
920
- return await getSubtleCrypto().sign("HMAC", key, encodeText(data));
921
- }
922
- async function verify(data, signature, secret) {
923
- const key = await getKey(secret, ["verify"]);
924
- return await getSubtleCrypto().verify("HMAC", key, signature, encodeText(data));
925
- }
926
- async function getKey(secret, usages) {
927
- return await getSubtleCrypto().importKey("raw", encodeText(secret), {
928
- name: "HMAC",
929
- hash: "SHA-256"
930
- }, false, usages);
931
- }
932
- function encodeJson(value) {
933
- return encodeBase64Url(encodeText(JSON.stringify(value)));
934
- }
935
- function decodeJson(value) {
936
- return JSON.parse(textDecoder.decode(decodeBase64Url(value)));
937
- }
938
- function encodeBase64Url(value) {
939
- const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value;
940
- return btoa(bytesToBinaryString(bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
941
- }
942
- function decodeBase64Url(value) {
943
- const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
944
- const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
945
- return binaryStringToBytes(atob(padded)).buffer;
946
- }
947
- function encodeText(value) {
948
- return toArrayBuffer(textEncoder.encode(value));
949
- }
950
- function toArrayBuffer(bytes) {
951
- return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
952
- }
953
- function bytesToBinaryString(bytes) {
954
- let binary = "";
955
- for (let offset = 0;offset < bytes.length; offset += BYTE_CHUNK_SIZE) {
956
- binary += String.fromCharCode(...bytes.subarray(offset, offset + BYTE_CHUNK_SIZE));
957
- }
958
- return binary;
959
- }
960
- function binaryStringToBytes(binary) {
961
- const bytes = new Uint8Array(binary.length);
962
- for (let index = 0;index < binary.length; index += 1) {
963
- bytes[index] = binary.charCodeAt(index);
964
- }
965
- return bytes;
966
- }
967
- function getSubtleCrypto() {
968
- const subtle = globalThis.crypto?.subtle;
969
- if (!subtle) {
970
- throw new Error("Web Crypto is not available in this runtime");
2305
+ await this.#onEvent("idle");
971
2306
  }
972
- return subtle;
973
- }
974
- function isJwtHeader(value) {
975
- return typeof value === "object" && value !== null && "alg" in value && value.alg === "HS256";
976
- }
977
- function isJwtPayload(value) {
978
- return typeof value === "object" && value !== null && !Array.isArray(value);
979
- }
980
- function isTimeValid(payload) {
981
- const now = Math.floor(Date.now() / 1000);
982
- if (typeof payload.exp === "number" && now >= payload.exp)
983
- return false;
984
- if (typeof payload.nbf === "number" && now < payload.nbf)
985
- return false;
986
- return true;
987
- }
988
-
989
- // src/sitemap/index.ts
990
- var createTimeoutController = (timeout) => {
991
- const controller = new AbortController;
992
- setTimeout(() => controller.abort(), timeout);
993
- return controller;
994
- };
995
- var discoverSitemaps = async (domain) => {
996
- const normalizedDomain = domain.replace(/\/$/, "");
997
- const commonLocations = [
998
- "/sitemap.xml",
999
- "/sitemap_index.xml",
1000
- "/sitemap/",
1001
- "/sitemaps.xml",
1002
- "/sitemap/sitemap.xml"
1003
- ];
1004
- const sitemaps = new Set;
1005
- const requestOptions = {
1006
- headers: {
1007
- "User-Agent": "SitemapCrawler/1.0"
1008
- },
1009
- signal: createTimeoutController(5000).signal
1010
- };
1011
- try {
1012
- const robotsTxt = await fetch(`${normalizedDomain}/robots.txt`, {
1013
- ...requestOptions
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);
1014
2332
  });
1015
- if (robotsTxt.ok) {
1016
- const text = await robotsTxt.text();
1017
- const sitemapMatches = text.match(/Sitemap: (.*)/gi);
1018
- if (sitemapMatches) {
1019
- sitemapMatches.forEach((match) => {
1020
- const url = match.replace("Sitemap:", "").trim();
1021
- sitemaps.add(url);
1022
- });
1023
- }
1024
- if (sitemaps.size > 0) {
1025
- return [...sitemaps];
1026
- }
2333
+ }
2334
+ async onSizeLessThan(limit) {
2335
+ if (this.#queue.size < limit) {
2336
+ return;
1027
2337
  }
1028
- } catch (error) {
1029
- console.warn("Could not fetch robots.txt:", error instanceof Error ? error.message : "Unknown error");
2338
+ await this.#onEvent("next", () => this.#queue.size < limit);
1030
2339
  }
1031
- for (const location of commonLocations) {
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
+ }
1032
2371
  try {
1033
- const response = await fetch(`${normalizedDomain}${location}`, {
1034
- ...requestOptions,
1035
- method: "HEAD"
1036
- });
1037
- if (response.ok) {
1038
- return [`${normalizedDomain}${location}`];
2372
+ return await Promise.race(races);
2373
+ } finally {
2374
+ if (timeoutId) {
2375
+ clearTimeout(timeoutId);
1039
2376
  }
1040
- } catch (_error) {}
2377
+ }
1041
2378
  }
1042
- return [];
1043
- };
1044
- var getDomainSitemap = async (domain, options = {}) => {
1045
- const {
1046
- filterIndexes,
1047
- concurrency = 10,
1048
- maxDepth = 5,
1049
- timeout = 1e4
1050
- } = options;
1051
- let normalizedDomain = domain.replace(/\/$/, "");
1052
- if (!normalizedDomain.startsWith("http")) {
1053
- normalizedDomain = `https://${normalizedDomain}`;
2379
+ #next() {
2380
+ this.#pending--;
2381
+ if (this.#pending === 0) {
2382
+ this.emit("pendingZero");
2383
+ }
2384
+ this.#processQueue();
2385
+ this.emit("next");
1054
2386
  }
1055
- const sitemapUrls = await discoverSitemaps(normalizedDomain);
1056
- if (sitemapUrls.length === 0) {
1057
- throw new Error("No sitemaps found for the domain");
2387
+ #processQueue() {
2388
+ while (this.#tryToStartAnother()) {}
2389
+ this.#updateRateLimitState();
1058
2390
  }
1059
- const queue = new PQueue({ concurrency });
1060
- const result = {
1061
- links: new Set,
1062
- visited: new Set
1063
- };
1064
- const locRegex = /<loc>([^<]+)<\/loc>/g;
1065
- const isXmlSitemap = (url) => url.endsWith(".xml") || url.toLowerCase().includes("sitemap");
1066
- const processSitemap = async (sitemapUrl, depth = 0) => {
1067
- if (depth > maxDepth || result.visited.has(sitemapUrl)) {
1068
- return;
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;
1069
2399
  }
1070
- result.visited.add(sitemapUrl);
1071
- try {
1072
- const controller = createTimeoutController(timeout);
1073
- const response = await fetch(sitemapUrl, {
1074
- headers: {
1075
- "User-Agent": "SitemapCrawler/1.0"
1076
- },
1077
- signal: controller.signal
1078
- });
1079
- if (!response.ok)
1080
- throw new Error(`HTTP error! status: ${response.status}`);
1081
- const data = await response.text();
1082
- const matches = [...data.matchAll(locRegex)].map((match) => match[1].trim()).filter((link) => {
1083
- if (filterIndexes && isXmlSitemap(link)) {
1084
- return link.includes(filterIndexes);
1085
- }
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) {
1086
2420
  return true;
1087
- });
1088
- for (const link of matches) {
1089
- if (isXmlSitemap(link)) {
1090
- queue.add(() => processSitemap(link, depth + 1));
1091
- } else {
1092
- result.links.add(link);
1093
- }
1094
2421
  }
1095
- } catch (error) {
1096
- console.warn(`Failed to process sitemap ${sitemapUrl}:`, error instanceof Error ? error.message : "Unknown error");
2422
+ this.#scheduleIntervalResume(this.#interval - (now - this.#strictTimestamps[0]));
2423
+ return false;
1097
2424
  }
1098
- };
1099
- await Promise.all(sitemapUrls.map((sitemapUrl) => queue.add(() => processSitemap(sitemapUrl))));
1100
- await queue.onIdle();
1101
- return [...result.links];
1102
- };
1103
-
1104
- // src/secrets/infisical.ts
1105
- import { z as z4 } from "zod";
1106
- var defaultFetch = globalThis.fetch;
1107
- var infisicalAuthResponseSchema = z4.object({
1108
- accessToken: z4.string()
1109
- });
1110
- var infisicalSecretSchema = z4.object({
1111
- secretKey: z4.string(),
1112
- secretValue: z4.string()
1113
- });
1114
- var infisicalSecretsResponseSchema = z4.object({
1115
- secrets: z4.array(infisicalSecretSchema)
1116
- });
1117
- async function loadInfisicalSecrets({
1118
- keys,
1119
- projectId,
1120
- environment,
1121
- siteUrl,
1122
- clientIdEnvKey,
1123
- clientSecretEnvKey
1124
- }) {
1125
- const clientId = process.env[clientIdEnvKey];
1126
- const clientSecret = process.env[clientSecretEnvKey];
1127
- if (!clientId || !clientSecret) {
1128
- throw new Error("Missing Infisical credentials");
1129
- }
1130
- const infisical = createInfisicalApiClient(siteUrl);
1131
- const { data: auth } = await infisical.request("login", {
1132
- reqBody: {
1133
- clientId,
1134
- clientSecret
2425
+ if (this.#intervalEnd === 0 || now >= this.#intervalEnd) {
2426
+ this.#intervalCount = this.#carryoverIntervalCount ? this.#pending : 0;
2427
+ this.#intervalEnd = now + this.#interval;
1135
2428
  }
1136
- });
1137
- const { data } = await infisical.request("listSecrets", {
1138
- reqHeaders: {
1139
- Authorization: `Bearer ${auth.accessToken}`
1140
- },
1141
- reqQuery: {
1142
- environment,
1143
- projectId,
1144
- secretPath: "/",
1145
- viewSecretValue: true,
1146
- expandSecretReferences: true,
1147
- includeImports: true
2429
+ if (this.#intervalCount < this.#intervalCap) {
2430
+ return true;
1148
2431
  }
1149
- });
1150
- const wantedKeys = new Set(keys);
1151
- return Object.fromEntries(data.secrets.filter((secret) => wantedKeys.has(secret.secretKey)).map((secret) => [secret.secretKey, secret.secretValue]));
1152
- }
1153
- function createInfisicalApiClient(siteUrl) {
1154
- return createApiClient({
1155
- name: "infisical",
1156
- baseApiUrls: {
1157
- infisical: siteUrl
1158
- },
1159
- defaultBaseApiUrl: "infisical",
1160
- fetch: getFetch(),
1161
- retries: 1,
1162
- endpoints: {
1163
- login: {
1164
- method: "POST",
1165
- path: "/api/v1/auth/universal-auth/login",
1166
- reqBodySchema: z4.object({
1167
- clientId: z4.string(),
1168
- clientSecret: z4.string()
1169
- }),
1170
- resSchema: infisicalAuthResponseSchema,
1171
- reqBodyFormat: "json"
1172
- },
1173
- listSecrets: {
1174
- method: "GET",
1175
- path: "/api/v4/secrets",
1176
- reqHeadersSchema: z4.object({
1177
- Authorization: z4.string()
1178
- }),
1179
- reqQuerySchema: z4.object({
1180
- projectId: z4.string(),
1181
- environment: z4.string(),
1182
- secretPath: z4.string(),
1183
- viewSecretValue: z4.boolean(),
1184
- expandSecretReferences: z4.boolean(),
1185
- includeImports: z4.boolean()
1186
- }),
1187
- resSchema: infisicalSecretsResponseSchema
1188
- }
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");
1189
2466
  }
1190
- });
1191
- }
1192
- function getFetch() {
1193
- return globalThis.fetch ?? defaultFetch;
1194
- }
1195
-
1196
- // src/secrets/vite.ts
1197
- function getViteDefine(secretSet) {
1198
- return Object.fromEntries(Object.entries(secretSet.secrets()).map(([key, value]) => [
1199
- `import.meta.env.${key}`,
1200
- JSON.stringify(value)
1201
- ]));
1202
- }
1203
- function viteSecretSetPlugin(secretSet) {
1204
- return {
1205
- name: "hanzio-secret-set",
1206
- config: () => ({
1207
- define: getViteDefine(secretSet)
1208
- })
1209
- };
2467
+ }
1210
2468
  }
1211
-
1212
2469
  // src/cool-console-log/index.ts
1213
2470
  var terminalColors = {
1214
2471
  reset: "\x1B[0m",
@@ -1313,783 +2570,936 @@ function logBanner(text, color = "cyan", options = {}) {
1313
2570
  console.log(`${c[color]}║${" ".repeat(padding)}${c.bold}${text}${c.reset}${c[color]}${" ".repeat(padding)}║${c.reset}`);
1314
2571
  console.log(`${c[color]}╚${line}╝${c.reset}`);
1315
2572
  }
1316
-
1317
- // src/secrets/index.ts
1318
- var DEFAULT_INFISICAL_SITE_URL = "https://eu.infisical.com";
1319
- var logger = createCoolLogger();
1320
- function getSecretEnvironment() {
1321
- const env = process.env.SECRETS_ENV || "development";
1322
- if (env === "production" || env === "prod")
1323
- return "prod";
1324
- if (env === "staging")
1325
- return "staging";
1326
- return "dev";
1327
- }
1328
- async function defineSecretSet(keys, options) {
1329
- const keySet = [...new Set(keys)];
1330
- const loader = options.loader ?? loadInfisicalSecrets;
1331
- let cachedSecrets = null;
1332
- const loadSecrets = async (force = false) => {
1333
- const envSecrets = readEnvSecrets(keySet, force ? cachedSecrets : null);
1334
- const keysMissingFromEnv = keySet.filter((key) => !envSecrets[key]);
1335
- const environment = resolveEnvironment(options.environment);
1336
- logSecretEnvironment(environment, keySet.length);
1337
- const loadedSecrets = keysMissingFromEnv.length === 0 && !force ? {} : await loader({
1338
- keys: keySet,
1339
- projectId: options.projectId,
1340
- environment,
1341
- siteUrl: options.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
1342
- clientIdEnvKey: options.clientIdEnvKey ?? "INFISICAL_CLIENT_ID",
1343
- clientSecretEnvKey: options.clientSecretEnvKey ?? "INFISICAL_CLIENT_SECRET"
1344
- });
1345
- const missingKeys = keySet.filter((key) => !(envSecrets[key] ?? loadedSecrets[key]));
1346
- if (missingKeys.length > 0) {
1347
- throw new Error(`Missing secrets: ${missingKeys.join(", ")}`);
1348
- }
1349
- const nextSecrets = Object.fromEntries(keySet.map((key) => [
1350
- key,
1351
- envSecrets[key] ?? loadedSecrets[key]
1352
- ]));
1353
- for (const [key, value] of Object.entries(nextSecrets)) {
1354
- process.env[key] = value;
1355
- }
1356
- cachedSecrets = nextSecrets;
1357
- return nextSecrets;
1358
- };
1359
- const secretSet = {
1360
- secret(key) {
1361
- const value = cachedSecrets?.[key] ?? process.env[key];
1362
- if (!value) {
1363
- throw new Error(`Secret ${key} not found`);
1364
- }
1365
- return value;
1366
- },
1367
- secrets() {
1368
- if (!cachedSecrets) {
1369
- throw new Error("Secrets not initialized.");
1370
- }
1371
- return cachedSecrets;
1372
- },
1373
- async reload() {
1374
- await loadSecrets(true);
1375
- return secretSet;
1376
- }
1377
- };
1378
- await loadSecrets();
1379
- return secretSet;
1380
- }
1381
- function resolveEnvironment(environment) {
1382
- if (typeof environment === "function")
1383
- return environment();
1384
- return environment ?? getSecretEnvironment();
1385
- }
1386
- function logSecretEnvironment(environment, secretCount) {
1387
- logger.info(colorize("Loading secrets", "cyan", "bold"), {
1388
- environment: colorize(environment, "brightCyan", "bold"),
1389
- count: secretCount
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)
1390
2582
  });
2583
+ const data = `${header}.${body}`;
2584
+ const signature = await sign(data, secret);
2585
+ return `${data}.${encodeBase64Url(signature)}`;
1391
2586
  }
1392
- function readEnvSecrets(keys, previousSecrets = null) {
1393
- return Object.fromEntries(keys.filter((key) => {
1394
- const value = process.env[key];
1395
- return value && value !== previousSecrets?.[key];
1396
- }).map((key) => [key, process.env[key]]));
1397
- }
1398
-
1399
- // src/typedswitch/index.ts
1400
- function runSwitch(value, keyOrCases, casesOrDefault, defaultCase) {
1401
- let key;
1402
- let cases;
1403
- let defaultHandler;
1404
- let discriminantKey;
1405
- if (typeof value === "string") {
1406
- key = value;
1407
- cases = keyOrCases;
1408
- defaultHandler = casesOrDefault;
1409
- } else {
1410
- if (typeof keyOrCases !== "string") {
1411
- throw new Error("Invalid typedSwitch call: object mode requires a string discriminant key");
1412
- }
1413
- discriminantKey = keyOrCases;
1414
- const discriminantValue = value[discriminantKey];
1415
- if (typeof discriminantValue !== "string") {
1416
- throw new Error(`Invalid discriminant value for key "${discriminantKey}": expected string, received ${typeof discriminantValue}`);
1417
- }
1418
- key = discriminantValue;
1419
- cases = casesOrDefault;
1420
- defaultHandler = defaultCase;
1421
- }
1422
- const handler = cases[key];
1423
- if (handler) {
1424
- return handler(value);
1425
- }
1426
- if (defaultHandler) {
1427
- return defaultHandler(value);
1428
- }
1429
- const availableCases = Object.keys(cases).join(", ");
1430
- const availableCasesSuffix = availableCases.length > 0 ? ` Available cases: ${availableCases}` : " Available cases: (none)";
1431
- const contextSuffix = discriminantKey ? ` (discriminant key: "${discriminantKey}")` : "";
1432
- throw new Error(`Unhandled case: ${key}${contextSuffix}.${availableCasesSuffix}`);
1433
- }
1434
- function typedSwitch(value, keyOrCases, casesOrDefault, defaultCase) {
1435
- return runSwitch(value, keyOrCases, casesOrDefault, defaultCase);
1436
- }
1437
- // src/array/index.ts
1438
- function chunkArray(array, chunkSize) {
1439
- if (!Number.isInteger(chunkSize) || chunkSize <= 0) {
1440
- throw new Error("Chunk size must be a positive integer");
1441
- }
1442
- const chunks = [];
1443
- for (let index = 0;index < array.length; index += chunkSize) {
1444
- chunks.push(array.slice(index, index + chunkSize));
1445
- }
1446
- return chunks;
1447
- }
1448
- function findArrayDifferenceByKey(current, next, key) {
1449
- const currentByKey = new Map(current.map((item) => [String(item[key]), item]));
1450
- const itemsToDelete = [...current];
1451
- const newItems = [];
1452
- const upsertItems = [];
1453
- for (const item of next) {
1454
- const keyValue = String(item[key]);
1455
- if (!currentByKey.has(keyValue)) {
1456
- newItems.push(item);
1457
- continue;
1458
- }
1459
- upsertItems.push(item);
1460
- const deleteIndex = itemsToDelete.findIndex((currentItem) => String(currentItem[key]) === keyValue);
1461
- if (deleteIndex >= 0) {
1462
- itemsToDelete.splice(deleteIndex, 1);
1463
- }
1464
- }
1465
- return {
1466
- new: newItems,
1467
- upsert: upsertItems,
1468
- delete: itemsToDelete
1469
- };
1470
- }
1471
- function getUniqueValues(array) {
1472
- const seen = new Map;
1473
- for (const element of array) {
1474
- const key = JSON.stringify(element);
1475
- if (!seen.has(key)) {
1476
- seen.set(key, element);
1477
- }
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;
1478
2607
  }
1479
- return Array.from(seen.values());
1480
2608
  }
1481
- function compact(array) {
1482
- return array.filter((item) => item != null);
2609
+ async function sign(data, secret) {
2610
+ const key = await getKey(secret, ["sign"]);
2611
+ return await getSubtleCrypto().sign("HMAC", key, encodeText(data));
1483
2612
  }
1484
- function getUniqueValuesByKey(array, key) {
1485
- const seen = new Set;
1486
- const result = [];
1487
- for (const item of array) {
1488
- const value = item[key];
1489
- if (seen.has(value))
1490
- continue;
1491
- seen.add(value);
1492
- result.push(item);
1493
- }
1494
- return result;
2613
+ async function verify(data, signature, secret) {
2614
+ const key = await getKey(secret, ["verify"]);
2615
+ return await getSubtleCrypto().verify("HMAC", key, signature, encodeText(data));
1495
2616
  }
1496
- function groupBy(array, keyOrFn) {
1497
- return array.reduce((result, item) => {
1498
- const groupKey = typeof keyOrFn === "function" ? keyOrFn(item) : item[keyOrFn];
1499
- result[groupKey] ??= [];
1500
- result[groupKey].push(item);
1501
- return result;
1502
- }, {});
2617
+ async function getKey(secret, usages) {
2618
+ return await getSubtleCrypto().importKey("raw", encodeText(secret), {
2619
+ name: "HMAC",
2620
+ hash: "SHA-256"
2621
+ }, false, usages);
1503
2622
  }
1504
- function keyBy(array, keyOrFn) {
1505
- const result = {};
1506
- for (const item of array) {
1507
- const recordKey = typeof keyOrFn === "function" ? keyOrFn(item) : item[keyOrFn];
1508
- result[recordKey] = item;
1509
- }
1510
- return result;
2623
+ function encodeJson(value) {
2624
+ return encodeBase64Url(encodeText(JSON.stringify(value)));
1511
2625
  }
1512
- function partition(array, predicate) {
1513
- const matched = [];
1514
- const unmatched = [];
1515
- array.forEach((item, index) => {
1516
- if (predicate(item, index)) {
1517
- matched.push(item);
1518
- } else {
1519
- unmatched.push(item);
1520
- }
1521
- });
1522
- return [matched, unmatched];
2626
+ function decodeJson(value) {
2627
+ return JSON.parse(textDecoder.decode(decodeBase64Url(value)));
1523
2628
  }
1524
- function pickItemsInArray(array, key, values) {
1525
- return array.filter((item) => values.includes(item[key]));
2629
+ function encodeBase64Url(value) {
2630
+ const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value;
2631
+ return btoa(bytesToBinaryString(bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
1526
2632
  }
1527
- // src/string/index.ts
1528
- function extractNumber(value) {
1529
- if (!value?.trim())
1530
- return;
1531
- const cleanedValue = value.replace(/[^\d.,-]/g, "");
1532
- if (!cleanedValue)
1533
- return;
1534
- const parsedNumber = Number.parseFloat(cleanedValue);
1535
- return Number.isNaN(parsedNumber) ? undefined : parsedNumber;
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;
1536
2637
  }
1537
- function generateId(input) {
1538
- const data = normalizeIdInput(input);
1539
- let hash = 0;
1540
- for (let index = 0;index < data.length; index++) {
1541
- const char = data.charCodeAt(index);
1542
- hash = (hash << 5) - hash + char;
1543
- hash |= 0;
1544
- }
1545
- return Math.abs(hash).toString(16).padStart(8, "0");
2638
+ function encodeText(value) {
2639
+ return toArrayBuffer(textEncoder.encode(value));
1546
2640
  }
1547
- function slugify(value) {
1548
- const source = "àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìłḿñńǹňôöòóœøōõṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·/_,:;";
1549
- const replacement = "aaaaaaaaaacccddeeeeeeeegghiiiiiilmnnnnooooooooprrsssssttuuuuuuuuuwxyyzzz------";
1550
- const specialCharacterPattern = new RegExp(source.split("").join("|"), "g");
1551
- 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(/-+$/, "");
2641
+ function toArrayBuffer(bytes) {
2642
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
1552
2643
  }
1553
- function normalizeIdInput(input) {
1554
- if (input === undefined)
1555
- return "undefined";
1556
- if (input === null)
1557
- return "null";
1558
- if (typeof input === "string") {
1559
- return decodeURIComponent(input).split("&").sort().join("&");
1560
- }
1561
- if (typeof input === "number" || typeof input === "boolean") {
1562
- return input.toString();
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));
1563
2648
  }
1564
- return JSON.stringify(sortJsonValue(input));
2649
+ return binary;
1565
2650
  }
1566
- function sortJsonValue(value) {
1567
- if (value === undefined)
1568
- return null;
1569
- if (Array.isArray(value)) {
1570
- return value.map((item) => sortJsonValue(item));
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);
1571
2655
  }
1572
- if (typeof value === "object" && value !== null) {
1573
- return Object.fromEntries(Object.entries(value).filter(([, itemValue]) => itemValue !== undefined).sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)).map(([key, itemValue]) => [key, sortJsonValue(itemValue)]));
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");
1574
2662
  }
1575
- return value;
2663
+ return subtle;
1576
2664
  }
1577
- // src/promise/index.ts
1578
- function isSuccess(result) {
1579
- return result.error === null;
2665
+ function isJwtHeader(value) {
2666
+ return typeof value === "object" && value !== null && "alg" in value && value.alg === "HS256";
1580
2667
  }
1581
- function isFailure(result) {
1582
- return result.error !== null;
2668
+ function isJwtPayload(value) {
2669
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1583
2670
  }
1584
- function unwrapResult(result) {
1585
- if (isSuccess(result))
1586
- return result.data;
1587
- throw result.error;
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;
1588
2678
  }
1589
- function backgroundPromise(promiseFactory, options = {}) {
1590
- const run = () => {
1591
- promiseFactory().then(() => options.onSuccess?.()).catch((error) => options.onError?.(error));
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
1592
2700
  };
1593
- if (options.scheduler) {
1594
- options.scheduler(run);
1595
- return;
1596
- }
1597
- run();
1598
- }
1599
- function backgroundPromiseSync(promiseFactory, options = {}) {
1600
- backgroundPromise(promiseFactory, {
1601
- ...options,
1602
- scheduler: options.scheduler ?? queueMicrotask
1603
- });
1604
- }
1605
- function promiseTimeout(ms) {
1606
- return new Promise((resolve) => setTimeout(resolve, ms));
1607
- }
1608
- async function tryCatch(promise) {
1609
2701
  try {
1610
- const data = await promise;
1611
- return { data, error: null };
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
+ }
1612
2718
  } catch (error) {
1613
- return { data: null, error };
2719
+ console.warn("Could not fetch robots.txt:", error instanceof Error ? error.message : "Unknown error");
1614
2720
  }
1615
- }
1616
- // src/cache/index.ts
1617
- function cacheFunction(options) {
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 = {}) => {
1618
2735
  const {
1619
- name,
1620
- fn,
1621
- cacheTimeMs = 10 * 60 * 1000,
1622
- refreshInBackground = false,
1623
- cacheKeyArgs,
1624
- cacheKeyFn,
1625
- onBackgroundRefreshError
2736
+ filterIndexes,
2737
+ concurrency = 10,
2738
+ maxDepth = 5,
2739
+ timeout = 1e4
1626
2740
  } = options;
1627
- const cache = new Map;
1628
- const pendingPromises = new Map;
1629
- const cleanupInterval = setInterval(() => {
1630
- const now = Date.now();
1631
- for (const [key, entry] of cache.entries()) {
1632
- if (now - entry.timestamp >= cacheTimeMs) {
1633
- cache.delete(key);
1634
- }
1635
- }
1636
- }, Math.min(cacheTimeMs / 2, 5 * 60 * 1000));
1637
- const wrappedFn = (...args) => {
1638
- const keyArgs = cacheKeyFn ? cacheKeyFn(...args) : cacheKeyArgs ? cacheKeyArgs.map((i) => args[i]) : args;
1639
- const key = `${name}:${JSON.stringify(keyArgs)}`;
1640
- const now = Date.now();
1641
- const pending = pendingPromises.get(key);
1642
- if (pending) {
1643
- return pending;
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;
1644
2759
  }
1645
- const cached = cache.get(key);
1646
- if (cached) {
1647
- const isCacheFresh = now - cached.timestamp < cacheTimeMs;
1648
- if (!isCacheFresh && !refreshInBackground) {
1649
- cache.delete(key);
1650
- } else {
1651
- if (isCacheFresh && !refreshInBackground) {
1652
- return cached.value;
1653
- }
1654
- if (refreshInBackground) {
1655
- const existingPending = pendingPromises.get(key);
1656
- if (existingPending) {
1657
- return existingPending;
1658
- }
1659
- const backgroundUpdate = fn(...args);
1660
- if (isPromiseLike(backgroundUpdate)) {
1661
- const backgroundPromise2 = backgroundUpdate.then((resolvedValue) => {
1662
- cache.set(key, { value: resolvedValue, timestamp: now });
1663
- pendingPromises.delete(key);
1664
- return resolvedValue;
1665
- }).catch((error) => {
1666
- pendingPromises.delete(key);
1667
- onBackgroundRefreshError?.(error, { name });
1668
- });
1669
- pendingPromises.set(key, backgroundPromise2);
1670
- } else {
1671
- cache.set(key, {
1672
- value: backgroundUpdate,
1673
- timestamp: now
1674
- });
1675
- }
1676
- return cached.value;
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);
1677
2775
  }
1678
- if (isCacheFresh) {
1679
- return cached.value;
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);
1680
2783
  }
1681
2784
  }
2785
+ } catch (error) {
2786
+ console.warn(`Failed to process sitemap ${sitemapUrl}:`, error instanceof Error ? error.message : "Unknown error");
1682
2787
  }
1683
- const result = fn(...args);
1684
- if (isPromiseLike(result)) {
1685
- const promise = result.then((resolvedValue) => {
1686
- cache.set(key, { value: resolvedValue, timestamp: now });
1687
- pendingPromises.delete(key);
1688
- return resolvedValue;
1689
- }).catch((error) => {
1690
- pendingPromises.delete(key);
1691
- throw error;
1692
- });
1693
- pendingPromises.set(key, promise);
1694
- return promise;
1695
- }
1696
- cache.set(key, { value: result, timestamp: now });
1697
- return result;
1698
- };
1699
- wrappedFn.clearCache = () => {
1700
- cache.clear();
1701
- pendingPromises.clear();
1702
- clearInterval(cleanupInterval);
1703
2788
  };
1704
- return wrappedFn;
1705
- }
1706
- function isPromiseLike(value) {
1707
- return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
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
+ }
1708
2805
  }
1709
- // src/state/index.ts
1710
- class SimpleStateMachine {
1711
- config;
1712
- item;
1713
- stateKey;
1714
- constructor(config, item, stateKey) {
1715
- this.config = config;
1716
- this.item = item;
1717
- this.stateKey = stateKey;
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.");
1718
2812
  }
1719
- getCurrentStatus() {
1720
- return this.item[this.stateKey];
2813
+ const controller = new AbortController;
2814
+ const cancel = () => controller.abort(new SecretLoadError("ABORTED", "Secret loading was cancelled."));
2815
+ if (options.signal?.aborted) {
2816
+ cancel();
2817
+ } else {
2818
+ options.signal?.addEventListener("abort", cancel, { once: true });
1721
2819
  }
1722
- async getAvailableActions() {
1723
- const actionsMap = this.config.stateActionMap[this.getCurrentStatus()] || {};
1724
- const result = {};
1725
- for (const [action, targetState] of Object.entries(actionsMap)) {
1726
- if (!targetState)
1727
- continue;
1728
- const validationResult = await this.validateTransition(targetState, { action });
1729
- result[action] = validationResult;
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";
1730
2860
  }
1731
- return result;
2861
+ throw new SecretLoadError("CONFIGURATION", "Invalid secret environment. Use dev, staging, prod, or an explicit { slug } option.");
1732
2862
  }
1733
- async getPossibleTransitions() {
1734
- const possibleTransitions = this.config.stateTransitions[this.getCurrentStatus()] || [];
1735
- const actionsMap = this.config.stateActionMap[this.getCurrentStatus()] || {};
1736
- const result = {};
1737
- for (const targetState of possibleTransitions) {
1738
- const actionsToTarget = Object.entries(actionsMap).filter(([, t]) => t === targetState);
1739
- if (actionsToTarget.length === 0) {
1740
- result[targetState] = await this.validateTransition(targetState);
1741
- continue;
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 [];
1742
2891
  }
1743
- let lastError = true;
1744
- let anyOk = false;
1745
- for (const [actionKey] of actionsToTarget) {
1746
- const r = await this.validateTransition(targetState, {
1747
- action: actionKey
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 {}
2909
+ };
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
+ }
1748
2925
  });
1749
- if (r === true) {
1750
- anyOk = true;
1751
- break;
2926
+ } catch (error) {
2927
+ if (error instanceof SecretLoadError) {
2928
+ throw error;
1752
2929
  }
1753
- lastError = r;
2930
+ throw new SecretLoadError("LOADER", "Secret loader failed.");
1754
2931
  }
1755
- result[targetState] = anyOk ? true : lastError;
1756
2932
  }
1757
- return result;
1758
- }
1759
- async executeAction(action, data) {
1760
- const actionsMap = this.config.stateActionMap[this.getCurrentStatus()];
1761
- if (!actionsMap) {
1762
- return {
1763
- success: false,
1764
- error: `No actions defined for state '${this.getCurrentStatus()}'`
1765
- };
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(", ")}`);
1766
2946
  }
1767
- const targetState = actionsMap[action];
1768
- if (!targetState) {
1769
- return {
1770
- success: false,
1771
- error: `Action '${action}' not allowed in state '${this.getCurrentStatus()}'`
1772
- };
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.");
1773
2953
  }
1774
- const validationResult = await this.validateTransition(targetState, {
1775
- action
1776
- });
1777
- if (validationResult === true) {
1778
- if (this.config.actions?.[action]) {
1779
- try {
1780
- const result = await this.config.actions[action](this.item, data);
1781
- return {
1782
- success: true,
1783
- newState: targetState,
1784
- result
1785
- };
1786
- } catch (error) {
1787
- return {
1788
- success: false,
1789
- error: error instanceof Error ? error.message : String(error)
1790
- };
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;
1791
2968
  }
2969
+ environmentVariables[key] = next[key];
2970
+ writes.set(key, next[key]);
1792
2971
  }
1793
- return {
1794
- success: true,
1795
- newState: targetState
1796
- };
1797
2972
  }
1798
- return {
1799
- success: false,
1800
- error: validationResult
1801
- };
1802
- }
1803
- async validateTransition(toState, options) {
1804
- const action = options?.action;
1805
- if (this.getCurrentStatus() === toState)
1806
- return true;
1807
- const allowedTransitions = this.config.stateTransitions[this.getCurrentStatus()] || [];
1808
- if (!allowedTransitions.includes(toState)) {
1809
- return `Invalid transition from '${this.getCurrentStatus()}' to '${toState}'`;
1810
- }
1811
- if (this.config.globalValidator) {
1812
- const globalResult = await this.config.globalValidator(this.item, this.getCurrentStatus(), toState, action);
1813
- if (globalResult !== true) {
1814
- return globalResult;
2973
+ cached = next;
2974
+ parsed = nextParsed;
2975
+ sources = nextSources;
2976
+ };
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;
3015
+ };
3016
+ const secretSet = {
3017
+ secret(key) {
3018
+ if (!Object.hasOwn(cached, key)) {
3019
+ throw new SecretLoadError("MISSING_SECRETS", `Secret ${key} not found`);
1815
3020
  }
3021
+ return cached[key];
3022
+ },
3023
+ secrets: () => ({ ...cached }),
3024
+ parsed: () => structuredClone(parsed),
3025
+ sources: () => ({ ...sources }),
3026
+ async reload(request) {
3027
+ await load(request);
3028
+ return secretSet;
1816
3029
  }
1817
- const stateValidator = this.config.validators?.[toState];
1818
- if (stateValidator) {
1819
- return await stateValidator(this.item, this.getCurrentStatus(), toState, action);
3030
+ };
3031
+ await load();
3032
+ return secretSet;
3033
+ }
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"
3047
+ };
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;
1820
3082
  }
1821
- return true;
1822
- }
1823
- getStates() {
1824
- return Object.keys(this.config.stateTransitions);
1825
- }
1826
- getInitialState() {
1827
- return this.getStates()[0];
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.`);
1828
3094
  }
1829
3095
  }
1830
- // src/math/linearRegressionTrend.ts
1831
- function linearRegressionTrend(values) {
1832
- const n = values.length;
1833
- if (n < 2)
1834
- return [];
1835
- for (const v of values) {
1836
- if (!Number.isFinite(v))
1837
- return [];
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.");
1838
3105
  }
1839
- let sumX = 0;
1840
- let sumY = 0;
1841
- let sumXY = 0;
1842
- let sumX2 = 0;
1843
- for (let i = 0;i < n; i++) {
1844
- const x = i;
1845
- const y = values[i];
1846
- sumX += x;
1847
- sumY += y;
1848
- sumXY += x * y;
1849
- sumX2 += x * x;
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.");
1850
3108
  }
1851
- const denom = n * sumX2 - sumX * sumX;
1852
- if (denom === 0)
1853
- return [];
1854
- const slope = (n * sumXY - sumX * sumY) / denom;
1855
- const intercept = (sumY - slope * sumX) / n;
1856
- const out = [];
1857
- for (let i = 0;i < n; i++) {
1858
- out.push(intercept + slope * i);
3109
+ if (!options.projectId.trim() || !options.environment.trim() || !(options.secretPath ?? "/").startsWith("/")) {
3110
+ throw new SecretLoadError("CONFIGURATION", "Infisical requires a projectId, environment, and absolute secretPath.");
1859
3111
  }
1860
- return out;
1861
3112
  }
1862
- // src/math/metricsSeriesColors.ts
1863
- var METRICS_SERIES_PALETTE_SIZE = 10;
1864
- function hashStringToColorIndex(str) {
1865
- let hash = 2166136261;
1866
- for (let i = 0;i < str.length; i++) {
1867
- hash ^= str.charCodeAt(i);
1868
- hash = Math.imul(hash, 16777619);
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.");
1869
3116
  }
1870
- return Math.abs(hash) % METRICS_SERIES_PALETTE_SIZE;
1871
- }
1872
- function circularHueDistance(a, b, paletteSize) {
1873
- const diff = Math.abs(a - b);
1874
- return Math.min(diff, paletteSize - diff);
1875
3117
  }
1876
- function pickHueMaxMinDistance(candidates, used, paletteSize) {
1877
- let best = candidates[0] ?? 0;
1878
- let bestScore = -1;
1879
- for (const cand of candidates) {
1880
- let minD = paletteSize;
1881
- for (const u of used) {
1882
- minD = Math.min(minD, circularHueDistance(cand, u, paletteSize));
1883
- }
1884
- if (minD > bestScore || minD === bestScore && cand < best) {
1885
- bestScore = minD;
1886
- best = cand;
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");
1887
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.");
1888
3137
  }
1889
- return best;
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 };
1890
3142
  }
1891
- function seriesColorKey(label, key) {
1892
- const fromLabel = label?.trim();
1893
- if (fromLabel)
1894
- return fromLabel;
1895
- return key.trim() || key;
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);
3176
+ }
3177
+ });
1896
3178
  }
1897
- function resolveSeriesColorMap(keys) {
1898
- const result = new Map;
1899
- const uniqueKeys = [...new Set(keys)];
1900
- if (uniqueKeys.length === 0)
1901
- return result;
1902
- const paletteSize = METRICS_SERIES_PALETTE_SIZE;
1903
- if (uniqueKeys.length === 1) {
1904
- const only = uniqueKeys[0];
1905
- result.set(only, hashStringToColorIndex(only));
1906
- return result;
1907
- }
1908
- const usedIndices = new Set;
1909
- const entries = uniqueKeys.map((key) => ({
1910
- key,
1911
- preferred: hashStringToColorIndex(key)
1912
- }));
1913
- for (const entry of entries) {
1914
- if (!usedIndices.has(entry.preferred)) {
1915
- result.set(entry.key, entry.preferred);
1916
- usedIndices.add(entry.preferred);
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.");
3192
+ }
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 }));
3198
+ }
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 });
3223
+ }
3224
+ token = data.token;
1917
3225
  }
3226
+ signal.throwIfAborted();
3227
+ operation = "listSecrets";
3228
+ return await listInfisicalSecrets(infisical, token, options);
3229
+ } catch (error) {
3230
+ throwInfisicalHttpError(error, operation, signal);
1918
3231
  }
1919
- const unassigned = entries.filter((e) => !result.has(e.key));
1920
- for (const entry of unassigned) {
1921
- const unused = [];
1922
- for (let i = 0;i < paletteSize; i++) {
1923
- if (!usedIndices.has(i))
1924
- unused.push(i);
1925
- }
1926
- const candidates = unused.length > 0 ? unused : Array.from({ length: paletteSize }, (_, i) => i);
1927
- const idx = pickHueMaxMinDistance(candidates, usedIndices, paletteSize);
1928
- result.set(entry.key, idx);
1929
- usedIndices.add(idx);
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;
1930
3252
  }
1931
- return result;
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 });
1932
3259
  }
1933
- function createResolveColorsByKeys(keys, getSeriesColor, getSeriesHoverColor) {
1934
- const colorMap = resolveSeriesColorMap(keys);
1935
- return {
1936
- getColor: (key) => getSeriesColor(colorMap.get(key) ?? hashStringToColorIndex(key)),
1937
- getHoverColor: (key) => getSeriesHoverColor(colorMap.get(key) ?? hashStringToColorIndex(key))
1938
- };
3260
+ function createInfisicalApiClient(siteUrl) {
3261
+ return createApiClient({
3262
+ name: "infisical",
3263
+ baseApiUrls: {
3264
+ infisical: siteUrl
3265
+ },
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
+ });
1939
3312
  }
1940
- function clamp(value, min, max) {
1941
- return Math.min(max, Math.max(min, value));
3313
+ function getFetch() {
3314
+ const fetchFn = globalThis.fetch ?? defaultFetch;
3315
+ return (input, init) => {
3316
+ init?.signal?.throwIfAborted();
3317
+ return fetchFn(input, init);
3318
+ };
1942
3319
  }
1943
- function withAlpha(color, alpha) {
1944
- const trimmed = color.trim();
1945
- if (trimmed.startsWith("#")) {
1946
- const hex = trimmed.slice(1);
1947
- const normalized = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
1948
- if (normalized.length === 6) {
1949
- const r = Number.parseInt(normalized.slice(0, 2), 16);
1950
- const g = Number.parseInt(normalized.slice(2, 4), 16);
1951
- const b = Number.parseInt(normalized.slice(4, 6), 16);
1952
- return `rgba(${r}, ${g}, ${b}, ${alpha})`;
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
+ }
1953
3329
  }
1954
- }
1955
- if (trimmed.startsWith("rgb(")) {
1956
- return trimmed.replace("rgb(", "rgba(").replace(")", `, ${alpha})`);
1957
- }
1958
- if (trimmed.startsWith("rgba(")) {
1959
- return trimmed.replace(/,\s*[\d.]+\)$/, `, ${alpha})`);
1960
- }
1961
- return trimmed;
3330
+ return out;
3331
+ };
3332
+ return Object.assign(loader, { source: "Cloudflare Worker bindings" });
1962
3333
  }
1963
- function getHeatmapCellStyle(params) {
1964
- const {
1965
- value,
1966
- min,
1967
- max,
1968
- baseColor,
1969
- neutralColor,
1970
- minAlpha = 0.08,
1971
- maxAlpha = 0.78
1972
- } = params;
1973
- if (value === null || value === undefined || Number.isNaN(value)) {
1974
- return {};
3334
+ function workerBindingToString(value) {
3335
+ if (value === undefined || value === null) {
3336
+ return;
1975
3337
  }
1976
- if (!Number.isFinite(min) || !Number.isFinite(max)) {
1977
- return {};
3338
+ if (typeof value === "string") {
3339
+ return value === "" ? undefined : value;
1978
3340
  }
1979
- if (max <= min) {
1980
- return {
1981
- backgroundColor: withAlpha(neutralColor ?? baseColor, 0.32)
1982
- };
3341
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
3342
+ return String(value);
1983
3343
  }
1984
- const normalized = clamp((value - min) / (max - min), 0, 1);
1985
- const alpha = minAlpha + (maxAlpha - minAlpha) * normalized;
1986
- return {
1987
- backgroundColor: withAlpha(baseColor, alpha)
1988
- };
3344
+ if (typeof value === "object") {
3345
+ return JSON.stringify(value);
3346
+ }
3347
+ return String(value);
1989
3348
  }
1990
- // src/date/index.ts
1991
- function getPreviousReportPeriod(periodType, referenceDate = new Date) {
1992
- const month = referenceDate.getMonth();
1993
- const year = referenceDate.getFullYear();
1994
- switch (periodType) {
1995
- case "MONTHLY": {
1996
- const prevMonth = month === 0 ? 11 : month - 1;
1997
- const prevYear = month === 0 ? year - 1 : year;
1998
- return {
1999
- start: new Date(prevYear, prevMonth, 1),
2000
- end: new Date(year, month, 0)
2001
- };
2002
- }
2003
- case "QUARTERLY": {
2004
- const qStart = Math.floor(month / 3) * 3;
2005
- const prevQStart = qStart === 0 ? 9 : qStart - 3;
2006
- const prevQYear = qStart === 0 ? year - 1 : year;
2007
- return {
2008
- start: new Date(prevQYear, prevQStart, 1),
2009
- end: new Date(prevQYear, prevQStart + 3, 0)
2010
- };
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.");
2011
3381
  }
2012
- case "YEARLY": {
2013
- return {
2014
- start: new Date(year - 1, 0, 1),
2015
- end: new Date(year - 1, 11, 31)
2016
- };
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.");
2017
3384
  }
2018
3385
  }
3386
+ return Object.fromEntries(keys.map((key) => [`import.meta.env.${key}`, JSON.stringify(values[key])]));
2019
3387
  }
2020
- function formatPeriodLabel(start, end, includeDay = false) {
2021
- const s = start instanceof Date ? start : new Date(start);
2022
- const e = end instanceof Date ? end : new Date(end);
2023
- const opts = {
2024
- year: "numeric",
2025
- month: "long",
2026
- ...includeDay && { day: "numeric" }
3388
+ function viteSecretSetPlugin(secretSet, options = {}) {
3389
+ return {
3390
+ name: "hanzio-secret-set",
3391
+ config: () => ({ define: getViteDefine(secretSet, options) })
2027
3392
  };
2028
- return `${s.toLocaleDateString("en-GB", opts)} — ${e.toLocaleDateString("en-GB", opts)}`;
2029
3393
  }
2030
- // src/url/index.ts
2031
- function getDomainFaviconUrl(domain, size = 128) {
2032
- return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=${size}`;
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
+ });
2033
3420
  }
2034
3421
  export {
2035
- zodToTypeString,
2036
- zodToExample,
2037
- zodMinMaxFilter,
2038
- zSingleOrArray,
2039
- withAlpha,
2040
- viteSecretSetPlugin,
2041
- unwrapResult,
2042
- typedSwitch,
2043
- tryCatch,
2044
- toGteLteFilter,
2045
- terminalColors,
2046
- slugify,
2047
- seriesColorKey,
2048
- resolveSeriesColorMap,
2049
- promiseTimeout,
2050
- pickItemsInArray,
2051
- partition,
2052
- normalizeSingleOrArray,
2053
- logOperationSummary,
2054
- logBanner,
2055
- loadInfisicalSecrets,
2056
- linearRegressionTrend,
2057
- keyBy,
2058
- jwtVerify,
2059
- jwtSign,
2060
- jsonToZodSchemaMapper,
2061
- isSuccess,
2062
- isFailure,
2063
- hashStringToColorIndex,
2064
- groupBy,
2065
- getViteDefine,
2066
- getUniqueValuesByKey,
2067
- getUniqueValues,
2068
- getSecretEnvironment,
2069
- getPreviousReportPeriod,
2070
- getHeatmapCellStyle,
2071
- getDomainSitemap,
2072
- getDomainFaviconUrl,
2073
- generateId,
2074
- formatPeriodLabel,
2075
- findArrayDifferenceByKey,
2076
- extractNumber,
2077
- defineSecretSet,
2078
- createZId,
2079
- createResolveColorsByKeys,
2080
- createCoolLogger,
2081
- createApiWrapper,
2082
- createApiClient,
2083
- compact,
2084
- colorize,
2085
- chunkArray,
2086
- cacheFunction,
2087
- backgroundPromiseSync,
2088
- backgroundPromise,
2089
- TimeoutError,
2090
- SimpleStateMachine,
2091
- ResponseValidationError,
2092
- PriorityQueue,
3422
+ ActionError,
3423
+ ApiError,
3424
+ ConfigError,
3425
+ DEFAULT_INFISICAL_SITE_URL,
3426
+ HttpResponseError,
2093
3427
  METRICS_SERIES_PALETTE_SIZE,
2094
- HttpResponseError
3428
+ NetworkError,
3429
+ PriorityQueue,
3430
+ RequestAbortedError,
3431
+ RequestTimeoutError,
3432
+ RequestValidationError,
3433
+ ResponseValidationError,
3434
+ SecretLoadError,
3435
+ SimpleStateMachine,
3436
+ TimeoutError,
3437
+ backgroundPromise,
3438
+ backgroundPromiseSync,
3439
+ cacheFunction,
3440
+ chunkArray,
3441
+ cloudflareWorkerEnvLoader,
3442
+ colorize,
3443
+ compact,
3444
+ createApiClient,
3445
+ createCoolLogger,
3446
+ createResolveColorsByKeys,
3447
+ createZId,
3448
+ defineAction,
3449
+ defineSecretSet2 as defineSecretSet,
3450
+ extractNumber,
3451
+ fetchInfisicalSecrets,
3452
+ findArrayDifferenceByKey,
3453
+ formatPeriodLabel,
3454
+ generateId,
3455
+ getDomainFaviconUrl,
3456
+ getDomainSitemap,
3457
+ getHeatmapCellStyle,
3458
+ getPreviousReportPeriod,
3459
+ getSecretEnvironment,
3460
+ getUniqueValues,
3461
+ getUniqueValuesByKey,
3462
+ getViteDefine,
3463
+ groupBy,
3464
+ 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,
3482
+ linearRegressionTrend,
3483
+ loadInfisicalSecrets,
3484
+ logBanner,
3485
+ logOperationSummary,
3486
+ normalizeSingleOrArray,
3487
+ partition,
3488
+ pickItemsInArray,
3489
+ processEnvLoader,
3490
+ promiseTimeout,
3491
+ resolveSeriesColorMap,
3492
+ seriesColorKey,
3493
+ slugify,
3494
+ terminalColors,
3495
+ toGteLteFilter,
3496
+ tryCatch,
3497
+ typedSwitch,
3498
+ unwrapResult,
3499
+ viteSecretSetPlugin,
3500
+ withAlpha,
3501
+ zSingleOrArray,
3502
+ zodMinMaxFilter,
3503
+ zodToExample,
3504
+ zodToTypeString
2095
3505
  };