hanzio 1.2.0 → 2.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.
- package/README.md +786 -239
- package/dist/api-wrapper/builders.d.ts +4 -3
- package/dist/api-wrapper/create-client.d.ts +1 -1
- package/dist/api-wrapper/describe.d.ts +16 -0
- package/dist/api-wrapper/endpoint.d.ts +18 -0
- package/dist/api-wrapper/errors.d.ts +39 -2
- package/dist/api-wrapper/index.d.ts +5 -0
- package/dist/api-wrapper/otel.d.ts +44 -0
- package/dist/api-wrapper/paginate.d.ts +12 -0
- package/dist/api-wrapper/redaction.d.ts +2 -0
- package/dist/api-wrapper/shared.d.ts +16 -2
- package/dist/api-wrapper/testing.d.ts +23 -0
- package/dist/api-wrapper/transport.d.ts +4 -5
- package/dist/api-wrapper/types.d.ts +103 -18
- package/dist/array/index.d.ts +50 -4
- package/dist/cache/createCache.d.ts +31 -0
- package/dist/cache/index.d.ts +19 -2
- package/dist/color/contrast.d.ts +40 -0
- package/dist/color/index.d.ts +2 -0
- package/dist/{math → color}/metricsSeriesColors.d.ts +14 -7
- package/dist/cool-console-log/index.d.ts +34 -5
- package/dist/crypto/encoding.d.ts +18 -0
- package/dist/crypto/hash.d.ts +33 -0
- package/dist/crypto/index.d.ts +4 -0
- package/dist/crypto/sealer.d.ts +25 -0
- package/dist/crypto/webhook.d.ts +20 -0
- package/dist/date/index.d.ts +46 -2
- package/dist/emitter/index.d.ts +21 -0
- package/dist/error/index.d.ts +12 -0
- package/dist/format/index.d.ts +102 -0
- package/dist/function/index.d.ts +60 -0
- package/dist/guard/index.d.ts +20 -0
- package/dist/i18n/index.d.ts +37 -0
- package/dist/index-0zvtcbzy.js +224 -0
- package/dist/index-181e6vfw.js +29 -0
- package/dist/index-8r2vdzjg.js +87 -0
- package/dist/index-hy7kbb99.js +157 -0
- package/dist/{src/secrets/infisical.js → index-j1ybktbr.js} +500 -538
- package/dist/index-k9yx6bne.js +89 -0
- package/dist/index-kj1phgrx.js +169 -0
- package/dist/index-mnkcfyx8.js +78 -0
- package/dist/{p-queue/index.js → index-pwt0dz63.js} +7 -11
- package/dist/index-rp5pqghz.js +157 -0
- package/dist/index-srt06xd2.js +15 -0
- package/dist/index-wjyb9bsd.js +263 -0
- package/dist/index.d.ts +7 -7
- package/dist/jwt/index.d.ts +50 -2
- package/dist/math/index.d.ts +2 -2
- package/dist/math/linearRegressionTrend.d.ts +14 -1
- package/dist/math/scale.d.ts +8 -0
- package/dist/object/index.d.ts +19 -0
- package/dist/p-queue/index.d.ts +1 -1
- package/dist/promise/index.d.ts +97 -2
- package/dist/sitemap/index.d.ts +48 -6
- package/dist/src/api-wrapper/index.js +60 -1130
- package/dist/src/api-wrapper/testing.js +20 -0
- package/dist/{cool-console-log → src/cool-console-log}/index.js +80 -33
- package/dist/src/crypto/index.js +195 -0
- package/dist/src/format/index.js +211 -0
- package/dist/src/i18n/index.js +36 -0
- package/dist/src/index.js +739 -3145
- package/dist/src/jwt/index.js +138 -92
- package/dist/src/p-queue/index.js +9 -437
- package/dist/src/secrets/index.js +44 -1428
- package/dist/src/secrets/vite.js +6 -39
- package/dist/src/secrets/worker.js +10 -273
- package/dist/src/sitemap/index.js +183 -511
- package/dist/src/storage/index.js +119 -0
- package/dist/src/zod/index.js +340 -167
- package/dist/state/index.d.ts +21 -0
- package/dist/storage/index.d.ts +58 -0
- package/dist/string/index.d.ts +75 -2
- package/dist/typedswitch/index.d.ts +31 -24
- package/dist/types/index.d.ts +11 -0
- package/dist/url/index.d.ts +50 -1
- package/dist/zod/index.d.ts +32 -3
- package/dist/zod/introspect.d.ts +19 -0
- package/dist/zod/jsonToZodSchemaMapper.d.ts +3 -1
- package/dist/zod/zodMinMaxFilter.d.ts +22 -3
- package/dist/zod/zodToTypeString.d.ts +8 -3
- package/package.json +78 -7
- package/src/api-wrapper/actions-impl.ts +11 -72
- package/src/api-wrapper/builders.ts +32 -25
- package/src/api-wrapper/create-client.ts +171 -41
- package/src/api-wrapper/describe.ts +157 -0
- package/src/api-wrapper/endpoint.ts +26 -0
- package/src/api-wrapper/error-reporting.ts +7 -1
- package/src/api-wrapper/errors.ts +107 -2
- package/src/api-wrapper/headers.ts +11 -6
- package/src/api-wrapper/index.ts +5 -0
- package/src/api-wrapper/otel.ts +139 -0
- package/src/api-wrapper/paginate.ts +28 -0
- package/src/api-wrapper/redaction.ts +42 -0
- package/src/api-wrapper/shared.ts +21 -2
- package/src/api-wrapper/testing.ts +48 -0
- package/src/api-wrapper/transport.ts +41 -43
- package/src/api-wrapper/types.ts +185 -18
- package/src/array/index.ts +195 -42
- package/src/cache/createCache.ts +157 -0
- package/src/cache/index.ts +132 -147
- package/src/color/contrast.ts +163 -0
- package/src/color/index.ts +22 -0
- package/src/{math → color}/metricsSeriesColors.ts +47 -49
- package/src/cool-console-log/index.ts +128 -41
- package/src/crypto/encoding.ts +83 -0
- package/src/crypto/hash.ts +170 -0
- package/src/crypto/index.ts +26 -0
- package/src/crypto/sealer.ts +168 -0
- package/src/crypto/webhook.ts +86 -0
- package/src/date/index.ts +109 -34
- package/src/emitter/index.ts +82 -0
- package/src/error/index.ts +63 -0
- package/src/format/index.ts +389 -0
- package/src/function/index.ts +201 -0
- package/src/guard/index.ts +80 -0
- package/src/i18n/index.ts +125 -0
- package/src/index.ts +11 -7
- package/src/jwt/index.ts +199 -125
- package/src/math/index.ts +5 -10
- package/src/math/linearRegressionTrend.ts +33 -8
- package/src/math/scale.ts +17 -0
- package/src/object/index.ts +99 -0
- package/src/p-queue/index.ts +2 -0
- package/src/promise/index.ts +304 -9
- package/src/sitemap/index.ts +293 -130
- package/src/state/index.ts +133 -81
- package/src/storage/index.ts +187 -0
- package/src/string/index.ts +261 -23
- package/src/typedswitch/index.ts +58 -38
- package/src/types/index.ts +14 -0
- package/src/url/index.ts +146 -1
- package/src/zod/index.ts +105 -9
- package/src/zod/introspect.ts +103 -0
- package/src/zod/jsonToZodSchemaMapper.ts +44 -21
- package/src/zod/zodMinMaxFilter.ts +37 -13
- package/src/zod/zodToTypeString.ts +184 -181
- package/dist/api-wrapper/index.js +0 -250
- package/dist/gzip/index.d.ts +0 -2
- package/dist/gzip/index.js +0 -16
- package/dist/index.js +0 -1823
- package/dist/jwt/index.js +0 -19
- package/dist/sitemap/index.js +0 -552
- package/dist/src/gzip/index.js +0 -16
- package/dist/src/secrets/loaders.js +0 -1529
- package/dist/zod/index.js +0 -210
|
@@ -4,20 +4,21 @@ import type { ApiEndpoint } from './types';
|
|
|
4
4
|
export type BuildUrlOptions = {
|
|
5
5
|
endpoint: ApiEndpoint;
|
|
6
6
|
params: PathParams | undefined;
|
|
7
|
-
queryParams:
|
|
7
|
+
queryParams: QueryParams;
|
|
8
8
|
baseApiUrls: Record<string, BaseApiUrl>;
|
|
9
9
|
defaultBaseApiUrl: string | undefined;
|
|
10
10
|
url: string | undefined;
|
|
11
11
|
doNotEncodeQueryParams?: boolean;
|
|
12
12
|
configError: (msg: string) => ConfigError;
|
|
13
13
|
};
|
|
14
|
-
export declare function formatQueryString(queryParams: Record<string, string>, doNotEncode?: boolean): string;
|
|
15
14
|
export declare function buildUrl(options: BuildUrlOptions): {
|
|
16
15
|
finalUrl: string;
|
|
17
16
|
fullUrl: string;
|
|
18
17
|
};
|
|
19
18
|
export declare function buildPathParams(endpoint: ApiEndpoint, params: PathParams | undefined, ctx: (over?: Partial<ApiErrorContext>) => ApiErrorContext): PathParams | undefined;
|
|
20
|
-
export declare function buildQueryParams(endpoint: ApiEndpoint, query: QueryParams | undefined, ctx: (over?: Partial<ApiErrorContext>) => ApiErrorContext):
|
|
19
|
+
export declare function buildQueryParams(endpoint: ApiEndpoint, query: QueryParams | undefined, ctx: (over?: Partial<ApiErrorContext>) => ApiErrorContext): QueryParams;
|
|
21
20
|
export declare function buildRequestBody(endpoint: ApiEndpoint, body: unknown, ctx: (over?: Partial<ApiErrorContext>) => ApiErrorContext): unknown;
|
|
22
21
|
export declare function buildHeaders(endpoint: ApiEndpoint, headers: Record<string, string> | undefined, defaultHeaders: Record<string, string>, ctx: (over?: Partial<ApiErrorContext>) => ApiErrorContext): Record<string, string>;
|
|
22
|
+
/** Joins with exactly one `/`; empty paths and query-only paths are appended as-is. */
|
|
23
|
+
export declare function joinUrlPath(base: string, path: string): string;
|
|
23
24
|
export declare function replacePathParams(path: string, params: PathParams | undefined, configError: (msg: string) => ConfigError): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApiAction, ApiClient, ApiClientConfigNoActions, ApiClientConfigWithActions, ApiEndpoint } from './types';
|
|
2
2
|
export interface CreateApiClientFn {
|
|
3
|
-
<T extends Record<string, ApiEndpoint>>(apiConfig: ApiClientConfigNoActions<T>): ApiClient<T, Record<string, never>>;
|
|
4
3
|
<T extends Record<string, ApiEndpoint>, A extends Record<string, ApiAction>>(apiConfig: ApiClientConfigWithActions<T, A>): ApiClient<T, A>;
|
|
4
|
+
<T extends Record<string, ApiEndpoint>>(apiConfig: ApiClientConfigNoActions<T>): ApiClient<T, Record<string, never>>;
|
|
5
5
|
}
|
|
6
6
|
export declare const createApiClient: CreateApiClientFn;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ApiError } from './errors';
|
|
2
|
+
/**
|
|
3
|
+
* A multi-line, human-readable summary of an `ApiError` that says up front
|
|
4
|
+
* whether the server, the caller's input, or the response shape is at fault.
|
|
5
|
+
* The client logs this via `logger.error` for every failed request.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ✗ github.usersGet failed: output validation failed: the response doesn't match resSchema
|
|
9
|
+
* GET https://api.github.com/users/1 · attempt 1/4 · 212ms
|
|
10
|
+
* [1] id: Expected number, received string
|
|
11
|
+
* value: "oops"
|
|
12
|
+
* fix: update resSchema (or resFormatter), or mark changed fields optional
|
|
13
|
+
*/
|
|
14
|
+
export declare function describeApiError(error: ApiError): string;
|
|
15
|
+
/** Flat, primitive attributes for structured loggers and log processors. */
|
|
16
|
+
export declare function apiErrorLogAttributes(error: ApiError): Record<string, string | number | boolean>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { ApiEndpoint } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Defines an endpoint with full inference, as an alternative to
|
|
5
|
+
* `satisfies ApiEndpoint`: `resFormatter` must return what `resSchema`
|
|
6
|
+
* accepts, and a literal `path` types its `:params` (making `reqParams`
|
|
7
|
+
* required when there's no `reqParamsSchema`).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* export const usersGet = defineEndpoint({
|
|
11
|
+
* method: 'GET',
|
|
12
|
+
* path: '/users/:userId',
|
|
13
|
+
* resSchema: User
|
|
14
|
+
* })
|
|
15
|
+
*/
|
|
16
|
+
export declare function defineEndpoint<const TEndpoint extends ApiEndpoint>(endpoint: TEndpoint & {
|
|
17
|
+
resFormatter?: (data: unknown, headers: Record<string, string>) => z.input<TEndpoint['resSchema']>;
|
|
18
|
+
}): TEndpoint;
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import type { ZodError } from 'zod';
|
|
2
2
|
import type { ApiErrorContext } from './shared';
|
|
3
3
|
import { type FormattedZodIssue } from './zod-issues';
|
|
4
|
+
/**
|
|
5
|
+
* Where a request failed, for logs and alerts:
|
|
6
|
+
* - `server_error`: the API returned 5xx
|
|
7
|
+
* - `client_error`: the API rejected the request (4xx: auth, params, body)
|
|
8
|
+
* - `input_validation`: the caller's data failed a request schema; nothing was sent
|
|
9
|
+
* - `output_validation`: the response wasn't JSON or didn't match `resSchema`
|
|
10
|
+
* - `response_rejected`: `checkResponse` rejected a valid response
|
|
11
|
+
* - `network` / `timeout` / `aborted`: the request didn't complete
|
|
12
|
+
* - `config`: the client or call is misconfigured
|
|
13
|
+
* - `action`: a composite action handler threw
|
|
14
|
+
*/
|
|
15
|
+
export type ApiErrorCategory = 'server_error' | 'client_error' | 'input_validation' | 'output_validation' | 'response_rejected' | 'network' | 'timeout' | 'aborted' | 'config' | 'action' | 'unknown';
|
|
4
16
|
export declare class ApiError extends Error {
|
|
5
17
|
context: ApiErrorContext;
|
|
6
18
|
cause?: unknown;
|
|
19
|
+
get category(): ApiErrorCategory;
|
|
7
20
|
constructor(message: string, context: ApiErrorContext, options?: {
|
|
8
21
|
cause?: unknown;
|
|
9
22
|
});
|
|
@@ -16,6 +29,7 @@ export declare class HttpResponseError extends ApiError {
|
|
|
16
29
|
readonly responseHeaders: Record<string, string>;
|
|
17
30
|
readonly requestId?: string;
|
|
18
31
|
constructor(status: number, body: string, responseHeaders: Record<string, string>, context: ApiErrorContext);
|
|
32
|
+
get category(): ApiErrorCategory;
|
|
19
33
|
toJSON(): Record<string, unknown>;
|
|
20
34
|
}
|
|
21
35
|
export declare class ResponseValidationError extends ApiError {
|
|
@@ -23,10 +37,16 @@ export declare class ResponseValidationError extends ApiError {
|
|
|
23
37
|
readonly rawResponse: unknown;
|
|
24
38
|
readonly issues: FormattedZodIssue[];
|
|
25
39
|
readonly validationIssues: string;
|
|
26
|
-
|
|
40
|
+
/** True when the body wasn't valid JSON (rather than failing `resSchema`). */
|
|
41
|
+
readonly invalidJson: boolean;
|
|
42
|
+
constructor(zodError: ZodError, rawResponse: unknown, context: ApiErrorContext, options?: {
|
|
43
|
+
invalidJson?: boolean;
|
|
44
|
+
});
|
|
45
|
+
get category(): ApiErrorCategory;
|
|
27
46
|
toJSON(): Record<string, unknown>;
|
|
28
47
|
}
|
|
29
|
-
|
|
48
|
+
/** Which request schema failed; `input` is an action's input schema. */
|
|
49
|
+
export type RequestValidationTarget = 'body' | 'query' | 'params' | 'headers' | 'input';
|
|
30
50
|
export declare class RequestValidationError extends ApiError {
|
|
31
51
|
readonly zodError: ZodError;
|
|
32
52
|
readonly target: RequestValidationTarget;
|
|
@@ -34,23 +54,39 @@ export declare class RequestValidationError extends ApiError {
|
|
|
34
54
|
readonly issues: FormattedZodIssue[];
|
|
35
55
|
readonly validationIssues: string;
|
|
36
56
|
constructor(zodError: ZodError, target: RequestValidationTarget, rawInput: unknown, context: ApiErrorContext);
|
|
57
|
+
get category(): ApiErrorCategory;
|
|
37
58
|
toJSON(): Record<string, unknown>;
|
|
38
59
|
}
|
|
39
60
|
export declare class RequestTimeoutError extends ApiError {
|
|
40
61
|
readonly timeoutMs: number;
|
|
41
62
|
constructor(timeoutMs: number, context: ApiErrorContext);
|
|
63
|
+
get category(): ApiErrorCategory;
|
|
42
64
|
}
|
|
43
65
|
export declare class NetworkError extends ApiError {
|
|
44
66
|
constructor(message: string, context: ApiErrorContext, cause?: unknown);
|
|
67
|
+
get category(): ApiErrorCategory;
|
|
45
68
|
}
|
|
46
69
|
export declare class RequestAbortedError extends ApiError {
|
|
47
70
|
constructor(context: ApiErrorContext, cause?: unknown);
|
|
71
|
+
get category(): ApiErrorCategory;
|
|
48
72
|
}
|
|
49
73
|
export declare class ConfigError extends ApiError {
|
|
50
74
|
constructor(message: string, context: ApiErrorContext);
|
|
75
|
+
get category(): ApiErrorCategory;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Thrown when the client's `checkResponse` rejects a response that passed
|
|
79
|
+
* schema validation (e.g. HTTP 200 with `{ success: false }`).
|
|
80
|
+
*/
|
|
81
|
+
export declare class ApiResponseError extends ApiError {
|
|
82
|
+
readonly data: unknown;
|
|
83
|
+
constructor(message: string, data: unknown, context: ApiErrorContext, cause?: unknown);
|
|
84
|
+
get category(): ApiErrorCategory;
|
|
85
|
+
toJSON(): Record<string, unknown>;
|
|
51
86
|
}
|
|
52
87
|
export declare class ActionError extends ApiError {
|
|
53
88
|
constructor(message: string, context: ApiErrorContext, cause?: unknown);
|
|
89
|
+
get category(): ApiErrorCategory;
|
|
54
90
|
}
|
|
55
91
|
export declare const isApiError: (e: unknown) => e is ApiError;
|
|
56
92
|
export declare const isHttpResponseError: (e: unknown) => e is HttpResponseError;
|
|
@@ -61,4 +97,5 @@ export declare const isNetworkError: (e: unknown) => e is NetworkError;
|
|
|
61
97
|
export declare const isRequestAbortedError: (e: unknown) => e is RequestAbortedError;
|
|
62
98
|
export declare const isConfigError: (e: unknown) => e is ConfigError;
|
|
63
99
|
export declare const isActionError: (e: unknown) => e is ActionError;
|
|
100
|
+
export declare const isApiResponseError: (e: unknown) => e is ApiResponseError;
|
|
64
101
|
export declare function isNonRetryableApiError(error: unknown): boolean;
|
|
@@ -3,3 +3,8 @@ export * from './types';
|
|
|
3
3
|
export * from './errors';
|
|
4
4
|
export { createApiClient } from './create-client';
|
|
5
5
|
export { defineAction } from './actions-impl';
|
|
6
|
+
export { type PaginateOptions, paginate } from './paginate';
|
|
7
|
+
export { defaultShouldRetry } from './transport';
|
|
8
|
+
export { apiErrorLogAttributes, describeApiError } from './describe';
|
|
9
|
+
export * from './otel';
|
|
10
|
+
export { defineEndpoint } from './endpoint';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ApiMiddleware, ApiMiddlewareContext } from './types';
|
|
2
|
+
export type OtelAttributes = Record<string, string | number | boolean | undefined>;
|
|
3
|
+
export type OtelSpanLike = {
|
|
4
|
+
setAttributes(attributes: OtelAttributes): unknown;
|
|
5
|
+
setStatus(status: {
|
|
6
|
+
code: number;
|
|
7
|
+
message?: string;
|
|
8
|
+
}): unknown;
|
|
9
|
+
recordException(exception: Error | string): unknown;
|
|
10
|
+
end(): void;
|
|
11
|
+
};
|
|
12
|
+
export type OtelTracerLike = {
|
|
13
|
+
startActiveSpan<F extends (span: OtelSpanLike) => unknown>(name: string, options: {
|
|
14
|
+
kind?: number;
|
|
15
|
+
attributes?: OtelAttributes;
|
|
16
|
+
}, fn: F): ReturnType<F>;
|
|
17
|
+
};
|
|
18
|
+
export type OtelMiddlewareOptions = {
|
|
19
|
+
/** Default: `client.endpoint`, or the endpoint key when the client has no `name`. */
|
|
20
|
+
spanName?: (context: ApiMiddlewareContext) => string;
|
|
21
|
+
/** Extra attributes set when the span starts. */
|
|
22
|
+
attributes?: (context: ApiMiddlewareContext) => OtelAttributes;
|
|
23
|
+
/**
|
|
24
|
+
* `url.full` keeps the query string with sensitive values redacted
|
|
25
|
+
* (default). Set `false` to drop the query string entirely.
|
|
26
|
+
*/
|
|
27
|
+
includeQuery?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Adds trace-context headers to the outgoing request, e.g.
|
|
30
|
+
* `(headers) => propagation.inject(context.active(), headers)`. Runs inside
|
|
31
|
+
* the active span. Not needed when fetch is already auto-instrumented.
|
|
32
|
+
*/
|
|
33
|
+
inject?: (headers: Record<string, string>) => void;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* One CLIENT span per logical request (all retries, validation and
|
|
37
|
+
* `checkResponse`), with OpenTelemetry HTTP semantic-convention attributes.
|
|
38
|
+
* It's the active span, so auto-instrumented fetch attempts become children.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* import { trace } from '@opentelemetry/api'
|
|
42
|
+
* createApiClient({ ...config, use: [otelMiddleware(trace.getTracer('app'))] })
|
|
43
|
+
*/
|
|
44
|
+
export declare function otelMiddleware(tracer: OtelTracerLike, options?: OtelMiddlewareOptions): ApiMiddleware;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type PaginateOptions = {
|
|
2
|
+
/** Stop after this many pages (default: no limit). */
|
|
3
|
+
maxPages?: number;
|
|
4
|
+
/** Checked before each page fetch; throws `signal.reason` when aborted. */
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Yields pages until `getNext` returns `null`/`undefined`. The first call to
|
|
9
|
+
* `fetchPage` gets `undefined` as cursor. Pages are fetched lazily, so
|
|
10
|
+
* breaking out of `for await` stops further requests.
|
|
11
|
+
*/
|
|
12
|
+
export declare function paginate<TPage, TCursor>(fetchPage: (cursor: TCursor | undefined) => Promise<TPage>, getNext: (page: TPage) => TCursor | null | undefined, options?: PaginateOptions): AsyncGenerator<TPage, void, undefined>;
|
|
@@ -1,8 +1,22 @@
|
|
|
1
|
+
import type { UrlQueryParams } from '../url';
|
|
1
2
|
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
2
3
|
export type RequestBodyFormat = 'json' | 'form-data';
|
|
3
4
|
export type BaseApiUrl = string | (() => string);
|
|
4
5
|
export type PathParams = Record<string, string | number>;
|
|
5
|
-
|
|
6
|
+
/** Array values repeat the key (`a=1&a=2`); `null`/`undefined` are skipped. */
|
|
7
|
+
export type QueryParams = UrlQueryParams;
|
|
8
|
+
/**
|
|
9
|
+
* Augment to type `meta` on requests, actions, hooks, middleware and errors:
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* declare module 'hanzio/api-wrapper' {
|
|
13
|
+
* interface ApiRequestMeta { traceId: string; tenantId?: string }
|
|
14
|
+
* }
|
|
15
|
+
*/
|
|
16
|
+
export interface ApiRequestMeta {
|
|
17
|
+
}
|
|
18
|
+
/** `ApiRequestMeta` once augmented, otherwise any record. */
|
|
19
|
+
export type RequestMeta = keyof ApiRequestMeta extends never ? Record<string, unknown> : ApiRequestMeta;
|
|
6
20
|
export type ApiErrorContext = {
|
|
7
21
|
endpoint: string;
|
|
8
22
|
method: HttpMethod;
|
|
@@ -12,5 +26,5 @@ export type ApiErrorContext = {
|
|
|
12
26
|
elapsedMs: number;
|
|
13
27
|
requestHeaders?: Record<string, string>;
|
|
14
28
|
requestBody?: unknown;
|
|
15
|
-
meta?:
|
|
29
|
+
meta?: RequestMeta;
|
|
16
30
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FetchLike } from './types';
|
|
2
|
+
export type MockFetch = FetchLike & {
|
|
3
|
+
/** Every request received, in order. Bodies are still readable. */
|
|
4
|
+
readonly calls: Request[];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* A fetch stand-in for tests: pass it as the client's `fetch` option instead
|
|
8
|
+
* of replacing `globalThis.fetch`. Test-runner agnostic.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const fetch = createMockFetch((request) =>
|
|
12
|
+
* request.url.endsWith('/users/1')
|
|
13
|
+
* ? jsonResponse({ id: 1 })
|
|
14
|
+
* : jsonResponse({ error: 'not found' }, { status: 404 })
|
|
15
|
+
* )
|
|
16
|
+
* const api = createApiClient({ ...config, fetch })
|
|
17
|
+
*/
|
|
18
|
+
export declare function createMockFetch(handler: (request: Request, callIndex: number) => Response | Promise<Response>): MockFetch;
|
|
19
|
+
/** A JSON `Response` with `content-type: application/json`. */
|
|
20
|
+
export declare function jsonResponse(body: unknown, init?: {
|
|
21
|
+
status?: number;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
}): Response;
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { type z } from 'zod';
|
|
2
|
-
import type { ApiEndpoint, OnResponseContext, OnRetryContext, RetryContext } from './types';
|
|
3
|
-
import type { ApiErrorContext, HttpMethod } from './shared';
|
|
2
|
+
import type { ApiEndpoint, FetchLike, OnResponseContext, OnRetryContext, RetryContext } from './types';
|
|
3
|
+
import type { ApiErrorContext, HttpMethod, RequestMeta } from './shared';
|
|
4
4
|
export type RequestConfig = {
|
|
5
5
|
method: HttpMethod;
|
|
6
6
|
fullUrl: string;
|
|
7
7
|
body: unknown;
|
|
8
8
|
headers: Record<string, string>;
|
|
9
9
|
timeoutMs?: number;
|
|
10
|
-
fetchFn:
|
|
10
|
+
fetchFn: FetchLike;
|
|
11
11
|
userSignal?: AbortSignal;
|
|
12
12
|
};
|
|
13
13
|
export declare function validateResponse<T extends z.ZodType>(schema: T, data: unknown, ctx: (over?: Partial<ApiErrorContext>) => ApiErrorContext): Promise<z.infer<T>>;
|
|
14
14
|
export declare function httpRequest(config: RequestConfig, ctx: (over?: Partial<ApiErrorContext>) => ApiErrorContext): Promise<Response>;
|
|
15
|
-
export declare function mergeSignals(a: AbortSignal, b: AbortSignal | undefined): AbortSignal;
|
|
16
15
|
export declare function readErrorResponse(response: Response): Promise<{
|
|
17
16
|
body: string;
|
|
18
17
|
headers: Record<string, string>;
|
|
@@ -38,7 +37,7 @@ type MakeRequestArgs<TValidated> = {
|
|
|
38
37
|
endpointName: string;
|
|
39
38
|
method: HttpMethod;
|
|
40
39
|
fullUrl: string;
|
|
41
|
-
meta?:
|
|
40
|
+
meta?: RequestMeta;
|
|
42
41
|
onResponse?: (context: OnResponseContext) => void | Promise<void>;
|
|
43
42
|
onRetry?: (context: OnRetryContext) => void | Promise<void>;
|
|
44
43
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
|
+
import type { Result } from '../promise';
|
|
2
3
|
import type { ApiError } from './errors';
|
|
3
|
-
import type { ApiErrorContext, BaseApiUrl, HttpMethod, PathParams, QueryParams, RequestBodyFormat } from './shared';
|
|
4
|
+
import type { ApiErrorContext, BaseApiUrl, HttpMethod, PathParams, QueryParams, RequestBodyFormat, RequestMeta } from './shared';
|
|
4
5
|
export interface ApiEndpoint<TReqBody extends z.ZodType | undefined = z.ZodType | undefined, TReqParams extends z.ZodType | undefined = z.ZodType | undefined, TReqQuery extends z.ZodType | undefined = z.ZodType | undefined, TReqHeaders extends z.ZodType | undefined = z.ZodType | undefined, TResponse extends z.ZodType = z.ZodType> {
|
|
5
6
|
method: HttpMethod;
|
|
6
7
|
path: string | ((baseUrl: string) => string);
|
|
@@ -12,7 +13,8 @@ export interface ApiEndpoint<TReqBody extends z.ZodType | undefined = z.ZodType
|
|
|
12
13
|
baseApiUrl?: string;
|
|
13
14
|
reqBodyFormat?: RequestBodyFormat;
|
|
14
15
|
defaultHeaders?: Record<string, string>;
|
|
15
|
-
|
|
16
|
+
/** Reshapes the raw body before `resSchema` parses it, so it returns the schema's input type. */
|
|
17
|
+
resFormatter?: (data: unknown, headers: Record<string, string>) => z.input<TResponse>;
|
|
16
18
|
reqDefaultQueryParams?: QueryParams;
|
|
17
19
|
doNotEncodeQueryParams?: boolean;
|
|
18
20
|
}
|
|
@@ -47,7 +49,7 @@ export type OnRequestContext = {
|
|
|
47
49
|
url: string;
|
|
48
50
|
headers: Record<string, string>;
|
|
49
51
|
body: unknown;
|
|
50
|
-
meta?:
|
|
52
|
+
meta?: RequestMeta;
|
|
51
53
|
};
|
|
52
54
|
export type OnResponseContext = {
|
|
53
55
|
endpoint: string;
|
|
@@ -55,7 +57,7 @@ export type OnResponseContext = {
|
|
|
55
57
|
url: string;
|
|
56
58
|
response: Response;
|
|
57
59
|
attempt: number;
|
|
58
|
-
meta?:
|
|
60
|
+
meta?: RequestMeta;
|
|
59
61
|
};
|
|
60
62
|
export type ActionCache = {
|
|
61
63
|
<T>(key: string, fn: () => Promise<T> | T, options?: {
|
|
@@ -70,76 +72,159 @@ export type ActionCache = {
|
|
|
70
72
|
};
|
|
71
73
|
export type ActionInvokeOptions = {
|
|
72
74
|
signal?: AbortSignal;
|
|
73
|
-
meta?:
|
|
75
|
+
meta?: RequestMeta;
|
|
74
76
|
};
|
|
75
77
|
export type ActionContext<TInput, TApi = any> = {
|
|
76
78
|
input: TInput;
|
|
77
79
|
api: TApi;
|
|
78
80
|
signal?: AbortSignal;
|
|
79
|
-
meta?:
|
|
81
|
+
meta?: RequestMeta;
|
|
80
82
|
logger?: Pick<Console, 'debug' | 'error'>;
|
|
81
83
|
cache: ActionCache;
|
|
82
84
|
};
|
|
83
85
|
export interface ApiAction<TInput = any, TOutput = any> {
|
|
84
86
|
handler: (ctx: ActionContext<TInput>) => Promise<TOutput> | TOutput;
|
|
87
|
+
/** Validates the caller's input at runtime; `handler` receives the parsed output. */
|
|
88
|
+
readonly input?: z.ZodType;
|
|
85
89
|
/** When true, the action accepts only optional `{ signal, meta }`; input is always `undefined`. */
|
|
86
90
|
readonly noRuntimeInput?: boolean;
|
|
87
91
|
}
|
|
88
92
|
type ActionDefinition<TInput, TOutput, TApi> = {
|
|
89
93
|
handler: (ctx: ActionContext<TInput, TApi>) => Promise<TOutput> | TOutput;
|
|
90
94
|
};
|
|
95
|
+
type SchemaActionDefinition<TSchema extends z.ZodType, TOutput, TApi> = {
|
|
96
|
+
input: TSchema;
|
|
97
|
+
handler: (ctx: ActionContext<z.output<TSchema>, TApi>) => Promise<TOutput> | TOutput;
|
|
98
|
+
};
|
|
91
99
|
export type DefineAction<TApi> = {
|
|
100
|
+
/** Input validated by a Zod schema: callers pass `z.input`, the handler gets `z.output`. */
|
|
101
|
+
<TSchema extends z.ZodType, TOutput>(def: SchemaActionDefinition<TSchema, TOutput, TApi>): ApiAction<z.input<TSchema>, TOutput>;
|
|
92
102
|
<TOutput>(def: ActionDefinition<undefined, TOutput, TApi>): ApiAction<undefined, TOutput>;
|
|
93
103
|
<TInput>(): <TOutput>(def: ActionDefinition<TInput, TOutput, TApi>) => ApiAction<TInput, TOutput>;
|
|
94
104
|
};
|
|
95
105
|
type ActionInput<A> = A extends ApiAction<infer I, any> ? I : never;
|
|
96
106
|
export type ActionOutput<A> = A extends ApiAction<any, infer O> ? O : never;
|
|
97
107
|
export type ActionArgs<A> = [ActionInput<A>] extends [undefined] ? [options?: ActionInvokeOptions] : [input: ActionInput<A>, options?: ActionInvokeOptions];
|
|
98
|
-
type
|
|
108
|
+
type CharsOf<S extends string, Acc extends string = never> = S extends `${infer C}${infer Rest}` ? CharsOf<Rest, Acc | C> : Acc;
|
|
109
|
+
type IdentStart = CharsOf<'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'>;
|
|
110
|
+
type WordChar = IdentStart | CharsOf<'0123456789'>;
|
|
111
|
+
type TakeWord<S extends string, Acc extends string = ''> = S extends `${infer C}${infer Rest}` ? C extends WordChar ? TakeWord<Rest, `${Acc}${C}`> : Acc : Acc;
|
|
112
|
+
/**
|
|
113
|
+
* `:name` placeholders in a literal path, matching the runtime rule (a letter
|
|
114
|
+
* or `_`, then word characters, so ports like `:8080` don't count).
|
|
115
|
+
* `never` for non-literal paths.
|
|
116
|
+
*/
|
|
117
|
+
export type PathParamNames<TPath> = TPath extends string ? string extends TPath ? never : TPath extends `${string}:${infer Rest}` ? (Rest extends `${IdentStart}${string}` ? TakeWord<Rest> : never) | PathParamNames<Rest> : never : never;
|
|
118
|
+
type EndpointPathParamNames<TEndpoint extends ApiEndpoint> = PathParamNames<TEndpoint['path']>;
|
|
119
|
+
type SchemaInputKeys<TEndpoint extends ApiEndpoint> = (TEndpoint['reqBodySchema'] extends z.ZodType ? 'reqBody' : never) | (TEndpoint['reqQuerySchema'] extends z.ZodType ? 'reqQuery' : never) | (TEndpoint['reqHeadersSchema'] extends z.ZodType ? 'reqHeaders' : never) | (TEndpoint['reqParamsSchema'] extends z.ZodType ? 'reqParams' : [EndpointPathParamNames<TEndpoint>] extends [never] ? never : 'reqParams');
|
|
99
120
|
type SchemaInputs<TEndpoint extends ApiEndpoint> = {
|
|
100
|
-
reqBody: TEndpoint['reqBodySchema'] extends z.ZodType ? z.
|
|
101
|
-
reqParams: TEndpoint['reqParamsSchema'] extends z.ZodType ? z.
|
|
102
|
-
reqQuery: TEndpoint['reqQuerySchema'] extends z.ZodType ? z.
|
|
103
|
-
reqHeaders: TEndpoint['reqHeadersSchema'] extends z.ZodType ? z.
|
|
121
|
+
reqBody: TEndpoint['reqBodySchema'] extends z.ZodType ? z.input<TEndpoint['reqBodySchema']> : unknown;
|
|
122
|
+
reqParams: TEndpoint['reqParamsSchema'] extends z.ZodType ? z.input<TEndpoint['reqParamsSchema']> : [EndpointPathParamNames<TEndpoint>] extends [never] ? PathParams : Record<EndpointPathParamNames<TEndpoint>, string | number>;
|
|
123
|
+
reqQuery: TEndpoint['reqQuerySchema'] extends z.ZodType ? z.input<TEndpoint['reqQuerySchema']> : QueryParams;
|
|
124
|
+
reqHeaders: TEndpoint['reqHeadersSchema'] extends z.ZodType ? z.input<TEndpoint['reqHeadersSchema']> : Record<string, string>;
|
|
104
125
|
};
|
|
105
126
|
type CommonRequestOptions = {
|
|
106
127
|
url?: string;
|
|
107
128
|
signal?: AbortSignal;
|
|
108
129
|
timeoutMs?: number;
|
|
109
130
|
retries?: number;
|
|
110
|
-
fetch?:
|
|
111
|
-
meta?:
|
|
131
|
+
fetch?: FetchLike;
|
|
132
|
+
meta?: RequestMeta;
|
|
112
133
|
};
|
|
113
134
|
export type RequestInput<TEndpoint extends ApiEndpoint> = Pick<SchemaInputs<TEndpoint>, SchemaInputKeys<TEndpoint>> & Partial<Omit<SchemaInputs<TEndpoint>, SchemaInputKeys<TEndpoint>>> & CommonRequestOptions;
|
|
114
135
|
type RequestArgs<TEndpoint extends ApiEndpoint> = SchemaInputKeys<TEndpoint> extends never ? [input?: RequestInput<TEndpoint>] : [input: RequestInput<TEndpoint>];
|
|
115
136
|
export type ApiClient<T extends Record<string, ApiEndpoint>, A extends Record<string, ApiAction> = Record<string, never>> = {
|
|
116
137
|
request: {
|
|
117
|
-
<K extends keyof T>(endpointKey: K, ...args: RequestArgs<T[K]>): Promise<ApiWrapperResponse<
|
|
138
|
+
<K extends keyof T>(endpointKey: K, ...args: RequestArgs<T[K]>): Promise<ApiWrapperResponse<EndpointResponse<T[K]>>>;
|
|
118
139
|
<K extends keyof A>(actionKey: K, ...args: ActionArgs<A[K]>): Promise<ActionOutput<A[K]>>;
|
|
119
140
|
};
|
|
120
141
|
cache: ActionCache;
|
|
142
|
+
/**
|
|
143
|
+
* The same endpoints and actions, resolving to `Result` instead of throwing
|
|
144
|
+
* `ApiError`s. Other errors (bugs in hooks or middleware) still throw.
|
|
145
|
+
*/
|
|
146
|
+
safe: {
|
|
147
|
+
[K in keyof T]: (...args: RequestArgs<T[K]>) => Promise<Result<ApiWrapperResponse<EndpointResponse<T[K]>>, ApiError>>;
|
|
148
|
+
} & {
|
|
149
|
+
[K in keyof A]: (...args: ActionArgs<A[K]>) => Promise<Result<ActionOutput<A[K]>, ApiError>>;
|
|
150
|
+
};
|
|
121
151
|
} & {
|
|
122
|
-
[K in keyof T]: (...args: RequestArgs<T[K]>) => Promise<ApiWrapperResponse<
|
|
152
|
+
[K in keyof T]: (...args: RequestArgs<T[K]>) => Promise<ApiWrapperResponse<EndpointResponse<T[K]>>>;
|
|
123
153
|
} & {
|
|
124
154
|
[K in keyof A]: (...args: ActionArgs<A[K]>) => Promise<ActionOutput<A[K]>>;
|
|
125
155
|
};
|
|
156
|
+
/**
|
|
157
|
+
* Any client, for helpers that accept whichever client they're given. Call
|
|
158
|
+
* endpoints through `request` or `safe`; results are `unknown`.
|
|
159
|
+
*/
|
|
160
|
+
export type AnyApiClient = {
|
|
161
|
+
request(key: string, ...args: any[]): Promise<unknown>;
|
|
162
|
+
cache: ActionCache;
|
|
163
|
+
safe: Record<string, (...args: any[]) => Promise<Result<unknown, ApiError>>>;
|
|
164
|
+
};
|
|
165
|
+
/** Parsed response data of an endpoint definition (`z.output` of `resSchema`). */
|
|
166
|
+
export type EndpointResponse<TEndpoint extends ApiEndpoint> = z.output<TEndpoint['resSchema']>;
|
|
167
|
+
/** Request input for an endpoint definition (`reqBody`, `reqParams`, …). */
|
|
168
|
+
export type EndpointRequest<TEndpoint extends ApiEndpoint> = RequestInput<TEndpoint>;
|
|
169
|
+
/** Response data of a client method: `InferResponse<typeof api, 'usersGet'>`. */
|
|
170
|
+
export type InferResponse<TClient, K extends keyof TClient> = TClient[K] extends (...args: never[]) => Promise<infer R> ? R extends ApiWrapperResponse<infer D> ? D : R : never;
|
|
171
|
+
/** First argument of a client method: `InferRequest<typeof api, 'usersGet'>`. */
|
|
172
|
+
export type InferRequest<TClient, K extends keyof TClient> = TClient[K] extends (...args: infer P) => unknown ? NonNullable<P[0]> : never;
|
|
126
173
|
export type ActionsFactoryHelpers<T extends Record<string, ApiEndpoint>, A extends Record<string, ApiAction> = Record<string, never>> = {
|
|
127
174
|
api: ApiClient<T, A>;
|
|
128
175
|
defineAction: DefineAction<ApiClient<T, A>>;
|
|
129
176
|
};
|
|
177
|
+
/** Any fetch-compatible function; avoids Bun's `typeof fetch` extras like `preconnect`. */
|
|
178
|
+
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
179
|
+
export type ApiMiddlewareContext = {
|
|
180
|
+
/** The client's `name`, when set. */
|
|
181
|
+
client?: string;
|
|
182
|
+
/** The endpoint key, e.g. `usersGet`. */
|
|
183
|
+
endpoint: string;
|
|
184
|
+
method: HttpMethod;
|
|
185
|
+
/** Full URL including the query string. */
|
|
186
|
+
url: string;
|
|
187
|
+
/** Outgoing headers. Mutate before `next()` to add headers (e.g. `traceparent`). */
|
|
188
|
+
headers: Record<string, string>;
|
|
189
|
+
meta?: RequestMeta;
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Wraps one logical request: `onRequest`, every retry attempt, validation and
|
|
193
|
+
* `checkResponse`. Call `next()` to continue; errors it throws are `ApiError`s
|
|
194
|
+
* that were already redacted and passed to `onError`.
|
|
195
|
+
*/
|
|
196
|
+
export type ApiMiddleware = (context: ApiMiddlewareContext, next: () => Promise<ApiWrapperResponse<unknown>>) => Promise<ApiWrapperResponse<unknown>>;
|
|
197
|
+
export type CheckResponseContext = {
|
|
198
|
+
endpoint: string;
|
|
199
|
+
method: HttpMethod;
|
|
200
|
+
url: string;
|
|
201
|
+
httpStatus: number;
|
|
202
|
+
attempt: number;
|
|
203
|
+
meta?: RequestMeta;
|
|
204
|
+
};
|
|
205
|
+
export type DefaultHeaders = Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
130
206
|
export interface ApiClientConfig<T extends Record<string, ApiEndpoint>, A extends Record<string, ApiAction> = Record<string, never>> {
|
|
131
207
|
name?: string;
|
|
132
208
|
baseApiUrls: Record<string, BaseApiUrl>;
|
|
133
209
|
defaultBaseApiUrl?: string;
|
|
134
210
|
endpoints: T;
|
|
135
|
-
|
|
211
|
+
/** Static headers, or a function resolved on every request (e.g. auth from a secret store). */
|
|
212
|
+
defaultHeaders?: DefaultHeaders;
|
|
136
213
|
timeoutMs?: number;
|
|
137
214
|
retries?: number;
|
|
138
215
|
retryDelayMs?: number | ((attempt: number) => number);
|
|
139
216
|
maxRetryDelayMs?: number;
|
|
140
217
|
shouldRetry?: (context: RetryContext) => boolean;
|
|
141
218
|
logger?: Pick<Console, 'debug' | 'error'>;
|
|
142
|
-
fetch?:
|
|
219
|
+
fetch?: FetchLike;
|
|
220
|
+
/** Middleware around each whole request, in array order (first is outermost). */
|
|
221
|
+
use?: ApiMiddleware[];
|
|
222
|
+
/**
|
|
223
|
+
* Runs after `resSchema` validation on every attempt. Throw to reject the
|
|
224
|
+
* response; non-`ApiError` throws become `ApiResponseError`, which the
|
|
225
|
+
* default retry policy does not retry.
|
|
226
|
+
*/
|
|
227
|
+
checkResponse?: (data: unknown, context: CheckResponseContext) => void | Promise<void>;
|
|
143
228
|
onRequest?: (context: OnRequestContext) => RequestInterceptorResult | Promise<RequestInterceptorResult>;
|
|
144
229
|
onResponse?: (context: OnResponseContext) => void | Promise<void>;
|
|
145
230
|
onError?: (error: ApiError) => void | Promise<void>;
|
|
@@ -151,4 +236,4 @@ export type ApiClientConfigNoActions<T extends Record<string, ApiEndpoint>> = Om
|
|
|
151
236
|
export type ApiClientConfigWithActions<T extends Record<string, ApiEndpoint>, A extends Record<string, ApiAction>> = Omit<ApiClientConfig<T, Record<string, never>>, 'actions'> & {
|
|
152
237
|
actions: A | ((helpers: ActionsFactoryHelpers<T, A>) => A);
|
|
153
238
|
};
|
|
154
|
-
export type { BaseApiUrl, HttpMethod, PathParams, QueryParams, RequestBodyFormat, ApiErrorContext } from './shared';
|
|
239
|
+
export type { ApiRequestMeta, RequestMeta, BaseApiUrl, HttpMethod, PathParams, QueryParams, RequestBodyFormat, ApiErrorContext } from './shared';
|
package/dist/array/index.d.ts
CHANGED
|
@@ -1,22 +1,68 @@
|
|
|
1
1
|
export interface ArrayDifference<TCurrent, TNext> {
|
|
2
2
|
new: TNext[];
|
|
3
|
+
/** Items present in both arrays (`update` + `unchanged`). */
|
|
3
4
|
upsert: TNext[];
|
|
4
5
|
delete: TCurrent[];
|
|
5
6
|
}
|
|
7
|
+
export interface ArrayDifferenceWithChanges<TCurrent, TNext> extends ArrayDifference<TCurrent, TNext> {
|
|
8
|
+
/** Matched items where `isEqual` returned false. */
|
|
9
|
+
update: TNext[];
|
|
10
|
+
/** Matched items where `isEqual` returned true. */
|
|
11
|
+
unchanged: TNext[];
|
|
12
|
+
}
|
|
6
13
|
type PropertyKeyValue = string | number | symbol;
|
|
7
14
|
type NonNullableValue<T> = T extends null | undefined ? never : T;
|
|
8
15
|
type ItemsMatchingKeyValue<T, TKey extends keyof T, TValue> = [
|
|
9
16
|
Extract<T, Record<TKey, TValue>>
|
|
10
17
|
] extends [never] ? T : Extract<T, Record<TKey, TValue>>;
|
|
11
18
|
export declare function chunkArray<T>(array: readonly T[], chunkSize: number): T[][];
|
|
12
|
-
|
|
19
|
+
type DifferenceKey<TCurrent, TNext> = (keyof TCurrent & keyof TNext) | ((item: TCurrent | TNext) => unknown);
|
|
20
|
+
/**
|
|
21
|
+
* Splits `next` into items that are new or also in `current` (`upsert`), and
|
|
22
|
+
* lists the `current` items missing from `next` (`delete`). Items match by a
|
|
23
|
+
* shared key name or a key function; key values are compared as strings.
|
|
24
|
+
* Pass `isEqual` to also split `upsert` into `update` and `unchanged`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function findArrayDifferenceByKey<TCurrent, TNext>(current: readonly TCurrent[], next: readonly TNext[], key: DifferenceKey<TCurrent, TNext>, options: {
|
|
27
|
+
isEqual: (current: TCurrent, next: TNext) => boolean;
|
|
28
|
+
}): ArrayDifferenceWithChanges<TCurrent, TNext>;
|
|
29
|
+
export declare function findArrayDifferenceByKey<TCurrent, TNext>(current: readonly TCurrent[], next: readonly TNext[], key: DifferenceKey<TCurrent, TNext>, options?: {
|
|
30
|
+
isEqual?: (current: TCurrent, next: TNext) => boolean;
|
|
31
|
+
}): ArrayDifference<TCurrent, TNext>;
|
|
13
32
|
export declare function getUniqueValues<T>(array: readonly T[]): T[];
|
|
14
33
|
export declare function compact<T>(array: readonly T[]): NonNullableValue<T>[];
|
|
15
|
-
export declare function getUniqueValuesByKey<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[]
|
|
34
|
+
export declare function getUniqueValuesByKey<T>(array: readonly T[], keyOrFn: keyof T | ((item: T) => unknown)): T[];
|
|
35
|
+
export declare function groupBy<T, TKey extends keyof T, TGroupKey extends Extract<T[TKey], PropertyKeyValue>>(array: readonly T[], key: TKey): Partial<Record<TGroupKey, T[]>>;
|
|
36
|
+
export declare function groupBy<T, TGroupKey extends PropertyKeyValue>(array: readonly T[], keyFn: (item: T) => TGroupKey): Partial<Record<TGroupKey, T[]>>;
|
|
37
|
+
export declare function countBy<T, TKey extends keyof T, TGroupKey extends Extract<T[TKey], PropertyKeyValue>>(array: readonly T[], key: TKey): Partial<Record<TGroupKey, number>>;
|
|
38
|
+
export declare function countBy<T, TGroupKey extends PropertyKeyValue>(array: readonly T[], keyFn: (item: T) => TGroupKey): Partial<Record<TGroupKey, number>>;
|
|
18
39
|
export declare function keyBy<T, TKey extends keyof T, TRecordKey extends Extract<T[TKey], PropertyKeyValue>>(array: readonly T[], key: TKey): Record<TRecordKey, T>;
|
|
19
40
|
export declare function keyBy<T, TRecordKey extends PropertyKeyValue>(array: readonly T[], keyFn: (item: T) => TRecordKey): Record<TRecordKey, T>;
|
|
20
41
|
export declare function partition<T>(array: readonly T[], predicate: (item: T, index: number) => boolean): [matched: T[], unmatched: T[]];
|
|
21
42
|
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]>[];
|
|
43
|
+
/** Wraps a single value in an array; `null`/`undefined` become `[]`. Arrays are copied. */
|
|
44
|
+
export declare function toArray<T>(value: T | readonly T[] | null | undefined): T[];
|
|
45
|
+
type NumericKey<T> = {
|
|
46
|
+
[K in keyof T]: T[K] extends number | null | undefined ? K : never;
|
|
47
|
+
}[keyof T];
|
|
48
|
+
/** Sums a numeric property or callback result; nullish values count as 0. */
|
|
49
|
+
export declare function sumBy<T>(array: readonly T[], fn: (item: T) => number | null | undefined): number;
|
|
50
|
+
export declare function sumBy<T>(array: readonly T[], key: NumericKey<T>): number;
|
|
51
|
+
type SortSelector<T> = keyof T | ((item: T) => unknown);
|
|
52
|
+
export type SortCriterion<T> = SortSelector<T> | {
|
|
53
|
+
by: SortSelector<T>;
|
|
54
|
+
order: 'asc' | 'desc';
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Returns a sorted copy. Each criterion is a key, a callback, or
|
|
58
|
+
* `{ by, order }`; later criteria break ties. The sort is stable, and
|
|
59
|
+
* `null`/`undefined` values always go last. Without criteria the items
|
|
60
|
+
* themselves are compared.
|
|
61
|
+
*/
|
|
62
|
+
export declare function sortBy<T>(array: readonly T[], ...criteria: SortCriterion<T>[]): T[];
|
|
63
|
+
/**
|
|
64
|
+
* Numbers from `start` up to (not including) `end`. With one argument it
|
|
65
|
+
* counts from 0. `step` defaults to 1, or -1 when `end < start`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function range(start: number, end?: number, step?: number): number[];
|
|
22
68
|
export {};
|