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.
- package/README.md +70 -4
- package/dist/api-wrapper/actions-impl.d.ts +4 -0
- package/dist/api-wrapper/builders.d.ts +23 -0
- package/dist/api-wrapper/create-client.d.ts +6 -0
- package/dist/api-wrapper/error-reporting.d.ts +7 -0
- package/dist/api-wrapper/errors.d.ts +64 -0
- package/dist/api-wrapper/headers.d.ts +10 -0
- package/dist/api-wrapper/index.d.ts +5 -84
- package/dist/api-wrapper/redaction.d.ts +2 -0
- package/dist/api-wrapper/shared.d.ts +16 -0
- package/dist/api-wrapper/size.d.ts +1 -0
- package/dist/api-wrapper/test-helpers.d.ts +6 -0
- package/dist/api-wrapper/transport.d.ts +55 -0
- package/dist/api-wrapper/types.d.ts +154 -0
- package/dist/api-wrapper/zod-issues.d.ts +17 -0
- package/dist/cache/index.d.ts +26 -0
- package/dist/secrets/core.d.ts +47 -0
- package/dist/secrets/deadline.d.ts +6 -0
- package/dist/secrets/environment.d.ts +9 -0
- package/dist/secrets/errors.d.ts +11 -0
- package/dist/secrets/index.d.ts +14 -23
- package/dist/secrets/infisical-cli.d.ts +6 -0
- package/dist/secrets/infisical-config.d.ts +17 -0
- package/dist/secrets/infisical-session.d.ts +4 -0
- package/dist/secrets/infisical.d.ts +14 -2
- package/dist/secrets/loaders.d.ts +9 -0
- package/dist/secrets/vite.d.ts +7 -3
- package/dist/secrets/worker-loader.d.ts +9 -0
- package/dist/secrets/worker.d.ts +3 -0
- package/dist/src/api-wrapper/index.js +1059 -177
- package/dist/src/index.js +2807 -1397
- package/dist/src/jwt/index.js +2 -2
- package/dist/src/p-queue/index.js +3 -3
- package/dist/src/secrets/index.js +1610 -407
- package/dist/src/secrets/infisical.js +1352 -214
- package/dist/src/secrets/loaders.js +1529 -0
- package/dist/src/secrets/vite.js +33 -11
- package/dist/src/secrets/worker.js +275 -0
- package/dist/src/sitemap/index.js +1 -1
- package/dist/src/zod/index.js +7 -7
- package/dist/typedswitch/index.d.ts +9 -5
- package/package.json +12 -3
- package/src/api-wrapper/actions-impl.ts +100 -0
- package/src/api-wrapper/builders.ts +200 -0
- package/src/api-wrapper/create-client.ts +393 -0
- package/src/api-wrapper/error-reporting.ts +31 -0
- package/src/api-wrapper/errors.ts +231 -0
- package/src/api-wrapper/headers.ts +79 -0
- package/src/api-wrapper/index.ts +5 -492
- package/src/api-wrapper/redaction.ts +62 -0
- package/src/api-wrapper/shared.ts +25 -0
- package/src/api-wrapper/size.ts +19 -0
- package/src/api-wrapper/test-helpers.ts +25 -0
- package/src/api-wrapper/transport.ts +350 -0
- package/src/api-wrapper/types.ts +259 -0
- package/src/api-wrapper/zod-issues.ts +157 -0
- package/src/cache/index.ts +228 -61
- package/src/secrets/core.ts +348 -0
- package/src/secrets/deadline.ts +69 -0
- package/src/secrets/environment.ts +69 -0
- package/src/secrets/errors.ts +29 -0
- package/src/secrets/index.ts +81 -140
- package/src/secrets/infisical-cli.ts +99 -0
- package/src/secrets/infisical-config.ts +70 -0
- package/src/secrets/infisical-session.ts +42 -0
- package/src/secrets/infisical.ts +219 -35
- package/src/secrets/loaders.ts +43 -0
- package/src/secrets/vite.ts +44 -15
- package/src/secrets/worker-loader.ts +56 -0
- package/src/secrets/worker.ts +4 -0
- 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/
|
|
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
|
|
1169
|
+
class ResponseValidationError extends ApiError {
|
|
206
1170
|
zodError;
|
|
207
1171
|
rawResponse;
|
|
1172
|
+
issues;
|
|
208
1173
|
validationIssues;
|
|
209
|
-
constructor(zodError, rawResponse) {
|
|
210
|
-
const issues = zodError
|
|
211
|
-
|
|
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.
|
|
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
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
1425
|
+
store.delete(key);
|
|
281
1426
|
throw error;
|
|
282
1427
|
}
|
|
283
1428
|
};
|
|
284
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (
|
|
292
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
return
|
|
304
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1718
|
+
await delay(delayMs);
|
|
324
1719
|
continue;
|
|
325
1720
|
}
|
|
326
|
-
|
|
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
|
|
1730
|
+
if (isNonRetryableApiError(error)) {
|
|
335
1731
|
throw error;
|
|
336
|
-
|
|
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(
|
|
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
|
|
347
|
-
const
|
|
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
|
-
|
|
1775
|
+
const data = await validateResponse(endpoint.resSchema, transformedData, ctx);
|
|
1776
|
+
return { data, responseSizeBytes };
|
|
350
1777
|
}
|
|
351
|
-
function
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
361
|
-
|
|
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
|
-
|
|
365
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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((
|
|
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
|
-
|
|
780
|
-
|
|
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
|
-
|
|
792
|
-
this
|
|
793
|
-
|
|
794
|
-
|
|
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
|
|
798
|
-
}
|
|
799
|
-
#processQueue() {
|
|
800
|
-
while (this.#tryToStartAnother()) {}
|
|
801
|
-
this.#updateRateLimitState();
|
|
2280
|
+
return this;
|
|
802
2281
|
}
|
|
803
|
-
|
|
804
|
-
|
|
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
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
if (this.#
|
|
830
|
-
this
|
|
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
|
|
845
|
-
return false;
|
|
2293
|
+
this.emit("next");
|
|
846
2294
|
}
|
|
847
|
-
|
|
848
|
-
if (this.#
|
|
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.#
|
|
2299
|
+
await this.#onEvent("empty");
|
|
856
2300
|
}
|
|
857
|
-
|
|
858
|
-
if (this.#
|
|
2301
|
+
async onIdle() {
|
|
2302
|
+
if (this.#queue.size === 0 && this.#pending === 0) {
|
|
859
2303
|
return;
|
|
860
2304
|
}
|
|
861
|
-
this.#
|
|
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
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
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
|
-
}
|
|
2377
|
+
}
|
|
1041
2378
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
2387
|
+
#processQueue() {
|
|
2388
|
+
while (this.#tryToStartAnother()) {}
|
|
2389
|
+
this.#updateRateLimitState();
|
|
1058
2390
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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
|
-
|
|
1096
|
-
|
|
2422
|
+
this.#scheduleIntervalResume(this.#interval - (now - this.#strictTimestamps[0]));
|
|
2423
|
+
return false;
|
|
1097
2424
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
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
|
-
|
|
1318
|
-
var
|
|
1319
|
-
var
|
|
1320
|
-
function
|
|
1321
|
-
const
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
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
|
|
1393
|
-
|
|
1394
|
-
const
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
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
|
|
1482
|
-
|
|
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
|
|
1485
|
-
const
|
|
1486
|
-
|
|
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
|
|
1497
|
-
return
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
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
|
|
1505
|
-
|
|
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
|
|
1513
|
-
|
|
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
|
|
1525
|
-
|
|
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
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
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
|
|
1538
|
-
|
|
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
|
|
1548
|
-
|
|
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
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
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
|
|
2649
|
+
return binary;
|
|
1565
2650
|
}
|
|
1566
|
-
function
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
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
|
-
|
|
1573
|
-
|
|
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
|
|
2663
|
+
return subtle;
|
|
1576
2664
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
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
|
|
1582
|
-
return
|
|
2668
|
+
function isJwtPayload(value) {
|
|
2669
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1583
2670
|
}
|
|
1584
|
-
function
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
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
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
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
|
|
1611
|
-
|
|
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
|
-
|
|
2719
|
+
console.warn("Could not fetch robots.txt:", error instanceof Error ? error.message : "Unknown error");
|
|
1614
2720
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
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
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
cacheKeyArgs,
|
|
1624
|
-
cacheKeyFn,
|
|
1625
|
-
onBackgroundRefreshError
|
|
2736
|
+
filterIndexes,
|
|
2737
|
+
concurrency = 10,
|
|
2738
|
+
maxDepth = 5,
|
|
2739
|
+
timeout = 1e4
|
|
1626
2740
|
} = options;
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
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
|
-
|
|
1646
|
-
|
|
1647
|
-
const
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
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
|
-
|
|
1679
|
-
|
|
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
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
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
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
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
|
-
|
|
1720
|
-
|
|
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
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
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
|
-
|
|
2861
|
+
throw new SecretLoadError("CONFIGURATION", "Invalid secret environment. Use dev, staging, prod, or an explicit { slug } option.");
|
|
1732
2862
|
}
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
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
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
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
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
2926
|
+
} catch (error) {
|
|
2927
|
+
if (error instanceof SecretLoadError) {
|
|
2928
|
+
throw error;
|
|
1752
2929
|
}
|
|
1753
|
-
|
|
2930
|
+
throw new SecretLoadError("LOADER", "Secret loader failed.");
|
|
1754
2931
|
}
|
|
1755
|
-
result[targetState] = anyOk ? true : lastError;
|
|
1756
2932
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
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
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
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
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
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
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
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
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
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
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
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
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
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
|
-
|
|
1840
|
-
|
|
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
|
-
|
|
1852
|
-
|
|
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
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
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
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
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
|
|
1898
|
-
|
|
1899
|
-
const
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
const
|
|
1903
|
-
if (
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
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
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
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
|
-
|
|
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
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
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
|
|
1941
|
-
|
|
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
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
const
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
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
|
-
|
|
1956
|
-
|
|
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
|
|
1964
|
-
|
|
1965
|
-
|
|
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 (
|
|
1977
|
-
return
|
|
3338
|
+
if (typeof value === "string") {
|
|
3339
|
+
return value === "" ? undefined : value;
|
|
1978
3340
|
}
|
|
1979
|
-
if (
|
|
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
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
};
|
|
3344
|
+
if (typeof value === "object") {
|
|
3345
|
+
return JSON.stringify(value);
|
|
3346
|
+
}
|
|
3347
|
+
return String(value);
|
|
1989
3348
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
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
|
-
|
|
2013
|
-
|
|
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
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
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
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
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
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
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
|
-
|
|
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
|
};
|