@kontent-ai/core-sdk 12.0.0-preview.11 → 12.0.0-preview.13
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/http/http.models.d.ts +1 -2
- package/dist/public_api.d.ts +4 -3
- package/dist/public_api.js +3 -2
- package/dist/public_api.js.map +1 -1
- package/dist/sdk/paging-sdk-query.d.ts +11 -0
- package/dist/sdk/paging-sdk-query.js +158 -0
- package/dist/sdk/paging-sdk-query.js.map +1 -0
- package/dist/sdk/sdk-models.d.ts +20 -7
- package/dist/sdk/sdk-query.d.ts +31 -0
- package/dist/sdk/sdk-query.js +115 -0
- package/dist/sdk/sdk-query.js.map +1 -0
- package/dist/sdk-info.js +1 -1
- package/dist/utils/error.utils.d.ts +2 -1
- package/dist/utils/error.utils.js +4 -1
- package/dist/utils/error.utils.js.map +1 -1
- package/dist/utils/retry.utils.d.ts +0 -1
- package/dist/utils/retry.utils.js +1 -1
- package/dist/utils/retry.utils.js.map +1 -1
- package/lib/http/http.models.ts +1 -2
- package/lib/public_api.ts +3 -7
- package/lib/sdk/paging-sdk-query.ts +253 -0
- package/lib/sdk/sdk-models.ts +24 -8
- package/lib/sdk/sdk-query.ts +190 -0
- package/lib/utils/error.utils.ts +6 -2
- package/lib/utils/retry.utils.ts +1 -1
- package/package.json +12 -11
- package/dist/sdk/sdk-queries.d.ts +0 -49
- package/dist/sdk/sdk-queries.js +0 -238
- package/dist/sdk/sdk-queries.js.map +0 -1
- package/lib/sdk/sdk-queries.ts +0 -413
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paging query helpers built on top of core query. Fetches multiple pages (continuation token or next page URL)
|
|
3
|
+
* and aggregates results.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { match, P } from "ts-pattern";
|
|
7
|
+
import type { GetNextPageData, PaginationConfig, RequestBody } from "../http/http.models.js";
|
|
8
|
+
import type { JsonValue } from "../models/json.models.js";
|
|
9
|
+
import { createSdkError } from "../utils/error.utils.js";
|
|
10
|
+
import type { NextPageStateWithRequest, PagingQuery, PagingQueryResult, QueryResponse } from "./sdk-models.js";
|
|
11
|
+
import { createQuery, type QueryPromiseResult, type ResolveQueryData, resolveQueryAsync } from "./sdk-query.js";
|
|
12
|
+
|
|
13
|
+
type PagingQueryPromiseResult<TResponsePayload extends JsonValue, TMeta> = ReturnType<
|
|
14
|
+
Pick<PagingQuery<TResponsePayload, TMeta>, "toAllPromise">["toAllPromise"]
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
type NoNextPageState = {
|
|
18
|
+
readonly hasNextPage: false;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type NextPageState = NextPageStateWithRequest | NoNextPageState;
|
|
22
|
+
|
|
23
|
+
type FetchAllPagesResult<TResponsePayload extends JsonValue, TMeta> =
|
|
24
|
+
| {
|
|
25
|
+
readonly success: true;
|
|
26
|
+
readonly responses: readonly QueryResponse<TResponsePayload, TMeta>[];
|
|
27
|
+
readonly error?: never;
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
readonly success: false;
|
|
31
|
+
readonly responses?: never;
|
|
32
|
+
readonly error: ReturnType<typeof createSdkError>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function createPagingQuery<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta>(
|
|
36
|
+
data: Omit<ResolveQueryData<TResponsePayload, TRequestBody, TMeta>, "nextPageState" | "pageIndex"> & {
|
|
37
|
+
readonly getNextPageData: GetNextPageData<TResponsePayload, TMeta>;
|
|
38
|
+
},
|
|
39
|
+
): PagingQuery<TResponsePayload, TMeta> {
|
|
40
|
+
const getPagingData: (
|
|
41
|
+
config: PaginationConfig | undefined,
|
|
42
|
+
) => Parameters<typeof resolvePagingQueryAsync<TResponsePayload, TRequestBody, TMeta>>[0] = (config) => {
|
|
43
|
+
return {
|
|
44
|
+
...data,
|
|
45
|
+
pageIndex: 0,
|
|
46
|
+
paginationConfig: config ?? {},
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
toUrl: () => {
|
|
52
|
+
return data.request.url;
|
|
53
|
+
},
|
|
54
|
+
...createQuery<TResponsePayload, TRequestBody, TMeta>(data),
|
|
55
|
+
toAllPromise: async (config?: PaginationConfig) => {
|
|
56
|
+
return await resolvePagingQueryAsync<TResponsePayload, TRequestBody, TMeta>(getPagingData(config));
|
|
57
|
+
},
|
|
58
|
+
pages: (config?: PaginationConfig) => createPagingQueryIterator<TResponsePayload, TRequestBody, TMeta>(getPagingData(config)),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function* createPagingQueryIterator<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta>(
|
|
63
|
+
data: Omit<Parameters<typeof resolvePagingQueryAsync<TResponsePayload, TRequestBody, TMeta>>[0], "pageIndex">,
|
|
64
|
+
): AsyncGenerator<QueryResponse<TResponsePayload, TMeta>> {
|
|
65
|
+
let nextPageState: NextPageState = { hasNextPage: true, pageSource: "firstRequest" };
|
|
66
|
+
let pageIndex: number = 0;
|
|
67
|
+
|
|
68
|
+
while (isNextPageAvailable(nextPageState)) {
|
|
69
|
+
const result: Awaited<QueryPromiseResult<TResponsePayload, TMeta>> = await resolveQueryAsync<TResponsePayload, TRequestBody, TMeta>(
|
|
70
|
+
{
|
|
71
|
+
...data,
|
|
72
|
+
nextPageState: nextPageState,
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (!result.success) {
|
|
77
|
+
throw result.error;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
yield result.response;
|
|
81
|
+
|
|
82
|
+
pageIndex++;
|
|
83
|
+
nextPageState = resolveNextPageState({
|
|
84
|
+
getNextPageData: data.getNextPageData,
|
|
85
|
+
paginationConfig: data.paginationConfig,
|
|
86
|
+
pageIndex: pageIndex,
|
|
87
|
+
response: result.response,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveNextPageState<TResponsePayload extends JsonValue, TMeta>({
|
|
93
|
+
paginationConfig,
|
|
94
|
+
getNextPageData,
|
|
95
|
+
pageIndex,
|
|
96
|
+
response,
|
|
97
|
+
}: {
|
|
98
|
+
readonly getNextPageData: GetNextPageData<TResponsePayload, TMeta>;
|
|
99
|
+
readonly paginationConfig: PaginationConfig;
|
|
100
|
+
readonly pageIndex: number;
|
|
101
|
+
readonly response: QueryResponse<TResponsePayload, TMeta> | undefined;
|
|
102
|
+
}): NextPageState {
|
|
103
|
+
return match({ getNextPageData, paginationConfig, pageIndex, response })
|
|
104
|
+
.returnType<NextPageState>()
|
|
105
|
+
.with({ response: undefined }, () => ({
|
|
106
|
+
hasNextPage: true,
|
|
107
|
+
pageSource: "firstRequest",
|
|
108
|
+
}))
|
|
109
|
+
.with({ paginationConfig: { maxPagesCount: 0 } }, () => ({
|
|
110
|
+
hasNextPage: false,
|
|
111
|
+
}))
|
|
112
|
+
.with({ paginationConfig: { maxPagesCount: pageIndex } }, () => ({
|
|
113
|
+
hasNextPage: false,
|
|
114
|
+
}))
|
|
115
|
+
.with({ response: P.not(undefined) }, (m) => {
|
|
116
|
+
const responsePageData = m.getNextPageData(m.response);
|
|
117
|
+
|
|
118
|
+
return match(responsePageData)
|
|
119
|
+
.returnType<NextPageState>()
|
|
120
|
+
.with({ continuationToken: P.string.minLength(1) }, (m) => ({
|
|
121
|
+
hasNextPage: true,
|
|
122
|
+
pageSource: "continuationToken",
|
|
123
|
+
continuationToken: m.continuationToken,
|
|
124
|
+
}))
|
|
125
|
+
.with({ nextPageUrl: P.string.minLength(1) }, (m) => ({
|
|
126
|
+
hasNextPage: true,
|
|
127
|
+
pageSource: "nextPageUrl",
|
|
128
|
+
nextPageUrl: m.nextPageUrl,
|
|
129
|
+
}))
|
|
130
|
+
.otherwise(() => ({
|
|
131
|
+
hasNextPage: false,
|
|
132
|
+
}));
|
|
133
|
+
})
|
|
134
|
+
.otherwise(() => {
|
|
135
|
+
return {
|
|
136
|
+
hasNextPage: false,
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function resolvePagingQueryAsync<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta>(
|
|
142
|
+
data: Omit<ResolveQueryData<TResponsePayload, TRequestBody, TMeta>, "nextPageState" | "getNextPageData"> & {
|
|
143
|
+
readonly getNextPageData: GetNextPageData<TResponsePayload, TMeta>;
|
|
144
|
+
readonly paginationConfig: PaginationConfig;
|
|
145
|
+
readonly pageIndex: number;
|
|
146
|
+
},
|
|
147
|
+
): Promise<PagingQueryPromiseResult<TResponsePayload, TMeta>> {
|
|
148
|
+
const { success, error, responses } = await fetchAllPagesAsync<TResponsePayload, TRequestBody, TMeta>(data);
|
|
149
|
+
|
|
150
|
+
if (!success) {
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
error,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return validateAndBuildPagingResult(responses, data.request.url);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function fetchAllPagesAsync<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta>(
|
|
161
|
+
data: Omit<ResolveQueryData<TResponsePayload, TRequestBody, TMeta>, "nextPageState"> & {
|
|
162
|
+
readonly getNextPageData: GetNextPageData<TResponsePayload, TMeta>;
|
|
163
|
+
readonly paginationConfig: PaginationConfig;
|
|
164
|
+
readonly pageIndex: number;
|
|
165
|
+
},
|
|
166
|
+
): Promise<FetchAllPagesResult<TResponsePayload, TMeta>> {
|
|
167
|
+
const initialPageState: NextPageState = resolveNextPageState({
|
|
168
|
+
getNextPageData: data.getNextPageData,
|
|
169
|
+
paginationConfig: data.paginationConfig,
|
|
170
|
+
pageIndex: data.pageIndex,
|
|
171
|
+
response: undefined,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return await fetchAllPagesInternal<TResponsePayload, TRequestBody, TMeta>({
|
|
175
|
+
queryData: data,
|
|
176
|
+
nextPageState: initialPageState,
|
|
177
|
+
responses: [],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function fetchAllPagesInternal<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta>({
|
|
182
|
+
queryData,
|
|
183
|
+
nextPageState,
|
|
184
|
+
responses,
|
|
185
|
+
}: {
|
|
186
|
+
readonly queryData: Omit<ResolveQueryData<TResponsePayload, TRequestBody, TMeta>, "nextPageState"> & {
|
|
187
|
+
readonly getNextPageData: GetNextPageData<TResponsePayload, TMeta>;
|
|
188
|
+
readonly paginationConfig: PaginationConfig;
|
|
189
|
+
readonly pageIndex: number;
|
|
190
|
+
};
|
|
191
|
+
readonly nextPageState: NextPageState;
|
|
192
|
+
readonly responses: readonly QueryResponse<TResponsePayload, TMeta>[];
|
|
193
|
+
}): Promise<FetchAllPagesResult<TResponsePayload, TMeta>> {
|
|
194
|
+
if (!isNextPageAvailable(nextPageState)) {
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
responses,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const { success, error, response } = await resolveQueryAsync<TResponsePayload, TRequestBody, TMeta>({
|
|
202
|
+
...queryData,
|
|
203
|
+
nextPageState: nextPageState,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (!success) {
|
|
207
|
+
return {
|
|
208
|
+
success: false,
|
|
209
|
+
error: error,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const updatedResponses: readonly QueryResponse<TResponsePayload, TMeta>[] = [...responses, response];
|
|
214
|
+
|
|
215
|
+
return await fetchAllPagesInternal<TResponsePayload, TRequestBody, TMeta>({
|
|
216
|
+
queryData: queryData,
|
|
217
|
+
nextPageState: resolveNextPageState({
|
|
218
|
+
getNextPageData: queryData.getNextPageData,
|
|
219
|
+
paginationConfig: queryData.paginationConfig,
|
|
220
|
+
pageIndex: updatedResponses.length,
|
|
221
|
+
response: response,
|
|
222
|
+
}),
|
|
223
|
+
responses: updatedResponses,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function validateAndBuildPagingResult<TResponsePayload extends JsonValue, TMeta>(
|
|
228
|
+
responses: readonly QueryResponse<TResponsePayload, TMeta>[],
|
|
229
|
+
requestUrl: string,
|
|
230
|
+
): PagingQueryResult<QueryResponse<TResponsePayload, TMeta>> {
|
|
231
|
+
const lastResponse = responses.at(-1);
|
|
232
|
+
|
|
233
|
+
if (!lastResponse) {
|
|
234
|
+
return {
|
|
235
|
+
success: false,
|
|
236
|
+
error: createSdkError({
|
|
237
|
+
reason: "noResponses",
|
|
238
|
+
url: requestUrl,
|
|
239
|
+
message: "No responses were processed. Expected at least one response to be fetched when using paging queries.",
|
|
240
|
+
}),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
success: true,
|
|
246
|
+
responses: [...responses],
|
|
247
|
+
lastContinuationToken: lastResponse.meta.continuationToken,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function isNextPageAvailable(nextPageState: NextPageState): nextPageState is NextPageStateWithRequest {
|
|
252
|
+
return nextPageState.hasNextPage;
|
|
253
|
+
}
|
package/lib/sdk/sdk-models.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import type { AdapterResponse, HttpResponse, HttpService, PaginationConfig, RequestBody, ResponseData } from "../http/http.models.js";
|
|
7
7
|
import type { KontentSdkError } from "../models/error.models.js";
|
|
8
|
-
import type { Prettify } from "../models/utility.models.js";
|
|
9
8
|
|
|
10
9
|
export type QueryResponseMeta<TMeta = unknown> = Pick<AdapterResponse, "status" | "responseHeaders" | "url"> & {
|
|
11
10
|
readonly continuationToken?: string;
|
|
@@ -47,21 +46,38 @@ export type SdkConfig = {
|
|
|
47
46
|
};
|
|
48
47
|
};
|
|
49
48
|
|
|
50
|
-
export type Query<TResponsePayload, TMeta
|
|
49
|
+
export type Query<TResponsePayload, TMeta> = {
|
|
51
50
|
toUrl(): string;
|
|
52
51
|
toPromise(): Promise<QueryResult<QueryResponse<TResponsePayload, TMeta>>>;
|
|
53
52
|
};
|
|
54
53
|
|
|
55
|
-
export type PagingQuery<TResponsePayload, TMeta
|
|
54
|
+
export type PagingQuery<TResponsePayload, TMeta> = Query<TResponsePayload, TMeta> & {
|
|
56
55
|
toAllPromise(config?: PaginationConfig): Promise<PagingQueryResult<QueryResponse<TResponsePayload, TMeta>>>;
|
|
56
|
+
pages(config?: PaginationConfig): AsyncGenerator<QueryResponse<TResponsePayload, TMeta>>;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
export type
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
export type NextPageStateWithRequest =
|
|
60
|
+
| {
|
|
61
|
+
readonly pageSource: "continuationToken";
|
|
62
|
+
readonly hasNextPage: true;
|
|
63
|
+
readonly continuationToken: string;
|
|
64
|
+
readonly nextPageUrl?: never;
|
|
65
|
+
}
|
|
66
|
+
| {
|
|
67
|
+
readonly pageSource: "nextPageUrl";
|
|
68
|
+
readonly hasNextPage: true;
|
|
69
|
+
readonly continuationToken?: never;
|
|
70
|
+
readonly nextPageUrl: string;
|
|
71
|
+
}
|
|
72
|
+
| {
|
|
73
|
+
readonly pageSource: "firstRequest";
|
|
74
|
+
readonly hasNextPage: true;
|
|
75
|
+
readonly continuationToken?: never;
|
|
76
|
+
readonly nextPageUrl?: never;
|
|
77
|
+
};
|
|
62
78
|
|
|
63
|
-
export type
|
|
64
|
-
|
|
79
|
+
export type SuccessfulHttpResponse<TResponsePayload extends ResponseData, TRequestBody extends RequestBody> = Extract<
|
|
80
|
+
HttpResponse<TResponsePayload, TRequestBody>,
|
|
65
81
|
{ readonly success: true }
|
|
66
82
|
>["response"];
|
|
67
83
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared query models/types intended to be reused across SDKs (e.g. Sync, Delivery, Management)
|
|
3
|
+
* to keep common code and behavior consistent.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ZodError, ZodType } from "zod";
|
|
7
|
+
import type { HttpService, RequestBody } from "../http/http.models.js";
|
|
8
|
+
import { getDefaultHttpService } from "../http/http.service.js";
|
|
9
|
+
import type { CommonHeaderNames, ContinuationHeaderName, Header, SDKInfo } from "../models/core.models.js";
|
|
10
|
+
import type { JsonValue } from "../models/json.models.js";
|
|
11
|
+
import { createSdkError } from "../utils/error.utils.js";
|
|
12
|
+
import { getSdkIdHeader } from "../utils/header.utils.js";
|
|
13
|
+
import type { NextPageStateWithRequest, Query, SdkConfig, SuccessfulHttpResponse } from "./sdk-models.js";
|
|
14
|
+
|
|
15
|
+
export type QueryPromiseResult<TResponsePayload extends JsonValue, TMeta> = ReturnType<
|
|
16
|
+
Pick<Query<TResponsePayload, TMeta>, "toPromise">["toPromise"]
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
type MetadataContextData = {
|
|
20
|
+
readonly continuationToken?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type MetadataMapper<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta> = (
|
|
24
|
+
response: SuccessfulHttpResponse<TResponsePayload, TRequestBody>,
|
|
25
|
+
data: MetadataContextData,
|
|
26
|
+
) => TMeta;
|
|
27
|
+
type MetadataMapperConfig<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta> = {
|
|
28
|
+
readonly mapMetadata: MetadataMapper<TResponsePayload, TRequestBody, TMeta>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type ResolveQueryData<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta> = {
|
|
32
|
+
readonly nextPageState: NextPageStateWithRequest;
|
|
33
|
+
readonly request: Parameters<HttpService["requestAsync"]>[number] & { readonly body: TRequestBody };
|
|
34
|
+
readonly config: SdkConfig;
|
|
35
|
+
readonly zodSchema: ZodType<TResponsePayload>;
|
|
36
|
+
readonly sdkInfo: SDKInfo;
|
|
37
|
+
readonly authorizationApiKey: string | undefined;
|
|
38
|
+
} & MetadataMapperConfig<TResponsePayload, TRequestBody, TMeta>;
|
|
39
|
+
|
|
40
|
+
export function createQuery<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta>(
|
|
41
|
+
data: Omit<ResolveQueryData<TResponsePayload, TRequestBody, TMeta>, "continuationToken" | "nextPageState" | "pageIndex">,
|
|
42
|
+
): Pick<Query<TResponsePayload, TMeta>, "toPromise"> {
|
|
43
|
+
return {
|
|
44
|
+
toPromise: async () => {
|
|
45
|
+
return await resolveQueryAsync<TResponsePayload, TRequestBody, TMeta>({
|
|
46
|
+
...data,
|
|
47
|
+
nextPageState: {
|
|
48
|
+
hasNextPage: true,
|
|
49
|
+
pageSource: "firstRequest",
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function extractContinuationToken(responseHeaders: readonly Header[]): string | undefined {
|
|
57
|
+
return responseHeaders.find((header) => header.name.toLowerCase() === ("X-Continuation" satisfies ContinuationHeaderName).toLowerCase())
|
|
58
|
+
?.value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getHttpService(config: SdkConfig) {
|
|
62
|
+
return config.httpService ?? getDefaultHttpService();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getCombinedRequestHeaders({
|
|
66
|
+
requestHeaders,
|
|
67
|
+
continuationToken,
|
|
68
|
+
authorizationApiKey,
|
|
69
|
+
sdkInfo,
|
|
70
|
+
}: {
|
|
71
|
+
readonly requestHeaders: readonly Header[];
|
|
72
|
+
readonly continuationToken: string | undefined;
|
|
73
|
+
readonly authorizationApiKey: string | undefined;
|
|
74
|
+
readonly sdkInfo: SDKInfo;
|
|
75
|
+
}): readonly Header[] {
|
|
76
|
+
return [
|
|
77
|
+
getSdkIdHeader({
|
|
78
|
+
host: sdkInfo.host,
|
|
79
|
+
name: sdkInfo.name,
|
|
80
|
+
version: sdkInfo.version,
|
|
81
|
+
}),
|
|
82
|
+
...requestHeaders,
|
|
83
|
+
...(continuationToken
|
|
84
|
+
? [
|
|
85
|
+
{
|
|
86
|
+
name: "X-Continuation" satisfies CommonHeaderNames,
|
|
87
|
+
value: continuationToken,
|
|
88
|
+
},
|
|
89
|
+
]
|
|
90
|
+
: []),
|
|
91
|
+
...(authorizationApiKey
|
|
92
|
+
? [
|
|
93
|
+
{
|
|
94
|
+
name: "Authorization" satisfies CommonHeaderNames,
|
|
95
|
+
value: `Bearer ${authorizationApiKey}`,
|
|
96
|
+
},
|
|
97
|
+
]
|
|
98
|
+
: []),
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function resolveQueryAsync<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta>({
|
|
103
|
+
config,
|
|
104
|
+
request,
|
|
105
|
+
mapMetadata,
|
|
106
|
+
zodSchema,
|
|
107
|
+
sdkInfo,
|
|
108
|
+
authorizationApiKey,
|
|
109
|
+
nextPageState,
|
|
110
|
+
}: ResolveQueryData<TResponsePayload, TRequestBody, TMeta>): QueryPromiseResult<TResponsePayload, TMeta> {
|
|
111
|
+
const { success, response, error } = await getHttpService(config).requestAsync<TResponsePayload, TRequestBody>({
|
|
112
|
+
body: request.body,
|
|
113
|
+
url: nextPageState?.nextPageUrl ?? request.url,
|
|
114
|
+
method: request.method,
|
|
115
|
+
requestHeaders: getCombinedRequestHeaders({
|
|
116
|
+
requestHeaders: request.requestHeaders ?? [],
|
|
117
|
+
continuationToken: nextPageState?.continuationToken,
|
|
118
|
+
authorizationApiKey: authorizationApiKey,
|
|
119
|
+
sdkInfo,
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (!success) {
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
error,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (config.responseValidation?.enable) {
|
|
131
|
+
const { isValid, error: validationError } = await validateResponseSchemaAsync(response.payload, zodSchema);
|
|
132
|
+
if (!isValid) {
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
error: createSdkError({
|
|
136
|
+
message: `Failed to validate response schema for url '${request.url}'`,
|
|
137
|
+
reason: "validationFailed",
|
|
138
|
+
zodError: validationError,
|
|
139
|
+
response,
|
|
140
|
+
url: request.url,
|
|
141
|
+
}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const continuationTokenFromResponse = extractContinuationToken(response.adapterResponse.responseHeaders);
|
|
147
|
+
|
|
148
|
+
const result: Awaited<QueryPromiseResult<TResponsePayload, TMeta>> = {
|
|
149
|
+
success: true,
|
|
150
|
+
response: {
|
|
151
|
+
payload: response.payload,
|
|
152
|
+
meta: {
|
|
153
|
+
url: response.adapterResponse.url,
|
|
154
|
+
responseHeaders: response.adapterResponse.responseHeaders,
|
|
155
|
+
status: response.adapterResponse.status,
|
|
156
|
+
continuationToken: continuationTokenFromResponse,
|
|
157
|
+
...mapMetadata(response, { continuationToken: continuationTokenFromResponse }),
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function validateResponseSchemaAsync<TResponsePayload extends JsonValue>(
|
|
166
|
+
payload: TResponsePayload,
|
|
167
|
+
zodSchema: ZodType<TResponsePayload>,
|
|
168
|
+
): Promise<
|
|
169
|
+
| {
|
|
170
|
+
readonly isValid: true;
|
|
171
|
+
readonly error?: never;
|
|
172
|
+
}
|
|
173
|
+
| {
|
|
174
|
+
readonly isValid: false;
|
|
175
|
+
readonly error: ZodError;
|
|
176
|
+
}
|
|
177
|
+
> {
|
|
178
|
+
const validateResult = await zodSchema.safeParseAsync(payload);
|
|
179
|
+
|
|
180
|
+
if (validateResult.success) {
|
|
181
|
+
return {
|
|
182
|
+
isValid: true,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
isValid: false,
|
|
188
|
+
error: validateResult.error,
|
|
189
|
+
};
|
|
190
|
+
}
|
package/lib/utils/error.utils.ts
CHANGED
|
@@ -8,8 +8,12 @@ export function createSdkError(details: ErrorDetails): KontentSdkError {
|
|
|
8
8
|
return new KontentSdkError(details);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function isKontent404Error(error:
|
|
12
|
-
return error.details.reason === "notFound";
|
|
11
|
+
export function isKontent404Error(error: unknown): boolean {
|
|
12
|
+
return isKontentSdkError(error) && error.details.reason === "notFound";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isKontentSdkError(error: unknown): error is KontentSdkError {
|
|
16
|
+
return error instanceof KontentSdkError;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
export function getErrorMessage({
|
package/lib/utils/retry.utils.ts
CHANGED
|
@@ -106,7 +106,7 @@ export function toRequiredRetryStrategyOptions(options?: RetryStrategyOptions):
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
function getDefaultRetryAttemptLogMessage(retryAttempt: number, maxRetries: number, url: string): string {
|
|
110
110
|
return `Retry attempt '${retryAttempt}' from a maximum of '${maxRetries}' retries. Requested url: '${url}'`;
|
|
111
111
|
}
|
|
112
112
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontent-ai/core-sdk",
|
|
3
|
-
"version": "12.0.0-preview.
|
|
3
|
+
"version": "12.0.0-preview.13",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/kontent-ai/core-sdk-js"
|
|
@@ -46,23 +46,24 @@
|
|
|
46
46
|
"clean": "tsx scripts/clean.ts",
|
|
47
47
|
"update:version": "tsx ./scripts/update-version.ts"
|
|
48
48
|
},
|
|
49
|
-
"dependencies": {
|
|
50
|
-
"zod": "4.3.6",
|
|
51
|
-
"ts-pattern": "5.9.0"
|
|
52
|
-
},
|
|
53
49
|
"peerDependencies": {
|
|
54
|
-
"
|
|
50
|
+
"ts-pattern": "^5.9.0",
|
|
51
|
+
"zod": "^4.3.6"
|
|
55
52
|
},
|
|
53
|
+
|
|
56
54
|
"devDependencies": {
|
|
57
55
|
"@kontent-ai/biome-config": "0.7.0",
|
|
58
|
-
"@kontent-ai/eslint-config": "2.
|
|
59
|
-
"eslint": "10.0.
|
|
60
|
-
"@biomejs/biome": "2.
|
|
61
|
-
"@types/node": "25.
|
|
56
|
+
"@kontent-ai/eslint-config": "2.4.1",
|
|
57
|
+
"eslint": "10.0.2",
|
|
58
|
+
"@biomejs/biome": "2.4.4",
|
|
59
|
+
"@types/node": "25.3.0",
|
|
62
60
|
"chalk": "5.6.2",
|
|
63
61
|
"typescript": "5.9.3",
|
|
64
62
|
"vitest": "4.0.18",
|
|
65
|
-
"dotenv": "17.
|
|
63
|
+
"dotenv": "17.3.1",
|
|
66
64
|
"tsx": "4.21.0"
|
|
65
|
+
},
|
|
66
|
+
"overrides": {
|
|
67
|
+
"minimatch": "10.2.3"
|
|
67
68
|
}
|
|
68
69
|
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared query models/types intended to be reused across SDKs (e.g. Sync, Delivery, Management)
|
|
3
|
-
* to keep common code and behavior consistent.
|
|
4
|
-
*/
|
|
5
|
-
import type { ZodType } from "zod";
|
|
6
|
-
import type { GetNextPageData, HttpService, RequestBody } from "../http/http.models.js";
|
|
7
|
-
import type { Header, SDKInfo } from "../models/core.models.js";
|
|
8
|
-
import type { JsonValue } from "../models/json.models.js";
|
|
9
|
-
import type { EmptyObject } from "../models/utility.models.js";
|
|
10
|
-
import type { PagingQuery, Query, SdkConfig, SuccessfulHttpResponse } from "./sdk-models.js";
|
|
11
|
-
type MetadataContextData = {
|
|
12
|
-
readonly continuationToken?: string;
|
|
13
|
-
};
|
|
14
|
-
type MetadataMapper<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta> = (response: SuccessfulHttpResponse<TResponsePayload, TRequestBody>, data: MetadataContextData) => TMeta;
|
|
15
|
-
type MetadataMapperConfig<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta> = {
|
|
16
|
-
readonly mapMetadata: MetadataMapper<TResponsePayload, TRequestBody, TMeta>;
|
|
17
|
-
};
|
|
18
|
-
type ResolveQueryData<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta> = {
|
|
19
|
-
readonly nextPageState: NextPageStateWithRequest;
|
|
20
|
-
readonly request: Parameters<HttpService["requestAsync"]>[number] & {
|
|
21
|
-
readonly body: TRequestBody;
|
|
22
|
-
};
|
|
23
|
-
readonly config: SdkConfig;
|
|
24
|
-
readonly zodSchema: ZodType<TResponsePayload>;
|
|
25
|
-
readonly sdkInfo: SDKInfo;
|
|
26
|
-
readonly authorizationApiKey: string | undefined;
|
|
27
|
-
} & MetadataMapperConfig<TResponsePayload, TRequestBody, TMeta>;
|
|
28
|
-
type NextPageStateWithRequest = {
|
|
29
|
-
readonly pageSource: "continuationToken";
|
|
30
|
-
readonly hasNextPage: true;
|
|
31
|
-
readonly continuationToken: string;
|
|
32
|
-
readonly nextPageUrl?: never;
|
|
33
|
-
} | {
|
|
34
|
-
readonly pageSource: "nextPageUrl";
|
|
35
|
-
readonly hasNextPage: true;
|
|
36
|
-
readonly continuationToken?: never;
|
|
37
|
-
readonly nextPageUrl: string;
|
|
38
|
-
} | {
|
|
39
|
-
readonly pageSource: "firstRequest";
|
|
40
|
-
readonly hasNextPage: true;
|
|
41
|
-
readonly continuationToken?: never;
|
|
42
|
-
readonly nextPageUrl?: never;
|
|
43
|
-
};
|
|
44
|
-
export declare function createQuery<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta = EmptyObject>(data: Omit<ResolveQueryData<TResponsePayload, TRequestBody, TMeta>, "continuationToken" | "nextPageState" | "pageIndex">): Pick<Query<TResponsePayload, TMeta>, "toPromise">;
|
|
45
|
-
export declare function createPagingQuery<TResponsePayload extends JsonValue, TRequestBody extends RequestBody, TMeta = EmptyObject>(data: Omit<ResolveQueryData<TResponsePayload, TRequestBody, TMeta>, "nextPageState" | "pageIndex"> & {
|
|
46
|
-
readonly getNextPageData: GetNextPageData<TResponsePayload, TMeta>;
|
|
47
|
-
}): Pick<PagingQuery<TResponsePayload, TMeta>, "toPromise" | "toAllPromise">;
|
|
48
|
-
export declare function extractContinuationToken(responseHeaders: readonly Header[]): string | undefined;
|
|
49
|
-
export {};
|