@financial-times/content-curation-client 5.2.0 → 5.4.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 +67 -56
- package/dist/_tsup-dts-rollup.d.cts +1384 -321
- package/dist/_tsup-dts-rollup.d.ts +1384 -321
- package/dist/index.cjs +479 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +476 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,195 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var ApiClientError = class extends Error {
|
|
3
|
+
/** The machine-readable error payload exposed by the client. */
|
|
4
|
+
payload;
|
|
5
|
+
/** The machine-readable error code. */
|
|
6
|
+
code;
|
|
7
|
+
/** Additional diagnostic detail captured by the client. */
|
|
8
|
+
details;
|
|
9
|
+
/** The API path associated with the failing request. */
|
|
10
|
+
path;
|
|
11
|
+
/** Optional timestamp supplied by the upstream service. */
|
|
12
|
+
timestamp;
|
|
13
|
+
/** Indicates whether the failure originated from transport, gateway, or service handling. */
|
|
14
|
+
origin;
|
|
15
|
+
/** The fully qualified request URL, when available. */
|
|
16
|
+
url;
|
|
17
|
+
/** The response content type, when a response was received. */
|
|
18
|
+
contentType;
|
|
19
|
+
/** A truncated copy of the raw response body, when captured. */
|
|
20
|
+
bodyText;
|
|
21
|
+
/** The upstream request identifier, when available. */
|
|
22
|
+
requestId;
|
|
23
|
+
/** The HTTP status code reported by the service or gateway. */
|
|
24
|
+
statusCode;
|
|
25
|
+
/** The underlying thrown error, if there was one. */
|
|
26
|
+
cause;
|
|
27
|
+
constructor(payload, statusCode, requestId, metadata = { origin: "service" }) {
|
|
28
|
+
super(payload.message);
|
|
29
|
+
this.name = new.target.name;
|
|
30
|
+
this.payload = payload;
|
|
31
|
+
this.statusCode = statusCode;
|
|
32
|
+
this.requestId = requestId;
|
|
33
|
+
this.code = payload.code;
|
|
34
|
+
this.details = payload.details;
|
|
35
|
+
this.path = payload.path;
|
|
36
|
+
this.timestamp = payload.timestamp;
|
|
37
|
+
this.origin = metadata.origin;
|
|
38
|
+
this.url = metadata.url;
|
|
39
|
+
this.contentType = metadata.contentType;
|
|
40
|
+
this.bodyText = metadata.bodyText;
|
|
41
|
+
this.cause = metadata.cause;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var ApiTransportError = class extends ApiClientError {
|
|
45
|
+
payload;
|
|
46
|
+
origin;
|
|
47
|
+
/** Whether the failure was caused by an abort, timeout, or generic network problem. */
|
|
48
|
+
transportKind;
|
|
49
|
+
constructor(payload, statusCode, requestId, metadata) {
|
|
50
|
+
super(payload, statusCode, requestId, {
|
|
51
|
+
...metadata,
|
|
52
|
+
origin: "transport"
|
|
53
|
+
});
|
|
54
|
+
this.payload = payload;
|
|
55
|
+
this.origin = "transport";
|
|
56
|
+
this.transportKind = metadata.transportKind;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var ApiGatewayError = class extends ApiClientError {
|
|
60
|
+
payload;
|
|
61
|
+
origin;
|
|
62
|
+
constructor(payload, statusCode, requestId, metadata = {}) {
|
|
63
|
+
super(payload, statusCode, requestId, {
|
|
64
|
+
...metadata,
|
|
65
|
+
origin: "gateway"
|
|
66
|
+
});
|
|
67
|
+
this.payload = payload;
|
|
68
|
+
this.origin = "gateway";
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var ApiServiceError = class extends ApiClientError {
|
|
72
|
+
payload;
|
|
73
|
+
origin;
|
|
74
|
+
constructor(payload, statusCode, requestId, metadata = {}) {
|
|
75
|
+
super(payload, statusCode, requestId, {
|
|
76
|
+
...metadata,
|
|
77
|
+
origin: "service"
|
|
78
|
+
});
|
|
79
|
+
this.payload = payload;
|
|
80
|
+
this.origin = "service";
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/internal/error-factories.ts
|
|
85
|
+
function createTransportError(path, url, error) {
|
|
86
|
+
const transportKind = classifyTransportFailure(error);
|
|
87
|
+
const message = describeTransportFailure(transportKind);
|
|
88
|
+
const causeDetails = getErrorDiagnosticMessage(error);
|
|
89
|
+
const details = causeDetails ? `${message} Cause: ${causeDetails}` : message;
|
|
90
|
+
return new ApiTransportError(
|
|
91
|
+
{
|
|
92
|
+
code: "API_TRANSPORT_ERROR",
|
|
93
|
+
message,
|
|
94
|
+
details,
|
|
95
|
+
path
|
|
96
|
+
},
|
|
97
|
+
0,
|
|
98
|
+
void 0,
|
|
99
|
+
{
|
|
100
|
+
transportKind,
|
|
101
|
+
url,
|
|
102
|
+
cause: error
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
function createGatewayError(path, url, statusCode, options) {
|
|
107
|
+
return new ApiGatewayError(
|
|
108
|
+
{
|
|
109
|
+
code: "API_GATEWAY_ERROR",
|
|
110
|
+
message: `The API gateway responded with status ${statusCode}.`,
|
|
111
|
+
details: options.details,
|
|
112
|
+
path
|
|
113
|
+
},
|
|
114
|
+
statusCode,
|
|
115
|
+
options.requestId,
|
|
116
|
+
{
|
|
117
|
+
url,
|
|
118
|
+
contentType: options.contentType,
|
|
119
|
+
bodyText: options.bodyText,
|
|
120
|
+
cause: options.cause
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
function createUnexpectedServiceResponseError(path, url, options) {
|
|
125
|
+
return new ApiServiceError(
|
|
126
|
+
{
|
|
127
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
128
|
+
message: "Unexpected API response format",
|
|
129
|
+
details: options.details,
|
|
130
|
+
path
|
|
131
|
+
},
|
|
132
|
+
500,
|
|
133
|
+
options.requestId,
|
|
134
|
+
{
|
|
135
|
+
url,
|
|
136
|
+
contentType: options.contentType,
|
|
137
|
+
bodyText: options.bodyText,
|
|
138
|
+
cause: options.cause
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
function classifyTransportFailure(error) {
|
|
143
|
+
const errorName = readStringProperty(error, "name");
|
|
144
|
+
if (errorName === "AbortError") {
|
|
145
|
+
return "abort";
|
|
146
|
+
}
|
|
147
|
+
if (errorName === "TimeoutError") {
|
|
148
|
+
return "timeout";
|
|
149
|
+
}
|
|
150
|
+
if (errorName === "FetchError" && readStringProperty(error, "type") === "request-timeout") {
|
|
151
|
+
return "timeout";
|
|
152
|
+
}
|
|
153
|
+
return "network";
|
|
154
|
+
}
|
|
155
|
+
function describeTransportFailure(transportKind) {
|
|
156
|
+
if (transportKind === "abort") {
|
|
157
|
+
return "The request was aborted before a response was received.";
|
|
158
|
+
}
|
|
159
|
+
if (transportKind === "timeout") {
|
|
160
|
+
return "The request timed out before a response was received.";
|
|
161
|
+
}
|
|
162
|
+
return "The request failed before a response was received.";
|
|
163
|
+
}
|
|
164
|
+
function getErrorDiagnosticMessage(error) {
|
|
165
|
+
const diagnosticParts = [
|
|
166
|
+
readCauseCode(error),
|
|
167
|
+
readStringProperty(error, "name"),
|
|
168
|
+
readStringProperty(error, "message")
|
|
169
|
+
].filter(
|
|
170
|
+
(value, index, values) => Boolean(value) && values.indexOf(value) === index
|
|
171
|
+
);
|
|
172
|
+
return diagnosticParts.length > 0 ? diagnosticParts.join(": ") : void 0;
|
|
173
|
+
}
|
|
174
|
+
function readCauseCode(error) {
|
|
175
|
+
if (typeof error !== "object" || error === null) {
|
|
176
|
+
return void 0;
|
|
177
|
+
}
|
|
178
|
+
const directCode = readStringProperty(error, "code");
|
|
179
|
+
if (directCode) {
|
|
180
|
+
return directCode;
|
|
181
|
+
}
|
|
182
|
+
const cause = error.cause;
|
|
183
|
+
return readStringProperty(cause, "code");
|
|
184
|
+
}
|
|
185
|
+
function readStringProperty(error, property) {
|
|
186
|
+
if (typeof error !== "object" || error === null) {
|
|
187
|
+
return void 0;
|
|
188
|
+
}
|
|
189
|
+
const value = error[property];
|
|
190
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
1
193
|
// src/schemas/response.ts
|
|
2
194
|
import { z } from "zod";
|
|
3
195
|
var ApiErrorCode = z.enum([
|
|
@@ -33,24 +225,242 @@ function ApiResponseSchema(dataSchema) {
|
|
|
33
225
|
return z.discriminatedUnion("status", [successSchema, errorSchema]);
|
|
34
226
|
}
|
|
35
227
|
|
|
36
|
-
// src/
|
|
37
|
-
var
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
228
|
+
// src/internal/response-handling.ts
|
|
229
|
+
var GATEWAY_STATUS_CODES = /* @__PURE__ */ new Set([499, 502, 503]);
|
|
230
|
+
var MAX_DIAGNOSTIC_BODY_LENGTH = 2e3;
|
|
231
|
+
function isGatewayStatusCode(statusCode) {
|
|
232
|
+
return GATEWAY_STATUS_CODES.has(statusCode);
|
|
233
|
+
}
|
|
234
|
+
function getResponseContentType(response) {
|
|
235
|
+
return response.headers?.get("content-type") ?? void 0;
|
|
236
|
+
}
|
|
237
|
+
function getResponseRequestId(response) {
|
|
238
|
+
return response.headers?.get("x-request-id") ?? response.headers?.get("request-id") ?? void 0;
|
|
239
|
+
}
|
|
240
|
+
async function readResponseBody(response) {
|
|
241
|
+
const contentType = getResponseContentType(response);
|
|
242
|
+
let rawBody = "";
|
|
243
|
+
try {
|
|
244
|
+
rawBody = await response.text();
|
|
245
|
+
} catch (error) {
|
|
246
|
+
await discardResponseBody(response);
|
|
247
|
+
throw error;
|
|
48
248
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
249
|
+
const bodyText = truncateBody(rawBody);
|
|
250
|
+
if (!isJsonContentType(contentType) || rawBody.length === 0) {
|
|
251
|
+
return {
|
|
252
|
+
contentType,
|
|
253
|
+
bodyText
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
return {
|
|
258
|
+
contentType,
|
|
259
|
+
bodyText,
|
|
260
|
+
json: JSON.parse(rawBody)
|
|
261
|
+
};
|
|
262
|
+
} catch (error) {
|
|
263
|
+
return {
|
|
264
|
+
contentType,
|
|
265
|
+
bodyText,
|
|
266
|
+
jsonParseError: toError(error)
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function parseApiResponse(responseDataSchema, responseBody) {
|
|
271
|
+
if (responseBody.json === void 0) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const parsedResponse = ApiResponseSchema(responseDataSchema).safeParse(responseBody.json);
|
|
275
|
+
return parsedResponse.success ? parsedResponse.data : null;
|
|
276
|
+
}
|
|
277
|
+
function parseApiErrorResponse(responseBody) {
|
|
278
|
+
if (responseBody.json === void 0) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
const parsedError = ApiErrorResponseSchema.safeParse(responseBody.json);
|
|
282
|
+
return parsedError.success ? parsedError.data : null;
|
|
283
|
+
}
|
|
284
|
+
function describeUnexpectedSuccessResponse(responseBody) {
|
|
285
|
+
if (responseBody.jsonParseError) {
|
|
286
|
+
return buildDiagnosticDetails(
|
|
287
|
+
"The service reported a JSON response but the body could not be parsed as JSON.",
|
|
288
|
+
responseBody
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
if (!responseBody.contentType || !isJsonContentType(responseBody.contentType)) {
|
|
292
|
+
return buildDiagnosticDetails(
|
|
293
|
+
"The service returned a success status without a JSON response body.",
|
|
294
|
+
responseBody
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
return buildDiagnosticDetails(
|
|
298
|
+
"The service returned JSON, but it did not match the expected Content Curation API envelope.",
|
|
299
|
+
responseBody
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
function describeGatewayResponse(statusCode, responseBody) {
|
|
303
|
+
if (responseBody.jsonParseError) {
|
|
304
|
+
return buildDiagnosticDetails(
|
|
305
|
+
`The non-success response with status ${statusCode} declared a JSON content type, but its body could not be parsed as JSON.`,
|
|
306
|
+
responseBody
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
if (!responseBody.contentType || !isJsonContentType(responseBody.contentType)) {
|
|
310
|
+
return buildDiagnosticDetails(
|
|
311
|
+
`The non-success response with status ${statusCode} was not a valid service JSON error envelope.`,
|
|
312
|
+
responseBody
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
return buildDiagnosticDetails(
|
|
316
|
+
`The non-success response with status ${statusCode} returned JSON, but it did not match the service error envelope.`,
|
|
317
|
+
responseBody
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
async function discardResponseBody(response) {
|
|
321
|
+
const body = response.body;
|
|
322
|
+
if (!body) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (typeof body.cancel === "function") {
|
|
326
|
+
try {
|
|
327
|
+
await body.cancel();
|
|
328
|
+
return;
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (typeof body.resume === "function" && typeof body.on === "function") {
|
|
333
|
+
await new Promise((resolve) => {
|
|
334
|
+
const stream = body;
|
|
335
|
+
const finish = () => resolve();
|
|
336
|
+
stream.on("end", finish);
|
|
337
|
+
stream.on("error", finish);
|
|
338
|
+
stream.resume();
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function buildDiagnosticDetails(message, responseBody) {
|
|
343
|
+
const detailParts = [message];
|
|
344
|
+
if (responseBody.contentType) {
|
|
345
|
+
detailParts.push(`Content-Type: ${responseBody.contentType}.`);
|
|
346
|
+
}
|
|
347
|
+
if (responseBody.jsonParseError) {
|
|
348
|
+
detailParts.push(`JSON parse error: ${responseBody.jsonParseError.message}.`);
|
|
349
|
+
}
|
|
350
|
+
if (responseBody.bodyText) {
|
|
351
|
+
detailParts.push("The raw response body is available in bodyText.");
|
|
352
|
+
}
|
|
353
|
+
return detailParts.join(" ");
|
|
354
|
+
}
|
|
355
|
+
function isJsonContentType(contentType) {
|
|
356
|
+
if (!contentType) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
const mimeType = contentType.split(";")[0]?.trim().toLowerCase();
|
|
360
|
+
if (!mimeType) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
return mimeType === "application/json" || mimeType.endsWith("+json");
|
|
364
|
+
}
|
|
365
|
+
function truncateBody(bodyText) {
|
|
366
|
+
if (bodyText.length === 0) {
|
|
367
|
+
return void 0;
|
|
368
|
+
}
|
|
369
|
+
if (bodyText.length <= MAX_DIAGNOSTIC_BODY_LENGTH) {
|
|
370
|
+
return bodyText;
|
|
371
|
+
}
|
|
372
|
+
return `${bodyText.slice(0, MAX_DIAGNOSTIC_BODY_LENGTH - 3)}...`;
|
|
373
|
+
}
|
|
374
|
+
function toError(error) {
|
|
375
|
+
if (error instanceof Error) {
|
|
376
|
+
return error;
|
|
377
|
+
}
|
|
378
|
+
const message = typeof error === "string" && error.length > 0 ? error : "Unknown error";
|
|
379
|
+
return new Error(message);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/internal/request-lifecycle.ts
|
|
383
|
+
function parseRequestBody(requestSchema, body) {
|
|
384
|
+
if (!requestSchema || body === void 0) {
|
|
385
|
+
return body;
|
|
386
|
+
}
|
|
387
|
+
return requestSchema.parse(body);
|
|
388
|
+
}
|
|
389
|
+
async function readApiResponseBody(path, url, response) {
|
|
390
|
+
try {
|
|
391
|
+
return await readResponseBody(response);
|
|
392
|
+
} catch (error) {
|
|
393
|
+
if (response.ok) {
|
|
394
|
+
throw createUnexpectedServiceResponseError(path, url, {
|
|
395
|
+
contentType: getResponseContentType(response),
|
|
396
|
+
cause: error,
|
|
397
|
+
details: "Failed to read the response body before the API response could be validated.",
|
|
398
|
+
requestId: getResponseRequestId(response)
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
throw createGatewayError(path, url, response.status, {
|
|
402
|
+
contentType: getResponseContentType(response),
|
|
403
|
+
cause: error,
|
|
404
|
+
details: "Failed to read the non-success response body, so the response could not be validated as a service error.",
|
|
405
|
+
requestId: getResponseRequestId(response)
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function parseSuccessfulResponse(path, url, response, responseBody, responseDataSchema) {
|
|
410
|
+
const parsedResponse = parseApiResponse(responseDataSchema, responseBody);
|
|
411
|
+
if (!parsedResponse) {
|
|
412
|
+
throw createUnexpectedServiceResponseError(path, url, {
|
|
413
|
+
contentType: responseBody.contentType,
|
|
414
|
+
bodyText: responseBody.bodyText,
|
|
415
|
+
details: describeUnexpectedSuccessResponse(responseBody),
|
|
416
|
+
requestId: getResponseRequestId(response)
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
if ("error" in parsedResponse) {
|
|
420
|
+
throw new ApiServiceError(
|
|
421
|
+
parsedResponse.error,
|
|
422
|
+
parsedResponse.statusCode,
|
|
423
|
+
parsedResponse.requestId,
|
|
424
|
+
{
|
|
425
|
+
url,
|
|
426
|
+
contentType: responseBody.contentType,
|
|
427
|
+
bodyText: responseBody.bodyText
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
return parsedResponse.data;
|
|
432
|
+
}
|
|
433
|
+
function createResponseError(path, url, response, responseBody) {
|
|
434
|
+
if (isGatewayStatusCode(response.status)) {
|
|
435
|
+
return createGatewayError(path, url, response.status, {
|
|
436
|
+
contentType: responseBody.contentType,
|
|
437
|
+
bodyText: responseBody.bodyText,
|
|
438
|
+
details: describeGatewayResponse(response.status, responseBody),
|
|
439
|
+
requestId: getResponseRequestId(response)
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
const parsedServiceError = parseApiErrorResponse(responseBody);
|
|
443
|
+
if (parsedServiceError) {
|
|
444
|
+
return new ApiServiceError(
|
|
445
|
+
parsedServiceError.error,
|
|
446
|
+
parsedServiceError.statusCode,
|
|
447
|
+
parsedServiceError.requestId,
|
|
448
|
+
{
|
|
449
|
+
url,
|
|
450
|
+
contentType: responseBody.contentType,
|
|
451
|
+
bodyText: responseBody.bodyText
|
|
452
|
+
}
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
return createGatewayError(path, url, response.status, {
|
|
456
|
+
contentType: responseBody.contentType,
|
|
457
|
+
bodyText: responseBody.bodyText,
|
|
458
|
+
details: describeGatewayResponse(response.status, responseBody),
|
|
459
|
+
requestId: getResponseRequestId(response)
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/base.ts
|
|
54
464
|
var BaseApiClient = class {
|
|
55
465
|
config;
|
|
56
466
|
constructor(config) {
|
|
@@ -58,55 +468,60 @@ var BaseApiClient = class {
|
|
|
58
468
|
...config,
|
|
59
469
|
fetch: config.fetch || fetch
|
|
60
470
|
};
|
|
61
|
-
this.config.baseUrl = this.config.baseUrl
|
|
471
|
+
this.config.baseUrl = trimTrailingSlashes(this.config.baseUrl);
|
|
62
472
|
}
|
|
63
473
|
/**
|
|
64
|
-
*
|
|
474
|
+
* Performs a request against the JSON API, validates the response envelope,
|
|
475
|
+
* and returns the typed `data` payload on success.
|
|
65
476
|
*/
|
|
66
477
|
async request(method, path, opts) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
...this.config.apiKey ? { "x-api-key": this.config.apiKey } : { Authorization: `Bearer ${this.config.apiToken}` }
|
|
77
|
-
},
|
|
78
|
-
body: bodyPayload ? JSON.stringify(bodyPayload) : void 0
|
|
79
|
-
});
|
|
80
|
-
const json = await response.json();
|
|
81
|
-
const parsedJson = ApiResponseSchema(opts.responseDataSchema).parse(json);
|
|
82
|
-
if ("error" in parsedJson) {
|
|
83
|
-
throw new ApiClientError(parsedJson.error, parsedJson.statusCode, parsedJson.requestId);
|
|
84
|
-
} else if (!("data" in parsedJson)) {
|
|
85
|
-
throw new ApiClientError(
|
|
86
|
-
{
|
|
87
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
88
|
-
message: "Unexpected API response format",
|
|
89
|
-
details: 'Response did not contain expected "data" field'
|
|
478
|
+
const bodyPayload = parseRequestBody(opts.requestSchema, opts.body);
|
|
479
|
+
const urlString = new URL(this.config.baseUrl + path).toString();
|
|
480
|
+
let response;
|
|
481
|
+
try {
|
|
482
|
+
response = await this.config.fetch(urlString, {
|
|
483
|
+
method,
|
|
484
|
+
headers: {
|
|
485
|
+
"Content-Type": "application/json",
|
|
486
|
+
...this.config.apiKey ? { "x-api-key": this.config.apiKey } : { Authorization: `Bearer ${this.config.apiToken}` }
|
|
90
487
|
},
|
|
91
|
-
|
|
92
|
-
|
|
488
|
+
body: bodyPayload === void 0 ? void 0 : JSON.stringify(bodyPayload)
|
|
489
|
+
});
|
|
490
|
+
} catch (error) {
|
|
491
|
+
throw createTransportError(path, urlString, error);
|
|
492
|
+
}
|
|
493
|
+
const responseBody = await readApiResponseBody(path, urlString, response);
|
|
494
|
+
if (response.ok) {
|
|
495
|
+
return parseSuccessfulResponse(
|
|
496
|
+
path,
|
|
497
|
+
urlString,
|
|
498
|
+
response,
|
|
499
|
+
responseBody,
|
|
500
|
+
opts.responseDataSchema
|
|
93
501
|
);
|
|
94
502
|
}
|
|
95
|
-
|
|
503
|
+
throw createResponseError(path, urlString, response, responseBody);
|
|
96
504
|
}
|
|
97
505
|
/**
|
|
98
|
-
* GET convenience method inferring response type from schema
|
|
506
|
+
* GET convenience method inferring response type from schema.
|
|
99
507
|
*/
|
|
100
508
|
get(path, responseDataSchema) {
|
|
101
509
|
return this.request("GET", path, { responseDataSchema });
|
|
102
510
|
}
|
|
103
511
|
/**
|
|
104
|
-
* PUT convenience method inferring both request and response types
|
|
512
|
+
* PUT convenience method inferring both request and response types.
|
|
105
513
|
*/
|
|
106
514
|
put(path, body, requestSchema, responseDataSchema) {
|
|
107
515
|
return this.request("PUT", path, { body, requestSchema, responseDataSchema });
|
|
108
516
|
}
|
|
109
517
|
};
|
|
518
|
+
function trimTrailingSlashes(value) {
|
|
519
|
+
let end = value.length;
|
|
520
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) {
|
|
521
|
+
end -= 1;
|
|
522
|
+
}
|
|
523
|
+
return end === value.length ? value : value.slice(0, end);
|
|
524
|
+
}
|
|
110
525
|
|
|
111
526
|
// src/schemas/ftpink/page/index.ts
|
|
112
527
|
import { z as z3 } from "zod";
|
|
@@ -229,13 +644,20 @@ var HeroSlice = defineSliceSchema({
|
|
|
229
644
|
// Maximum number of stories to display in the hero
|
|
230
645
|
})
|
|
231
646
|
});
|
|
647
|
+
var StoryGroupSlice = defineSliceSchema({
|
|
648
|
+
typeName: "StoryGroup",
|
|
649
|
+
propsShape: BasePageItemProps.extend({
|
|
650
|
+
storySlots: z3.array(z3.string().uuid()).nonempty()
|
|
651
|
+
})
|
|
652
|
+
});
|
|
232
653
|
var SliceApiInputSchema = z3.discriminatedUnion("type", [
|
|
233
654
|
HomepageSlice.InputSchema,
|
|
234
655
|
InteractiveSlice.InputSchema,
|
|
235
656
|
ExperimentSlice.InputSchema,
|
|
236
657
|
StripSlice.InputSchema,
|
|
237
658
|
TopperSlice.InputSchema,
|
|
238
|
-
HeroSlice.InputSchema
|
|
659
|
+
HeroSlice.InputSchema,
|
|
660
|
+
StoryGroupSlice.InputSchema
|
|
239
661
|
]);
|
|
240
662
|
var SliceApiOutputSchema = z3.discriminatedUnion("type", [
|
|
241
663
|
HomepageSlice.OutputSchema,
|
|
@@ -243,7 +665,8 @@ var SliceApiOutputSchema = z3.discriminatedUnion("type", [
|
|
|
243
665
|
ExperimentSlice.OutputSchema,
|
|
244
666
|
StripSlice.OutputSchema,
|
|
245
667
|
TopperSlice.OutputSchema,
|
|
246
|
-
HeroSlice.OutputSchema
|
|
668
|
+
HeroSlice.OutputSchema,
|
|
669
|
+
StoryGroupSlice.OutputSchema
|
|
247
670
|
]);
|
|
248
671
|
var BasePagePropertiesSchema = z3.object({
|
|
249
672
|
title: z3.string(),
|
|
@@ -417,7 +840,7 @@ var PageClient = class extends BaseApiClient {
|
|
|
417
840
|
try {
|
|
418
841
|
return await this.get(`/v1/page/${pageId}/draft`, PageDraftSchema);
|
|
419
842
|
} catch (error) {
|
|
420
|
-
if (error instanceof
|
|
843
|
+
if (error instanceof ApiServiceError && error.code === "NOT_FOUND") {
|
|
421
844
|
return null;
|
|
422
845
|
}
|
|
423
846
|
throw error;
|
|
@@ -455,8 +878,11 @@ export {
|
|
|
455
878
|
ApiErrorCode,
|
|
456
879
|
ApiErrorPayloadSchema,
|
|
457
880
|
ApiErrorResponseSchema,
|
|
881
|
+
ApiGatewayError,
|
|
458
882
|
ApiResponseSchema,
|
|
883
|
+
ApiServiceError,
|
|
459
884
|
ApiSuccessResponseSchema,
|
|
885
|
+
ApiTransportError,
|
|
460
886
|
DraftContributorInputSchema,
|
|
461
887
|
DraftContributorSchema,
|
|
462
888
|
DraftSchema,
|