hanzio 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +510 -0
  2. package/dist/api-wrapper/index.d.ts +84 -0
  3. package/dist/api-wrapper/index.js +250 -0
  4. package/dist/array/index.d.ts +22 -0
  5. package/dist/cache/index.d.ts +19 -0
  6. package/dist/date/index.d.ts +11 -0
  7. package/dist/gzip/index.d.ts +2 -0
  8. package/dist/gzip/index.js +16 -0
  9. package/dist/index.d.ts +12 -0
  10. package/dist/index.js +1565 -0
  11. package/dist/jwt/index.d.ts +3 -0
  12. package/dist/jwt/index.js +19 -0
  13. package/dist/math/index.d.ts +2 -0
  14. package/dist/math/linearRegressionTrend.d.ts +5 -0
  15. package/dist/math/metricsSeriesColors.d.ts +42 -0
  16. package/dist/p-queue/index.d.ts +95 -0
  17. package/dist/p-queue/index.js +439 -0
  18. package/dist/promise/index.d.ts +21 -0
  19. package/dist/sitemap/index.d.ts +14 -0
  20. package/dist/sitemap/index.js +552 -0
  21. package/dist/state/index.d.ts +101 -0
  22. package/dist/string/index.d.ts +8 -0
  23. package/dist/typedswitch/index.d.ts +124 -0
  24. package/dist/url/index.d.ts +1 -0
  25. package/dist/zod/index.d.ts +8 -0
  26. package/dist/zod/index.js +210 -0
  27. package/dist/zod/jsonToZodSchemaMapper.d.ts +6 -0
  28. package/dist/zod/zodMinMaxFilter.d.ts +14 -0
  29. package/dist/zod/zodToTypeString.d.ts +9 -0
  30. package/package.json +102 -0
  31. package/src/api-wrapper/index.ts +492 -0
  32. package/src/array/index.ts +187 -0
  33. package/src/cache/index.ts +154 -0
  34. package/src/date/index.ts +54 -0
  35. package/src/gzip/index.ts +29 -0
  36. package/src/index.ts +12 -0
  37. package/src/jwt/index.ts +25 -0
  38. package/src/math/index.ts +11 -0
  39. package/src/math/linearRegressionTrend.ts +36 -0
  40. package/src/math/metricsSeriesColors.ts +208 -0
  41. package/src/p-queue/index.ts +690 -0
  42. package/src/promise/index.ts +73 -0
  43. package/src/sitemap/index.ts +195 -0
  44. package/src/state/index.ts +331 -0
  45. package/src/string/index.ts +86 -0
  46. package/src/typedswitch/index.ts +309 -0
  47. package/src/url/index.ts +5 -0
  48. package/src/zod/index.ts +29 -0
  49. package/src/zod/jsonToZodSchemaMapper.ts +44 -0
  50. package/src/zod/zodMinMaxFilter.ts +28 -0
  51. package/src/zod/zodToTypeString.ts +222 -0
