hanzio 1.0.3 → 1.0.6
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 +21 -1
- package/dist/index.d.ts +1 -0
- package/dist/secrets/index.d.ts +27 -0
- package/dist/secrets/vite.d.ts +9 -0
- package/dist/src/api-wrapper/index.js +250 -0
- package/dist/src/gzip/index.js +16 -0
- package/dist/src/index.js +1967 -0
- package/dist/src/jwt/index.js +19 -0
- package/dist/src/p-queue/index.js +439 -0
- package/dist/src/secrets/index.js +145 -0
- package/dist/src/secrets/vite.js +19 -0
- package/dist/src/sitemap/index.js +552 -0
- package/dist/src/zod/index.js +210 -0
- package/package.json +3 -2
- package/src/index.ts +2 -1
- package/src/secrets/index.ts +206 -0
- package/src/secrets/vite.ts +30 -0
|
@@ -0,0 +1,1967 @@
|
|
|
1
|
+
// src/zod/index.ts
|
|
2
|
+
import { z as z3 } from "zod";
|
|
3
|
+
|
|
4
|
+
// src/zod/jsonToZodSchemaMapper.ts
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
function jsonToZodSchemaMapper(jsonObject, zodSchema, config) {
|
|
7
|
+
const result = {};
|
|
8
|
+
const shape = zodSchema instanceof z.ZodObject ? zodSchema.shape : {};
|
|
9
|
+
for (const key of Object.keys(shape)) {
|
|
10
|
+
const fieldConfig = config[key];
|
|
11
|
+
let value = jsonObject[key];
|
|
12
|
+
if (fieldConfig) {
|
|
13
|
+
value = fieldConfig(jsonObject);
|
|
14
|
+
}
|
|
15
|
+
if (shape[key] instanceof z.ZodObject && value && typeof value === "object") {
|
|
16
|
+
result[key] = jsonToZodSchemaMapper(value, shape[key], config[key] || {});
|
|
17
|
+
} else {
|
|
18
|
+
result[key] = value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return zodSchema.parse(result);
|
|
22
|
+
}
|
|
23
|
+
// src/zod/zodMinMaxFilter.ts
|
|
24
|
+
import { z as z2 } from "zod";
|
|
25
|
+
var zodMinMaxFilter = z2.object({
|
|
26
|
+
min: z2.number().optional(),
|
|
27
|
+
max: z2.number().optional()
|
|
28
|
+
});
|
|
29
|
+
function toGteLteFilter(filter) {
|
|
30
|
+
if (!filter)
|
|
31
|
+
return;
|
|
32
|
+
const hasMin = filter.min !== undefined;
|
|
33
|
+
const hasMax = filter.max !== undefined;
|
|
34
|
+
if (!hasMin && !hasMax)
|
|
35
|
+
return;
|
|
36
|
+
return {
|
|
37
|
+
...hasMin && { gte: filter.min },
|
|
38
|
+
...hasMax && { lte: filter.max }
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// src/zod/zodToTypeString.ts
|
|
42
|
+
function getDefProp(def, prop) {
|
|
43
|
+
return def?.[prop];
|
|
44
|
+
}
|
|
45
|
+
function zodToTypeString(schema, indent = 0) {
|
|
46
|
+
const pad = " ".repeat(indent);
|
|
47
|
+
const innerPad = " ".repeat(indent + 1);
|
|
48
|
+
const def = schema._zod?.def ?? schema._def;
|
|
49
|
+
if (!def)
|
|
50
|
+
return "unknown";
|
|
51
|
+
const schemaType = getDefProp(def, "type") ?? getDefProp(def, "typeName");
|
|
52
|
+
switch (schemaType) {
|
|
53
|
+
case "object": {
|
|
54
|
+
const shape = getDefProp(def, "shape");
|
|
55
|
+
if (!shape || Object.keys(shape).length === 0)
|
|
56
|
+
return "{}";
|
|
57
|
+
const fields = Object.entries(shape).map(([key, value]) => {
|
|
58
|
+
const valueType = getDefProp(value._zod?.def ?? value._def, "type");
|
|
59
|
+
const isOptional = valueType === "optional";
|
|
60
|
+
const innerSchema = isOptional ? getDefProp(value._zod?.def ?? value._def, "innerType") ?? value : value;
|
|
61
|
+
const typeStr = zodToTypeString(innerSchema, indent + 1);
|
|
62
|
+
return `${innerPad}${key}${isOptional ? "?" : ""}: ${typeStr}`;
|
|
63
|
+
});
|
|
64
|
+
return `{
|
|
65
|
+
${fields.join(`
|
|
66
|
+
`)}
|
|
67
|
+
${pad}}`;
|
|
68
|
+
}
|
|
69
|
+
case "array": {
|
|
70
|
+
const itemType = getDefProp(def, "element") ?? getDefProp(def, "type");
|
|
71
|
+
return itemType ? `${zodToTypeString(itemType, indent)}[]` : "unknown[]";
|
|
72
|
+
}
|
|
73
|
+
case "string":
|
|
74
|
+
return "string";
|
|
75
|
+
case "number":
|
|
76
|
+
return "number";
|
|
77
|
+
case "boolean":
|
|
78
|
+
return "boolean";
|
|
79
|
+
case "date":
|
|
80
|
+
return "Date";
|
|
81
|
+
case "null":
|
|
82
|
+
return "null";
|
|
83
|
+
case "undefined":
|
|
84
|
+
return "undefined";
|
|
85
|
+
case "any":
|
|
86
|
+
return "any";
|
|
87
|
+
case "unknown":
|
|
88
|
+
return "unknown";
|
|
89
|
+
case "record": {
|
|
90
|
+
const valueType = getDefProp(def, "valueType");
|
|
91
|
+
return valueType ? `Record<string, ${zodToTypeString(valueType, indent)}>` : "Record<string, unknown>";
|
|
92
|
+
}
|
|
93
|
+
case "enum": {
|
|
94
|
+
const values = getDefProp(def, "values");
|
|
95
|
+
return values ? values.map((v) => `"${v}"`).join(" | ") : "string";
|
|
96
|
+
}
|
|
97
|
+
case "literal": {
|
|
98
|
+
const value = getDefProp(def, "value");
|
|
99
|
+
return typeof value === "string" ? `"${value}"` : String(value);
|
|
100
|
+
}
|
|
101
|
+
case "union": {
|
|
102
|
+
const options = getDefProp(def, "options");
|
|
103
|
+
return options ? options.map((o) => zodToTypeString(o, indent)).join(" | ") : "unknown";
|
|
104
|
+
}
|
|
105
|
+
case "optional": {
|
|
106
|
+
const inner = getDefProp(def, "innerType");
|
|
107
|
+
return inner ? `${zodToTypeString(inner, indent)} | undefined` : "unknown";
|
|
108
|
+
}
|
|
109
|
+
case "nullable": {
|
|
110
|
+
const inner = getDefProp(def, "innerType");
|
|
111
|
+
return inner ? `${zodToTypeString(inner, indent)} | null` : "unknown";
|
|
112
|
+
}
|
|
113
|
+
case "default": {
|
|
114
|
+
const inner = getDefProp(def, "innerType");
|
|
115
|
+
return inner ? zodToTypeString(inner, indent) : "unknown";
|
|
116
|
+
}
|
|
117
|
+
case "ZodObject":
|
|
118
|
+
case "ZodArray":
|
|
119
|
+
case "ZodString":
|
|
120
|
+
case "ZodNumber":
|
|
121
|
+
case "ZodBoolean":
|
|
122
|
+
case "ZodDate":
|
|
123
|
+
case "ZodNull":
|
|
124
|
+
case "ZodUndefined":
|
|
125
|
+
case "ZodAny":
|
|
126
|
+
case "ZodUnknown":
|
|
127
|
+
case "ZodRecord":
|
|
128
|
+
case "ZodEnum":
|
|
129
|
+
case "ZodLiteral":
|
|
130
|
+
case "ZodUnion":
|
|
131
|
+
case "ZodOptional":
|
|
132
|
+
case "ZodNullable":
|
|
133
|
+
case "ZodDefault":
|
|
134
|
+
return zodToTypeString(schema, indent);
|
|
135
|
+
default:
|
|
136
|
+
return "unknown";
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function zodToExample(schema) {
|
|
140
|
+
const def = schema._zod?.def ?? schema._def;
|
|
141
|
+
if (!def)
|
|
142
|
+
return null;
|
|
143
|
+
const schemaType = getDefProp(def, "type") ?? getDefProp(def, "typeName");
|
|
144
|
+
switch (schemaType) {
|
|
145
|
+
case "object": {
|
|
146
|
+
const shape = getDefProp(def, "shape");
|
|
147
|
+
if (!shape)
|
|
148
|
+
return {};
|
|
149
|
+
const result = {};
|
|
150
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
151
|
+
const valueType = getDefProp(value._zod?.def ?? value._def, "type");
|
|
152
|
+
const isOptional = valueType === "optional";
|
|
153
|
+
if (!isOptional) {
|
|
154
|
+
result[key] = zodToExample(value);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
case "array": {
|
|
160
|
+
const itemType = getDefProp(def, "element") ?? getDefProp(def, "type");
|
|
161
|
+
return itemType ? [zodToExample(itemType)] : [];
|
|
162
|
+
}
|
|
163
|
+
case "string":
|
|
164
|
+
return "";
|
|
165
|
+
case "number":
|
|
166
|
+
return 0;
|
|
167
|
+
case "boolean":
|
|
168
|
+
return false;
|
|
169
|
+
case "enum": {
|
|
170
|
+
const values = getDefProp(def, "values");
|
|
171
|
+
return values?.[0] ?? "";
|
|
172
|
+
}
|
|
173
|
+
case "literal":
|
|
174
|
+
return getDefProp(def, "value");
|
|
175
|
+
case "optional":
|
|
176
|
+
case "default": {
|
|
177
|
+
const inner = getDefProp(def, "innerType");
|
|
178
|
+
return inner ? zodToExample(inner) : null;
|
|
179
|
+
}
|
|
180
|
+
case "record":
|
|
181
|
+
return {};
|
|
182
|
+
default:
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/zod/index.ts
|
|
188
|
+
function createZId(typeName) {
|
|
189
|
+
return z3.string().min(1, `${typeName} ID is required`).brand();
|
|
190
|
+
}
|
|
191
|
+
function zSingleOrArray(schema) {
|
|
192
|
+
return z3.union([z3.array(schema), schema]);
|
|
193
|
+
}
|
|
194
|
+
function normalizeSingleOrArray(value) {
|
|
195
|
+
if (value == null)
|
|
196
|
+
return [];
|
|
197
|
+
if (Array.isArray(value))
|
|
198
|
+
return [...value];
|
|
199
|
+
return [value];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/api-wrapper/index.ts
|
|
203
|
+
import { ZodError } from "zod";
|
|
204
|
+
|
|
205
|
+
class ResponseValidationError extends Error {
|
|
206
|
+
zodError;
|
|
207
|
+
rawResponse;
|
|
208
|
+
validationIssues;
|
|
209
|
+
constructor(zodError, rawResponse) {
|
|
210
|
+
const issues = zodError.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
|
|
211
|
+
super(`Response validation failed: ${issues}`);
|
|
212
|
+
this.name = "ResponseValidationError";
|
|
213
|
+
this.zodError = zodError;
|
|
214
|
+
this.rawResponse = rawResponse;
|
|
215
|
+
this.validationIssues = issues;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
class HttpResponseError extends Error {
|
|
220
|
+
status;
|
|
221
|
+
body;
|
|
222
|
+
constructor(status, body) {
|
|
223
|
+
super(`HTTP error: ${status} - ${body}`);
|
|
224
|
+
this.name = "HttpResponseError";
|
|
225
|
+
this.status = status;
|
|
226
|
+
this.body = body;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function createApiClient(apiConfig) {
|
|
230
|
+
const request = async (endpointKey, input = {}) => {
|
|
231
|
+
const endpoint = apiConfig.endpoints[endpointKey];
|
|
232
|
+
if (!endpoint) {
|
|
233
|
+
throw new Error(`Unknown API endpoint: ${String(endpointKey)}`);
|
|
234
|
+
}
|
|
235
|
+
const { finalUrl, fullUrl } = buildUrl(endpoint, input.reqParams, input.reqQuery, apiConfig.baseApiUrls, apiConfig.defaultBaseApiUrl, input.url);
|
|
236
|
+
const endpointName = apiConfig.name ? `${apiConfig.name}.${String(endpointKey)}` : String(endpointKey);
|
|
237
|
+
const startedAt = Date.now();
|
|
238
|
+
let retryCount = 0;
|
|
239
|
+
let httpStatus = 0;
|
|
240
|
+
apiConfig.onRequestStart?.({ endpoint: endpointName, url: fullUrl });
|
|
241
|
+
try {
|
|
242
|
+
const config = {
|
|
243
|
+
method: endpoint.method,
|
|
244
|
+
url: finalUrl,
|
|
245
|
+
queryParams: buildQueryParams(endpoint, input.reqQuery),
|
|
246
|
+
body: buildRequestBody(endpoint, input.reqBody),
|
|
247
|
+
headers: buildHeaders(endpoint, input.reqHeaders, apiConfig.defaultHeaders ?? {}),
|
|
248
|
+
doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
|
|
249
|
+
timeoutMs: apiConfig.timeoutMs,
|
|
250
|
+
fetchFn: apiConfig.fetch ?? fetch
|
|
251
|
+
};
|
|
252
|
+
const result = await makeRequestWithRetry(config, apiConfig.retries ?? 3, apiConfig.retryDelayMs ?? 300, (response) => validateAndTransformResponse(endpoint, response), apiConfig.shouldRetry ?? defaultShouldRetry, apiConfig.logger);
|
|
253
|
+
retryCount = result.retryCount;
|
|
254
|
+
httpStatus = result.httpStatus;
|
|
255
|
+
const responseTimeMs = Date.now() - startedAt;
|
|
256
|
+
const responseSizeMb = calculateSizeInMb(result.validatedData);
|
|
257
|
+
const requestSizeMb = calculateSizeInMb({
|
|
258
|
+
method: config.method,
|
|
259
|
+
url: fullUrl,
|
|
260
|
+
body: config.body,
|
|
261
|
+
headers: config.headers
|
|
262
|
+
});
|
|
263
|
+
apiConfig.onRequestEnd?.({
|
|
264
|
+
endpoint: endpointName,
|
|
265
|
+
url: fullUrl,
|
|
266
|
+
status: httpStatus,
|
|
267
|
+
retryCount,
|
|
268
|
+
responseTimeMs
|
|
269
|
+
});
|
|
270
|
+
return {
|
|
271
|
+
data: result.validatedData,
|
|
272
|
+
requestSizeMb,
|
|
273
|
+
responseSizeMb,
|
|
274
|
+
requestSizeTotalMb: requestSizeMb + responseSizeMb,
|
|
275
|
+
responseTimeMs,
|
|
276
|
+
httpStatus,
|
|
277
|
+
retryCount
|
|
278
|
+
};
|
|
279
|
+
} catch (error) {
|
|
280
|
+
apiConfig.logger?.error?.(error);
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
return { request };
|
|
285
|
+
}
|
|
286
|
+
var createApiWrapper = createApiClient;
|
|
287
|
+
async function validateResponse(schema, data) {
|
|
288
|
+
try {
|
|
289
|
+
return await schema.parseAsync(data);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (error instanceof ZodError) {
|
|
292
|
+
throw new ResponseValidationError(error, data);
|
|
293
|
+
}
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async function httpRequest(config) {
|
|
298
|
+
const queryString = config.doNotEncodeQueryParams ? Object.entries(config.queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(config.queryParams).toString();
|
|
299
|
+
const fullUrl = queryString ? `${config.url}?${queryString}` : config.url;
|
|
300
|
+
const abortController = new AbortController;
|
|
301
|
+
const timeout = config.timeoutMs === undefined ? undefined : setTimeout(() => abortController.abort(), config.timeoutMs);
|
|
302
|
+
try {
|
|
303
|
+
return await config.fetchFn(fullUrl, {
|
|
304
|
+
method: config.method,
|
|
305
|
+
headers: config.headers,
|
|
306
|
+
body: buildFetchBody(config.body, config.headers),
|
|
307
|
+
signal: abortController.signal
|
|
308
|
+
});
|
|
309
|
+
} finally {
|
|
310
|
+
if (timeout)
|
|
311
|
+
clearTimeout(timeout);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async function makeRequestWithRetry(config, maxRetries, retryDelayMs, validateFn, shouldRetry, logger) {
|
|
315
|
+
let retryCount = 0;
|
|
316
|
+
while (true) {
|
|
317
|
+
try {
|
|
318
|
+
logger?.debug?.("API request", config);
|
|
319
|
+
const response = await httpRequest(config);
|
|
320
|
+
if (response.status >= 400) {
|
|
321
|
+
if (retryCount < maxRetries && shouldRetry({ response, retryCount, maxRetries })) {
|
|
322
|
+
retryCount++;
|
|
323
|
+
await delay(retryDelayMs);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
throw new HttpResponseError(response.status, await response.text());
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
validatedData: await validateFn(response),
|
|
330
|
+
retryCount,
|
|
331
|
+
httpStatus: response.status
|
|
332
|
+
};
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (error instanceof HttpResponseError)
|
|
335
|
+
throw error;
|
|
336
|
+
if (retryCount >= maxRetries || !shouldRetry({ error, retryCount, maxRetries })) {
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
retryCount++;
|
|
340
|
+
await delay(retryDelayMs);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async function validateAndTransformResponse(endpoint, response) {
|
|
345
|
+
const responseHeaders = getHeadersAsObject(response.headers);
|
|
346
|
+
const contentType = responseHeaders["content-type"] ?? "";
|
|
347
|
+
const responseData = contentType.includes("application/json") ? await response.json() : await response.text();
|
|
348
|
+
const transformedData = endpoint.resFormatter ? endpoint.resFormatter(responseData, responseHeaders) : responseData;
|
|
349
|
+
return validateResponse(endpoint.resSchema, transformedData);
|
|
350
|
+
}
|
|
351
|
+
function buildUrl(endpoint, params, reqQuery, baseApiUrls, defaultBaseApiUrl, url) {
|
|
352
|
+
if (url) {
|
|
353
|
+
const finalUrl2 = replacePathParams(url, params);
|
|
354
|
+
const queryString2 = new URLSearchParams(buildQueryParams(endpoint, reqQuery)).toString();
|
|
355
|
+
return {
|
|
356
|
+
finalUrl: finalUrl2,
|
|
357
|
+
fullUrl: queryString2 ? `${finalUrl2}?${queryString2}` : finalUrl2
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
|
|
361
|
+
if (!defaultBaseUrl) {
|
|
362
|
+
throw new Error("At least one base API URL is required");
|
|
363
|
+
}
|
|
364
|
+
const baseUrlKey = endpoint.baseApiUrl ?? defaultBaseUrl;
|
|
365
|
+
const configBaseUrl = baseApiUrls[baseUrlKey];
|
|
366
|
+
if (!configBaseUrl) {
|
|
367
|
+
throw new Error(`Unknown base API URL: ${baseUrlKey}`);
|
|
368
|
+
}
|
|
369
|
+
const baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
|
|
370
|
+
const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
|
|
371
|
+
const finalUrl = replacePathParams(`${baseUrl}${path}`, params);
|
|
372
|
+
const queryString = new URLSearchParams(buildQueryParams(endpoint, reqQuery)).toString();
|
|
373
|
+
return {
|
|
374
|
+
finalUrl,
|
|
375
|
+
fullUrl: queryString ? `${finalUrl}?${queryString}` : finalUrl
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function buildQueryParams(endpoint, query) {
|
|
379
|
+
const defaultParams = endpoint.reqDefaultQueryParams ?? {};
|
|
380
|
+
const parsedQuery = endpoint.reqQuerySchema ? endpoint.reqQuerySchema.parse(query) : query ?? {};
|
|
381
|
+
const merged = { ...defaultParams, ...parsedQuery };
|
|
382
|
+
return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
|
|
383
|
+
}
|
|
384
|
+
function buildRequestBody(endpoint, body) {
|
|
385
|
+
if (!endpoint.reqBodySchema)
|
|
386
|
+
return body;
|
|
387
|
+
const parsedBody = endpoint.reqBodySchema.parse(body);
|
|
388
|
+
if (endpoint.reqBodyFormat !== "form-data") {
|
|
389
|
+
return parsedBody;
|
|
390
|
+
}
|
|
391
|
+
const formData = new FormData;
|
|
392
|
+
for (const [key, value] of Object.entries(parsedBody)) {
|
|
393
|
+
if (value == null)
|
|
394
|
+
continue;
|
|
395
|
+
formData.set(key, value instanceof Blob ? value : String(value));
|
|
396
|
+
}
|
|
397
|
+
return formData;
|
|
398
|
+
}
|
|
399
|
+
function buildHeaders(endpoint, headers, defaultHeaders) {
|
|
400
|
+
const contentType = endpoint.reqBodyFormat === "json" ? "application/json" : undefined;
|
|
401
|
+
const parsedHeaders = endpoint.reqHeadersSchema ? endpoint.reqHeadersSchema.parse(headers) : headers ?? {};
|
|
402
|
+
return {
|
|
403
|
+
...defaultHeaders,
|
|
404
|
+
...contentType && { "Content-Type": contentType },
|
|
405
|
+
...endpoint.defaultHeaders,
|
|
406
|
+
...parsedHeaders
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function buildFetchBody(body, headers) {
|
|
410
|
+
if (body === undefined || body === null)
|
|
411
|
+
return;
|
|
412
|
+
if (body instanceof FormData || body instanceof Blob)
|
|
413
|
+
return body;
|
|
414
|
+
if (headers["Content-Type"] === "application/json")
|
|
415
|
+
return JSON.stringify(body);
|
|
416
|
+
return body;
|
|
417
|
+
}
|
|
418
|
+
function replacePathParams(path, params) {
|
|
419
|
+
return path.replace(/:(\w+)/g, (_, key) => {
|
|
420
|
+
const value = params?.[key];
|
|
421
|
+
if (value === undefined) {
|
|
422
|
+
throw new Error(`Missing required path parameter: ${key}`);
|
|
423
|
+
}
|
|
424
|
+
return encodeURIComponent(String(value));
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function getHeadersAsObject(headers) {
|
|
428
|
+
const result = {};
|
|
429
|
+
headers.forEach((value, key) => {
|
|
430
|
+
result[key] = value;
|
|
431
|
+
});
|
|
432
|
+
return result;
|
|
433
|
+
}
|
|
434
|
+
function calculateSizeInMb(data) {
|
|
435
|
+
const sizeInBytes = new TextEncoder().encode(JSON.stringify(data)).length;
|
|
436
|
+
return sizeInBytes / (1024 * 1024);
|
|
437
|
+
}
|
|
438
|
+
function defaultShouldRetry({ error, response }) {
|
|
439
|
+
if (error)
|
|
440
|
+
return true;
|
|
441
|
+
return response ? response.status >= 500 : false;
|
|
442
|
+
}
|
|
443
|
+
function delay(ms) {
|
|
444
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/p-queue/index.ts
|
|
448
|
+
class TimeoutError extends Error {
|
|
449
|
+
constructor(message) {
|
|
450
|
+
super(message);
|
|
451
|
+
this.name = "TimeoutError";
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
class PriorityQueue {
|
|
456
|
+
#queue = [];
|
|
457
|
+
enqueue(run, options = {}) {
|
|
458
|
+
const element = {
|
|
459
|
+
priority: options.priority ?? 0,
|
|
460
|
+
id: options.id,
|
|
461
|
+
run
|
|
462
|
+
};
|
|
463
|
+
const index = this.#queue.findIndex((item) => item.priority < element.priority);
|
|
464
|
+
if (index === -1) {
|
|
465
|
+
this.#queue.push(element);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
this.#queue.splice(index, 0, element);
|
|
469
|
+
}
|
|
470
|
+
dequeue() {
|
|
471
|
+
return this.#queue.shift()?.run;
|
|
472
|
+
}
|
|
473
|
+
filter(options) {
|
|
474
|
+
return this.#queue.filter((item) => Object.entries(options).every(([key, value]) => item[key] === value)).map((item) => item.run);
|
|
475
|
+
}
|
|
476
|
+
setPriority(id, priority) {
|
|
477
|
+
const index = this.#queue.findIndex((item2) => item2.id === id);
|
|
478
|
+
if (index === -1) {
|
|
479
|
+
throw new ReferenceError(`No promise function with the id "${id}" exists in the queue.`);
|
|
480
|
+
}
|
|
481
|
+
const [item] = this.#queue.splice(index, 1);
|
|
482
|
+
this.enqueue(item.run, { ...item, priority });
|
|
483
|
+
}
|
|
484
|
+
remove(id) {
|
|
485
|
+
const index = this.#queue.findIndex((item) => item.id === id);
|
|
486
|
+
if (index !== -1) {
|
|
487
|
+
this.#queue.splice(index, 1);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
get size() {
|
|
491
|
+
return this.#queue.length;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
class PQueue {
|
|
496
|
+
#listeners = new Map;
|
|
497
|
+
#queue;
|
|
498
|
+
#queueClass;
|
|
499
|
+
#pending = 0;
|
|
500
|
+
#concurrency;
|
|
501
|
+
#isPaused;
|
|
502
|
+
#intervalCap;
|
|
503
|
+
#interval;
|
|
504
|
+
#carryoverIntervalCount;
|
|
505
|
+
#strict;
|
|
506
|
+
#intervalCount = 0;
|
|
507
|
+
#intervalEnd = 0;
|
|
508
|
+
#intervalTimer;
|
|
509
|
+
#strictTimestamps = [];
|
|
510
|
+
#rateLimited = false;
|
|
511
|
+
#idCounter = 1n;
|
|
512
|
+
#runningTasks = new Map;
|
|
513
|
+
timeout;
|
|
514
|
+
constructor(options = {}) {
|
|
515
|
+
const {
|
|
516
|
+
concurrency = Number.POSITIVE_INFINITY,
|
|
517
|
+
autoStart = true,
|
|
518
|
+
queueClass = PriorityQueue,
|
|
519
|
+
intervalCap = Number.POSITIVE_INFINITY,
|
|
520
|
+
interval = 0,
|
|
521
|
+
carryoverIntervalCount,
|
|
522
|
+
carryoverConcurrencyCount,
|
|
523
|
+
strict = false,
|
|
524
|
+
timeout
|
|
525
|
+
} = options;
|
|
526
|
+
if (!(typeof concurrency === "number" && concurrency >= 1)) {
|
|
527
|
+
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
|
528
|
+
}
|
|
529
|
+
if (!(typeof intervalCap === "number" && intervalCap >= 1)) {
|
|
530
|
+
throw new TypeError("Expected `intervalCap` to be a number from 1 and up");
|
|
531
|
+
}
|
|
532
|
+
if (!(typeof interval === "number" && Number.isFinite(interval) && interval >= 0)) {
|
|
533
|
+
throw new TypeError("Expected `interval` to be a finite number >= 0");
|
|
534
|
+
}
|
|
535
|
+
if (strict && interval === 0) {
|
|
536
|
+
throw new TypeError("The `strict` option requires a non-zero `interval`");
|
|
537
|
+
}
|
|
538
|
+
if (strict && intervalCap === Number.POSITIVE_INFINITY) {
|
|
539
|
+
throw new TypeError("The `strict` option requires a finite `intervalCap`");
|
|
540
|
+
}
|
|
541
|
+
if (timeout !== undefined && !(Number.isFinite(timeout) && timeout > 0)) {
|
|
542
|
+
throw new TypeError("Expected `timeout` to be a positive finite number");
|
|
543
|
+
}
|
|
544
|
+
this.#queueClass = queueClass;
|
|
545
|
+
this.#queue = new queueClass;
|
|
546
|
+
this.#concurrency = concurrency;
|
|
547
|
+
this.#isPaused = !autoStart;
|
|
548
|
+
this.#intervalCap = intervalCap;
|
|
549
|
+
this.#interval = interval;
|
|
550
|
+
this.#carryoverIntervalCount = carryoverIntervalCount ?? carryoverConcurrencyCount ?? false;
|
|
551
|
+
this.#strict = strict;
|
|
552
|
+
this.timeout = timeout;
|
|
553
|
+
}
|
|
554
|
+
on(event, listener) {
|
|
555
|
+
const listeners = this.#listeners.get(event) ?? new Set;
|
|
556
|
+
listeners.add(listener);
|
|
557
|
+
this.#listeners.set(event, listeners);
|
|
558
|
+
return this;
|
|
559
|
+
}
|
|
560
|
+
off(event, listener) {
|
|
561
|
+
this.#listeners.get(event)?.delete(listener);
|
|
562
|
+
return this;
|
|
563
|
+
}
|
|
564
|
+
emit(event, ...arguments_) {
|
|
565
|
+
const listeners = this.#listeners.get(event);
|
|
566
|
+
if (!listeners || listeners.size === 0) {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
for (const listener of [...listeners]) {
|
|
570
|
+
listener(...arguments_);
|
|
571
|
+
}
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
get concurrency() {
|
|
575
|
+
return this.#concurrency;
|
|
576
|
+
}
|
|
577
|
+
set concurrency(newConcurrency) {
|
|
578
|
+
if (!(typeof newConcurrency === "number" && newConcurrency >= 1)) {
|
|
579
|
+
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
|
580
|
+
}
|
|
581
|
+
this.#concurrency = newConcurrency;
|
|
582
|
+
this.#processQueue();
|
|
583
|
+
}
|
|
584
|
+
get size() {
|
|
585
|
+
return this.#queue.size;
|
|
586
|
+
}
|
|
587
|
+
sizeBy(options) {
|
|
588
|
+
return this.#queue.filter(options).length;
|
|
589
|
+
}
|
|
590
|
+
get pending() {
|
|
591
|
+
return this.#pending;
|
|
592
|
+
}
|
|
593
|
+
get isPaused() {
|
|
594
|
+
return this.#isPaused;
|
|
595
|
+
}
|
|
596
|
+
get isRateLimited() {
|
|
597
|
+
this.#updateRateLimitState();
|
|
598
|
+
return this.#rateLimited;
|
|
599
|
+
}
|
|
600
|
+
get isSaturated() {
|
|
601
|
+
return this.#pending >= this.#concurrency && this.#queue.size > 0 || this.isRateLimited && this.#queue.size > 0;
|
|
602
|
+
}
|
|
603
|
+
get runningTasks() {
|
|
604
|
+
return [...this.#runningTasks.values()].map((task) => ({ ...task }));
|
|
605
|
+
}
|
|
606
|
+
setPriority(id, priority) {
|
|
607
|
+
if (typeof priority !== "number" || !Number.isFinite(priority)) {
|
|
608
|
+
throw new TypeError("Expected `priority` to be a finite number");
|
|
609
|
+
}
|
|
610
|
+
this.#queue.setPriority(id, priority);
|
|
611
|
+
}
|
|
612
|
+
async add(function_, options = {}) {
|
|
613
|
+
const taskOptions = {
|
|
614
|
+
timeout: this.timeout,
|
|
615
|
+
...options,
|
|
616
|
+
id: options.id ?? (this.#idCounter++).toString()
|
|
617
|
+
};
|
|
618
|
+
return new Promise((resolve, reject) => {
|
|
619
|
+
const taskSymbol = Symbol(taskOptions.id);
|
|
620
|
+
let didRun = false;
|
|
621
|
+
let queuedAbortHandler;
|
|
622
|
+
let runningAbortHandler;
|
|
623
|
+
const cleanupQueuedAbort = () => {
|
|
624
|
+
if (queuedAbortHandler) {
|
|
625
|
+
taskOptions.signal?.removeEventListener("abort", queuedAbortHandler);
|
|
626
|
+
queuedAbortHandler = undefined;
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
const run = async () => {
|
|
630
|
+
didRun = true;
|
|
631
|
+
cleanupQueuedAbort();
|
|
632
|
+
this.#pending++;
|
|
633
|
+
this.#runningTasks.set(taskSymbol, {
|
|
634
|
+
id: taskOptions.id,
|
|
635
|
+
priority: taskOptions.priority ?? 0,
|
|
636
|
+
startTime: Date.now(),
|
|
637
|
+
timeout: taskOptions.timeout
|
|
638
|
+
});
|
|
639
|
+
try {
|
|
640
|
+
taskOptions.signal?.throwIfAborted();
|
|
641
|
+
const operation = Promise.resolve(function_({ signal: taskOptions.signal }));
|
|
642
|
+
const result = await this.#withTimeoutAndAbort(operation, taskOptions, (listener) => {
|
|
643
|
+
runningAbortHandler = listener;
|
|
644
|
+
});
|
|
645
|
+
resolve(result);
|
|
646
|
+
this.emit("completed", result);
|
|
647
|
+
} catch (error) {
|
|
648
|
+
reject(error);
|
|
649
|
+
this.emit("error", error);
|
|
650
|
+
} finally {
|
|
651
|
+
if (runningAbortHandler) {
|
|
652
|
+
taskOptions.signal?.removeEventListener("abort", runningAbortHandler);
|
|
653
|
+
}
|
|
654
|
+
this.#runningTasks.delete(taskSymbol);
|
|
655
|
+
queueMicrotask(() => {
|
|
656
|
+
this.#next();
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
if (taskOptions.signal?.aborted) {
|
|
661
|
+
reject(taskOptions.signal.reason);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
if (taskOptions.signal) {
|
|
665
|
+
queuedAbortHandler = () => {
|
|
666
|
+
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);
|
|
778
|
+
};
|
|
779
|
+
setAbortListener(listener);
|
|
780
|
+
options.signal?.addEventListener("abort", listener, { once: true });
|
|
781
|
+
}));
|
|
782
|
+
}
|
|
783
|
+
try {
|
|
784
|
+
return await Promise.race(races);
|
|
785
|
+
} finally {
|
|
786
|
+
if (timeoutId) {
|
|
787
|
+
clearTimeout(timeoutId);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
#next() {
|
|
792
|
+
this.#pending--;
|
|
793
|
+
if (this.#pending === 0) {
|
|
794
|
+
this.emit("pendingZero");
|
|
795
|
+
}
|
|
796
|
+
this.#processQueue();
|
|
797
|
+
this.emit("next");
|
|
798
|
+
}
|
|
799
|
+
#processQueue() {
|
|
800
|
+
while (this.#tryToStartAnother()) {}
|
|
801
|
+
this.#updateRateLimitState();
|
|
802
|
+
}
|
|
803
|
+
#tryToStartAnother() {
|
|
804
|
+
if (this.#queue.size === 0) {
|
|
805
|
+
this.#clearIntervalTimer();
|
|
806
|
+
this.emit("empty");
|
|
807
|
+
if (this.#pending === 0) {
|
|
808
|
+
this.emit("idle");
|
|
809
|
+
}
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
if (this.#isPaused || this.#pending >= this.#concurrency || !this.#doesIntervalAllowAnother()) {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
const job = this.#queue.dequeue();
|
|
816
|
+
if (!job) {
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
this.#consumeIntervalSlot();
|
|
820
|
+
this.emit("active");
|
|
821
|
+
job();
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
#doesIntervalAllowAnother() {
|
|
825
|
+
if (this.#intervalCap === Number.POSITIVE_INFINITY || this.#interval === 0) {
|
|
826
|
+
return true;
|
|
827
|
+
}
|
|
828
|
+
const now = Date.now();
|
|
829
|
+
if (this.#strict) {
|
|
830
|
+
this.#strictTimestamps = this.#strictTimestamps.filter((timestamp) => now - timestamp < this.#interval);
|
|
831
|
+
if (this.#strictTimestamps.length < this.#intervalCap) {
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
this.#scheduleIntervalResume(this.#interval - (now - this.#strictTimestamps[0]));
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
if (this.#intervalEnd === 0 || now >= this.#intervalEnd) {
|
|
838
|
+
this.#intervalCount = this.#carryoverIntervalCount ? this.#pending : 0;
|
|
839
|
+
this.#intervalEnd = now + this.#interval;
|
|
840
|
+
}
|
|
841
|
+
if (this.#intervalCount < this.#intervalCap) {
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
844
|
+
this.#scheduleIntervalResume(this.#intervalEnd - now);
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
#consumeIntervalSlot() {
|
|
848
|
+
if (this.#intervalCap === Number.POSITIVE_INFINITY || this.#interval === 0) {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
if (this.#strict) {
|
|
852
|
+
this.#strictTimestamps.push(Date.now());
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
this.#intervalCount++;
|
|
856
|
+
}
|
|
857
|
+
#scheduleIntervalResume(delay2) {
|
|
858
|
+
if (this.#intervalTimer) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
this.#intervalTimer = setTimeout(() => {
|
|
862
|
+
this.#intervalTimer = undefined;
|
|
863
|
+
this.#processQueue();
|
|
864
|
+
}, Math.max(0, delay2));
|
|
865
|
+
}
|
|
866
|
+
#clearIntervalTimer() {
|
|
867
|
+
if (this.#intervalTimer) {
|
|
868
|
+
clearTimeout(this.#intervalTimer);
|
|
869
|
+
this.#intervalTimer = undefined;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
#updateRateLimitState() {
|
|
873
|
+
const previous = this.#rateLimited;
|
|
874
|
+
const isRateLimited = this.#queue.size > 0 && this.#intervalCap !== Number.POSITIVE_INFINITY && this.#interval > 0 && !this.#doesIntervalAllowAnother();
|
|
875
|
+
this.#rateLimited = isRateLimited;
|
|
876
|
+
if (previous !== isRateLimited) {
|
|
877
|
+
this.emit(isRateLimited ? "rateLimit" : "rateLimitCleared");
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/jwt/index.ts
|
|
883
|
+
import * as jose from "jose";
|
|
884
|
+
async function jwtSign(payload, secret) {
|
|
885
|
+
const secretKey = new TextEncoder().encode(secret);
|
|
886
|
+
return await new jose.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().sign(secretKey);
|
|
887
|
+
}
|
|
888
|
+
async function jwtVerify2(token, secret) {
|
|
889
|
+
try {
|
|
890
|
+
const secretKey = new TextEncoder().encode(secret);
|
|
891
|
+
const { payload } = await jose.jwtVerify(token, secretKey);
|
|
892
|
+
return payload;
|
|
893
|
+
} catch (_error) {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// src/gzip/index.ts
|
|
899
|
+
import pako from "pako";
|
|
900
|
+
function compressString(input) {
|
|
901
|
+
const uint8Array = new TextEncoder().encode(input);
|
|
902
|
+
const compressed = pako.deflate(uint8Array);
|
|
903
|
+
return btoa(String.fromCharCode.apply(null, compressed));
|
|
904
|
+
}
|
|
905
|
+
function decompressString(compressedString) {
|
|
906
|
+
const compressedData = new Uint8Array(atob(compressedString).split("").map((char) => char.charCodeAt(0)));
|
|
907
|
+
const decompressed = pako.inflate(compressedData);
|
|
908
|
+
return new TextDecoder().decode(decompressed);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// src/sitemap/index.ts
|
|
912
|
+
var createTimeoutController = (timeout) => {
|
|
913
|
+
const controller = new AbortController;
|
|
914
|
+
setTimeout(() => controller.abort(), timeout);
|
|
915
|
+
return controller;
|
|
916
|
+
};
|
|
917
|
+
var discoverSitemaps = async (domain) => {
|
|
918
|
+
const normalizedDomain = domain.replace(/\/$/, "");
|
|
919
|
+
const commonLocations = [
|
|
920
|
+
"/sitemap.xml",
|
|
921
|
+
"/sitemap_index.xml",
|
|
922
|
+
"/sitemap/",
|
|
923
|
+
"/sitemaps.xml",
|
|
924
|
+
"/sitemap/sitemap.xml"
|
|
925
|
+
];
|
|
926
|
+
const sitemaps = new Set;
|
|
927
|
+
const requestOptions = {
|
|
928
|
+
headers: {
|
|
929
|
+
"User-Agent": "SitemapCrawler/1.0"
|
|
930
|
+
},
|
|
931
|
+
signal: createTimeoutController(5000).signal
|
|
932
|
+
};
|
|
933
|
+
try {
|
|
934
|
+
const robotsTxt = await fetch(`${normalizedDomain}/robots.txt`, {
|
|
935
|
+
...requestOptions
|
|
936
|
+
});
|
|
937
|
+
if (robotsTxt.ok) {
|
|
938
|
+
const text = await robotsTxt.text();
|
|
939
|
+
const sitemapMatches = text.match(/Sitemap: (.*)/gi);
|
|
940
|
+
if (sitemapMatches) {
|
|
941
|
+
sitemapMatches.forEach((match) => {
|
|
942
|
+
const url = match.replace("Sitemap:", "").trim();
|
|
943
|
+
sitemaps.add(url);
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
if (sitemaps.size > 0) {
|
|
947
|
+
return [...sitemaps];
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
} catch (error) {
|
|
951
|
+
console.warn("Could not fetch robots.txt:", error instanceof Error ? error.message : "Unknown error");
|
|
952
|
+
}
|
|
953
|
+
for (const location of commonLocations) {
|
|
954
|
+
try {
|
|
955
|
+
const response = await fetch(`${normalizedDomain}${location}`, {
|
|
956
|
+
...requestOptions,
|
|
957
|
+
method: "HEAD"
|
|
958
|
+
});
|
|
959
|
+
if (response.ok) {
|
|
960
|
+
return [`${normalizedDomain}${location}`];
|
|
961
|
+
}
|
|
962
|
+
} catch (_error) {}
|
|
963
|
+
}
|
|
964
|
+
return [];
|
|
965
|
+
};
|
|
966
|
+
var getDomainSitemap = async (domain, options = {}) => {
|
|
967
|
+
const {
|
|
968
|
+
filterIndexes,
|
|
969
|
+
concurrency = 10,
|
|
970
|
+
maxDepth = 5,
|
|
971
|
+
timeout = 1e4
|
|
972
|
+
} = options;
|
|
973
|
+
let normalizedDomain = domain.replace(/\/$/, "");
|
|
974
|
+
if (!normalizedDomain.startsWith("http")) {
|
|
975
|
+
normalizedDomain = `https://${normalizedDomain}`;
|
|
976
|
+
}
|
|
977
|
+
const sitemapUrls = await discoverSitemaps(normalizedDomain);
|
|
978
|
+
if (sitemapUrls.length === 0) {
|
|
979
|
+
throw new Error("No sitemaps found for the domain");
|
|
980
|
+
}
|
|
981
|
+
const queue = new PQueue({ concurrency });
|
|
982
|
+
const result = {
|
|
983
|
+
links: new Set,
|
|
984
|
+
visited: new Set
|
|
985
|
+
};
|
|
986
|
+
const locRegex = /<loc>([^<]+)<\/loc>/g;
|
|
987
|
+
const isXmlSitemap = (url) => url.endsWith(".xml") || url.toLowerCase().includes("sitemap");
|
|
988
|
+
const processSitemap = async (sitemapUrl, depth = 0) => {
|
|
989
|
+
if (depth > maxDepth || result.visited.has(sitemapUrl)) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
result.visited.add(sitemapUrl);
|
|
993
|
+
try {
|
|
994
|
+
const controller = createTimeoutController(timeout);
|
|
995
|
+
const response = await fetch(sitemapUrl, {
|
|
996
|
+
headers: {
|
|
997
|
+
"User-Agent": "SitemapCrawler/1.0"
|
|
998
|
+
},
|
|
999
|
+
signal: controller.signal
|
|
1000
|
+
});
|
|
1001
|
+
if (!response.ok)
|
|
1002
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
1003
|
+
const data = await response.text();
|
|
1004
|
+
const matches = [...data.matchAll(locRegex)].map((match) => match[1].trim()).filter((link) => {
|
|
1005
|
+
if (filterIndexes && isXmlSitemap(link)) {
|
|
1006
|
+
return link.includes(filterIndexes);
|
|
1007
|
+
}
|
|
1008
|
+
return true;
|
|
1009
|
+
});
|
|
1010
|
+
for (const link of matches) {
|
|
1011
|
+
if (isXmlSitemap(link)) {
|
|
1012
|
+
queue.add(() => processSitemap(link, depth + 1));
|
|
1013
|
+
} else {
|
|
1014
|
+
result.links.add(link);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
console.warn(`Failed to process sitemap ${sitemapUrl}:`, error instanceof Error ? error.message : "Unknown error");
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
await Promise.all(sitemapUrls.map((sitemapUrl) => queue.add(() => processSitemap(sitemapUrl))));
|
|
1022
|
+
await queue.onIdle();
|
|
1023
|
+
return [...result.links];
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
// src/secrets/vite.ts
|
|
1027
|
+
function getViteDefine(secretSet) {
|
|
1028
|
+
return Object.fromEntries(Object.entries(secretSet.secrets()).map(([key, value]) => [
|
|
1029
|
+
`import.meta.env.${key}`,
|
|
1030
|
+
JSON.stringify(value)
|
|
1031
|
+
]));
|
|
1032
|
+
}
|
|
1033
|
+
function viteSecretSetPlugin(secretSet) {
|
|
1034
|
+
return {
|
|
1035
|
+
name: "hanzio-secret-set",
|
|
1036
|
+
config: () => ({
|
|
1037
|
+
define: getViteDefine(secretSet)
|
|
1038
|
+
})
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// src/secrets/index.ts
|
|
1043
|
+
import { InfisicalSDK } from "@infisical/sdk";
|
|
1044
|
+
import { networkInterfaces } from "node:os";
|
|
1045
|
+
var DEFAULT_INFISICAL_SITE_URL = "https://eu.infisical.com";
|
|
1046
|
+
var LOCAL_IP_VALUE = "LOCAL_IP";
|
|
1047
|
+
var VPN_INTERFACE_RE = /^(utun|tun|tailscale|wg|ipsec|ppp)/i;
|
|
1048
|
+
function getSecretEnvironment() {
|
|
1049
|
+
const env = process.env.SECRETS_ENV || "development";
|
|
1050
|
+
if (env === "production" || env === "prod")
|
|
1051
|
+
return "prod";
|
|
1052
|
+
if (env === "staging")
|
|
1053
|
+
return "staging";
|
|
1054
|
+
return "dev";
|
|
1055
|
+
}
|
|
1056
|
+
async function defineSecretSet(keys, options) {
|
|
1057
|
+
const keySet = [...new Set(keys)];
|
|
1058
|
+
const loader = options.loader ?? loadInfisicalSecrets;
|
|
1059
|
+
const getLocalIp = options.getLocalIp ?? getLocalIpAddress;
|
|
1060
|
+
let cachedSecrets = null;
|
|
1061
|
+
const resolveValue = (value) => {
|
|
1062
|
+
if (value === LOCAL_IP_VALUE)
|
|
1063
|
+
return getLocalIp();
|
|
1064
|
+
return value;
|
|
1065
|
+
};
|
|
1066
|
+
const loadSecrets = async (force = false) => {
|
|
1067
|
+
const envSecrets = readEnvSecrets(keySet, force ? cachedSecrets : null);
|
|
1068
|
+
const keysMissingFromEnv = keySet.filter((key) => !envSecrets[key]);
|
|
1069
|
+
const loadedSecrets = keysMissingFromEnv.length === 0 && !force ? {} : await loader({
|
|
1070
|
+
keys: keySet,
|
|
1071
|
+
projectId: options.projectId,
|
|
1072
|
+
environment: resolveEnvironment(options.environment),
|
|
1073
|
+
siteUrl: options.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
|
|
1074
|
+
clientIdEnvKey: options.clientIdEnvKey ?? "INFISICAL_CLIENT_ID",
|
|
1075
|
+
clientSecretEnvKey: options.clientSecretEnvKey ?? "INFISICAL_CLIENT_SECRET"
|
|
1076
|
+
});
|
|
1077
|
+
const missingKeys = keySet.filter((key) => !(envSecrets[key] ?? loadedSecrets[key]));
|
|
1078
|
+
if (missingKeys.length > 0) {
|
|
1079
|
+
throw new Error(`Missing secrets: ${missingKeys.join(", ")}`);
|
|
1080
|
+
}
|
|
1081
|
+
const nextSecrets = Object.fromEntries(keySet.map((key) => [
|
|
1082
|
+
key,
|
|
1083
|
+
resolveValue(envSecrets[key] ?? loadedSecrets[key])
|
|
1084
|
+
]));
|
|
1085
|
+
for (const [key, value] of Object.entries(nextSecrets)) {
|
|
1086
|
+
process.env[key] = value;
|
|
1087
|
+
}
|
|
1088
|
+
cachedSecrets = nextSecrets;
|
|
1089
|
+
return nextSecrets;
|
|
1090
|
+
};
|
|
1091
|
+
const secretSet = {
|
|
1092
|
+
secret(key) {
|
|
1093
|
+
const value = cachedSecrets?.[key] ?? process.env[key];
|
|
1094
|
+
if (!value) {
|
|
1095
|
+
throw new Error(`Secret ${key} not found`);
|
|
1096
|
+
}
|
|
1097
|
+
return resolveValue(value);
|
|
1098
|
+
},
|
|
1099
|
+
secrets() {
|
|
1100
|
+
if (!cachedSecrets) {
|
|
1101
|
+
throw new Error("Secrets not initialized.");
|
|
1102
|
+
}
|
|
1103
|
+
return cachedSecrets;
|
|
1104
|
+
},
|
|
1105
|
+
async reload() {
|
|
1106
|
+
await loadSecrets(true);
|
|
1107
|
+
return secretSet;
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
await loadSecrets();
|
|
1111
|
+
return secretSet;
|
|
1112
|
+
}
|
|
1113
|
+
async function loadInfisicalSecrets({
|
|
1114
|
+
keys,
|
|
1115
|
+
projectId,
|
|
1116
|
+
environment,
|
|
1117
|
+
siteUrl,
|
|
1118
|
+
clientIdEnvKey,
|
|
1119
|
+
clientSecretEnvKey
|
|
1120
|
+
}) {
|
|
1121
|
+
const clientId = process.env[clientIdEnvKey];
|
|
1122
|
+
const clientSecret = process.env[clientSecretEnvKey];
|
|
1123
|
+
if (!clientId || !clientSecret) {
|
|
1124
|
+
throw new Error("Missing Infisical credentials");
|
|
1125
|
+
}
|
|
1126
|
+
const client = new InfisicalSDK({ siteUrl });
|
|
1127
|
+
await client.auth().universalAuth.login({
|
|
1128
|
+
clientId,
|
|
1129
|
+
clientSecret
|
|
1130
|
+
});
|
|
1131
|
+
const { secrets } = await client.secrets().listSecrets({
|
|
1132
|
+
environment,
|
|
1133
|
+
projectId
|
|
1134
|
+
});
|
|
1135
|
+
const wantedKeys = new Set(keys);
|
|
1136
|
+
return Object.fromEntries(secrets.filter((secret) => wantedKeys.has(secret.secretKey)).map((secret) => [secret.secretKey, secret.secretValue]));
|
|
1137
|
+
}
|
|
1138
|
+
function resolveEnvironment(environment) {
|
|
1139
|
+
if (typeof environment === "function")
|
|
1140
|
+
return environment();
|
|
1141
|
+
return environment ?? getSecretEnvironment();
|
|
1142
|
+
}
|
|
1143
|
+
function readEnvSecrets(keys, previousSecrets = null) {
|
|
1144
|
+
return Object.fromEntries(keys.filter((key) => {
|
|
1145
|
+
const value = process.env[key];
|
|
1146
|
+
return value && value !== previousSecrets?.[key];
|
|
1147
|
+
}).map((key) => [key, process.env[key]]));
|
|
1148
|
+
}
|
|
1149
|
+
function getLocalIpAddress() {
|
|
1150
|
+
const interfaces = networkInterfaces();
|
|
1151
|
+
for (const name of Object.keys(interfaces)) {
|
|
1152
|
+
if (VPN_INTERFACE_RE.test(name))
|
|
1153
|
+
continue;
|
|
1154
|
+
const nets = interfaces[name];
|
|
1155
|
+
if (!nets)
|
|
1156
|
+
continue;
|
|
1157
|
+
for (const net of nets) {
|
|
1158
|
+
if (net.family === "IPv4" && !net.internal) {
|
|
1159
|
+
return net.address;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
return "127.0.0.1";
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// src/typedswitch/index.ts
|
|
1167
|
+
function runSwitch(value, keyOrCases, casesOrDefault, defaultCase) {
|
|
1168
|
+
let key;
|
|
1169
|
+
let cases;
|
|
1170
|
+
let defaultHandler;
|
|
1171
|
+
let discriminantKey;
|
|
1172
|
+
if (typeof value === "string") {
|
|
1173
|
+
key = value;
|
|
1174
|
+
cases = keyOrCases;
|
|
1175
|
+
defaultHandler = casesOrDefault;
|
|
1176
|
+
} else {
|
|
1177
|
+
if (typeof keyOrCases !== "string") {
|
|
1178
|
+
throw new Error("Invalid typedSwitch call: object mode requires a string discriminant key");
|
|
1179
|
+
}
|
|
1180
|
+
discriminantKey = keyOrCases;
|
|
1181
|
+
const discriminantValue = value[discriminantKey];
|
|
1182
|
+
if (typeof discriminantValue !== "string") {
|
|
1183
|
+
throw new Error(`Invalid discriminant value for key "${discriminantKey}": expected string, received ${typeof discriminantValue}`);
|
|
1184
|
+
}
|
|
1185
|
+
key = discriminantValue;
|
|
1186
|
+
cases = casesOrDefault;
|
|
1187
|
+
defaultHandler = defaultCase;
|
|
1188
|
+
}
|
|
1189
|
+
const handler = cases[key];
|
|
1190
|
+
if (handler) {
|
|
1191
|
+
return handler(value);
|
|
1192
|
+
}
|
|
1193
|
+
if (defaultHandler) {
|
|
1194
|
+
return defaultHandler(value);
|
|
1195
|
+
}
|
|
1196
|
+
const availableCases = Object.keys(cases).join(", ");
|
|
1197
|
+
const availableCasesSuffix = availableCases.length > 0 ? ` Available cases: ${availableCases}` : " Available cases: (none)";
|
|
1198
|
+
const contextSuffix = discriminantKey ? ` (discriminant key: "${discriminantKey}")` : "";
|
|
1199
|
+
throw new Error(`Unhandled case: ${key}${contextSuffix}.${availableCasesSuffix}`);
|
|
1200
|
+
}
|
|
1201
|
+
function typedSwitch(value, keyOrCases, casesOrDefault, defaultCase) {
|
|
1202
|
+
return runSwitch(value, keyOrCases, casesOrDefault, defaultCase);
|
|
1203
|
+
}
|
|
1204
|
+
// src/array/index.ts
|
|
1205
|
+
function chunkArray(array, chunkSize) {
|
|
1206
|
+
if (!Number.isInteger(chunkSize) || chunkSize <= 0) {
|
|
1207
|
+
throw new Error("Chunk size must be a positive integer");
|
|
1208
|
+
}
|
|
1209
|
+
const chunks = [];
|
|
1210
|
+
for (let index = 0;index < array.length; index += chunkSize) {
|
|
1211
|
+
chunks.push(array.slice(index, index + chunkSize));
|
|
1212
|
+
}
|
|
1213
|
+
return chunks;
|
|
1214
|
+
}
|
|
1215
|
+
function findArrayDifferenceByKey(current, next, key) {
|
|
1216
|
+
const currentByKey = new Map(current.map((item) => [String(item[key]), item]));
|
|
1217
|
+
const itemsToDelete = [...current];
|
|
1218
|
+
const newItems = [];
|
|
1219
|
+
const upsertItems = [];
|
|
1220
|
+
for (const item of next) {
|
|
1221
|
+
const keyValue = String(item[key]);
|
|
1222
|
+
if (!currentByKey.has(keyValue)) {
|
|
1223
|
+
newItems.push(item);
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
upsertItems.push(item);
|
|
1227
|
+
const deleteIndex = itemsToDelete.findIndex((currentItem) => String(currentItem[key]) === keyValue);
|
|
1228
|
+
if (deleteIndex >= 0) {
|
|
1229
|
+
itemsToDelete.splice(deleteIndex, 1);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return {
|
|
1233
|
+
new: newItems,
|
|
1234
|
+
upsert: upsertItems,
|
|
1235
|
+
delete: itemsToDelete
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
function getUniqueValues(array) {
|
|
1239
|
+
const seen = new Map;
|
|
1240
|
+
for (const element of array) {
|
|
1241
|
+
const key = JSON.stringify(element);
|
|
1242
|
+
if (!seen.has(key)) {
|
|
1243
|
+
seen.set(key, element);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
return Array.from(seen.values());
|
|
1247
|
+
}
|
|
1248
|
+
function compact(array) {
|
|
1249
|
+
return array.filter((item) => item != null);
|
|
1250
|
+
}
|
|
1251
|
+
function getUniqueValuesByKey(array, key) {
|
|
1252
|
+
const seen = new Set;
|
|
1253
|
+
const result = [];
|
|
1254
|
+
for (const item of array) {
|
|
1255
|
+
const value = item[key];
|
|
1256
|
+
if (seen.has(value))
|
|
1257
|
+
continue;
|
|
1258
|
+
seen.add(value);
|
|
1259
|
+
result.push(item);
|
|
1260
|
+
}
|
|
1261
|
+
return result;
|
|
1262
|
+
}
|
|
1263
|
+
function groupBy(array, keyOrFn) {
|
|
1264
|
+
return array.reduce((result, item) => {
|
|
1265
|
+
const groupKey = typeof keyOrFn === "function" ? keyOrFn(item) : item[keyOrFn];
|
|
1266
|
+
result[groupKey] ??= [];
|
|
1267
|
+
result[groupKey].push(item);
|
|
1268
|
+
return result;
|
|
1269
|
+
}, {});
|
|
1270
|
+
}
|
|
1271
|
+
function keyBy(array, keyOrFn) {
|
|
1272
|
+
const result = {};
|
|
1273
|
+
for (const item of array) {
|
|
1274
|
+
const recordKey = typeof keyOrFn === "function" ? keyOrFn(item) : item[keyOrFn];
|
|
1275
|
+
result[recordKey] = item;
|
|
1276
|
+
}
|
|
1277
|
+
return result;
|
|
1278
|
+
}
|
|
1279
|
+
function partition(array, predicate) {
|
|
1280
|
+
const matched = [];
|
|
1281
|
+
const unmatched = [];
|
|
1282
|
+
array.forEach((item, index) => {
|
|
1283
|
+
if (predicate(item, index)) {
|
|
1284
|
+
matched.push(item);
|
|
1285
|
+
} else {
|
|
1286
|
+
unmatched.push(item);
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
return [matched, unmatched];
|
|
1290
|
+
}
|
|
1291
|
+
function pickItemsInArray(array, key, values) {
|
|
1292
|
+
return array.filter((item) => values.includes(item[key]));
|
|
1293
|
+
}
|
|
1294
|
+
// src/string/index.ts
|
|
1295
|
+
function extractNumber(value) {
|
|
1296
|
+
if (!value?.trim())
|
|
1297
|
+
return;
|
|
1298
|
+
const cleanedValue = value.replace(/[^\d.,-]/g, "");
|
|
1299
|
+
if (!cleanedValue)
|
|
1300
|
+
return;
|
|
1301
|
+
const parsedNumber = Number.parseFloat(cleanedValue);
|
|
1302
|
+
return Number.isNaN(parsedNumber) ? undefined : parsedNumber;
|
|
1303
|
+
}
|
|
1304
|
+
function generateId(input) {
|
|
1305
|
+
const data = normalizeIdInput(input);
|
|
1306
|
+
let hash = 0;
|
|
1307
|
+
for (let index = 0;index < data.length; index++) {
|
|
1308
|
+
const char = data.charCodeAt(index);
|
|
1309
|
+
hash = (hash << 5) - hash + char;
|
|
1310
|
+
hash |= 0;
|
|
1311
|
+
}
|
|
1312
|
+
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
1313
|
+
}
|
|
1314
|
+
function slugify(value) {
|
|
1315
|
+
const source = "àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìłḿñńǹňôöòóœøōõṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·/_,:;";
|
|
1316
|
+
const replacement = "aaaaaaaaaacccddeeeeeeeegghiiiiiilmnnnnooooooooprrsssssttuuuuuuuuuwxyyzzz------";
|
|
1317
|
+
const specialCharacterPattern = new RegExp(source.split("").join("|"), "g");
|
|
1318
|
+
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(/-+$/, "");
|
|
1319
|
+
}
|
|
1320
|
+
function normalizeIdInput(input) {
|
|
1321
|
+
if (input === undefined)
|
|
1322
|
+
return "undefined";
|
|
1323
|
+
if (input === null)
|
|
1324
|
+
return "null";
|
|
1325
|
+
if (typeof input === "string") {
|
|
1326
|
+
return decodeURIComponent(input).split("&").sort().join("&");
|
|
1327
|
+
}
|
|
1328
|
+
if (typeof input === "number" || typeof input === "boolean") {
|
|
1329
|
+
return input.toString();
|
|
1330
|
+
}
|
|
1331
|
+
return JSON.stringify(sortJsonValue(input));
|
|
1332
|
+
}
|
|
1333
|
+
function sortJsonValue(value) {
|
|
1334
|
+
if (value === undefined)
|
|
1335
|
+
return null;
|
|
1336
|
+
if (Array.isArray(value)) {
|
|
1337
|
+
return value.map((item) => sortJsonValue(item));
|
|
1338
|
+
}
|
|
1339
|
+
if (typeof value === "object" && value !== null) {
|
|
1340
|
+
return Object.fromEntries(Object.entries(value).filter(([, itemValue]) => itemValue !== undefined).sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)).map(([key, itemValue]) => [key, sortJsonValue(itemValue)]));
|
|
1341
|
+
}
|
|
1342
|
+
return value;
|
|
1343
|
+
}
|
|
1344
|
+
// src/promise/index.ts
|
|
1345
|
+
function isSuccess(result) {
|
|
1346
|
+
return result.error === null;
|
|
1347
|
+
}
|
|
1348
|
+
function isFailure(result) {
|
|
1349
|
+
return result.error !== null;
|
|
1350
|
+
}
|
|
1351
|
+
function unwrapResult(result) {
|
|
1352
|
+
if (isSuccess(result))
|
|
1353
|
+
return result.data;
|
|
1354
|
+
throw result.error;
|
|
1355
|
+
}
|
|
1356
|
+
function backgroundPromise(promiseFactory, options = {}) {
|
|
1357
|
+
const run = () => {
|
|
1358
|
+
promiseFactory().then(() => options.onSuccess?.()).catch((error) => options.onError?.(error));
|
|
1359
|
+
};
|
|
1360
|
+
if (options.scheduler) {
|
|
1361
|
+
options.scheduler(run);
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
run();
|
|
1365
|
+
}
|
|
1366
|
+
function backgroundPromiseSync(promiseFactory, options = {}) {
|
|
1367
|
+
backgroundPromise(promiseFactory, {
|
|
1368
|
+
...options,
|
|
1369
|
+
scheduler: options.scheduler ?? queueMicrotask
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
function promiseTimeout(ms) {
|
|
1373
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1374
|
+
}
|
|
1375
|
+
async function tryCatch(promise) {
|
|
1376
|
+
try {
|
|
1377
|
+
const data = await promise;
|
|
1378
|
+
return { data, error: null };
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
return { data: null, error };
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
// src/cache/index.ts
|
|
1384
|
+
function cacheFunction(options) {
|
|
1385
|
+
const {
|
|
1386
|
+
name,
|
|
1387
|
+
fn,
|
|
1388
|
+
cacheTimeMs = 10 * 60 * 1000,
|
|
1389
|
+
refreshInBackground = false,
|
|
1390
|
+
cacheKeyArgs,
|
|
1391
|
+
cacheKeyFn,
|
|
1392
|
+
onBackgroundRefreshError
|
|
1393
|
+
} = options;
|
|
1394
|
+
const cache = new Map;
|
|
1395
|
+
const pendingPromises = new Map;
|
|
1396
|
+
const cleanupInterval = setInterval(() => {
|
|
1397
|
+
const now = Date.now();
|
|
1398
|
+
for (const [key, entry] of cache.entries()) {
|
|
1399
|
+
if (now - entry.timestamp >= cacheTimeMs) {
|
|
1400
|
+
cache.delete(key);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}, Math.min(cacheTimeMs / 2, 5 * 60 * 1000));
|
|
1404
|
+
const wrappedFn = (...args) => {
|
|
1405
|
+
const keyArgs = cacheKeyFn ? cacheKeyFn(...args) : cacheKeyArgs ? cacheKeyArgs.map((i) => args[i]) : args;
|
|
1406
|
+
const key = `${name}:${JSON.stringify(keyArgs)}`;
|
|
1407
|
+
const now = Date.now();
|
|
1408
|
+
const pending = pendingPromises.get(key);
|
|
1409
|
+
if (pending) {
|
|
1410
|
+
return pending;
|
|
1411
|
+
}
|
|
1412
|
+
const cached = cache.get(key);
|
|
1413
|
+
if (cached) {
|
|
1414
|
+
const isCacheFresh = now - cached.timestamp < cacheTimeMs;
|
|
1415
|
+
if (!isCacheFresh && !refreshInBackground) {
|
|
1416
|
+
cache.delete(key);
|
|
1417
|
+
} else {
|
|
1418
|
+
if (isCacheFresh && !refreshInBackground) {
|
|
1419
|
+
return cached.value;
|
|
1420
|
+
}
|
|
1421
|
+
if (refreshInBackground) {
|
|
1422
|
+
const existingPending = pendingPromises.get(key);
|
|
1423
|
+
if (existingPending) {
|
|
1424
|
+
return existingPending;
|
|
1425
|
+
}
|
|
1426
|
+
const backgroundUpdate = fn(...args);
|
|
1427
|
+
if (isPromiseLike(backgroundUpdate)) {
|
|
1428
|
+
const backgroundPromise2 = backgroundUpdate.then((resolvedValue) => {
|
|
1429
|
+
cache.set(key, { value: resolvedValue, timestamp: now });
|
|
1430
|
+
pendingPromises.delete(key);
|
|
1431
|
+
return resolvedValue;
|
|
1432
|
+
}).catch((error) => {
|
|
1433
|
+
pendingPromises.delete(key);
|
|
1434
|
+
onBackgroundRefreshError?.(error, { name });
|
|
1435
|
+
});
|
|
1436
|
+
pendingPromises.set(key, backgroundPromise2);
|
|
1437
|
+
} else {
|
|
1438
|
+
cache.set(key, {
|
|
1439
|
+
value: backgroundUpdate,
|
|
1440
|
+
timestamp: now
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
return cached.value;
|
|
1444
|
+
}
|
|
1445
|
+
if (isCacheFresh) {
|
|
1446
|
+
return cached.value;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
const result = fn(...args);
|
|
1451
|
+
if (isPromiseLike(result)) {
|
|
1452
|
+
const promise = result.then((resolvedValue) => {
|
|
1453
|
+
cache.set(key, { value: resolvedValue, timestamp: now });
|
|
1454
|
+
pendingPromises.delete(key);
|
|
1455
|
+
return resolvedValue;
|
|
1456
|
+
}).catch((error) => {
|
|
1457
|
+
pendingPromises.delete(key);
|
|
1458
|
+
throw error;
|
|
1459
|
+
});
|
|
1460
|
+
pendingPromises.set(key, promise);
|
|
1461
|
+
return promise;
|
|
1462
|
+
}
|
|
1463
|
+
cache.set(key, { value: result, timestamp: now });
|
|
1464
|
+
return result;
|
|
1465
|
+
};
|
|
1466
|
+
wrappedFn.clearCache = () => {
|
|
1467
|
+
cache.clear();
|
|
1468
|
+
pendingPromises.clear();
|
|
1469
|
+
clearInterval(cleanupInterval);
|
|
1470
|
+
};
|
|
1471
|
+
return wrappedFn;
|
|
1472
|
+
}
|
|
1473
|
+
function isPromiseLike(value) {
|
|
1474
|
+
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
1475
|
+
}
|
|
1476
|
+
// src/state/index.ts
|
|
1477
|
+
class SimpleStateMachine {
|
|
1478
|
+
config;
|
|
1479
|
+
item;
|
|
1480
|
+
stateKey;
|
|
1481
|
+
constructor(config, item, stateKey) {
|
|
1482
|
+
this.config = config;
|
|
1483
|
+
this.item = item;
|
|
1484
|
+
this.stateKey = stateKey;
|
|
1485
|
+
}
|
|
1486
|
+
getCurrentStatus() {
|
|
1487
|
+
return this.item[this.stateKey];
|
|
1488
|
+
}
|
|
1489
|
+
async getAvailableActions() {
|
|
1490
|
+
const actionsMap = this.config.stateActionMap[this.getCurrentStatus()] || {};
|
|
1491
|
+
const result = {};
|
|
1492
|
+
for (const [action, targetState] of Object.entries(actionsMap)) {
|
|
1493
|
+
if (!targetState)
|
|
1494
|
+
continue;
|
|
1495
|
+
const validationResult = await this.validateTransition(targetState, { action });
|
|
1496
|
+
result[action] = validationResult;
|
|
1497
|
+
}
|
|
1498
|
+
return result;
|
|
1499
|
+
}
|
|
1500
|
+
async getPossibleTransitions() {
|
|
1501
|
+
const possibleTransitions = this.config.stateTransitions[this.getCurrentStatus()] || [];
|
|
1502
|
+
const actionsMap = this.config.stateActionMap[this.getCurrentStatus()] || {};
|
|
1503
|
+
const result = {};
|
|
1504
|
+
for (const targetState of possibleTransitions) {
|
|
1505
|
+
const actionsToTarget = Object.entries(actionsMap).filter(([, t]) => t === targetState);
|
|
1506
|
+
if (actionsToTarget.length === 0) {
|
|
1507
|
+
result[targetState] = await this.validateTransition(targetState);
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1510
|
+
let lastError = true;
|
|
1511
|
+
let anyOk = false;
|
|
1512
|
+
for (const [actionKey] of actionsToTarget) {
|
|
1513
|
+
const r = await this.validateTransition(targetState, {
|
|
1514
|
+
action: actionKey
|
|
1515
|
+
});
|
|
1516
|
+
if (r === true) {
|
|
1517
|
+
anyOk = true;
|
|
1518
|
+
break;
|
|
1519
|
+
}
|
|
1520
|
+
lastError = r;
|
|
1521
|
+
}
|
|
1522
|
+
result[targetState] = anyOk ? true : lastError;
|
|
1523
|
+
}
|
|
1524
|
+
return result;
|
|
1525
|
+
}
|
|
1526
|
+
async executeAction(action, data) {
|
|
1527
|
+
const actionsMap = this.config.stateActionMap[this.getCurrentStatus()];
|
|
1528
|
+
if (!actionsMap) {
|
|
1529
|
+
return {
|
|
1530
|
+
success: false,
|
|
1531
|
+
error: `No actions defined for state '${this.getCurrentStatus()}'`
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
const targetState = actionsMap[action];
|
|
1535
|
+
if (!targetState) {
|
|
1536
|
+
return {
|
|
1537
|
+
success: false,
|
|
1538
|
+
error: `Action '${action}' not allowed in state '${this.getCurrentStatus()}'`
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
const validationResult = await this.validateTransition(targetState, {
|
|
1542
|
+
action
|
|
1543
|
+
});
|
|
1544
|
+
if (validationResult === true) {
|
|
1545
|
+
if (this.config.actions?.[action]) {
|
|
1546
|
+
try {
|
|
1547
|
+
const result = await this.config.actions[action](this.item, data);
|
|
1548
|
+
return {
|
|
1549
|
+
success: true,
|
|
1550
|
+
newState: targetState,
|
|
1551
|
+
result
|
|
1552
|
+
};
|
|
1553
|
+
} catch (error) {
|
|
1554
|
+
return {
|
|
1555
|
+
success: false,
|
|
1556
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
return {
|
|
1561
|
+
success: true,
|
|
1562
|
+
newState: targetState
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
return {
|
|
1566
|
+
success: false,
|
|
1567
|
+
error: validationResult
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
async validateTransition(toState, options) {
|
|
1571
|
+
const action = options?.action;
|
|
1572
|
+
if (this.getCurrentStatus() === toState)
|
|
1573
|
+
return true;
|
|
1574
|
+
const allowedTransitions = this.config.stateTransitions[this.getCurrentStatus()] || [];
|
|
1575
|
+
if (!allowedTransitions.includes(toState)) {
|
|
1576
|
+
return `Invalid transition from '${this.getCurrentStatus()}' to '${toState}'`;
|
|
1577
|
+
}
|
|
1578
|
+
if (this.config.globalValidator) {
|
|
1579
|
+
const globalResult = await this.config.globalValidator(this.item, this.getCurrentStatus(), toState, action);
|
|
1580
|
+
if (globalResult !== true) {
|
|
1581
|
+
return globalResult;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
const stateValidator = this.config.validators?.[toState];
|
|
1585
|
+
if (stateValidator) {
|
|
1586
|
+
return await stateValidator(this.item, this.getCurrentStatus(), toState, action);
|
|
1587
|
+
}
|
|
1588
|
+
return true;
|
|
1589
|
+
}
|
|
1590
|
+
getStates() {
|
|
1591
|
+
return Object.keys(this.config.stateTransitions);
|
|
1592
|
+
}
|
|
1593
|
+
getInitialState() {
|
|
1594
|
+
return this.getStates()[0];
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
// src/math/linearRegressionTrend.ts
|
|
1598
|
+
function linearRegressionTrend(values) {
|
|
1599
|
+
const n = values.length;
|
|
1600
|
+
if (n < 2)
|
|
1601
|
+
return [];
|
|
1602
|
+
for (const v of values) {
|
|
1603
|
+
if (!Number.isFinite(v))
|
|
1604
|
+
return [];
|
|
1605
|
+
}
|
|
1606
|
+
let sumX = 0;
|
|
1607
|
+
let sumY = 0;
|
|
1608
|
+
let sumXY = 0;
|
|
1609
|
+
let sumX2 = 0;
|
|
1610
|
+
for (let i = 0;i < n; i++) {
|
|
1611
|
+
const x = i;
|
|
1612
|
+
const y = values[i];
|
|
1613
|
+
sumX += x;
|
|
1614
|
+
sumY += y;
|
|
1615
|
+
sumXY += x * y;
|
|
1616
|
+
sumX2 += x * x;
|
|
1617
|
+
}
|
|
1618
|
+
const denom = n * sumX2 - sumX * sumX;
|
|
1619
|
+
if (denom === 0)
|
|
1620
|
+
return [];
|
|
1621
|
+
const slope = (n * sumXY - sumX * sumY) / denom;
|
|
1622
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
1623
|
+
const out = [];
|
|
1624
|
+
for (let i = 0;i < n; i++) {
|
|
1625
|
+
out.push(intercept + slope * i);
|
|
1626
|
+
}
|
|
1627
|
+
return out;
|
|
1628
|
+
}
|
|
1629
|
+
// src/math/metricsSeriesColors.ts
|
|
1630
|
+
var METRICS_SERIES_PALETTE_SIZE = 10;
|
|
1631
|
+
function hashStringToColorIndex(str) {
|
|
1632
|
+
let hash = 2166136261;
|
|
1633
|
+
for (let i = 0;i < str.length; i++) {
|
|
1634
|
+
hash ^= str.charCodeAt(i);
|
|
1635
|
+
hash = Math.imul(hash, 16777619);
|
|
1636
|
+
}
|
|
1637
|
+
return Math.abs(hash) % METRICS_SERIES_PALETTE_SIZE;
|
|
1638
|
+
}
|
|
1639
|
+
function circularHueDistance(a, b, paletteSize) {
|
|
1640
|
+
const diff = Math.abs(a - b);
|
|
1641
|
+
return Math.min(diff, paletteSize - diff);
|
|
1642
|
+
}
|
|
1643
|
+
function pickHueMaxMinDistance(candidates, used, paletteSize) {
|
|
1644
|
+
let best = candidates[0] ?? 0;
|
|
1645
|
+
let bestScore = -1;
|
|
1646
|
+
for (const cand of candidates) {
|
|
1647
|
+
let minD = paletteSize;
|
|
1648
|
+
for (const u of used) {
|
|
1649
|
+
minD = Math.min(minD, circularHueDistance(cand, u, paletteSize));
|
|
1650
|
+
}
|
|
1651
|
+
if (minD > bestScore || minD === bestScore && cand < best) {
|
|
1652
|
+
bestScore = minD;
|
|
1653
|
+
best = cand;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
return best;
|
|
1657
|
+
}
|
|
1658
|
+
function seriesColorKey(label, key) {
|
|
1659
|
+
const fromLabel = label?.trim();
|
|
1660
|
+
if (fromLabel)
|
|
1661
|
+
return fromLabel;
|
|
1662
|
+
return key.trim() || key;
|
|
1663
|
+
}
|
|
1664
|
+
function resolveSeriesColorMap(keys) {
|
|
1665
|
+
const result = new Map;
|
|
1666
|
+
const uniqueKeys = [...new Set(keys)];
|
|
1667
|
+
if (uniqueKeys.length === 0)
|
|
1668
|
+
return result;
|
|
1669
|
+
const paletteSize = METRICS_SERIES_PALETTE_SIZE;
|
|
1670
|
+
if (uniqueKeys.length === 1) {
|
|
1671
|
+
const only = uniqueKeys[0];
|
|
1672
|
+
result.set(only, hashStringToColorIndex(only));
|
|
1673
|
+
return result;
|
|
1674
|
+
}
|
|
1675
|
+
const usedIndices = new Set;
|
|
1676
|
+
const entries = uniqueKeys.map((key) => ({
|
|
1677
|
+
key,
|
|
1678
|
+
preferred: hashStringToColorIndex(key)
|
|
1679
|
+
}));
|
|
1680
|
+
for (const entry of entries) {
|
|
1681
|
+
if (!usedIndices.has(entry.preferred)) {
|
|
1682
|
+
result.set(entry.key, entry.preferred);
|
|
1683
|
+
usedIndices.add(entry.preferred);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
const unassigned = entries.filter((e) => !result.has(e.key));
|
|
1687
|
+
for (const entry of unassigned) {
|
|
1688
|
+
const unused = [];
|
|
1689
|
+
for (let i = 0;i < paletteSize; i++) {
|
|
1690
|
+
if (!usedIndices.has(i))
|
|
1691
|
+
unused.push(i);
|
|
1692
|
+
}
|
|
1693
|
+
const candidates = unused.length > 0 ? unused : Array.from({ length: paletteSize }, (_, i) => i);
|
|
1694
|
+
const idx = pickHueMaxMinDistance(candidates, usedIndices, paletteSize);
|
|
1695
|
+
result.set(entry.key, idx);
|
|
1696
|
+
usedIndices.add(idx);
|
|
1697
|
+
}
|
|
1698
|
+
return result;
|
|
1699
|
+
}
|
|
1700
|
+
function createResolveColorsByKeys(keys, getSeriesColor, getSeriesHoverColor) {
|
|
1701
|
+
const colorMap = resolveSeriesColorMap(keys);
|
|
1702
|
+
return {
|
|
1703
|
+
getColor: (key) => getSeriesColor(colorMap.get(key) ?? hashStringToColorIndex(key)),
|
|
1704
|
+
getHoverColor: (key) => getSeriesHoverColor(colorMap.get(key) ?? hashStringToColorIndex(key))
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
function clamp(value, min, max) {
|
|
1708
|
+
return Math.min(max, Math.max(min, value));
|
|
1709
|
+
}
|
|
1710
|
+
function withAlpha(color, alpha) {
|
|
1711
|
+
const trimmed = color.trim();
|
|
1712
|
+
if (trimmed.startsWith("#")) {
|
|
1713
|
+
const hex = trimmed.slice(1);
|
|
1714
|
+
const normalized = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
|
|
1715
|
+
if (normalized.length === 6) {
|
|
1716
|
+
const r = Number.parseInt(normalized.slice(0, 2), 16);
|
|
1717
|
+
const g = Number.parseInt(normalized.slice(2, 4), 16);
|
|
1718
|
+
const b = Number.parseInt(normalized.slice(4, 6), 16);
|
|
1719
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
if (trimmed.startsWith("rgb(")) {
|
|
1723
|
+
return trimmed.replace("rgb(", "rgba(").replace(")", `, ${alpha})`);
|
|
1724
|
+
}
|
|
1725
|
+
if (trimmed.startsWith("rgba(")) {
|
|
1726
|
+
return trimmed.replace(/,\s*[\d.]+\)$/, `, ${alpha})`);
|
|
1727
|
+
}
|
|
1728
|
+
return trimmed;
|
|
1729
|
+
}
|
|
1730
|
+
function getHeatmapCellStyle(params) {
|
|
1731
|
+
const {
|
|
1732
|
+
value,
|
|
1733
|
+
min,
|
|
1734
|
+
max,
|
|
1735
|
+
baseColor,
|
|
1736
|
+
neutralColor,
|
|
1737
|
+
minAlpha = 0.08,
|
|
1738
|
+
maxAlpha = 0.78
|
|
1739
|
+
} = params;
|
|
1740
|
+
if (value === null || value === undefined || Number.isNaN(value)) {
|
|
1741
|
+
return {};
|
|
1742
|
+
}
|
|
1743
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
1744
|
+
return {};
|
|
1745
|
+
}
|
|
1746
|
+
if (max <= min) {
|
|
1747
|
+
return {
|
|
1748
|
+
backgroundColor: withAlpha(neutralColor ?? baseColor, 0.32)
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
const normalized = clamp((value - min) / (max - min), 0, 1);
|
|
1752
|
+
const alpha = minAlpha + (maxAlpha - minAlpha) * normalized;
|
|
1753
|
+
return {
|
|
1754
|
+
backgroundColor: withAlpha(baseColor, alpha)
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
// src/date/index.ts
|
|
1758
|
+
function getPreviousReportPeriod(periodType, referenceDate = new Date) {
|
|
1759
|
+
const month = referenceDate.getMonth();
|
|
1760
|
+
const year = referenceDate.getFullYear();
|
|
1761
|
+
switch (periodType) {
|
|
1762
|
+
case "MONTHLY": {
|
|
1763
|
+
const prevMonth = month === 0 ? 11 : month - 1;
|
|
1764
|
+
const prevYear = month === 0 ? year - 1 : year;
|
|
1765
|
+
return {
|
|
1766
|
+
start: new Date(prevYear, prevMonth, 1),
|
|
1767
|
+
end: new Date(year, month, 0)
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
case "QUARTERLY": {
|
|
1771
|
+
const qStart = Math.floor(month / 3) * 3;
|
|
1772
|
+
const prevQStart = qStart === 0 ? 9 : qStart - 3;
|
|
1773
|
+
const prevQYear = qStart === 0 ? year - 1 : year;
|
|
1774
|
+
return {
|
|
1775
|
+
start: new Date(prevQYear, prevQStart, 1),
|
|
1776
|
+
end: new Date(prevQYear, prevQStart + 3, 0)
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
case "YEARLY": {
|
|
1780
|
+
return {
|
|
1781
|
+
start: new Date(year - 1, 0, 1),
|
|
1782
|
+
end: new Date(year - 1, 11, 31)
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
function formatPeriodLabel(start, end, includeDay = false) {
|
|
1788
|
+
const s = start instanceof Date ? start : new Date(start);
|
|
1789
|
+
const e = end instanceof Date ? end : new Date(end);
|
|
1790
|
+
const opts = {
|
|
1791
|
+
year: "numeric",
|
|
1792
|
+
month: "long",
|
|
1793
|
+
...includeDay && { day: "numeric" }
|
|
1794
|
+
};
|
|
1795
|
+
return `${s.toLocaleDateString("en-GB", opts)} — ${e.toLocaleDateString("en-GB", opts)}`;
|
|
1796
|
+
}
|
|
1797
|
+
// src/url/index.ts
|
|
1798
|
+
function getDomainFaviconUrl(domain, size = 128) {
|
|
1799
|
+
return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=${size}`;
|
|
1800
|
+
}
|
|
1801
|
+
// src/cool-console-log/index.ts
|
|
1802
|
+
var terminalColors = {
|
|
1803
|
+
reset: "\x1B[0m",
|
|
1804
|
+
bold: "\x1B[1m",
|
|
1805
|
+
dim: "\x1B[2m",
|
|
1806
|
+
italic: "\x1B[3m",
|
|
1807
|
+
underline: "\x1B[4m",
|
|
1808
|
+
black: "\x1B[30m",
|
|
1809
|
+
red: "\x1B[31m",
|
|
1810
|
+
green: "\x1B[32m",
|
|
1811
|
+
yellow: "\x1B[33m",
|
|
1812
|
+
blue: "\x1B[34m",
|
|
1813
|
+
magenta: "\x1B[35m",
|
|
1814
|
+
cyan: "\x1B[36m",
|
|
1815
|
+
white: "\x1B[37m",
|
|
1816
|
+
gray: "\x1B[90m",
|
|
1817
|
+
brightRed: "\x1B[91m",
|
|
1818
|
+
brightGreen: "\x1B[92m",
|
|
1819
|
+
brightYellow: "\x1B[93m",
|
|
1820
|
+
brightBlue: "\x1B[94m",
|
|
1821
|
+
brightMagenta: "\x1B[95m",
|
|
1822
|
+
brightCyan: "\x1B[96m",
|
|
1823
|
+
brightWhite: "\x1B[97m",
|
|
1824
|
+
bgBlack: "\x1B[40m",
|
|
1825
|
+
bgRed: "\x1B[41m",
|
|
1826
|
+
bgGreen: "\x1B[42m",
|
|
1827
|
+
bgYellow: "\x1B[43m",
|
|
1828
|
+
bgBlue: "\x1B[44m",
|
|
1829
|
+
bgMagenta: "\x1B[45m",
|
|
1830
|
+
bgCyan: "\x1B[46m",
|
|
1831
|
+
bgWhite: "\x1B[47m"
|
|
1832
|
+
};
|
|
1833
|
+
var levelColors = {
|
|
1834
|
+
debug: { badge: terminalColors.gray, text: terminalColors.dim },
|
|
1835
|
+
info: { badge: terminalColors.cyan, text: terminalColors.reset },
|
|
1836
|
+
warn: { badge: terminalColors.yellow, text: terminalColors.yellow },
|
|
1837
|
+
error: { badge: terminalColors.red, text: terminalColors.brightRed }
|
|
1838
|
+
};
|
|
1839
|
+
function formatAttributes(attrs) {
|
|
1840
|
+
if (!attrs || Object.keys(attrs).length === 0)
|
|
1841
|
+
return "";
|
|
1842
|
+
const formatted = Object.entries(attrs).map(([k, v]) => `${terminalColors.dim}${k}=${terminalColors.reset}${v}`).join(" ");
|
|
1843
|
+
return ` ${formatted}`;
|
|
1844
|
+
}
|
|
1845
|
+
function createCoolLogger(options = {}) {
|
|
1846
|
+
const { colorsInDevOnly = true, onLog } = options;
|
|
1847
|
+
const isDev = true;
|
|
1848
|
+
const useColors = colorsInDevOnly ? isDev : true;
|
|
1849
|
+
const createLogMethod = (level) => (message, attributes) => {
|
|
1850
|
+
onLog?.(level, message, attributes);
|
|
1851
|
+
if (level === "debug" && !isDev)
|
|
1852
|
+
return;
|
|
1853
|
+
if (useColors) {
|
|
1854
|
+
const { badge, text } = levelColors[level];
|
|
1855
|
+
const label = level.toUpperCase().padEnd(5);
|
|
1856
|
+
const attrStr = formatAttributes(attributes);
|
|
1857
|
+
console.log(`${badge}[${label}]${terminalColors.reset} ${text}${message}${terminalColors.reset}${attrStr}`);
|
|
1858
|
+
} else {
|
|
1859
|
+
console[level](`[${level.toUpperCase()}]`, message, attributes ?? "");
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
return {
|
|
1863
|
+
debug: createLogMethod("debug"),
|
|
1864
|
+
info: createLogMethod("info"),
|
|
1865
|
+
warn: createLogMethod("warn"),
|
|
1866
|
+
error: createLogMethod("error")
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
function logOperationSummary(operationName, durationMs, success, options = {}) {
|
|
1870
|
+
const { devOnly = true, error } = options;
|
|
1871
|
+
const isDev = true;
|
|
1872
|
+
if (devOnly && !isDev)
|
|
1873
|
+
return;
|
|
1874
|
+
const c = terminalColors;
|
|
1875
|
+
const statusIcon = success ? `${c.brightGreen}✓${c.reset}` : `${c.brightRed}✗${c.reset}`;
|
|
1876
|
+
const statusColor = success ? c.green : c.red;
|
|
1877
|
+
const durationStr = `${c.dim}${durationMs.toFixed(1)}ms${c.reset}`;
|
|
1878
|
+
const errorMessage = error ? ` ${c.dim}·${c.reset} ${c.red}${error instanceof Error ? error.message : String(error)}${c.reset}` : "";
|
|
1879
|
+
console.log(`${statusColor}${c.bold}${operationName}${c.reset} ${statusIcon} ${c.dim}·${c.reset} ${durationStr}${errorMessage}`);
|
|
1880
|
+
}
|
|
1881
|
+
function colorize(text, color, ...modifiers) {
|
|
1882
|
+
const isDev = true;
|
|
1883
|
+
if (!isDev)
|
|
1884
|
+
return text;
|
|
1885
|
+
const colorCodes = [
|
|
1886
|
+
terminalColors[color],
|
|
1887
|
+
...modifiers.map((m) => terminalColors[m])
|
|
1888
|
+
].join("");
|
|
1889
|
+
return `${colorCodes}${text}${terminalColors.reset}`;
|
|
1890
|
+
}
|
|
1891
|
+
function logBanner(text, color = "cyan", options = {}) {
|
|
1892
|
+
const { devOnly = true } = options;
|
|
1893
|
+
const isDev = true;
|
|
1894
|
+
if (devOnly && !isDev) {
|
|
1895
|
+
console.log(`=== ${text} ===`);
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
const c = terminalColors;
|
|
1899
|
+
const padding = 2;
|
|
1900
|
+
const line = "═".repeat(text.length + padding * 2);
|
|
1901
|
+
console.log(`${c[color]}╔${line}╗${c.reset}`);
|
|
1902
|
+
console.log(`${c[color]}║${" ".repeat(padding)}${c.bold}${text}${c.reset}${c[color]}${" ".repeat(padding)}║${c.reset}`);
|
|
1903
|
+
console.log(`${c[color]}╚${line}╝${c.reset}`);
|
|
1904
|
+
}
|
|
1905
|
+
export {
|
|
1906
|
+
zodToTypeString,
|
|
1907
|
+
zodToExample,
|
|
1908
|
+
zodMinMaxFilter,
|
|
1909
|
+
zSingleOrArray,
|
|
1910
|
+
withAlpha,
|
|
1911
|
+
viteSecretSetPlugin,
|
|
1912
|
+
unwrapResult,
|
|
1913
|
+
typedSwitch,
|
|
1914
|
+
tryCatch,
|
|
1915
|
+
toGteLteFilter,
|
|
1916
|
+
terminalColors,
|
|
1917
|
+
slugify,
|
|
1918
|
+
seriesColorKey,
|
|
1919
|
+
resolveSeriesColorMap,
|
|
1920
|
+
promiseTimeout,
|
|
1921
|
+
pickItemsInArray,
|
|
1922
|
+
partition,
|
|
1923
|
+
normalizeSingleOrArray,
|
|
1924
|
+
logOperationSummary,
|
|
1925
|
+
logBanner,
|
|
1926
|
+
linearRegressionTrend,
|
|
1927
|
+
keyBy,
|
|
1928
|
+
jwtVerify2 as jwtVerify,
|
|
1929
|
+
jwtSign,
|
|
1930
|
+
jsonToZodSchemaMapper,
|
|
1931
|
+
isSuccess,
|
|
1932
|
+
isFailure,
|
|
1933
|
+
hashStringToColorIndex,
|
|
1934
|
+
groupBy,
|
|
1935
|
+
getViteDefine,
|
|
1936
|
+
getUniqueValuesByKey,
|
|
1937
|
+
getUniqueValues,
|
|
1938
|
+
getSecretEnvironment,
|
|
1939
|
+
getPreviousReportPeriod,
|
|
1940
|
+
getHeatmapCellStyle,
|
|
1941
|
+
getDomainSitemap,
|
|
1942
|
+
getDomainFaviconUrl,
|
|
1943
|
+
generateId,
|
|
1944
|
+
formatPeriodLabel,
|
|
1945
|
+
findArrayDifferenceByKey,
|
|
1946
|
+
extractNumber,
|
|
1947
|
+
defineSecretSet,
|
|
1948
|
+
decompressString,
|
|
1949
|
+
createZId,
|
|
1950
|
+
createResolveColorsByKeys,
|
|
1951
|
+
createCoolLogger,
|
|
1952
|
+
createApiWrapper,
|
|
1953
|
+
createApiClient,
|
|
1954
|
+
compressString,
|
|
1955
|
+
compact,
|
|
1956
|
+
colorize,
|
|
1957
|
+
chunkArray,
|
|
1958
|
+
cacheFunction,
|
|
1959
|
+
backgroundPromiseSync,
|
|
1960
|
+
backgroundPromise,
|
|
1961
|
+
TimeoutError,
|
|
1962
|
+
SimpleStateMachine,
|
|
1963
|
+
ResponseValidationError,
|
|
1964
|
+
PriorityQueue,
|
|
1965
|
+
METRICS_SERIES_PALETTE_SIZE,
|
|
1966
|
+
HttpResponseError
|
|
1967
|
+
};
|