@lokalise/api-contracts 6.7.0 → 6.9.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/dist/HttpStatusCodes.d.ts +2 -0
- package/dist/HttpStatusCodes.js +3 -1
- package/dist/HttpStatusCodes.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/new/clientTypes.d.ts +80 -0
- package/dist/new/clientTypes.js +2 -0
- package/dist/new/clientTypes.js.map +1 -0
- package/dist/new/constants.d.ts +1 -0
- package/dist/new/constants.js +2 -0
- package/dist/new/constants.js.map +1 -0
- package/dist/new/contractResponse.d.ts +67 -0
- package/dist/new/contractResponse.js +101 -0
- package/dist/new/contractResponse.js.map +1 -0
- package/dist/new/defineApiContract.d.ts +48 -0
- package/dist/new/defineApiContract.js +100 -0
- package/dist/new/defineApiContract.js.map +1 -0
- package/dist/new/inferTypes.d.ts +94 -0
- package/dist/new/inferTypes.js +2 -0
- package/dist/new/inferTypes.js.map +1 -0
- package/dist/typeUtils.d.ts +22 -0
- package/dist/typeUtils.js +2 -0
- package/dist/typeUtils.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1,3 @@
|
|
|
1
1
|
export type HttpStatusCode = 100 | 101 | 102 | 103 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511;
|
|
2
|
+
export declare const SUCCESSFUL_HTTP_STATUS_CODES: readonly [200, 201, 202, 203, 204, 205, 206, 207, 208, 226];
|
|
3
|
+
export type SuccessfulHttpStatusCode = (typeof SUCCESSFUL_HTTP_STATUS_CODES)[number];
|
package/dist/HttpStatusCodes.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HttpStatusCodes.js","sourceRoot":"","sources":["../src/HttpStatusCodes.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"HttpStatusCodes.js","sourceRoot":"","sources":["../src/HttpStatusCodes.ts"],"names":[],"mappings":"AAiEA,MAAM,CAAC,MAAM,4BAA4B,GAAG;IAC1C,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;CACxC,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export * from './apiContracts.ts';
|
|
2
2
|
export * from './contractBuilder.ts';
|
|
3
3
|
export * from './HttpStatusCodes.ts';
|
|
4
|
+
export * from './new/clientTypes.ts';
|
|
5
|
+
export * from './new/constants.ts';
|
|
6
|
+
export * from './new/contractResponse.ts';
|
|
7
|
+
export * from './new/defineApiContract.ts';
|
|
8
|
+
export * from './new/inferTypes.ts';
|
|
4
9
|
export * from './pathUtils.ts';
|
|
5
10
|
export * from './rest/restContractBuilder.ts';
|
|
6
11
|
export * from './sse/dualModeContracts.ts';
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,11 @@ export * from "./apiContracts.js";
|
|
|
2
2
|
// Universal contract builder
|
|
3
3
|
export * from "./contractBuilder.js";
|
|
4
4
|
export * from "./HttpStatusCodes.js";
|
|
5
|
+
export * from "./new/clientTypes.js";
|
|
6
|
+
export * from "./new/constants.js";
|
|
7
|
+
export * from "./new/contractResponse.js";
|
|
8
|
+
export * from "./new/defineApiContract.js";
|
|
9
|
+
export * from "./new/inferTypes.js";
|
|
5
10
|
export * from "./pathUtils.js";
|
|
6
11
|
export * from "./rest/restContractBuilder.js";
|
|
7
12
|
// Dual-mode (hybrid) contracts
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,6BAA6B;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,+BAA+B,CAAA;AAC7C,+BAA+B;AAC/B,cAAc,4BAA4B,CAAA;AAC1C,oBAAoB;AACpB,cAAc,8BAA8B,CAAA;AAC5C,gBAAgB;AAChB,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,6BAA6B;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,+BAA+B,CAAA;AAC7C,+BAA+B;AAC/B,cAAc,4BAA4B,CAAA;AAC1C,oBAAoB;AACpB,cAAc,8BAA8B,CAAA;AAC5C,gBAAgB;AAChB,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { z } from 'zod/v4';
|
|
2
|
+
import type { InferSchemaInput, InferSchemaOutput } from '../apiContracts.ts';
|
|
3
|
+
import type { HttpStatusCode, SuccessfulHttpStatusCode } from '../HttpStatusCodes.ts';
|
|
4
|
+
import type { Prettify } from '../typeUtils.ts';
|
|
5
|
+
import type { ContractNoBody } from './constants.ts';
|
|
6
|
+
import type { ResponsesByStatusCode, SseSchemaByEventName } from './contractResponse.ts';
|
|
7
|
+
import type { ApiContract } from './defineApiContract.ts';
|
|
8
|
+
import type { ContractResponseMode, SseEventOf } from './inferTypes.ts';
|
|
9
|
+
export type HeadersParam<T> = T | (() => T) | (() => Promise<T>);
|
|
10
|
+
type ExtractRequestBody<T> = T extends {
|
|
11
|
+
requestBodySchema: z.ZodType;
|
|
12
|
+
} ? T['requestBodySchema'] : undefined;
|
|
13
|
+
type StreamingParam<T extends ResponsesByStatusCode, TIsStreaming extends boolean> = ContractResponseMode<T> extends 'dual' ? {
|
|
14
|
+
streaming: TIsStreaming;
|
|
15
|
+
} : {
|
|
16
|
+
streaming?: never;
|
|
17
|
+
};
|
|
18
|
+
export type DefaultStreaming<T extends ResponsesByStatusCode> = ContractResponseMode<T> extends 'sse' ? true : false;
|
|
19
|
+
type RequiredWhenDefined<T, TKey extends string, TExtra = T> = [T] extends [undefined] ? {
|
|
20
|
+
[K in TKey]?: undefined;
|
|
21
|
+
} : {
|
|
22
|
+
[K in TKey]: TExtra;
|
|
23
|
+
};
|
|
24
|
+
export type ClientRequestParams<TApiContract extends ApiContract, TIsStreaming extends boolean> = Prettify<StreamingParam<TApiContract['responsesByStatusCode'], TIsStreaming> & RequiredWhenDefined<InferSchemaInput<TApiContract['requestPathParamsSchema']>, 'pathParams'> & RequiredWhenDefined<InferSchemaInput<ExtractRequestBody<TApiContract>>, 'body'> & RequiredWhenDefined<InferSchemaInput<TApiContract['requestQuerySchema']>, 'queryParams'> & RequiredWhenDefined<InferSchemaInput<TApiContract['requestHeaderSchema']>, 'headers', HeadersParam<InferSchemaInput<TApiContract['requestHeaderSchema']>>> & {
|
|
25
|
+
pathPrefix?: string;
|
|
26
|
+
}>;
|
|
27
|
+
type InferClientResponseHeaders<TApiContract extends ApiContract> = TApiContract['responseHeaderSchema'] extends z.ZodType ? Omit<Record<string, string>, keyof InferSchemaOutput<TApiContract['responseHeaderSchema']>> & InferSchemaOutput<TApiContract['responseHeaderSchema']> : Record<string, string>;
|
|
28
|
+
/**
|
|
29
|
+
* Maps a single responsesByStatusCode entry value to its TypeScript body type.
|
|
30
|
+
*/
|
|
31
|
+
type InferClientResponseBody<T> = T extends typeof ContractNoBody ? null : T extends z.ZodType ? InferSchemaOutput<T> : T extends {
|
|
32
|
+
_tag: 'TextResponse';
|
|
33
|
+
} ? string : T extends {
|
|
34
|
+
_tag: 'BlobResponse';
|
|
35
|
+
} ? Blob : T extends {
|
|
36
|
+
_tag: 'SseResponse';
|
|
37
|
+
schemaByEventName: infer S extends SseSchemaByEventName;
|
|
38
|
+
} ? AsyncIterable<SseEventOf<S>> : T extends {
|
|
39
|
+
_tag: 'AnyOfResponses';
|
|
40
|
+
responses: Array<infer Item>;
|
|
41
|
+
} ? InferClientResponseBody<Item> : never;
|
|
42
|
+
/**
|
|
43
|
+
* Like InferClientResponseBody but returns only SSE bodies — non-SSE entries resolve to never.
|
|
44
|
+
*/
|
|
45
|
+
type SseInferClientResponseBody<T> = Extract<InferClientResponseBody<T>, AsyncIterable<unknown>>;
|
|
46
|
+
/**
|
|
47
|
+
* Like InferClientResponseBody but returns only non-SSE bodies — SSE entries resolve to never.
|
|
48
|
+
*/
|
|
49
|
+
type NonSseInferClientResponseBody<T> = Exclude<InferClientResponseBody<T>, AsyncIterable<unknown>>;
|
|
50
|
+
/**
|
|
51
|
+
* Infers a discriminated union of `{ statusCode, headers, body }` for SSE mode:
|
|
52
|
+
* - success status codes → SSE body only (AsyncIterable)
|
|
53
|
+
* - error status codes → body as-is (all kinds)
|
|
54
|
+
*
|
|
55
|
+
* Headers are typed via `InferClientResponseHeaders`: known headers from `responseHeaderSchema`
|
|
56
|
+
* are strongly typed; all other headers remain accessible as `string | undefined`.
|
|
57
|
+
*/
|
|
58
|
+
export type InferSseClientResponse<TApiContract extends ApiContract> = {
|
|
59
|
+
[K in keyof TApiContract['responsesByStatusCode'] & HttpStatusCode]: {
|
|
60
|
+
statusCode: K;
|
|
61
|
+
headers: InferClientResponseHeaders<TApiContract>;
|
|
62
|
+
body: K extends SuccessfulHttpStatusCode ? SseInferClientResponseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>> : InferClientResponseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>>;
|
|
63
|
+
};
|
|
64
|
+
}[keyof TApiContract['responsesByStatusCode'] & HttpStatusCode];
|
|
65
|
+
/**
|
|
66
|
+
* Infers a discriminated union of `{ statusCode, headers, body }` for non-SSE mode:
|
|
67
|
+
* - success status codes → non-SSE body only (JSON / text / blob / null)
|
|
68
|
+
* - error status codes → body as-is (all kinds)
|
|
69
|
+
*
|
|
70
|
+
* Headers are typed via `InferClientResponseHeaders`: known headers from `responseHeaderSchema`
|
|
71
|
+
* are strongly typed; all other headers remain accessible as `string | undefined`.
|
|
72
|
+
*/
|
|
73
|
+
export type InferNonSseClientResponse<TApiContract extends ApiContract> = {
|
|
74
|
+
[K in keyof TApiContract['responsesByStatusCode'] & HttpStatusCode]: {
|
|
75
|
+
statusCode: K;
|
|
76
|
+
headers: InferClientResponseHeaders<TApiContract>;
|
|
77
|
+
body: K extends SuccessfulHttpStatusCode ? NonSseInferClientResponseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>> : InferClientResponseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>>;
|
|
78
|
+
};
|
|
79
|
+
}[keyof TApiContract['responsesByStatusCode'] & HttpStatusCode];
|
|
80
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientTypes.js","sourceRoot":"","sources":["../../src/new/clientTypes.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ContractNoBody: unique symbol;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/new/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { z } from 'zod/v4';
|
|
2
|
+
import type { HttpStatusCode } from '../HttpStatusCodes.ts';
|
|
3
|
+
import { ContractNoBody } from './constants.ts';
|
|
4
|
+
export type TypedTextResponse = {
|
|
5
|
+
readonly _tag: 'TextResponse';
|
|
6
|
+
readonly contentType: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const textResponse: (contentType: string) => TypedTextResponse;
|
|
9
|
+
export declare const isTextResponse: (value: ApiContractResponse) => value is TypedTextResponse;
|
|
10
|
+
export type TypedBlobResponse = {
|
|
11
|
+
readonly _tag: 'BlobResponse';
|
|
12
|
+
readonly contentType: string;
|
|
13
|
+
};
|
|
14
|
+
export declare const blobResponse: (contentType: string) => TypedBlobResponse;
|
|
15
|
+
export declare const isBlobResponse: (value: ApiContractResponse) => value is TypedBlobResponse;
|
|
16
|
+
export type SseSchemaByEventName = Record<string, z.ZodType>;
|
|
17
|
+
export type TypedSseResponse<T extends SseSchemaByEventName = SseSchemaByEventName> = {
|
|
18
|
+
readonly _tag: 'SseResponse';
|
|
19
|
+
readonly schemaByEventName: T;
|
|
20
|
+
};
|
|
21
|
+
export declare const sseResponse: <T extends SseSchemaByEventName>(schemaByEventName: T) => TypedSseResponse<T>;
|
|
22
|
+
export declare const isSseResponse: (value: ApiContractResponse) => value is TypedSseResponse;
|
|
23
|
+
export type TypedJsonResponse = z.ZodType;
|
|
24
|
+
export type TypedApiContractResponse = TypedJsonResponse | TypedTextResponse | TypedBlobResponse | TypedSseResponse;
|
|
25
|
+
export type AnyOfResponses<T extends TypedApiContractResponse = TypedApiContractResponse> = {
|
|
26
|
+
readonly _tag: 'AnyOfResponses';
|
|
27
|
+
readonly responses: T[];
|
|
28
|
+
};
|
|
29
|
+
export declare const anyOfResponses: <T extends TypedApiContractResponse>(responses: T[]) => AnyOfResponses<T>;
|
|
30
|
+
export declare const isAnyOfResponses: (value: ApiContractResponse) => value is AnyOfResponses;
|
|
31
|
+
export type ApiContractResponse = typeof ContractNoBody | TypedApiContractResponse | AnyOfResponses;
|
|
32
|
+
export type ResponsesByStatusCode = Partial<Record<HttpStatusCode, ApiContractResponse>>;
|
|
33
|
+
export type ResponseKind = {
|
|
34
|
+
kind: 'noContent';
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'text';
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'blob';
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'json';
|
|
41
|
+
schema: z.ZodType;
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'sse';
|
|
44
|
+
schemaByEventName: SseSchemaByEventName;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Resolves a contract's response entry for a given status code into a concrete `ResponseKind`,
|
|
48
|
+
* taking the response `content-type` into account.
|
|
49
|
+
*
|
|
50
|
+
* Returns `null` when the content-type cannot be matched to any entry in the contract,
|
|
51
|
+
* indicating the response is unexpected and should be treated as an error by the caller.
|
|
52
|
+
*
|
|
53
|
+
* @param schemaEntry - The contract entry for the matched status code (`ContractNoBody`,
|
|
54
|
+
* a Zod schema, `textResponse`, `blobResponse`, `sseResponse`, or `anyOfResponses`).
|
|
55
|
+
* @param contentType - The `content-type` header value from the actual HTTP response,
|
|
56
|
+
* or `undefined` when the header is absent.
|
|
57
|
+
* @param strict - When `true` (default), returns `null` if the `content-type` is absent or does
|
|
58
|
+
* not match the contract entry. When `false`, falls back to the entry's declared kind instead of
|
|
59
|
+
* returning `null` — only applies to single-entry responses; `anyOfResponses` always requires a
|
|
60
|
+
* content-type to disambiguate regardless of this flag.
|
|
61
|
+
*/
|
|
62
|
+
export declare const resolveContractResponse: (schemaEntry: ApiContractResponse, contentType: string | undefined, strict?: boolean) => ResponseKind | null;
|
|
63
|
+
/**
|
|
64
|
+
* Combines status-code lookup and content-type resolution into a single call.
|
|
65
|
+
* Returns `null` when the status code is not in the contract or the content-type cannot be matched.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveResponseEntry(responsesByStatusCode: ResponsesByStatusCode, statusCode: number, contentType: string | undefined, strictContentType: boolean): ResponseKind | null;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ContractNoBody } from "./constants.js";
|
|
2
|
+
export const textResponse = (contentType) => ({
|
|
3
|
+
_tag: 'TextResponse',
|
|
4
|
+
contentType,
|
|
5
|
+
});
|
|
6
|
+
export const isTextResponse = (value) => typeof value === 'object' && value !== null && '_tag' in value && value._tag === 'TextResponse';
|
|
7
|
+
export const blobResponse = (contentType) => ({
|
|
8
|
+
_tag: 'BlobResponse',
|
|
9
|
+
contentType,
|
|
10
|
+
});
|
|
11
|
+
export const isBlobResponse = (value) => typeof value === 'object' && value !== null && '_tag' in value && value._tag === 'BlobResponse';
|
|
12
|
+
export const sseResponse = (schemaByEventName) => ({
|
|
13
|
+
_tag: 'SseResponse',
|
|
14
|
+
schemaByEventName,
|
|
15
|
+
});
|
|
16
|
+
export const isSseResponse = (value) => typeof value === 'object' && value !== null && '_tag' in value && value._tag === 'SseResponse';
|
|
17
|
+
export const anyOfResponses = (responses) => ({
|
|
18
|
+
_tag: 'AnyOfResponses',
|
|
19
|
+
responses,
|
|
20
|
+
});
|
|
21
|
+
export const isAnyOfResponses = (value) => typeof value === 'object' && value !== null && '_tag' in value && value._tag === 'AnyOfResponses';
|
|
22
|
+
const matchTypedResponse = (entry, contentType) => {
|
|
23
|
+
if (isTextResponse(entry)) {
|
|
24
|
+
return contentType.includes(entry.contentType) ? { kind: 'text' } : null;
|
|
25
|
+
}
|
|
26
|
+
if (isBlobResponse(entry)) {
|
|
27
|
+
return contentType.includes(entry.contentType) ? { kind: 'blob' } : null;
|
|
28
|
+
}
|
|
29
|
+
if (isSseResponse(entry)) {
|
|
30
|
+
return contentType.includes('text/event-stream')
|
|
31
|
+
? { kind: 'sse', schemaByEventName: entry.schemaByEventName }
|
|
32
|
+
: null;
|
|
33
|
+
}
|
|
34
|
+
if (contentType.includes('application/json')) {
|
|
35
|
+
return { kind: 'json', schema: entry };
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
};
|
|
39
|
+
const resolveByKind = (entry) => {
|
|
40
|
+
if (isTextResponse(entry)) {
|
|
41
|
+
return { kind: 'text' };
|
|
42
|
+
}
|
|
43
|
+
if (isBlobResponse(entry)) {
|
|
44
|
+
return { kind: 'blob' };
|
|
45
|
+
}
|
|
46
|
+
if (isSseResponse(entry)) {
|
|
47
|
+
return { kind: 'sse', schemaByEventName: entry.schemaByEventName };
|
|
48
|
+
}
|
|
49
|
+
return { kind: 'json', schema: entry };
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Resolves a contract's response entry for a given status code into a concrete `ResponseKind`,
|
|
53
|
+
* taking the response `content-type` into account.
|
|
54
|
+
*
|
|
55
|
+
* Returns `null` when the content-type cannot be matched to any entry in the contract,
|
|
56
|
+
* indicating the response is unexpected and should be treated as an error by the caller.
|
|
57
|
+
*
|
|
58
|
+
* @param schemaEntry - The contract entry for the matched status code (`ContractNoBody`,
|
|
59
|
+
* a Zod schema, `textResponse`, `blobResponse`, `sseResponse`, or `anyOfResponses`).
|
|
60
|
+
* @param contentType - The `content-type` header value from the actual HTTP response,
|
|
61
|
+
* or `undefined` when the header is absent.
|
|
62
|
+
* @param strict - When `true` (default), returns `null` if the `content-type` is absent or does
|
|
63
|
+
* not match the contract entry. When `false`, falls back to the entry's declared kind instead of
|
|
64
|
+
* returning `null` — only applies to single-entry responses; `anyOfResponses` always requires a
|
|
65
|
+
* content-type to disambiguate regardless of this flag.
|
|
66
|
+
*/
|
|
67
|
+
export const resolveContractResponse = (schemaEntry, contentType, strict = true) => {
|
|
68
|
+
if (schemaEntry === ContractNoBody) {
|
|
69
|
+
return { kind: 'noContent' };
|
|
70
|
+
}
|
|
71
|
+
if (isAnyOfResponses(schemaEntry)) {
|
|
72
|
+
// AnyOfResponses always requires content-type to disambiguate — strict mode has no effect here
|
|
73
|
+
if (!contentType) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
for (const item of schemaEntry.responses) {
|
|
77
|
+
const resolved = matchTypedResponse(item, contentType);
|
|
78
|
+
if (resolved) {
|
|
79
|
+
return resolved;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
if (!contentType) {
|
|
85
|
+
return strict ? null : resolveByKind(schemaEntry);
|
|
86
|
+
}
|
|
87
|
+
const matched = matchTypedResponse(schemaEntry, contentType);
|
|
88
|
+
return matched ?? (strict ? null : resolveByKind(schemaEntry));
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Combines status-code lookup and content-type resolution into a single call.
|
|
92
|
+
* Returns `null` when the status code is not in the contract or the content-type cannot be matched.
|
|
93
|
+
*/
|
|
94
|
+
export function resolveResponseEntry(responsesByStatusCode, statusCode, contentType, strictContentType) {
|
|
95
|
+
const schemaEntry = responsesByStatusCode[statusCode];
|
|
96
|
+
if (!schemaEntry) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return resolveContractResponse(schemaEntry, contentType, strictContentType);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=contractResponse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contractResponse.js","sourceRoot":"","sources":["../../src/new/contractResponse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import type { CommonRouteDefinitionMetadata, InferSchemaOutput, RoutePathResolver } from '../apiContracts.ts';
|
|
3
|
+
import type { Exactly } from '../typeUtils.ts';
|
|
4
|
+
import { ContractNoBody } from './constants.ts';
|
|
5
|
+
import { type ResponsesByStatusCode, type SseSchemaByEventName } from './contractResponse.ts';
|
|
6
|
+
export type RequestPathParamsSchema = z.ZodObject;
|
|
7
|
+
export type RequestQuerySchema = z.ZodObject;
|
|
8
|
+
export type RequestHeaderSchema = z.ZodObject;
|
|
9
|
+
export type ResponseHeaderSchema = z.ZodObject;
|
|
10
|
+
export type CommonApiContract = {
|
|
11
|
+
pathResolver: RoutePathResolver<any>;
|
|
12
|
+
requestPathParamsSchema?: RequestPathParamsSchema;
|
|
13
|
+
requestQuerySchema?: RequestQuerySchema;
|
|
14
|
+
requestHeaderSchema?: RequestHeaderSchema;
|
|
15
|
+
responseHeaderSchema?: ResponseHeaderSchema;
|
|
16
|
+
responsesByStatusCode: ResponsesByStatusCode;
|
|
17
|
+
metadata?: CommonRouteDefinitionMetadata;
|
|
18
|
+
summary?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
tags?: readonly string[];
|
|
21
|
+
};
|
|
22
|
+
export type GetApiContract = CommonApiContract & {
|
|
23
|
+
method: 'get';
|
|
24
|
+
requestBodySchema?: never;
|
|
25
|
+
};
|
|
26
|
+
export type DeleteApiContract = CommonApiContract & {
|
|
27
|
+
method: 'delete';
|
|
28
|
+
requestBodySchema?: never;
|
|
29
|
+
};
|
|
30
|
+
export type PayloadApiContract = CommonApiContract & {
|
|
31
|
+
method: 'post' | 'put' | 'patch';
|
|
32
|
+
requestBodySchema: typeof ContractNoBody | z.ZodType;
|
|
33
|
+
};
|
|
34
|
+
export type ApiContract = GetApiContract | DeleteApiContract | PayloadApiContract;
|
|
35
|
+
type TypedPathApiContract<T extends RequestPathParamsSchema> = Omit<ApiContract, 'pathResolver' | 'requestPathParamsSchema'> & {
|
|
36
|
+
pathResolver: RoutePathResolver<InferSchemaOutput<T>>;
|
|
37
|
+
requestPathParamsSchema?: T;
|
|
38
|
+
};
|
|
39
|
+
export declare const defineApiContract: <PathParamsSchema extends RequestPathParamsSchema, const Contract extends TypedPathApiContract<PathParamsSchema>>(contract: Exactly<Contract, TypedPathApiContract<PathParamsSchema>> & {
|
|
40
|
+
requestPathParamsSchema?: PathParamsSchema;
|
|
41
|
+
}) => Contract;
|
|
42
|
+
export declare const mapApiContractToPath: (routeConfig: ApiContract) => string;
|
|
43
|
+
export declare const describeApiContract: (routeConfig: ApiContract) => string;
|
|
44
|
+
export declare const getSseSchemaByEventName: (routeConfig: ApiContract) => SseSchemaByEventName | null;
|
|
45
|
+
export declare const hasAnySuccessSseResponse: (apiContract: ApiContract) => boolean;
|
|
46
|
+
export declare const getSuccessResponseSchema: (routeConfig: ApiContract) => z.ZodType | null;
|
|
47
|
+
export declare const getIsEmptyResponseExpected: (routeConfig: ApiContract) => boolean;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { SUCCESSFUL_HTTP_STATUS_CODES } from "../HttpStatusCodes.js";
|
|
3
|
+
import { ContractNoBody } from "./constants.js";
|
|
4
|
+
import { isAnyOfResponses, isBlobResponse, isSseResponse, isTextResponse, } from "./contractResponse.js";
|
|
5
|
+
export const defineApiContract = (contract) => contract;
|
|
6
|
+
export const mapApiContractToPath = (routeConfig) => {
|
|
7
|
+
if (!routeConfig.requestPathParamsSchema) {
|
|
8
|
+
return routeConfig.pathResolver(undefined);
|
|
9
|
+
}
|
|
10
|
+
const resolverParams = Object.keys(routeConfig.requestPathParamsSchema.shape).reduce((acc, key) => {
|
|
11
|
+
acc[key] = `:${key}`;
|
|
12
|
+
return acc;
|
|
13
|
+
}, {});
|
|
14
|
+
return routeConfig.pathResolver(resolverParams);
|
|
15
|
+
};
|
|
16
|
+
export const describeApiContract = (routeConfig) => {
|
|
17
|
+
return `${routeConfig.method.toUpperCase()} ${mapApiContractToPath(routeConfig)}`;
|
|
18
|
+
};
|
|
19
|
+
export const getSseSchemaByEventName = (routeConfig) => {
|
|
20
|
+
const result = {};
|
|
21
|
+
for (const value of Object.values(routeConfig.responsesByStatusCode)) {
|
|
22
|
+
if (isSseResponse(value)) {
|
|
23
|
+
Object.assign(result, value.schemaByEventName);
|
|
24
|
+
}
|
|
25
|
+
else if (isAnyOfResponses(value)) {
|
|
26
|
+
for (const response of value.responses) {
|
|
27
|
+
if (isSseResponse(response)) {
|
|
28
|
+
Object.assign(result, response.schemaByEventName);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
34
|
+
};
|
|
35
|
+
export const hasAnySuccessSseResponse = (apiContract) => {
|
|
36
|
+
for (const code of SUCCESSFUL_HTTP_STATUS_CODES) {
|
|
37
|
+
const value = apiContract.responsesByStatusCode[code];
|
|
38
|
+
if (!value) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (isSseResponse(value)) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
else if (isAnyOfResponses(value)) {
|
|
45
|
+
for (const response of value.responses) {
|
|
46
|
+
if (isSseResponse(response)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
};
|
|
54
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: it is acceptable
|
|
55
|
+
export const getSuccessResponseSchema = (routeConfig) => {
|
|
56
|
+
const schemas = [];
|
|
57
|
+
let hasDirectNonJsonEntry = false;
|
|
58
|
+
for (const code of SUCCESSFUL_HTTP_STATUS_CODES) {
|
|
59
|
+
const value = routeConfig.responsesByStatusCode[code];
|
|
60
|
+
if (!value) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (isAnyOfResponses(value)) {
|
|
64
|
+
for (const response of value.responses) {
|
|
65
|
+
if (!isSseResponse(response) && !isTextResponse(response) && !isBlobResponse(response)) {
|
|
66
|
+
schemas.push(response);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else if (value === ContractNoBody ||
|
|
71
|
+
isSseResponse(value) ||
|
|
72
|
+
isTextResponse(value) ||
|
|
73
|
+
isBlobResponse(value)) {
|
|
74
|
+
hasDirectNonJsonEntry = true;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
schemas.push(value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (schemas.length > 1) {
|
|
81
|
+
return z.union(schemas);
|
|
82
|
+
}
|
|
83
|
+
const firstSchema = schemas.at(0);
|
|
84
|
+
if (firstSchema) {
|
|
85
|
+
return firstSchema;
|
|
86
|
+
}
|
|
87
|
+
return hasDirectNonJsonEntry ? z.never() : null;
|
|
88
|
+
};
|
|
89
|
+
export const getIsEmptyResponseExpected = (routeConfig) => {
|
|
90
|
+
let isEmptyResponseExpected = true;
|
|
91
|
+
for (const code of SUCCESSFUL_HTTP_STATUS_CODES) {
|
|
92
|
+
const value = routeConfig.responsesByStatusCode[code];
|
|
93
|
+
if (value && value !== ContractNoBody) {
|
|
94
|
+
isEmptyResponseExpected = false;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return isEmptyResponseExpected;
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=defineApiContract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defineApiContract.js","sourceRoot":"","sources":["../../src/new/defineApiContract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { z } from 'zod/v4';
|
|
2
|
+
import type { SuccessfulHttpStatusCode } from '../HttpStatusCodes.ts';
|
|
3
|
+
import type { ValueOf } from '../typeUtils.ts';
|
|
4
|
+
import type { ContractNoBody } from './constants.ts';
|
|
5
|
+
import type { ResponsesByStatusCode } from './contractResponse.ts';
|
|
6
|
+
type ExtractSuccessResponses<T extends ResponsesByStatusCode> = ValueOf<T, Extract<keyof T, SuccessfulHttpStatusCode>>;
|
|
7
|
+
/**
|
|
8
|
+
* Returns true if all success responses have no body (ContractNoBody or no success status codes defined).
|
|
9
|
+
*/
|
|
10
|
+
export type IsNoBodySuccessResponse<T extends ResponsesByStatusCode> = [
|
|
11
|
+
ExtractSuccessResponses<T>
|
|
12
|
+
] extends [typeof ContractNoBody | undefined] ? true : false;
|
|
13
|
+
type UnpackAnyOf<T> = T extends {
|
|
14
|
+
_tag: 'AnyOfResponses';
|
|
15
|
+
responses: Array<infer Item>;
|
|
16
|
+
} ? Item : T;
|
|
17
|
+
type FlatSuccessResponses<T extends ResponsesByStatusCode> = UnpackAnyOf<ExtractSuccessResponses<T>>;
|
|
18
|
+
type SseSchemaOf<T> = T extends {
|
|
19
|
+
_tag: 'SseResponse';
|
|
20
|
+
schemaByEventName: infer S;
|
|
21
|
+
} ? S : never;
|
|
22
|
+
/**
|
|
23
|
+
* Extracts the merged SSE event schema map from a responsesByStatusCode map.
|
|
24
|
+
* Returns the union of all `schemaByEventName` objects from TypedSseResponse entries,
|
|
25
|
+
* including those nested inside AnyOfResponses.
|
|
26
|
+
*/
|
|
27
|
+
export type InferSseSuccessResponses<T extends ResponsesByStatusCode> = SseSchemaOf<FlatSuccessResponses<T>>;
|
|
28
|
+
/**
|
|
29
|
+
* Returns true if any success status code entry is a JSON Zod schema,
|
|
30
|
+
* or an AnyOfResponses containing one.
|
|
31
|
+
*/
|
|
32
|
+
export type HasAnyJsonSuccessResponse<T extends ResponsesByStatusCode> = Extract<FlatSuccessResponses<T>, z.ZodType> extends never ? false : true;
|
|
33
|
+
type JsonSchemaOf<T> = T extends z.ZodType ? T : never;
|
|
34
|
+
/**
|
|
35
|
+
* Extracts the union of JSON Zod schemas from all success responses,
|
|
36
|
+
* including those nested inside AnyOfResponses. Text, Blob, and SSE responses are excluded.
|
|
37
|
+
*/
|
|
38
|
+
export type InferJsonSuccessResponses<T extends ResponsesByStatusCode> = JsonSchemaOf<FlatSuccessResponses<T>>;
|
|
39
|
+
type NonSseBodyOf<T> = T extends {
|
|
40
|
+
_tag: 'SseResponse';
|
|
41
|
+
} ? never : T extends {
|
|
42
|
+
_tag: 'BlobResponse';
|
|
43
|
+
} ? Blob : T extends {
|
|
44
|
+
_tag: 'TextResponse';
|
|
45
|
+
} ? string : T extends z.ZodType ? z.output<T> : undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Infers the TypeScript output type of all non-SSE success responses.
|
|
48
|
+
* JSON schemas → z.output<T>. TextResponse → string. BlobResponse → Blob.
|
|
49
|
+
* ContractNoBody → undefined. SseResponse → never (excluded).
|
|
50
|
+
* AnyOfResponses are unpacked before mapping.
|
|
51
|
+
*/
|
|
52
|
+
export type InferNonSseSuccessResponses<T extends ResponsesByStatusCode> = NonSseBodyOf<FlatSuccessResponses<T>>;
|
|
53
|
+
/**
|
|
54
|
+
* Discriminated union of SSE events inferred from a schemaByEventName map.
|
|
55
|
+
* Aligns with the browser MessageEvent shape.
|
|
56
|
+
*/
|
|
57
|
+
export type SseEventOf<S> = {
|
|
58
|
+
[K in keyof S]: K extends string ? {
|
|
59
|
+
type: K;
|
|
60
|
+
data: S[K] extends z.ZodType ? z.output<S[K]> : never;
|
|
61
|
+
lastEventId: string;
|
|
62
|
+
retry: number | undefined;
|
|
63
|
+
} : never;
|
|
64
|
+
}[keyof S];
|
|
65
|
+
/**
|
|
66
|
+
* Returns true if any success status code entry is TypedSseResponse,
|
|
67
|
+
* or an AnyOfResponses containing a TypedSseResponse.
|
|
68
|
+
*/
|
|
69
|
+
export type HasAnySseSuccessResponse<T extends ResponsesByStatusCode> = Extract<FlatSuccessResponses<T>, {
|
|
70
|
+
_tag: 'SseResponse';
|
|
71
|
+
}> extends never ? false : true;
|
|
72
|
+
/**
|
|
73
|
+
* Returns true if any success status code entry has a non-SSE response
|
|
74
|
+
* (JSON, text, blob, or no-body). Mirrors HasAnySseSuccessResponse.
|
|
75
|
+
*/
|
|
76
|
+
export type HasAnyNonSseSuccessResponse<T extends ResponsesByStatusCode> = Exclude<FlatSuccessResponses<T>, {
|
|
77
|
+
_tag: 'SseResponse';
|
|
78
|
+
}> extends never ? false : true;
|
|
79
|
+
/**
|
|
80
|
+
* Classifies a contract's response mode into one of three cases:
|
|
81
|
+
* - 'dual' — SSE + non-SSE success responses; caller chooses via streaming param
|
|
82
|
+
* - 'sse' — SSE-only success responses; always streams
|
|
83
|
+
* - 'non-sse' — JSON / text / blob / no-body; never streams
|
|
84
|
+
*/
|
|
85
|
+
export type ContractResponseMode<T extends ResponsesByStatusCode> = HasAnySseSuccessResponse<T> extends true ? HasAnyNonSseSuccessResponse<T> extends true ? 'dual' : 'sse' : 'non-sse';
|
|
86
|
+
/**
|
|
87
|
+
* Union of response mode literals available for a given responsesByStatusCode map.
|
|
88
|
+
*/
|
|
89
|
+
export type AvailableResponseModes<T extends ResponsesByStatusCode> = (HasAnyJsonSuccessResponse<T> extends true ? 'json' : never) | (HasAnySseSuccessResponse<T> extends true ? 'sse' : never) | (Extract<FlatSuccessResponses<T>, {
|
|
90
|
+
_tag: 'BlobResponse';
|
|
91
|
+
}> extends never ? never : 'blob') | (Extract<FlatSuccessResponses<T>, {
|
|
92
|
+
_tag: 'TextResponse';
|
|
93
|
+
}> extends never ? never : 'text') | (Extract<FlatSuccessResponses<T>, typeof ContractNoBody> extends never ? never : 'noContent');
|
|
94
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inferTypes.js","sourceRoot":"","sources":["../../src/new/inferTypes.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flattens an intersection type into a single object type, making hover tooltips
|
|
3
|
+
* show the fully-resolved shape instead of `A & B & C`.
|
|
4
|
+
*/
|
|
5
|
+
export type Prettify<T> = {
|
|
6
|
+
[K in keyof T]: T[K];
|
|
7
|
+
} & {};
|
|
8
|
+
/**
|
|
9
|
+
* Returns true when T is a union with more than one member.
|
|
10
|
+
*/
|
|
11
|
+
export type IsUnion<T, U = T> = (T extends unknown ? ([U] extends [T] ? 0 : 1) : never) extends 0 ? false : true;
|
|
12
|
+
/**
|
|
13
|
+
* Helper to prevent extra keys. If T has keys not in U, it forces an error.
|
|
14
|
+
*/
|
|
15
|
+
export type Exactly<T, U> = T & {
|
|
16
|
+
[K in keyof T]: K extends keyof U ? T[K] : never;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Extracts a union of value types from an object type.
|
|
20
|
+
* Optionally constrained to a subset of keys via ValueType.
|
|
21
|
+
*/
|
|
22
|
+
export type ValueOf<ObjectType, ValueType extends keyof ObjectType = keyof ObjectType> = ObjectType[ValueType];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeUtils.js","sourceRoot":"","sources":["../src/typeUtils.ts"],"names":[],"mappings":""}
|