@lokalise/api-contracts 6.14.0 → 6.15.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 +69 -28
- package/dist/new/clientTypes.d.ts +51 -38
- package/dist/new/contractResponse.d.ts +49 -2
- package/dist/new/contractResponse.js +77 -11
- package/dist/new/contractResponse.js.map +1 -1
- package/dist/new/defineApiContract.js +50 -21
- package/dist/new/defineApiContract.js.map +1 -1
- package/dist/new/inferTypes.d.ts +26 -9
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -68,13 +68,55 @@ const downloadPhoto = defineApiContract({
|
|
|
68
68
|
|
|
69
69
|
> **Deprecated:** `textResponse` is deprecated in favour of `blobResponse`. Both declare the same protocol fact (the response `content-type`); they differ only in whether the client materializes the body as `string` or `Blob` — a consumer concern, not part of the contract. Where a string is needed, call `.text()` on the returned `Blob`. `textResponse` will be removed in a future major release.
|
|
70
70
|
|
|
71
|
+
### Multiple content types
|
|
72
|
+
|
|
73
|
+
When a single status code can return more than one media type, map it to a `content` object keyed
|
|
74
|
+
by media type. Each value is a *body descriptor*: a bare Zod schema (JSON), `blobBody()` (opaque
|
|
75
|
+
binary or text), or `sseBody()` (Server-Sent Events). Add `allowNoBody: true` to also accept an
|
|
76
|
+
empty body.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { defineApiContract, blobBody, sseBody } from '@lokalise/api-contracts'
|
|
80
|
+
import { z } from 'zod/v4'
|
|
81
|
+
|
|
82
|
+
const downloadReport = defineApiContract({
|
|
83
|
+
method: 'get',
|
|
84
|
+
pathResolver: () => '/report',
|
|
85
|
+
responsesByStatusCode: {
|
|
86
|
+
200: {
|
|
87
|
+
description: 'Report in the requested format',
|
|
88
|
+
content: {
|
|
89
|
+
'application/json': z.object({ rows: z.array(z.string()) }),
|
|
90
|
+
'application/vnd.api+json': z.object({ data: z.object({ rows: z.array(z.string()) }) }),
|
|
91
|
+
'text/csv': blobBody(),
|
|
92
|
+
'application/pdf': blobBody(),
|
|
93
|
+
'text/event-stream': sseBody({ row: z.object({ value: z.string() }) }),
|
|
94
|
+
},
|
|
95
|
+
allowNoBody: true,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Media types are matched **exactly** — parameters stripped, case-insensitive — so distinct keys such
|
|
102
|
+
as `application/json` and `application/vnd.api+json` never collide, and a single status code may
|
|
103
|
+
expose any number of media types (including several JSON variants). The shape maps 1:1 to the
|
|
104
|
+
OpenAPI Response Object. The matched media type is **not** surfaced on the client response; read it
|
|
105
|
+
from `headers['content-type']` if you need to discriminate.
|
|
106
|
+
|
|
107
|
+
> **Deprecated:** `anyOfResponses` is deprecated in favour of content maps. A flat list can't
|
|
108
|
+
> declare multiple JSON media types, matches content-types by substring (false positives), and is
|
|
109
|
+
> order-dependent — content maps fix all three. `anyOfResponses` still works and will be removed in
|
|
110
|
+
> a future major release.
|
|
111
|
+
|
|
71
112
|
### SSE and dual-mode routes
|
|
72
113
|
|
|
73
|
-
Use `sseResponse()` inside `responsesByStatusCode`
|
|
74
|
-
|
|
114
|
+
Use `sseResponse()` inside `responsesByStatusCode` for an SSE-only response. For a route that
|
|
115
|
+
returns either JSON or an SSE stream depending on the `Accept` header, use a content map (above)
|
|
116
|
+
with both an `application/json` and a `text/event-stream` entry.
|
|
75
117
|
|
|
76
118
|
```ts
|
|
77
|
-
import { defineApiContract, sseResponse,
|
|
119
|
+
import { defineApiContract, sseResponse, sseBody } from '@lokalise/api-contracts'
|
|
78
120
|
import { z } from 'zod/v4'
|
|
79
121
|
|
|
80
122
|
// SSE-only
|
|
@@ -94,13 +136,15 @@ const chatCompletion = defineApiContract({
|
|
|
94
136
|
pathResolver: () => '/chat/completions',
|
|
95
137
|
requestBodySchema: z.object({ message: z.string() }),
|
|
96
138
|
responsesByStatusCode: {
|
|
97
|
-
200:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
139
|
+
200: {
|
|
140
|
+
content: {
|
|
141
|
+
'application/json': z.object({ text: z.string() }),
|
|
142
|
+
'text/event-stream': sseBody({
|
|
143
|
+
chunk: z.object({ delta: z.string() }),
|
|
144
|
+
done: z.object({ finish_reason: z.string() }),
|
|
145
|
+
}),
|
|
146
|
+
},
|
|
147
|
+
},
|
|
104
148
|
},
|
|
105
149
|
})
|
|
106
150
|
```
|
|
@@ -156,13 +200,7 @@ The `'2xx'` range key participates in SSE detection and success/error type narro
|
|
|
156
200
|
All response factories accept an optional `ResponseOptions` object as their last argument.
|
|
157
201
|
|
|
158
202
|
```ts
|
|
159
|
-
import {
|
|
160
|
-
defineApiContract,
|
|
161
|
-
noBodyResponse,
|
|
162
|
-
blobResponse,
|
|
163
|
-
sseResponse,
|
|
164
|
-
anyOfResponses,
|
|
165
|
-
} from '@lokalise/api-contracts'
|
|
203
|
+
import { defineApiContract, noBodyResponse, blobBody, sseBody } from '@lokalise/api-contracts'
|
|
166
204
|
import { z } from 'zod/v4'
|
|
167
205
|
|
|
168
206
|
const contract = defineApiContract({
|
|
@@ -172,19 +210,22 @@ const contract = defineApiContract({
|
|
|
172
210
|
responsesByStatusCode: {
|
|
173
211
|
201: z.object({ id: z.string() }).describe('Created resource'),
|
|
174
212
|
204: noBodyResponse({ description: 'Deleted — no content returned' }),
|
|
175
|
-
200:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
213
|
+
200: {
|
|
214
|
+
description: 'Multiple response formats available',
|
|
215
|
+
content: {
|
|
216
|
+
'application/json': z.object({ id: z.string() }).describe('JSON representation'),
|
|
217
|
+
'text/csv': blobBody(),
|
|
218
|
+
'application/pdf': blobBody(),
|
|
219
|
+
'text/event-stream': sseBody({ update: z.object({ id: z.string() }) }),
|
|
220
|
+
},
|
|
221
|
+
},
|
|
184
222
|
},
|
|
185
223
|
})
|
|
186
224
|
```
|
|
187
225
|
|
|
226
|
+
A content-map entry carries a single `description` for the whole response; per-media descriptions
|
|
227
|
+
aren't supported (a JSON descriptor can still carry its own via `.describe()`).
|
|
228
|
+
|
|
188
229
|
`getSseSchemaByEventName(contract)` extracts SSE event schemas from a contract:
|
|
189
230
|
|
|
190
231
|
```ts
|
|
@@ -254,7 +295,7 @@ const contract = defineApiContract({
|
|
|
254
295
|
|
|
255
296
|
### Type utilities
|
|
256
297
|
|
|
257
|
-
**`InferNonSseSuccessResponses<T>`** — TypeScript output type of all non-SSE 2xx responses. JSON schemas → `z.output<T>`, `textResponse` → `string`, `blobResponse` → `Blob`, `ContractNoBody`/`NoBodyResponse` → `undefined`, `sseResponse` → `never` (excluded). `anyOfResponses` entries are unpacked before mapping.
|
|
298
|
+
**`InferNonSseSuccessResponses<T>`** — TypeScript output type of all non-SSE 2xx responses. JSON schemas → `z.output<T>`, `textResponse` → `string`, `blobResponse` → `Blob`, `ContractNoBody`/`NoBodyResponse` → `undefined`, `sseResponse` → `never` (excluded). `anyOfResponses` and content-map entries are unpacked before mapping.
|
|
258
299
|
|
|
259
300
|
```ts
|
|
260
301
|
import type { InferNonSseSuccessResponses } from '@lokalise/api-contracts'
|
|
@@ -358,7 +399,7 @@ getIsEmptyResponseExpected(deleteUser) // true
|
|
|
358
399
|
getIsEmptyResponseExpected(getUser) // false
|
|
359
400
|
```
|
|
360
401
|
|
|
361
|
-
**`hasAnySuccessSseResponse`** — `true` when any 2xx entry (exact code or `'2xx'` range key) is an SSE response (including inside `anyOfResponses`).
|
|
402
|
+
**`hasAnySuccessSseResponse`** — `true` when any 2xx entry (exact code or `'2xx'` range key) is an SSE response (including inside `anyOfResponses` or a content map).
|
|
362
403
|
|
|
363
404
|
```ts
|
|
364
405
|
import { hasAnySuccessSseResponse } from '@lokalise/api-contracts'
|
|
@@ -50,40 +50,61 @@ type SseInferClientResponseBody<T> = Extract<InferClientResponseBody<T>, AsyncIt
|
|
|
50
50
|
* Like InferClientResponseBody but returns only non-SSE bodies — SSE entries resolve to never.
|
|
51
51
|
*/
|
|
52
52
|
type NonSseInferClientResponseBody<T> = Exclude<InferClientResponseBody<T>, AsyncIterable<unknown>>;
|
|
53
|
-
type
|
|
54
|
-
|
|
53
|
+
type InferContentDescriptorBody<TDescriptor> = TDescriptor extends {
|
|
54
|
+
_tag: 'BlobBody';
|
|
55
|
+
} ? Blob : TDescriptor extends {
|
|
56
|
+
_tag: 'SseBody';
|
|
57
|
+
schemaByEventName: infer S extends SseSchemaByEventName;
|
|
58
|
+
} ? AsyncIterable<SseEventOf<S>> : TDescriptor extends z.ZodType ? InferSchemaOutput<TDescriptor> : never;
|
|
59
|
+
type ContentEntryVariants<TEntry> = (TEntry extends {
|
|
60
|
+
content: infer C;
|
|
61
|
+
} ? {
|
|
62
|
+
[CT in keyof C & string]: {
|
|
63
|
+
body: InferContentDescriptorBody<C[CT]>;
|
|
64
|
+
};
|
|
65
|
+
}[keyof C & string] : never) | (TEntry extends {
|
|
66
|
+
allowNoBody: true;
|
|
67
|
+
} ? {
|
|
68
|
+
body: null;
|
|
69
|
+
} : never);
|
|
70
|
+
type IsContentEntry<V> = V extends {
|
|
71
|
+
content: object;
|
|
72
|
+
} ? true : V extends {
|
|
73
|
+
allowNoBody: true;
|
|
74
|
+
} ? true : false;
|
|
75
|
+
type SseContentVariants<TEntry> = Extract<ContentEntryVariants<TEntry>, {
|
|
76
|
+
body: AsyncIterable<unknown>;
|
|
77
|
+
}>;
|
|
78
|
+
type NonSseContentVariants<TEntry> = Exclude<ContentEntryVariants<TEntry>, {
|
|
79
|
+
body: AsyncIterable<unknown>;
|
|
80
|
+
}>;
|
|
81
|
+
/** Response mode for a given status class: success codes filter by SSE/non-SSE; others pass all. */
|
|
82
|
+
type ResponseBodyMode = 'sse' | 'non-sse' | 'all';
|
|
83
|
+
type ContentVariantsForMode<TEntry, TMode extends ResponseBodyMode> = TMode extends 'sse' ? SseContentVariants<TEntry> : TMode extends 'non-sse' ? NonSseContentVariants<TEntry> : ContentEntryVariants<TEntry>;
|
|
84
|
+
type LegacyBodyForMode<V, TMode extends ResponseBodyMode> = TMode extends 'sse' ? SseInferClientResponseBody<V> : TMode extends 'non-sse' ? NonSseInferClientResponseBody<V> : InferClientResponseBody<V>;
|
|
85
|
+
/** Attaches `statusCode` + `headers` to each `{ body }` variant. */
|
|
86
|
+
type WithMeta<TStatusCode, THeaders, TVariant> = TVariant extends unknown ? Prettify<{
|
|
87
|
+
statusCode: TStatusCode;
|
|
88
|
+
headers: THeaders;
|
|
89
|
+
} & TVariant> : never;
|
|
90
|
+
/**
|
|
91
|
+
* Builds the response union member(s) for a status code `K` holding value `V`.
|
|
92
|
+
* Content-map entries expand to one body variant per media type; legacy entries keep the original
|
|
93
|
+
* `{ statusCode, headers, body }` shape (so existing contracts are byte-for-byte unchanged).
|
|
94
|
+
*/
|
|
95
|
+
type ResponseMember<TStatusCode, THeaders, V, TMode extends ResponseBodyMode> = IsContentEntry<V> extends true ? WithMeta<TStatusCode, THeaders, ContentVariantsForMode<V, TMode>> : {
|
|
96
|
+
statusCode: TStatusCode;
|
|
97
|
+
headers: THeaders;
|
|
98
|
+
body: LegacyBodyForMode<V, TMode>;
|
|
99
|
+
};
|
|
55
100
|
type ExactStatusCodes<TApiContract extends ApiContract> = keyof TApiContract['responsesByStatusCode'] & HttpStatusCode;
|
|
56
101
|
type RangeStatusCodes<TApiContract extends ApiContract> = {
|
|
57
102
|
[K in keyof TApiContract['responsesByStatusCode'] & HttpStatusCodeRange]: ExpandStatusRangeKey<K>;
|
|
58
103
|
}[keyof TApiContract['responsesByStatusCode'] & HttpStatusCodeRange];
|
|
59
104
|
type DefaultSuccessStatusCodes<TApiContract extends ApiContract> = Exclude<SuccessfulHttpStatusCode, ExactStatusCodes<TApiContract> | RangeStatusCodes<TApiContract>>;
|
|
60
105
|
type DefaultNonSuccessStatusCodes<TApiContract extends ApiContract> = Exclude<Exclude<HttpStatusCode, SuccessfulHttpStatusCode>, ExactStatusCodes<TApiContract> | RangeStatusCodes<TApiContract>>;
|
|
61
|
-
type WildcardSseEntry<TApiContract extends ApiContract, K extends WildcardStatusCodeKey> = K extends 'default' ?
|
|
62
|
-
|
|
63
|
-
headers: InferClientResponseHeaders<TApiContract>;
|
|
64
|
-
body: SseInferClientResponseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>>;
|
|
65
|
-
} | {
|
|
66
|
-
statusCode: DefaultNonSuccessStatusCodes<TApiContract>;
|
|
67
|
-
headers: InferClientResponseHeaders<TApiContract>;
|
|
68
|
-
body: InferClientResponseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>>;
|
|
69
|
-
} : {
|
|
70
|
-
statusCode: Exclude<ExpandStatusRangeKey<K>, ExactStatusCodes<TApiContract>>;
|
|
71
|
-
headers: InferClientResponseHeaders<TApiContract>;
|
|
72
|
-
body: WildcardSseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>, K>;
|
|
73
|
-
};
|
|
74
|
-
type WildcardNonSseEntry<TApiContract extends ApiContract, K extends WildcardStatusCodeKey> = K extends 'default' ? {
|
|
75
|
-
statusCode: DefaultSuccessStatusCodes<TApiContract>;
|
|
76
|
-
headers: InferClientResponseHeaders<TApiContract>;
|
|
77
|
-
body: NonSseInferClientResponseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>>;
|
|
78
|
-
} | {
|
|
79
|
-
statusCode: DefaultNonSuccessStatusCodes<TApiContract>;
|
|
80
|
-
headers: InferClientResponseHeaders<TApiContract>;
|
|
81
|
-
body: InferClientResponseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>>;
|
|
82
|
-
} : {
|
|
83
|
-
statusCode: Exclude<ExpandStatusRangeKey<K>, ExactStatusCodes<TApiContract>>;
|
|
84
|
-
headers: InferClientResponseHeaders<TApiContract>;
|
|
85
|
-
body: WildcardNonSseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>, K>;
|
|
86
|
-
};
|
|
106
|
+
type WildcardSseEntry<TApiContract extends ApiContract, K extends WildcardStatusCodeKey> = K extends 'default' ? ResponseMember<DefaultSuccessStatusCodes<TApiContract>, InferClientResponseHeaders<TApiContract>, NonNullable<TApiContract['responsesByStatusCode'][K]>, 'sse'> | ResponseMember<DefaultNonSuccessStatusCodes<TApiContract>, InferClientResponseHeaders<TApiContract>, NonNullable<TApiContract['responsesByStatusCode'][K]>, 'all'> : ResponseMember<Exclude<ExpandStatusRangeKey<K>, ExactStatusCodes<TApiContract>>, InferClientResponseHeaders<TApiContract>, NonNullable<TApiContract['responsesByStatusCode'][K]>, K extends '2xx' ? 'sse' : 'all'>;
|
|
107
|
+
type WildcardNonSseEntry<TApiContract extends ApiContract, K extends WildcardStatusCodeKey> = K extends 'default' ? ResponseMember<DefaultSuccessStatusCodes<TApiContract>, InferClientResponseHeaders<TApiContract>, NonNullable<TApiContract['responsesByStatusCode'][K]>, 'non-sse'> | ResponseMember<DefaultNonSuccessStatusCodes<TApiContract>, InferClientResponseHeaders<TApiContract>, NonNullable<TApiContract['responsesByStatusCode'][K]>, 'all'> : ResponseMember<Exclude<ExpandStatusRangeKey<K>, ExactStatusCodes<TApiContract>>, InferClientResponseHeaders<TApiContract>, NonNullable<TApiContract['responsesByStatusCode'][K]>, K extends '2xx' ? 'non-sse' : 'all'>;
|
|
87
108
|
/**
|
|
88
109
|
* Infers a discriminated union of `{ statusCode, headers, body }` for SSE mode:
|
|
89
110
|
* - exact success status codes and `'2xx'` range → SSE body only (AsyncIterable)
|
|
@@ -96,11 +117,7 @@ type WildcardNonSseEntry<TApiContract extends ApiContract, K extends WildcardSta
|
|
|
96
117
|
* are strongly typed; all other headers remain accessible as `string | undefined`.
|
|
97
118
|
*/
|
|
98
119
|
export type InferSseClientResponse<TApiContract extends ApiContract> = {
|
|
99
|
-
[K in keyof TApiContract['responsesByStatusCode'] & HttpStatusCode]:
|
|
100
|
-
statusCode: K;
|
|
101
|
-
headers: InferClientResponseHeaders<TApiContract>;
|
|
102
|
-
body: K extends SuccessfulHttpStatusCode ? SseInferClientResponseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>> : InferClientResponseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>>;
|
|
103
|
-
};
|
|
120
|
+
[K in keyof TApiContract['responsesByStatusCode'] & HttpStatusCode]: ResponseMember<K, InferClientResponseHeaders<TApiContract>, NonNullable<TApiContract['responsesByStatusCode'][K]>, K extends SuccessfulHttpStatusCode ? 'sse' : 'all'>;
|
|
104
121
|
}[keyof TApiContract['responsesByStatusCode'] & HttpStatusCode] | {
|
|
105
122
|
[K in keyof TApiContract['responsesByStatusCode'] & WildcardStatusCodeKey]: WildcardSseEntry<TApiContract, K>;
|
|
106
123
|
}[keyof TApiContract['responsesByStatusCode'] & WildcardStatusCodeKey];
|
|
@@ -116,11 +133,7 @@ export type InferSseClientResponse<TApiContract extends ApiContract> = {
|
|
|
116
133
|
* are strongly typed; all other headers remain accessible as `string | undefined`.
|
|
117
134
|
*/
|
|
118
135
|
export type InferNonSseClientResponse<TApiContract extends ApiContract> = {
|
|
119
|
-
[K in keyof TApiContract['responsesByStatusCode'] & HttpStatusCode]:
|
|
120
|
-
statusCode: K;
|
|
121
|
-
headers: InferClientResponseHeaders<TApiContract>;
|
|
122
|
-
body: K extends SuccessfulHttpStatusCode ? NonSseInferClientResponseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>> : InferClientResponseBody<NonNullable<TApiContract['responsesByStatusCode'][K]>>;
|
|
123
|
-
};
|
|
136
|
+
[K in keyof TApiContract['responsesByStatusCode'] & HttpStatusCode]: ResponseMember<K, InferClientResponseHeaders<TApiContract>, NonNullable<TApiContract['responsesByStatusCode'][K]>, K extends SuccessfulHttpStatusCode ? 'non-sse' : 'all'>;
|
|
124
137
|
}[keyof TApiContract['responsesByStatusCode'] & HttpStatusCode] | {
|
|
125
138
|
[K in keyof TApiContract['responsesByStatusCode'] & WildcardStatusCodeKey]: WildcardNonSseEntry<TApiContract, K>;
|
|
126
139
|
}[keyof TApiContract['responsesByStatusCode'] & WildcardStatusCodeKey];
|
|
@@ -41,6 +41,13 @@ export type AnyOfResponses<T extends TypedApiContractResponse = TypedApiContract
|
|
|
41
41
|
readonly responses: T[];
|
|
42
42
|
readonly description?: string;
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* @deprecated Use a content-map response entry instead — `{ content: { <mediaType>: descriptor } }`
|
|
46
|
+
* (see {@link blobBody} / {@link sseBody}). A flat `anyOfResponses` list can't declare multiple JSON
|
|
47
|
+
* media types, matches content-types by substring (false positives), and is order-dependent;
|
|
48
|
+
* content maps match media types exactly and map 1:1 to the OpenAPI Response Object. Will be removed
|
|
49
|
+
* in a future major release.
|
|
50
|
+
*/
|
|
44
51
|
export declare const anyOfResponses: <T extends TypedApiContractResponse>(responses: T[], options?: ResponseOptions) => AnyOfResponses<T>;
|
|
45
52
|
export declare const isAnyOfResponses: (value: ApiContractResponse) => value is AnyOfResponses;
|
|
46
53
|
export type NoBodyResponse = {
|
|
@@ -50,7 +57,47 @@ export type NoBodyResponse = {
|
|
|
50
57
|
export declare const noBodyResponse: (options?: ResponseOptions) => NoBodyResponse;
|
|
51
58
|
export declare const isNoBodyResponse: (value: ApiContractResponse) => value is NoBodyResponse;
|
|
52
59
|
export type ApiContractResponse = typeof ContractNoBody | NoBodyResponse | TypedApiContractResponse | AnyOfResponses;
|
|
53
|
-
|
|
60
|
+
/** Opaque binary body; the media type is supplied by the content-map key. */
|
|
61
|
+
export type BlobBody = {
|
|
62
|
+
readonly _tag: 'BlobBody';
|
|
63
|
+
};
|
|
64
|
+
export declare const blobBody: () => BlobBody;
|
|
65
|
+
export declare const isBlobBody: (value: BodyDescriptor) => value is BlobBody;
|
|
66
|
+
/** Server-Sent Events body; the media type is supplied by the content-map key. */
|
|
67
|
+
export type SseBody<T extends SseSchemaByEventName = SseSchemaByEventName> = {
|
|
68
|
+
readonly _tag: 'SseBody';
|
|
69
|
+
readonly schemaByEventName: T;
|
|
70
|
+
};
|
|
71
|
+
export declare const sseBody: <T extends SseSchemaByEventName>(schemaByEventName: T) => SseBody<T>;
|
|
72
|
+
export declare const isSseBody: (value: BodyDescriptor) => value is SseBody;
|
|
73
|
+
export declare const isJsonBody: (value: BodyDescriptor) => value is z.ZodType;
|
|
74
|
+
/**
|
|
75
|
+
* A value in a {@link ResponseContentMap}; the media type is the map key, so a
|
|
76
|
+
* descriptor never carries a content type itself. A bare Zod schema is JSON.
|
|
77
|
+
*/
|
|
78
|
+
export type BodyDescriptor = z.ZodType | BlobBody | SseBody;
|
|
79
|
+
/** Maps a response media type (e.g. `application/json`) to the body it carries. */
|
|
80
|
+
export type ResponseContentMap = Record<string, BodyDescriptor>;
|
|
81
|
+
/** A content-map response carrying a body for one or more media types. */
|
|
82
|
+
export type BodyContentResponseEntry = {
|
|
83
|
+
readonly description?: string;
|
|
84
|
+
readonly content: ResponseContentMap;
|
|
85
|
+
readonly allowNoBody?: boolean;
|
|
86
|
+
};
|
|
87
|
+
/** A content-map response that never carries a body. */
|
|
88
|
+
export type NoBodyContentResponseEntry = {
|
|
89
|
+
readonly description?: string;
|
|
90
|
+
readonly content?: never;
|
|
91
|
+
readonly allowNoBody: true;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* A content-map response entry. Either a body response (`content` required,
|
|
95
|
+
* optionally `allowNoBody`) or a no-body response (`allowNoBody: true`, no
|
|
96
|
+
* `content`). The union forces at least one of `content` / `allowNoBody`.
|
|
97
|
+
*/
|
|
98
|
+
export type ResponseEntry = BodyContentResponseEntry | NoBodyContentResponseEntry;
|
|
99
|
+
export declare const isContentResponseEntry: (value: ApiContractResponse | ResponseEntry) => value is ResponseEntry;
|
|
100
|
+
export type ResponsesByStatusCode = Partial<Record<HttpStatusCode | WildcardStatusCodeKey, ApiContractResponse | ResponseEntry>>;
|
|
54
101
|
export type ResponseKind = {
|
|
55
102
|
kind: 'noContent';
|
|
56
103
|
} | {
|
|
@@ -80,7 +127,7 @@ export type ResponseKind = {
|
|
|
80
127
|
* returning `null` — only applies to single-entry responses; `anyOfResponses` always requires a
|
|
81
128
|
* content-type to disambiguate regardless of this flag.
|
|
82
129
|
*/
|
|
83
|
-
export declare const resolveContractResponse: (schemaEntry: ApiContractResponse, contentType: string | undefined, strict?: boolean) => ResponseKind | null;
|
|
130
|
+
export declare const resolveContractResponse: (schemaEntry: ApiContractResponse | ResponseEntry, contentType: string | undefined, strict?: boolean) => ResponseKind | null;
|
|
84
131
|
/**
|
|
85
132
|
* Combines status-code lookup and content-type resolution into a single call.
|
|
86
133
|
* Lookup precedence: exact code → range key (e.g. `'4xx'`) → `'default'`.
|
|
@@ -25,6 +25,13 @@ export const sseResponse = (schemaByEventName, options) => ({
|
|
|
25
25
|
});
|
|
26
26
|
export const isSseResponse = (value) => typeof value === 'object' && value !== null && '_tag' in value && value._tag === 'SseResponse';
|
|
27
27
|
export const isJsonResponse = (value) => typeof value === 'object' && value !== null && !('_tag' in value);
|
|
28
|
+
/**
|
|
29
|
+
* @deprecated Use a content-map response entry instead — `{ content: { <mediaType>: descriptor } }`
|
|
30
|
+
* (see {@link blobBody} / {@link sseBody}). A flat `anyOfResponses` list can't declare multiple JSON
|
|
31
|
+
* media types, matches content-types by substring (false positives), and is order-dependent;
|
|
32
|
+
* content maps match media types exactly and map 1:1 to the OpenAPI Response Object. Will be removed
|
|
33
|
+
* in a future major release.
|
|
34
|
+
*/
|
|
28
35
|
export const anyOfResponses = (responses, options) => ({
|
|
29
36
|
_tag: 'AnyOfResponses',
|
|
30
37
|
responses,
|
|
@@ -36,6 +43,18 @@ export const noBodyResponse = (options) => ({
|
|
|
36
43
|
...(options?.description !== undefined && { description: options.description }),
|
|
37
44
|
});
|
|
38
45
|
export const isNoBodyResponse = (value) => typeof value === 'object' && value !== null && '_tag' in value && value._tag === 'NoBodyResponse';
|
|
46
|
+
export const blobBody = () => ({ _tag: 'BlobBody' });
|
|
47
|
+
export const isBlobBody = (value) => typeof value === 'object' && value !== null && '_tag' in value && value._tag === 'BlobBody';
|
|
48
|
+
export const sseBody = (schemaByEventName) => ({
|
|
49
|
+
_tag: 'SseBody',
|
|
50
|
+
schemaByEventName,
|
|
51
|
+
});
|
|
52
|
+
export const isSseBody = (value) => typeof value === 'object' && value !== null && '_tag' in value && value._tag === 'SseBody';
|
|
53
|
+
export const isJsonBody = (value) => typeof value === 'object' && value !== null && !('_tag' in value);
|
|
54
|
+
export const isContentResponseEntry = (value) => typeof value === 'object' &&
|
|
55
|
+
value !== null &&
|
|
56
|
+
!('_tag' in value) &&
|
|
57
|
+
('content' in value || 'allowNoBody' in value);
|
|
39
58
|
const matchTypedResponse = (entry, contentType) => {
|
|
40
59
|
if (isTextResponse(entry)) {
|
|
41
60
|
return contentType.includes(entry.contentType) ? { kind: 'text' } : null;
|
|
@@ -65,6 +84,60 @@ const resolveByKind = (entry) => {
|
|
|
65
84
|
}
|
|
66
85
|
return { kind: 'json', schema: entry };
|
|
67
86
|
};
|
|
87
|
+
const normalizeMediaType = (contentType) => (contentType.split(';')[0] ?? contentType).trim().toLowerCase();
|
|
88
|
+
const descriptorToKind = (descriptor) => {
|
|
89
|
+
if (isBlobBody(descriptor)) {
|
|
90
|
+
return { kind: 'blob' };
|
|
91
|
+
}
|
|
92
|
+
if (isSseBody(descriptor)) {
|
|
93
|
+
return { kind: 'sse', schemaByEventName: descriptor.schemaByEventName };
|
|
94
|
+
}
|
|
95
|
+
return { kind: 'json', schema: descriptor };
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Resolves a content-map {@link ResponseEntry}. Body media types are matched by exact
|
|
99
|
+
* (parameter-stripped, case-insensitive) content-type equality, so e.g. `application/json`
|
|
100
|
+
* and `application/json+01` are kept distinct.
|
|
101
|
+
*/
|
|
102
|
+
const resolveContentEntry = (entry, contentType, strict) => {
|
|
103
|
+
if (!entry.content) {
|
|
104
|
+
return { kind: 'noContent' };
|
|
105
|
+
}
|
|
106
|
+
const entries = Object.entries(entry.content);
|
|
107
|
+
if (!contentType) {
|
|
108
|
+
if (entry.allowNoBody) {
|
|
109
|
+
return { kind: 'noContent' };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const target = normalizeMediaType(contentType);
|
|
114
|
+
for (const [mediaType, descriptor] of entries) {
|
|
115
|
+
if (normalizeMediaType(mediaType) === target) {
|
|
116
|
+
return descriptorToKind(descriptor);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// No content-type (without allowNoBody), or no media type matched: in non-strict mode fall
|
|
121
|
+
// back to the sole descriptor when the entry declares exactly one.
|
|
122
|
+
const onlyDescriptor = entries.length === 1 ? entries[0]?.[1] : undefined;
|
|
123
|
+
return !strict && onlyDescriptor ? descriptorToKind(onlyDescriptor) : null;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Resolves a legacy `anyOfResponses` entry. It always requires a content-type to disambiguate,
|
|
127
|
+
* so `strict` has no effect here.
|
|
128
|
+
*/
|
|
129
|
+
const resolveAnyOf = (schemaEntry, contentType) => {
|
|
130
|
+
if (!contentType) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
for (const item of schemaEntry.responses) {
|
|
134
|
+
const resolved = matchTypedResponse(item, contentType);
|
|
135
|
+
if (resolved) {
|
|
136
|
+
return resolved;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
};
|
|
68
141
|
/**
|
|
69
142
|
* Resolves a contract's response entry for a given status code into a concrete `ResponseKind`,
|
|
70
143
|
* taking the response `content-type` into account.
|
|
@@ -82,21 +155,14 @@ const resolveByKind = (entry) => {
|
|
|
82
155
|
* content-type to disambiguate regardless of this flag.
|
|
83
156
|
*/
|
|
84
157
|
export const resolveContractResponse = (schemaEntry, contentType, strict = true) => {
|
|
158
|
+
if (isContentResponseEntry(schemaEntry)) {
|
|
159
|
+
return resolveContentEntry(schemaEntry, contentType, strict);
|
|
160
|
+
}
|
|
85
161
|
if (schemaEntry === ContractNoBody || isNoBodyResponse(schemaEntry)) {
|
|
86
162
|
return { kind: 'noContent' };
|
|
87
163
|
}
|
|
88
164
|
if (isAnyOfResponses(schemaEntry)) {
|
|
89
|
-
|
|
90
|
-
if (!contentType) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
for (const item of schemaEntry.responses) {
|
|
94
|
-
const resolved = matchTypedResponse(item, contentType);
|
|
95
|
-
if (resolved) {
|
|
96
|
-
return resolved;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return null;
|
|
165
|
+
return resolveAnyOf(schemaEntry, contentType);
|
|
100
166
|
}
|
|
101
167
|
if (!contentType) {
|
|
102
168
|
return strict ? null : resolveByKind(schemaEntry);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contractResponse.js","sourceRoot":"","sources":["../../src/new/contractResponse.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAY/C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,WAAmB,EACnB,OAAyB,EACN,EAAE,CAAC,CAAC;IACvB,IAAI,EAAE,cAAc;IACpB,WAAW;IACX,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;CAChF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAA0B,EAA8B,EAAE,CACvF,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,CAAA;AAQjG,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,WAAmB,EACnB,OAAyB,EACN,EAAE,CAAC,CAAC;IACvB,IAAI,EAAE,cAAc;IACpB,WAAW;IACX,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;CAChF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAA0B,EAA8B,EAAE,CACvF,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,CAAA;AAUjG,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,iBAAoB,EACpB,OAAyB,EACJ,EAAE,CAAC,CAAC;IACzB,IAAI,EAAE,aAAa;IACnB,iBAAiB;IACjB,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;CAChF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAA0B,EAA6B,EAAE,CACrF,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,CAAA;AAIhG,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAA0B,EAA8B,EAAE,CACvF,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,CAAA;AAcnE,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,SAAc,EACd,OAAyB,EACN,EAAE,CAAC,CAAC;IACvB,IAAI,EAAE,gBAAgB;IACtB,SAAS;IACT,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;CAChF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAA0B,EAA2B,EAAE,CACtF,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,CAAA;AAOnG,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAyB,EAAkB,EAAE,CAAC,CAAC;IAC5E,IAAI,EAAE,gBAAgB;IACtB,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;CAChF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAA0B,EAA2B,EAAE,CACtF,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"contractResponse.js","sourceRoot":"","sources":["../../src/new/contractResponse.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAY/C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,WAAmB,EACnB,OAAyB,EACN,EAAE,CAAC,CAAC;IACvB,IAAI,EAAE,cAAc;IACpB,WAAW;IACX,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;CAChF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAA0B,EAA8B,EAAE,CACvF,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,CAAA;AAQjG,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,WAAmB,EACnB,OAAyB,EACN,EAAE,CAAC,CAAC;IACvB,IAAI,EAAE,cAAc;IACpB,WAAW;IACX,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;CAChF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAA0B,EAA8B,EAAE,CACvF,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,CAAA;AAUjG,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,iBAAoB,EACpB,OAAyB,EACJ,EAAE,CAAC,CAAC;IACzB,IAAI,EAAE,aAAa;IACnB,iBAAiB;IACjB,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;CAChF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAA0B,EAA6B,EAAE,CACrF,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,CAAA;AAIhG,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAA0B,EAA8B,EAAE,CACvF,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,CAAA;AAcnE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,SAAc,EACd,OAAyB,EACN,EAAE,CAAC,CAAC;IACvB,IAAI,EAAE,gBAAgB;IACtB,SAAS;IACT,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;CAChF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAA0B,EAA2B,EAAE,CACtF,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,CAAA;AAOnG,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAyB,EAAkB,EAAE,CAAC,CAAC;IAC5E,IAAI,EAAE,gBAAgB;IACtB,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;CAChF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAA0B,EAA2B,EAAE,CACtF,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,CAAA;AAyBnG,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAa,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAA;AAE9D,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAqB,EAAqB,EAAE,CACrE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,CAAA;AAQ7F,MAAM,CAAC,MAAM,OAAO,GAAG,CAAiC,iBAAoB,EAAc,EAAE,CAAC,CAAC;IAC5F,IAAI,EAAE,SAAS;IACf,iBAAiB;CAClB,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAqB,EAAoB,EAAE,CACnE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAA;AAE5F,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAqB,EAAsB,EAAE,CACtE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,CAAA;AAgCnE,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,KAA0C,EAClB,EAAE,CAC1B,OAAO,KAAK,KAAK,QAAQ;IACzB,KAAK,KAAK,IAAI;IACd,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC;IAClB,CAAC,SAAS,IAAI,KAAK,IAAI,aAAa,IAAI,KAAK,CAAC,CAAA;AAahD,MAAM,kBAAkB,GAAG,CACzB,KAA+B,EAC/B,WAAmB,EACE,EAAE;IACvB,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1E,CAAC;IAED,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1E,CAAC;IAED,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,WAAW,CAAC,QAAQ,CAAC,mBAAmB,CAAC;YAC9C,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,EAAE;YAC7D,CAAC,CAAC,IAAI,CAAA;IACV,CAAC;IAED,IAAI,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IACxC,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED,MAAM,aAAa,GAAG,CAAC,KAA+B,EAAgB,EAAE;IACtE,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACzB,CAAC;IACD,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACzB,CAAC;IACD,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,EAAE,CAAA;IACpE,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AACxC,CAAC,CAAA;AAED,MAAM,kBAAkB,GAAG,CAAC,WAAmB,EAAU,EAAE,CACzD,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;AAEjE,MAAM,gBAAgB,GAAG,CAAC,UAA0B,EAAgB,EAAE;IACpE,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACzB,CAAC;IACD,IAAI,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,iBAAiB,EAAE,UAAU,CAAC,iBAAiB,EAAE,CAAA;IACzE,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,CAAA;AAC7C,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,CAC1B,KAAoB,EACpB,WAA+B,EAC/B,MAAe,EACM,EAAE;IACvB,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAA;IAC9B,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAE7C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YACtB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAA;QAC9B,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAA;QAC9C,KAAK,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,IAAI,OAAO,EAAE,CAAC;YAC9C,IAAI,kBAAkB,CAAC,SAAS,CAAC,KAAK,MAAM,EAAE,CAAC;gBAC7C,OAAO,gBAAgB,CAAC,UAAU,CAAC,CAAA;YACrC,CAAC;QACH,CAAC;IACH,CAAC;IAED,2FAA2F;IAC3F,mEAAmE;IACnE,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACzE,OAAO,CAAC,MAAM,IAAI,cAAc,CAAC,CAAC,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAC5E,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,YAAY,GAAG,CACnB,WAA2B,EAC3B,WAA+B,EACV,EAAE;IACvB,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,SAAS,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;QACtD,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAA;QACjB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,WAAgD,EAChD,WAA+B,EAC/B,MAAM,GAAG,IAAI,EACQ,EAAE;IACvB,IAAI,sBAAsB,CAAC,WAAW,CAAC,EAAE,CAAC;QACxC,OAAO,mBAAmB,CAAC,WAAW,EAAE,WAAW,EAAE,MAAM,CAAC,CAAA;IAC9D,CAAC;IAED,IAAI,WAAW,KAAK,cAAc,IAAI,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC;QACpE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAA;IAC9B,CAAC;IAED,IAAI,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC;QAClC,OAAO,YAAY,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;IAC/C,CAAC;IAED,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,WAAW,CAAC,CAAA;IACnD,CAAC;IAED,MAAM,OAAO,GAAG,kBAAkB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;IAE5D,OAAO,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAA;AAChE,CAAC,CAAA;AAED,SAAS,WAAW,CAAC,UAAkB;IACrC,IAAI,UAAU,IAAI,GAAG,IAAI,UAAU,GAAG,GAAG;QAAE,OAAO,KAAK,CAAA;IACvD,IAAI,UAAU,IAAI,GAAG,IAAI,UAAU,GAAG,GAAG;QAAE,OAAO,KAAK,CAAA;IACvD,IAAI,UAAU,IAAI,GAAG,IAAI,UAAU,GAAG,GAAG;QAAE,OAAO,KAAK,CAAA;IACvD,IAAI,UAAU,IAAI,GAAG,IAAI,UAAU,GAAG,GAAG;QAAE,OAAO,KAAK,CAAA;IACvD,IAAI,UAAU,IAAI,GAAG,IAAI,UAAU,GAAG,GAAG;QAAE,OAAO,KAAK,CAAA;IACvD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,qBAA4C,EAC5C,UAAkB,EAClB,WAA+B,EAC/B,iBAA0B;IAE1B,MAAM,UAAU,GAAG,qBAAqB,CAAC,UAA4B,CAAC,CAAA;IACtE,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,uBAAuB,CAAC,UAAU,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAA;IAC5E,CAAC;IAED,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,CAAC,CAAA;IACxC,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,UAAU,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAA;QAClD,IAAI,UAAU,EAAE,CAAC;YACf,OAAO,uBAAuB,CAAC,UAAU,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAA;QAC5E,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAG,qBAAqB,CAAC,OAAO,CAAA;IAClD,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,uBAAuB,CAAC,YAAY,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAA;IAC9E,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
2
|
import { SUCCESSFUL_HTTP_STATUS_CODES } from "../HttpStatusCodes.js";
|
|
3
3
|
import { ContractNoBody } from "./constants.js";
|
|
4
|
-
import { isAnyOfResponses, isBlobResponse, isNoBodyResponse, isSseResponse, isTextResponse, } from "./contractResponse.js";
|
|
4
|
+
import { isAnyOfResponses, isBlobResponse, isContentResponseEntry, isJsonBody, isNoBodyResponse, isSseBody, isSseResponse, isTextResponse, } from "./contractResponse.js";
|
|
5
5
|
export const defineApiContract = (contract) => contract;
|
|
6
6
|
export const mapApiContractToPath = (routeConfig) => {
|
|
7
7
|
if (!routeConfig.requestPathParamsSchema) {
|
|
@@ -16,17 +16,31 @@ export const mapApiContractToPath = (routeConfig) => {
|
|
|
16
16
|
export const describeApiContract = (routeConfig) => {
|
|
17
17
|
return `${routeConfig.method.toUpperCase()} ${mapApiContractToPath(routeConfig)}`;
|
|
18
18
|
};
|
|
19
|
+
/** Collects every SSE event-schema map declared by a single response entry (legacy or content-map). */
|
|
20
|
+
const collectSseSchemaMaps = (value) => {
|
|
21
|
+
if (isContentResponseEntry(value)) {
|
|
22
|
+
const maps = [];
|
|
23
|
+
for (const descriptor of Object.values(value.content ?? {})) {
|
|
24
|
+
if (isSseBody(descriptor)) {
|
|
25
|
+
maps.push(descriptor.schemaByEventName);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return maps;
|
|
29
|
+
}
|
|
30
|
+
if (isSseResponse(value)) {
|
|
31
|
+
return [value.schemaByEventName];
|
|
32
|
+
}
|
|
33
|
+
if (isAnyOfResponses(value)) {
|
|
34
|
+
return value.responses.filter(isSseResponse).map((response) => response.schemaByEventName);
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
};
|
|
19
38
|
export const getSseSchemaByEventName = (routeConfig) => {
|
|
20
39
|
const result = {};
|
|
21
40
|
for (const value of Object.values(routeConfig.responsesByStatusCode)) {
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
else if (isAnyOfResponses(value)) {
|
|
26
|
-
for (const response of value.responses) {
|
|
27
|
-
if (isSseResponse(response)) {
|
|
28
|
-
Object.assign(result, response.schemaByEventName);
|
|
29
|
-
}
|
|
41
|
+
if (value) {
|
|
42
|
+
for (const map of collectSseSchemaMaps(value)) {
|
|
43
|
+
Object.assign(result, map);
|
|
30
44
|
}
|
|
31
45
|
}
|
|
32
46
|
}
|
|
@@ -35,19 +49,9 @@ export const getSseSchemaByEventName = (routeConfig) => {
|
|
|
35
49
|
export const hasAnySuccessSseResponse = (apiContract) => {
|
|
36
50
|
for (const code of [...SUCCESSFUL_HTTP_STATUS_CODES, '2xx', 'default']) {
|
|
37
51
|
const value = apiContract.responsesByStatusCode[code];
|
|
38
|
-
if (
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (isSseResponse(value)) {
|
|
52
|
+
if (value && collectSseSchemaMaps(value).length > 0) {
|
|
42
53
|
return true;
|
|
43
54
|
}
|
|
44
|
-
else if (isAnyOfResponses(value)) {
|
|
45
|
-
for (const response of value.responses) {
|
|
46
|
-
if (isSseResponse(response)) {
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
55
|
}
|
|
52
56
|
return false;
|
|
53
57
|
};
|
|
@@ -61,6 +65,21 @@ export const getSuccessResponseSchema = (routeConfig) => {
|
|
|
61
65
|
if (!value) {
|
|
62
66
|
continue;
|
|
63
67
|
}
|
|
68
|
+
if (isContentResponseEntry(value)) {
|
|
69
|
+
if (!value.content) {
|
|
70
|
+
hasDirectNonJsonEntry = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
for (const descriptor of Object.values(value.content)) {
|
|
74
|
+
if (isJsonBody(descriptor)) {
|
|
75
|
+
schemas.push(descriptor);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
hasDirectNonJsonEntry = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
64
83
|
if (isAnyOfResponses(value)) {
|
|
65
84
|
for (const response of value.responses) {
|
|
66
85
|
if (!isSseResponse(response) && !isTextResponse(response) && !isBlobResponse(response)) {
|
|
@@ -93,7 +112,17 @@ export const getIsEmptyResponseExpected = (routeConfig) => {
|
|
|
93
112
|
let isEmptyResponseExpected = true;
|
|
94
113
|
for (const code of SUCCESSFUL_HTTP_STATUS_CODES) {
|
|
95
114
|
const value = routeConfig.responsesByStatusCode[code];
|
|
96
|
-
if (
|
|
115
|
+
if (!value) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (isContentResponseEntry(value)) {
|
|
119
|
+
if (value.content) {
|
|
120
|
+
isEmptyResponseExpected = false;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (value !== ContractNoBody && !isNoBodyResponse(value)) {
|
|
97
126
|
isEmptyResponseExpected = false;
|
|
98
127
|
break;
|
|
99
128
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defineApiContract.js","sourceRoot":"","sources":["../../src/new/defineApiContract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAM1B,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAA;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,
|
|
1
|
+
{"version":3,"file":"defineApiContract.js","sourceRoot":"","sources":["../../src/new/defineApiContract.ts"],"names":[],"mappings":"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"}
|
package/dist/new/inferTypes.d.ts
CHANGED
|
@@ -2,22 +2,39 @@ import type { z } from 'zod/v4';
|
|
|
2
2
|
import type { SuccessfulHttpStatusCode } from '../HttpStatusCodes.ts';
|
|
3
3
|
import type { ValueOf } from '../typeUtils.ts';
|
|
4
4
|
import type { ContractNoBody } from './constants.ts';
|
|
5
|
-
import type { NoBodyResponse, ResponsesByStatusCode } from './contractResponse.ts';
|
|
5
|
+
import type { NoBodyResponse, ResponseEntry, ResponsesByStatusCode } from './contractResponse.ts';
|
|
6
6
|
type ExtractSuccessResponses<T extends ResponsesByStatusCode> = ValueOf<T, Extract<keyof T, SuccessfulHttpStatusCode | '2xx' | 'default'>>;
|
|
7
|
+
type UnpackAnyOf<T> = T extends {
|
|
8
|
+
_tag: 'AnyOfResponses';
|
|
9
|
+
responses: Array<infer Item>;
|
|
10
|
+
} ? Item : T;
|
|
11
|
+
type ContentDescriptorsOf<TEntry> = TEntry extends {
|
|
12
|
+
content: infer TContent;
|
|
13
|
+
} ? TContent[keyof TContent] : never;
|
|
14
|
+
type NormalizeContentDescriptor<TDescriptor> = TDescriptor extends {
|
|
15
|
+
_tag: 'SseBody';
|
|
16
|
+
schemaByEventName: infer S;
|
|
17
|
+
} ? {
|
|
18
|
+
_tag: 'SseResponse';
|
|
19
|
+
schemaByEventName: S;
|
|
20
|
+
} : TDescriptor extends {
|
|
21
|
+
_tag: 'BlobBody';
|
|
22
|
+
} ? {
|
|
23
|
+
_tag: 'BlobResponse';
|
|
24
|
+
} : TDescriptor;
|
|
25
|
+
type FlatContentSuccessResponses<TEntry> = NormalizeContentDescriptor<ContentDescriptorsOf<TEntry>> | (TEntry extends {
|
|
26
|
+
allowNoBody: true;
|
|
27
|
+
} ? typeof ContractNoBody : never);
|
|
28
|
+
type FlatSuccessResponses<T extends ResponsesByStatusCode> = UnpackAnyOf<Exclude<ExtractSuccessResponses<T>, ResponseEntry>> | FlatContentSuccessResponses<Extract<ExtractSuccessResponses<T>, ResponseEntry>>;
|
|
7
29
|
/**
|
|
8
|
-
* Returns true if all success responses have no body
|
|
9
|
-
*
|
|
30
|
+
* Returns true if all success responses have no body (ContractNoBody, noBodyResponse(),
|
|
31
|
+
* a no-body content entry, or no success status codes defined).
|
|
10
32
|
*
|
|
11
33
|
* @deprecated No known consumers — will be removed in a future release.
|
|
12
34
|
*/
|
|
13
35
|
export type IsNoBodySuccessResponse<T extends ResponsesByStatusCode> = [
|
|
14
|
-
|
|
36
|
+
FlatSuccessResponses<T>
|
|
15
37
|
] extends [typeof ContractNoBody | NoBodyResponse | undefined] ? true : false;
|
|
16
|
-
type UnpackAnyOf<T> = T extends {
|
|
17
|
-
_tag: 'AnyOfResponses';
|
|
18
|
-
responses: Array<infer Item>;
|
|
19
|
-
} ? Item : T;
|
|
20
|
-
type FlatSuccessResponses<T extends ResponsesByStatusCode> = UnpackAnyOf<ExtractSuccessResponses<T>>;
|
|
21
38
|
type SseSchemaOf<T> = T extends {
|
|
22
39
|
_tag: 'SseResponse';
|
|
23
40
|
schemaByEventName: infer S;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lokalise/api-contracts",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.15.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist"
|
|
6
6
|
],
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@biomejs/biome": "^2.5.0",
|
|
40
|
-
"@lokalise/biome-config": "^3.1.0",
|
|
41
|
-
"@lokalise/tsconfig": "^4.0.0",
|
|
42
40
|
"@types/node": "^25.9.3",
|
|
43
41
|
"@vitest/coverage-v8": "^4.1.9",
|
|
44
42
|
"rimraf": "^6.1.2",
|
|
45
43
|
"typescript": "6.0.3",
|
|
46
44
|
"vitest": "^4.1.9",
|
|
47
|
-
"zod": "^4.3.6"
|
|
45
|
+
"zod": "^4.3.6",
|
|
46
|
+
"@lokalise/biome-config": "^3.1.1",
|
|
47
|
+
"@lokalise/tsconfig": "^5.0.0"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "rimraf dist && tsc --project tsconfig.build.json",
|