@@ -0,0 +1,250 @@
1
+ // src/api-wrapper/index.ts
2
+ import { ZodError } from "zod";
3
+
4
+ class ResponseValidationError extends Error {
5
+ zodError;
6
+ rawResponse;
7
+ validationIssues;
8
+ constructor(zodError, rawResponse) {
9
+ const issues = zodError.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
10
+ super(`Response validation failed: ${issues}`);
11
+ this.name = "ResponseValidationError";
12
+ this.zodError = zodError;
13
+ this.rawResponse = rawResponse;
14
+ this.validationIssues = issues;
15
+ }
16
+ }
17
+
18
+ class HttpResponseError extends Error {
19
+ status;
20
+ body;
21
+ constructor(status, body) {
22
+ super(`HTTP error: ${status} - ${body}`);
23
+ this.name = "HttpResponseError";
24
+ this.status = status;
25
+ this.body = body;
26
+ }
27
+ }
28
+ function createApiClient(apiConfig) {
29
+ const request = async (endpointKey, input = {}) => {
30
+ const endpoint = apiConfig.endpoints[endpointKey];
31
+ if (!endpoint) {
32
+ throw new Error(`Unknown API endpoint: ${String(endpointKey)}`);
33
+ }
34
+ const { finalUrl, fullUrl } = buildUrl(endpoint, input.reqParams, input.reqQuery, apiConfig.baseApiUrls, apiConfig.defaultBaseApiUrl, input.url);
35
+ const endpointName = apiConfig.name ? `${apiConfig.name}.${String(endpointKey)}` : String(endpointKey);
36
+ const startedAt = Date.now();
37
+ let retryCount = 0;
38
+ let httpStatus = 0;
39
+ apiConfig.onRequestStart?.({ endpoint: endpointName, url: fullUrl });
40
+ try {
41
+ const config = {
42
+ method: endpoint.method,
43
+ url: finalUrl,
44
+ queryParams: buildQueryParams(endpoint, input.reqQuery),
45
+ body: buildRequestBody(endpoint, input.reqBody),
46
+ headers: buildHeaders(endpoint, input.reqHeaders, apiConfig.defaultHeaders ?? {}),
47
+ doNotEncodeQueryParams: endpoint.doNotEncodeQueryParams,
48
+ timeoutMs: apiConfig.timeoutMs,
49
+ fetchFn: apiConfig.fetch ?? fetch
50
+ };
51
+ const result = await makeRequestWithRetry(config, apiConfig.retries ?? 3, apiConfig.retryDelayMs ?? 300, (response) => validateAndTransformResponse(endpoint, response), apiConfig.shouldRetry ?? defaultShouldRetry, apiConfig.logger);
52
+ retryCount = result.retryCount;
53
+ httpStatus = result.httpStatus;
54
+ const responseTimeMs = Date.now() - startedAt;
55
+ const responseSizeMb = calculateSizeInMb(result.validatedData);
56
+ const requestSizeMb = calculateSizeInMb({
57
+ method: config.method,
58
+ url: fullUrl,
59
+ body: config.body,
60
+ headers: config.headers
61
+ });
62
+ apiConfig.onRequestEnd?.({
63
+ endpoint: endpointName,
64
+ url: fullUrl,
65
+ status: httpStatus,
66
+ retryCount,
67
+ responseTimeMs
68
+ });
69
+ return {
70
+ data: result.validatedData,
71
+ requestSizeMb,
72
+ responseSizeMb,
73
+ requestSizeTotalMb: requestSizeMb + responseSizeMb,
74
+ responseTimeMs,
75
+ httpStatus,
76
+ retryCount
77
+ };
78
+ } catch (error) {
79
+ apiConfig.logger?.error?.(error);
80
+ throw error;
81
+ }
82
+ };
83
+ return { request };
84
+ }
85
+ var createApiWrapper = createApiClient;
86
+ async function validateResponse(schema, data) {
87
+ try {
88
+ return await schema.parseAsync(data);
89
+ } catch (error) {
90
+ if (error instanceof ZodError) {
91
+ throw new ResponseValidationError(error, data);
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+ async function httpRequest(config) {
97
+ const queryString = config.doNotEncodeQueryParams ? Object.entries(config.queryParams).map(([key, value]) => `${key}=${value}`).join("&") : new URLSearchParams(config.queryParams).toString();
98
+ const fullUrl = queryString ? `${config.url}?${queryString}` : config.url;
99
+ const abortController = new AbortController;
100
+ const timeout = config.timeoutMs === undefined ? undefined : setTimeout(() => abortController.abort(), config.timeoutMs);
101
+ try {
102
+ return await config.fetchFn(fullUrl, {
103
+ method: config.method,
104
+ headers: config.headers,
105
+ body: buildFetchBody(config.body, config.headers),
106
+ signal: abortController.signal
107
+ });
108
+ } finally {
109
+ if (timeout)
110
+ clearTimeout(timeout);
111
+ }
112
+ }
113
+ async function makeRequestWithRetry(config, maxRetries, retryDelayMs, validateFn, shouldRetry, logger) {
114
+ let retryCount = 0;
115
+ while (true) {
116
+ try {
117
+ logger?.debug?.("API request", config);
118
+ const response = await httpRequest(config);
119
+ if (response.status >= 400) {
120
+ if (retryCount < maxRetries && shouldRetry({ response, retryCount, maxRetries })) {
121
+ retryCount++;
122
+ await delay(retryDelayMs);
123
+ continue;
124
+ }
125
+ throw new HttpResponseError(response.status, await response.text());
126
+ }
127
+ return {
128
+ validatedData: await validateFn(response),
129
+ retryCount,
130
+ httpStatus: response.status
131
+ };
132
+ } catch (error) {
133
+ if (error instanceof HttpResponseError)
134
+ throw error;
135
+ if (retryCount >= maxRetries || !shouldRetry({ error, retryCount, maxRetries })) {
136
+ throw error;
137
+ }
138
+ retryCount++;
139
+ await delay(retryDelayMs);
140
+ }
141
+ }
142
+ }
143
+ async function validateAndTransformResponse(endpoint, response) {
144
+ const responseHeaders = getHeadersAsObject(response.headers);
145
+ const contentType = responseHeaders["content-type"] ?? "";
146
+ const responseData = contentType.includes("application/json") ? await response.json() : await response.text();
147
+ const transformedData = endpoint.resFormatter ? endpoint.resFormatter(responseData, responseHeaders) : responseData;
148
+ return validateResponse(endpoint.resSchema, transformedData);
149
+ }
150
+ function buildUrl(endpoint, params, reqQuery, baseApiUrls, defaultBaseApiUrl, url) {
151
+ if (url) {
152
+ const finalUrl2 = replacePathParams(url, params);
153
+ const queryString2 = new URLSearchParams(buildQueryParams(endpoint, reqQuery)).toString();
154
+ return {
155
+ finalUrl: finalUrl2,
156
+ fullUrl: queryString2 ? `${finalUrl2}?${queryString2}` : finalUrl2
157
+ };
158
+ }
159
+ const defaultBaseUrl = defaultBaseApiUrl ?? Object.keys(baseApiUrls)[0];
160
+ if (!defaultBaseUrl) {
161
+ throw new Error("At least one base API URL is required");
162
+ }
163
+ const baseUrlKey = endpoint.baseApiUrl ?? defaultBaseUrl;
164
+ const configBaseUrl = baseApiUrls[baseUrlKey];
165
+ if (!configBaseUrl) {
166
+ throw new Error(`Unknown base API URL: ${baseUrlKey}`);
167
+ }
168
+ const baseUrl = typeof configBaseUrl === "function" ? configBaseUrl() : configBaseUrl;
169
+ const path = typeof endpoint.path === "function" ? endpoint.path(baseUrl) : endpoint.path;
170
+ const finalUrl = replacePathParams(`${baseUrl}${path}`, params);
171
+ const queryString = new URLSearchParams(buildQueryParams(endpoint, reqQuery)).toString();
172
+ return {
173
+ finalUrl,
174
+ fullUrl: queryString ? `${finalUrl}?${queryString}` : finalUrl
175
+ };
176
+ }
177
+ function buildQueryParams(endpoint, query) {
178
+ const defaultParams = endpoint.reqDefaultQueryParams ?? {};
179
+ const parsedQuery = endpoint.reqQuerySchema ? endpoint.reqQuerySchema.parse(query) : query ?? {};
180
+ const merged = { ...defaultParams, ...parsedQuery };
181
+ return Object.fromEntries(Object.entries(merged).filter(([, value]) => value !== undefined).map(([key, value]) => [key, String(value)]));
182
+ }
183
+ function buildRequestBody(endpoint, body) {
184
+ if (!endpoint.reqBodySchema)
185
+ return body;
186
+ const parsedBody = endpoint.reqBodySchema.parse(body);
187
+ if (endpoint.reqBodyFormat !== "form-data") {
188
+ return parsedBody;
189
+ }
190
+ const formData = new FormData;
191
+ for (const [key, value] of Object.entries(parsedBody)) {
192
+ if (value == null)
193
+ continue;
194
+ formData.set(key, value instanceof Blob ? value : String(value));
195
+ }
196
+ return formData;
197
+ }
198
+ function buildHeaders(endpoint, headers, defaultHeaders) {
199
+ const contentType = endpoint.reqBodyFormat === "json" ? "application/json" : undefined;
200
+ const parsedHeaders = endpoint.reqHeadersSchema ? endpoint.reqHeadersSchema.parse(headers) : headers ?? {};
201
+ return {
202
+ ...defaultHeaders,
203
+ ...contentType && { "Content-Type": contentType },
204
+ ...endpoint.defaultHeaders,
205
+ ...parsedHeaders
206
+ };
207
+ }
208
+ function buildFetchBody(body, headers) {
209
+ if (body === undefined || body === null)
210
+ return;
211
+ if (body instanceof FormData || body instanceof Blob)
212
+ return body;
213
+ if (headers["Content-Type"] === "application/json")
214
+ return JSON.stringify(body);
215
+ return body;
216
+ }
217
+ function replacePathParams(path, params) {
218
+ return path.replace(/:(\w+)/g, (_, key) => {
219
+ const value = params?.[key];
220
+ if (value === undefined) {
221
+ throw new Error(`Missing required path parameter: ${key}`);
222
+ }
223
+ return encodeURIComponent(String(value));
224
+ });
225
+ }
226
+ function getHeadersAsObject(headers) {
227
+ const result = {};
228
+ headers.forEach((value, key) => {
229
+ result[key] = value;
230
+ });
231
+ return result;
232
+ }
233
+ function calculateSizeInMb(data) {
234
+ const sizeInBytes = new TextEncoder().encode(JSON.stringify(data)).length;
235
+ return sizeInBytes / (1024 * 1024);
236
+ }
237
+ function defaultShouldRetry({ error, response }) {
238
+ if (error)
239
+ return true;
240
+ return response ? response.status >= 500 : false;
241
+ }
242
+ function delay(ms) {
243
+ return new Promise((resolve) => setTimeout(resolve, ms));
244
+ }
245
+ export {
246
+ createApiWrapper,
247
+ createApiClient,
248
+ ResponseValidationError,
249
+ HttpResponseError
250
+ };
@@ -0,0 +1,22 @@
1
+ export interface ArrayDifference<TCurrent, TNext> {
2
+ new: TNext[];
3
+ upsert: TNext[];
4
+ delete: TCurrent[];
5
+ }
6
+ type PropertyKeyValue = string | number | symbol;
7
+ type NonNullableValue<T> = T extends null | undefined ? never : T;
8
+ type ItemsMatchingKeyValue<T, TKey extends keyof T, TValue> = [
9
+ Extract<T, Record<TKey, TValue>>
10
+ ] extends [never] ? T : Extract<T, Record<TKey, TValue>>;
11
+ export declare function chunkArray<T>(array: readonly T[], chunkSize: number): T[][];
12
+ export declare function findArrayDifferenceByKey<TCurrent, TNext, TKey extends keyof TCurrent & keyof TNext>(current: readonly TCurrent[], next: readonly TNext[], key: TKey): ArrayDifference<TCurrent, TNext>;
13
+ export declare function getUniqueValues<T>(array: readonly T[]): T[];
14
+ export declare function compact<T>(array: readonly T[]): NonNullableValue<T>[];
15
+ export declare function getUniqueValuesByKey<T, TKey extends keyof T>(array: readonly T[], key: TKey): T[];
16
+ export declare function groupBy<T, TKey extends keyof T, TGroupKey extends Extract<T[TKey], PropertyKeyValue>>(array: readonly T[], key: TKey): Record<TGroupKey, T[]>;
17
+ export declare function groupBy<T, TGroupKey extends PropertyKeyValue>(array: readonly T[], keyFn: (item: T) => TGroupKey): Record<TGroupKey, T[]>;
18
+ export declare function keyBy<T, TKey extends keyof T, TRecordKey extends Extract<T[TKey], PropertyKeyValue>>(array: readonly T[], key: TKey): Record<TRecordKey, T>;
19
+ export declare function keyBy<T, TRecordKey extends PropertyKeyValue>(array: readonly T[], keyFn: (item: T) => TRecordKey): Record<TRecordKey, T>;
20
+ export declare function partition<T>(array: readonly T[], predicate: (item: T, index: number) => boolean): [matched: T[], unmatched: T[]];
21
+ export declare function pickItemsInArray<T, TKey extends keyof T, const TValue extends readonly T[TKey][]>(array: readonly T[], key: TKey, values: TValue): ItemsMatchingKeyValue<T, TKey, TValue[number]>[];
22
+ export {};
@@ -0,0 +1,19 @@
1
+ type AnyFunction = (...args: never[]) => unknown;
2
+ type CachedFunction<T extends AnyFunction> = {
3
+ (...args: Parameters<T>): ReturnType<T>;
4
+ clearCache: () => void;
5
+ };
6
+ export type CacheFunctionOptions<T extends AnyFunction> = {
7
+ name: string;
8
+ fn: T;
9
+ cacheTimeMs?: number;
10
+ refreshInBackground?: boolean;
11
+ cacheKeyArgs?: number[];
12
+ cacheKeyFn?: (...args: Parameters<T>) => unknown[];
13
+ /** Called when a background refresh fails (default: silent). */
14
+ onBackgroundRefreshError?: (error: unknown, context: {
15
+ name: string;
16
+ }) => void;
17
+ };
18
+ export declare function cacheFunction<T extends AnyFunction>(options: CacheFunctionOptions<T>): CachedFunction<T>;
19
+ export {};
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Calculate the previous period date range for a given period type.
3
+ */
4
+ export declare function getPreviousReportPeriod(periodType: 'MONTHLY' | 'QUARTERLY' | 'YEARLY', referenceDate?: Date): {
5
+ start: Date;
6
+ end: Date;
7
+ };
8
+ /**
9
+ * Format a date range as a human-readable period label.
10
+ */
11
+ export declare function formatPeriodLabel(start: Date | string, end: Date | string, includeDay?: boolean): string;
@@ -0,0 +1,2 @@
1
+ export declare function compressString(input: string): string;
2
+ export declare function decompressString(compressedString: string): string;
@@ -0,0 +1,16 @@
1
+ // src/gzip/index.ts
2
+ import pako from "pako";
3
+ function compressString(input) {
4
+ const uint8Array = new TextEncoder().encode(input);
5
+ const compressed = pako.deflate(uint8Array);
6
+ return btoa(String.fromCharCode.apply(null, compressed));
7
+ }
8
+ function decompressString(compressedString) {
9
+ const compressedData = new Uint8Array(atob(compressedString).split("").map((char) => char.charCodeAt(0)));
10
+ const decompressed = pako.inflate(compressedData);
11
+ return new TextDecoder().decode(decompressed);
12
+ }
13
+ export {
14
+ decompressString,
15
+ compressString
16
+ };
@@ -0,0 +1,12 @@
1
+ export * from './typedswitch';
2
+ export * from './array';
3
+ export * from './string';
4
+ export * from './promise';
5
+ export * from './cache';
6
+ export * from './state';
7
+ export * from './math';
8
+ export * from './date';
9
+ export * from './url';
10
+ export * from './zod';
11
+ export * from './api-wrapper';
12
+ export * from './p-queue';