@fourtwelvelabs/fetch-contentful 0.4.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +153 -0
- package/README.md +192 -57
- package/dist/cli/index.mjs +21 -72
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +309 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +254 -26
- package/dist/index.d.ts +254 -26
- package/dist/index.mjs +305 -140
- package/dist/index.mjs.map +1 -1
- package/docs/tada.md +31 -29
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -6,11 +6,112 @@ var graphql = require('graphql');
|
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
8
|
|
|
9
|
+
// src/annotate.ts
|
|
10
|
+
var ESC = String.fromCharCode(27);
|
|
11
|
+
var ANSI = {
|
|
12
|
+
reset: `${ESC}[0m`,
|
|
13
|
+
bold: `${ESC}[1m`,
|
|
14
|
+
dim: `${ESC}[2m`,
|
|
15
|
+
red: `${ESC}[31m`
|
|
16
|
+
};
|
|
17
|
+
var DEFAULT_CONTEXT_LINES = 3;
|
|
18
|
+
function supportsColor() {
|
|
19
|
+
if (typeof process === "undefined" || !process.env) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const forced = process.env.FORCE_COLOR;
|
|
23
|
+
const disabled = process.env.NO_COLOR;
|
|
24
|
+
if (forced) {
|
|
25
|
+
return forced !== "0" && forced !== "false";
|
|
26
|
+
}
|
|
27
|
+
if (disabled) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return Boolean(process.stderr?.isTTY);
|
|
31
|
+
}
|
|
32
|
+
function makePalette(enabled) {
|
|
33
|
+
const paint = (code) => (text) => enabled ? `${code}${text}${ANSI.reset}` : text;
|
|
34
|
+
return {
|
|
35
|
+
dim: paint(ANSI.dim),
|
|
36
|
+
red: paint(ANSI.red),
|
|
37
|
+
bold: paint(ANSI.bold)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function collectMarks(errors, lineCount) {
|
|
41
|
+
const marks = [];
|
|
42
|
+
const seen = /* @__PURE__ */ new Set();
|
|
43
|
+
for (const error of errors) {
|
|
44
|
+
for (const location of error.locations ?? []) {
|
|
45
|
+
const line = Math.trunc(location.line);
|
|
46
|
+
if (!(line >= 1 && line <= lineCount)) continue;
|
|
47
|
+
const reported = Math.trunc(location.column);
|
|
48
|
+
const column = reported >= 1 ? reported : 1;
|
|
49
|
+
const key = `${line}:${column}:${error.message}`;
|
|
50
|
+
if (seen.has(key)) continue;
|
|
51
|
+
seen.add(key);
|
|
52
|
+
marks.push({ line, column, message: error.message });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return marks.sort((a, b) => a.line - b.line || a.column - b.column);
|
|
56
|
+
}
|
|
57
|
+
function excerptsFor(marks, lineCount, contextLines) {
|
|
58
|
+
const excerpts = [];
|
|
59
|
+
for (const mark of marks) {
|
|
60
|
+
const start = Math.max(1, mark.line - contextLines);
|
|
61
|
+
const end = Math.min(lineCount, mark.line + contextLines);
|
|
62
|
+
const previous = excerpts[excerpts.length - 1];
|
|
63
|
+
if (previous && start <= previous.end + 1) {
|
|
64
|
+
previous.end = Math.max(previous.end, end);
|
|
65
|
+
} else {
|
|
66
|
+
excerpts.push({ start, end });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return excerpts;
|
|
70
|
+
}
|
|
71
|
+
function annotateQuery(query, errors, options = {}) {
|
|
72
|
+
const lines = query.split("\n");
|
|
73
|
+
const marks = collectMarks(errors, lines.length);
|
|
74
|
+
if (marks.length === 0) return "";
|
|
75
|
+
const color = makePalette(options.color ?? supportsColor());
|
|
76
|
+
const excerpts = excerptsFor(marks, lines.length, options.contextLines ?? DEFAULT_CONTEXT_LINES);
|
|
77
|
+
const gutter = String(
|
|
78
|
+
excerpts.reduce((widest, excerpt) => Math.max(widest, excerpt.end), 0)
|
|
79
|
+
).length;
|
|
80
|
+
const blank = " ".repeat(gutter);
|
|
81
|
+
const out = [];
|
|
82
|
+
for (const [index, excerpt] of excerpts.entries()) {
|
|
83
|
+
if (index > 0) out.push(color.dim(` ${blank} ...`));
|
|
84
|
+
const window = lines.slice(excerpt.start - 1, excerpt.end);
|
|
85
|
+
for (const [offset, source] of window.entries()) {
|
|
86
|
+
const line = excerpt.start + offset;
|
|
87
|
+
const number = String(line).padStart(gutter, " ");
|
|
88
|
+
const here = marks.filter((mark) => mark.line === line);
|
|
89
|
+
if (here.length === 0) {
|
|
90
|
+
out.push(`${color.dim(` ${number} |`)} ${source}`);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
out.push(`${color.red(`> ${number} |`)} ${color.bold(source)}`);
|
|
94
|
+
for (const mark of here) {
|
|
95
|
+
const lead = " ".repeat(mark.column - 1);
|
|
96
|
+
out.push(`${color.dim(` ${blank} |`)} ${lead}${color.red(`^ ${mark.message}`)}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return out.join("\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
9
103
|
// src/errors.ts
|
|
10
104
|
var FetchContentfulError = class extends Error {
|
|
11
105
|
code;
|
|
12
106
|
status;
|
|
13
107
|
errors;
|
|
108
|
+
/**
|
|
109
|
+
* The GraphQL query exactly as it was sent to Contentful — fragments
|
|
110
|
+
* inlined, arguments injected, and (for a subquery) generated. This is
|
|
111
|
+
* the text the `line`/`column` in {@link FetchContentfulError.errors}
|
|
112
|
+
* point into, so it is the text to annotate; see `annotateQuery`.
|
|
113
|
+
*/
|
|
114
|
+
query;
|
|
14
115
|
/** Internal: whether a retry may succeed. */
|
|
15
116
|
retryable;
|
|
16
117
|
/** Internal: server-requested retry delay (from Retry-After), in ms. */
|
|
@@ -21,6 +122,7 @@ var FetchContentfulError = class extends Error {
|
|
|
21
122
|
this.code = options.code;
|
|
22
123
|
this.status = options.status;
|
|
23
124
|
this.errors = options.errors;
|
|
125
|
+
this.query = options.query;
|
|
24
126
|
this.retryable = options.retryable ?? false;
|
|
25
127
|
this.retryAfterMs = options.retryAfterMs;
|
|
26
128
|
}
|
|
@@ -136,16 +238,14 @@ function getOperation(document) {
|
|
|
136
238
|
);
|
|
137
239
|
const operation = operations[0];
|
|
138
240
|
if (!operation || operations.length > 1) {
|
|
139
|
-
throw new FetchContentfulError(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
);
|
|
241
|
+
throw new FetchContentfulError("fetch-contentful expects exactly one operation per document.", {
|
|
242
|
+
code: "CONFIG"
|
|
243
|
+
});
|
|
143
244
|
}
|
|
144
245
|
if (operation.operation !== "query") {
|
|
145
|
-
throw new FetchContentfulError(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
);
|
|
246
|
+
throw new FetchContentfulError("fetch-contentful only supports query operations.", {
|
|
247
|
+
code: "CONFIG"
|
|
248
|
+
});
|
|
149
249
|
}
|
|
150
250
|
return operation;
|
|
151
251
|
}
|
|
@@ -157,41 +257,34 @@ function inlineFragments(document) {
|
|
|
157
257
|
}
|
|
158
258
|
}
|
|
159
259
|
function inlineSelectionSet(selectionSet, stack) {
|
|
160
|
-
const selections = selectionSet.selections.map(
|
|
161
|
-
(selection)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
{ code: "CONFIG" }
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
if (stack.includes(name)) {
|
|
172
|
-
throw new FetchContentfulError(
|
|
173
|
-
`Fragment cycle detected involving "${name}".`,
|
|
174
|
-
{ code: "CONFIG" }
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
return {
|
|
178
|
-
kind: graphql.Kind.INLINE_FRAGMENT,
|
|
179
|
-
typeCondition: fragment.typeCondition,
|
|
180
|
-
selectionSet: inlineSelectionSet(fragment.selectionSet, [
|
|
181
|
-
...stack,
|
|
182
|
-
name
|
|
183
|
-
])
|
|
184
|
-
};
|
|
260
|
+
const selections = selectionSet.selections.map((selection) => {
|
|
261
|
+
if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
|
|
262
|
+
const name = selection.name.value;
|
|
263
|
+
const fragment = fragments.get(name);
|
|
264
|
+
if (!fragment) {
|
|
265
|
+
throw new FetchContentfulError(`Unknown fragment "${name}" referenced in query.`, {
|
|
266
|
+
code: "CONFIG"
|
|
267
|
+
});
|
|
185
268
|
}
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
};
|
|
269
|
+
if (stack.includes(name)) {
|
|
270
|
+
throw new FetchContentfulError(`Fragment cycle detected involving "${name}".`, {
|
|
271
|
+
code: "CONFIG"
|
|
272
|
+
});
|
|
191
273
|
}
|
|
192
|
-
return
|
|
274
|
+
return {
|
|
275
|
+
kind: graphql.Kind.INLINE_FRAGMENT,
|
|
276
|
+
typeCondition: fragment.typeCondition,
|
|
277
|
+
selectionSet: inlineSelectionSet(fragment.selectionSet, [...stack, name])
|
|
278
|
+
};
|
|
193
279
|
}
|
|
194
|
-
|
|
280
|
+
if (selection.selectionSet) {
|
|
281
|
+
return {
|
|
282
|
+
...selection,
|
|
283
|
+
selectionSet: inlineSelectionSet(selection.selectionSet, stack)
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return selection;
|
|
287
|
+
});
|
|
195
288
|
return { ...selectionSet, selections };
|
|
196
289
|
}
|
|
197
290
|
const definitions = document.definitions.filter((definition) => definition.kind !== graphql.Kind.FRAGMENT_DEFINITION).map(
|
|
@@ -226,10 +319,9 @@ function planSplits(document, options) {
|
|
|
226
319
|
continue;
|
|
227
320
|
}
|
|
228
321
|
if (selection.kind !== graphql.Kind.FIELD) {
|
|
229
|
-
throw new FetchContentfulError(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
);
|
|
322
|
+
throw new FetchContentfulError("Fragment spreads must be inlined before split planning.", {
|
|
323
|
+
code: "CONFIG"
|
|
324
|
+
});
|
|
233
325
|
}
|
|
234
326
|
const field = selection;
|
|
235
327
|
const isExplicitSplit = hasDirective(field, SPLIT_DIRECTIVE);
|
|
@@ -242,10 +334,7 @@ function planSplits(document, options) {
|
|
|
242
334
|
}
|
|
243
335
|
const shouldSplit = depth > 0 && field.selectionSet !== void 0 && !hasDirective(field, NO_SPLIT_DIRECTIVE) && (isExplicitSplit || isAutoSplit);
|
|
244
336
|
if (shouldSplit) {
|
|
245
|
-
const planned = withDirective(
|
|
246
|
-
withoutDirective(field, SPLIT_DIRECTIVE),
|
|
247
|
-
NO_SPLIT_DIRECTIVE
|
|
248
|
-
);
|
|
337
|
+
const planned = withDirective(withoutDirective(field, SPLIT_DIRECTIVE), NO_SPLIT_DIRECTIVE);
|
|
249
338
|
plans.push({
|
|
250
339
|
path: [...path],
|
|
251
340
|
responseKey: responseKeyOf(field),
|
|
@@ -344,10 +433,7 @@ function whereIdInArgument() {
|
|
|
344
433
|
}
|
|
345
434
|
function buildSubquery(plan, typename, batchSize, context) {
|
|
346
435
|
const rootKey = `${lowerFirst(typename)}${COLLECTION_SUFFIX}`;
|
|
347
|
-
const args = [
|
|
348
|
-
whereIdInArgument(),
|
|
349
|
-
intArgument("limit", batchSize)
|
|
350
|
-
];
|
|
436
|
+
const args = [whereIdInArgument(), intArgument("limit", batchSize)];
|
|
351
437
|
if (context.preview) {
|
|
352
438
|
args.push(booleanArgument("preview", true));
|
|
353
439
|
}
|
|
@@ -356,10 +442,7 @@ function buildSubquery(plan, typename, batchSize, context) {
|
|
|
356
442
|
}
|
|
357
443
|
const rootField = {
|
|
358
444
|
...namedField(rootKey, void 0, [
|
|
359
|
-
namedField("items", void 0, [
|
|
360
|
-
namedField("sys", void 0, [namedField("id")]),
|
|
361
|
-
plan.field
|
|
362
|
-
])
|
|
445
|
+
namedField("items", void 0, [namedField("sys", void 0, [namedField("id")]), plan.field])
|
|
363
446
|
]),
|
|
364
447
|
arguments: args
|
|
365
448
|
};
|
|
@@ -409,6 +492,58 @@ function stripInternalDirectives(document) {
|
|
|
409
492
|
});
|
|
410
493
|
}
|
|
411
494
|
|
|
495
|
+
// src/unresolvable.ts
|
|
496
|
+
var UNRESOLVABLE_LINK_CODE = "UNRESOLVABLE_LINK";
|
|
497
|
+
var COLLECTION_SUFFIX2 = "Collection";
|
|
498
|
+
function isRecord(value) {
|
|
499
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
500
|
+
}
|
|
501
|
+
function isCollectionValue(value) {
|
|
502
|
+
return isRecord(value) && Array.isArray(value["items"]);
|
|
503
|
+
}
|
|
504
|
+
function isCollectionKey(key) {
|
|
505
|
+
return key.endsWith(COLLECTION_SUFFIX2) && key !== COLLECTION_SUFFIX2;
|
|
506
|
+
}
|
|
507
|
+
function isUnresolvableLinkError(error) {
|
|
508
|
+
const contentful = error.extensions?.["contentful"];
|
|
509
|
+
return isRecord(contentful) && contentful["code"] === UNRESOLVABLE_LINK_CODE;
|
|
510
|
+
}
|
|
511
|
+
function partitionUnresolvableLinks(errors) {
|
|
512
|
+
const unresolvable = [];
|
|
513
|
+
const fatal = [];
|
|
514
|
+
for (const error of errors) {
|
|
515
|
+
if (isUnresolvableLinkError(error)) {
|
|
516
|
+
unresolvable.push(error);
|
|
517
|
+
} else {
|
|
518
|
+
fatal.push(error);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return { unresolvable, fatal };
|
|
522
|
+
}
|
|
523
|
+
function walk(value) {
|
|
524
|
+
if (Array.isArray(value)) {
|
|
525
|
+
return value.map(walk);
|
|
526
|
+
}
|
|
527
|
+
if (isRecord(value)) {
|
|
528
|
+
const result = {};
|
|
529
|
+
for (const [key, child] of Object.entries(value)) {
|
|
530
|
+
result[key] = isCollectionKey(key) && isCollectionValue(child) ? stripCollection(child) : walk(child);
|
|
531
|
+
}
|
|
532
|
+
return result;
|
|
533
|
+
}
|
|
534
|
+
return value;
|
|
535
|
+
}
|
|
536
|
+
function stripCollection(collection) {
|
|
537
|
+
const result = {};
|
|
538
|
+
for (const [key, value] of Object.entries(collection)) {
|
|
539
|
+
result[key] = key === "items" ? value.filter((item) => item !== null && item !== void 0).map(walk) : walk(value);
|
|
540
|
+
}
|
|
541
|
+
return result;
|
|
542
|
+
}
|
|
543
|
+
function omitUnresolvedLinks(data) {
|
|
544
|
+
return walk(data);
|
|
545
|
+
}
|
|
546
|
+
|
|
412
547
|
// src/client.ts
|
|
413
548
|
function graphqlEndpoint(space, environment) {
|
|
414
549
|
return `https://graphql.contentful.com/content/v1/spaces/${encodeURIComponent(
|
|
@@ -444,6 +579,35 @@ function pickDeclaredVariables(document, variables) {
|
|
|
444
579
|
function isRetryableStatus(status) {
|
|
445
580
|
return status === 408 || status === 429 || status >= 500;
|
|
446
581
|
}
|
|
582
|
+
function isRecord2(value) {
|
|
583
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
584
|
+
}
|
|
585
|
+
function reportUnresolvableLinks(errors, context) {
|
|
586
|
+
if (!context.onUnresolvableLink) return;
|
|
587
|
+
try {
|
|
588
|
+
context.onUnresolvableLink(errors);
|
|
589
|
+
} catch {
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async function readGraphQLErrors(response) {
|
|
593
|
+
try {
|
|
594
|
+
const payload = await response.json();
|
|
595
|
+
const errors = payload?.errors;
|
|
596
|
+
return Array.isArray(errors) && errors.length > 0 ? errors : void 0;
|
|
597
|
+
} catch {
|
|
598
|
+
return void 0;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function describeFailure(summary, errors, query, context) {
|
|
602
|
+
if (!errors || errors.length === 0) return summary;
|
|
603
|
+
const message = `${summary} ${errors.map((error) => error.message).join("; ")}`;
|
|
604
|
+
if (context.annotateQueryOnError === false) return message;
|
|
605
|
+
const annotated = annotateQuery(query, errors);
|
|
606
|
+
return annotated ? `${message}
|
|
607
|
+
|
|
608
|
+
${annotated}
|
|
609
|
+
` : message;
|
|
610
|
+
}
|
|
447
611
|
async function rawRequest(document, variables, context) {
|
|
448
612
|
const query = graphql.print(stripInternalDirectives(document));
|
|
449
613
|
const body = JSON.stringify({
|
|
@@ -469,16 +633,24 @@ async function rawRequest(document, variables, context) {
|
|
|
469
633
|
} catch (cause) {
|
|
470
634
|
throw new FetchContentfulError(
|
|
471
635
|
`Network error while contacting Contentful: ${String(cause)}`,
|
|
472
|
-
{ code: "NETWORK", retryable: true, cause }
|
|
636
|
+
{ code: "NETWORK", query, retryable: true, cause }
|
|
473
637
|
);
|
|
474
638
|
}
|
|
475
639
|
if (!response.ok) {
|
|
476
640
|
const retryable = isRetryableStatus(response.status);
|
|
641
|
+
const errors = retryable ? void 0 : await readGraphQLErrors(response);
|
|
477
642
|
throw new FetchContentfulError(
|
|
478
|
-
|
|
643
|
+
describeFailure(
|
|
644
|
+
`Contentful responded with HTTP ${response.status}.`,
|
|
645
|
+
errors,
|
|
646
|
+
query,
|
|
647
|
+
context
|
|
648
|
+
),
|
|
479
649
|
{
|
|
480
650
|
code: "HTTP",
|
|
481
651
|
status: response.status,
|
|
652
|
+
errors,
|
|
653
|
+
query,
|
|
482
654
|
retryable,
|
|
483
655
|
retryAfterMs: retryable ? parseRetryAfter(response.headers.get("Retry-After")) : void 0
|
|
484
656
|
}
|
|
@@ -488,22 +660,29 @@ async function rawRequest(document, variables, context) {
|
|
|
488
660
|
try {
|
|
489
661
|
payload = await response.json();
|
|
490
662
|
} catch (cause) {
|
|
491
|
-
throw new FetchContentfulError(
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
663
|
+
throw new FetchContentfulError("Contentful returned an unreadable response body.", {
|
|
664
|
+
code: "NETWORK",
|
|
665
|
+
query,
|
|
666
|
+
retryable: true,
|
|
667
|
+
cause
|
|
668
|
+
});
|
|
495
669
|
}
|
|
496
670
|
if (payload.errors && payload.errors.length > 0) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
671
|
+
const { unresolvable, fatal } = partitionUnresolvableLinks(payload.errors);
|
|
672
|
+
const tolerable = context.unresolvableLinks !== "error" && fatal.length === 0 && unresolvable.length > 0 && isRecord2(payload.data);
|
|
673
|
+
if (!tolerable) {
|
|
674
|
+
throw new FetchContentfulError(
|
|
675
|
+
describeFailure("Contentful returned GraphQL errors:", payload.errors, query, context),
|
|
676
|
+
{ code: "GRAPHQL", errors: payload.errors, query }
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
reportUnresolvableLinks(unresolvable, context);
|
|
501
680
|
}
|
|
502
681
|
if (!payload.data || typeof payload.data !== "object") {
|
|
503
|
-
throw new FetchContentfulError(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
);
|
|
682
|
+
throw new FetchContentfulError("Contentful returned no data and no errors.", {
|
|
683
|
+
code: "GRAPHQL",
|
|
684
|
+
query
|
|
685
|
+
});
|
|
507
686
|
}
|
|
508
687
|
return payload.data;
|
|
509
688
|
}, context.retry);
|
|
@@ -519,7 +698,6 @@ function readEnvSettings() {
|
|
|
519
698
|
space: void 0,
|
|
520
699
|
environment: void 0,
|
|
521
700
|
deliveryToken: void 0,
|
|
522
|
-
token: void 0,
|
|
523
701
|
previewToken: void 0
|
|
524
702
|
};
|
|
525
703
|
}
|
|
@@ -534,7 +712,6 @@ function readEnvSettings() {
|
|
|
534
712
|
process.env.CONTENTFUL_ENVIRONMENT || process.env.NEXT_PUBLIC_CONTENTFUL_ENVIRONMENT
|
|
535
713
|
),
|
|
536
714
|
deliveryToken,
|
|
537
|
-
token: deliveryToken,
|
|
538
715
|
// No `NEXT_PUBLIC_` fallback: see the note at the top of this file.
|
|
539
716
|
previewToken: orUndefined(process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN)
|
|
540
717
|
};
|
|
@@ -576,20 +753,18 @@ function injectIntoField(field, args) {
|
|
|
576
753
|
return { ...field, arguments: [...argumentsOf(field), ...additions] };
|
|
577
754
|
}
|
|
578
755
|
function injectIntoSelectionSet(selectionSet, args) {
|
|
579
|
-
const selections = selectionSet.selections.map(
|
|
580
|
-
(selection)
|
|
581
|
-
|
|
582
|
-
return injectIntoField(selection, args);
|
|
583
|
-
}
|
|
584
|
-
if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
|
|
585
|
-
return {
|
|
586
|
-
...selection,
|
|
587
|
-
selectionSet: injectIntoSelectionSet(selection.selectionSet, args)
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
return selection;
|
|
756
|
+
const selections = selectionSet.selections.map((selection) => {
|
|
757
|
+
if (selection.kind === graphql.Kind.FIELD) {
|
|
758
|
+
return injectIntoField(selection, args);
|
|
591
759
|
}
|
|
592
|
-
|
|
760
|
+
if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
|
|
761
|
+
return {
|
|
762
|
+
...selection,
|
|
763
|
+
selectionSet: injectIntoSelectionSet(selection.selectionSet, args)
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
return selection;
|
|
767
|
+
});
|
|
593
768
|
return { ...selectionSet, selections };
|
|
594
769
|
}
|
|
595
770
|
function injectRootArgs(document, args) {
|
|
@@ -630,27 +805,24 @@ async function fetchLocales(context) {
|
|
|
630
805
|
if (context.signal) init.signal = context.signal;
|
|
631
806
|
response = await context.fetch(localesEndpoint(context), init);
|
|
632
807
|
} catch (cause) {
|
|
633
|
-
throw new FetchContentfulError(
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
808
|
+
throw new FetchContentfulError(`Network error while fetching locales: ${String(cause)}`, {
|
|
809
|
+
code: "NETWORK",
|
|
810
|
+
retryable: true,
|
|
811
|
+
cause
|
|
812
|
+
});
|
|
637
813
|
}
|
|
638
814
|
if (!response.ok) {
|
|
639
|
-
throw new FetchContentfulError(
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
retryable: response.status === 429 || response.status >= 500
|
|
645
|
-
}
|
|
646
|
-
);
|
|
815
|
+
throw new FetchContentfulError(`Locale request failed with HTTP ${response.status}.`, {
|
|
816
|
+
code: "HTTP",
|
|
817
|
+
status: response.status,
|
|
818
|
+
retryable: response.status === 429 || response.status >= 500
|
|
819
|
+
});
|
|
647
820
|
}
|
|
648
821
|
const payload = await response.json();
|
|
649
822
|
if (!Array.isArray(payload.items)) {
|
|
650
|
-
throw new FetchContentfulError(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
);
|
|
823
|
+
throw new FetchContentfulError("Locale response did not include an items array.", {
|
|
824
|
+
code: "NETWORK"
|
|
825
|
+
});
|
|
654
826
|
}
|
|
655
827
|
return payload.items.map((item) => ({
|
|
656
828
|
code: item.code,
|
|
@@ -678,24 +850,22 @@ function clearLocaleCache() {
|
|
|
678
850
|
}
|
|
679
851
|
|
|
680
852
|
// src/shape.ts
|
|
681
|
-
var
|
|
682
|
-
function
|
|
853
|
+
var COLLECTION_SUFFIX3 = "Collection";
|
|
854
|
+
function isRecord3(value) {
|
|
683
855
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
684
856
|
}
|
|
685
|
-
function
|
|
686
|
-
return
|
|
857
|
+
function isCollectionValue2(value) {
|
|
858
|
+
return isRecord3(value) && Array.isArray(value["items"]);
|
|
687
859
|
}
|
|
688
860
|
function shapeData(data) {
|
|
689
861
|
if (Array.isArray(data)) {
|
|
690
862
|
return data.map((item) => shapeData(item));
|
|
691
863
|
}
|
|
692
|
-
if (
|
|
864
|
+
if (isRecord3(data)) {
|
|
693
865
|
const shaped = {};
|
|
694
866
|
for (const [key, value] of Object.entries(data)) {
|
|
695
|
-
if (key.endsWith(
|
|
696
|
-
shaped[key.slice(0, -
|
|
697
|
-
value.items
|
|
698
|
-
);
|
|
867
|
+
if (key.endsWith(COLLECTION_SUFFIX3) && key !== COLLECTION_SUFFIX3 && isCollectionValue2(value)) {
|
|
868
|
+
shaped[key.slice(0, -COLLECTION_SUFFIX3.length)] = shapeData(value.items);
|
|
699
869
|
} else {
|
|
700
870
|
shaped[key] = shapeData(value);
|
|
701
871
|
}
|
|
@@ -705,7 +875,7 @@ function shapeData(data) {
|
|
|
705
875
|
return data;
|
|
706
876
|
}
|
|
707
877
|
function unwrapSingleRoot(data) {
|
|
708
|
-
if (
|
|
878
|
+
if (isRecord3(data)) {
|
|
709
879
|
const keys = Object.keys(data);
|
|
710
880
|
if (keys.length === 1) {
|
|
711
881
|
return data[keys[0]];
|
|
@@ -720,7 +890,7 @@ var DEFAULT_RETRIES = 5;
|
|
|
720
890
|
var DEFAULT_RETRY_DELAY_MS = 250;
|
|
721
891
|
var DEFAULT_MAX_RETRY_DELAY_MS = 8e3;
|
|
722
892
|
var DEFAULT_SPLIT_BATCH_SIZE = 50;
|
|
723
|
-
function
|
|
893
|
+
function isRecord4(value) {
|
|
724
894
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
725
895
|
}
|
|
726
896
|
function collectAtPath(data, path) {
|
|
@@ -728,7 +898,7 @@ function collectAtPath(data, path) {
|
|
|
728
898
|
for (const segment of path) {
|
|
729
899
|
const next = [];
|
|
730
900
|
for (const value of current) {
|
|
731
|
-
if (
|
|
901
|
+
if (isRecord4(value)) {
|
|
732
902
|
const child = value[segment];
|
|
733
903
|
if (Array.isArray(child)) {
|
|
734
904
|
next.push(...child);
|
|
@@ -739,14 +909,14 @@ function collectAtPath(data, path) {
|
|
|
739
909
|
}
|
|
740
910
|
current = next;
|
|
741
911
|
}
|
|
742
|
-
return current.filter(
|
|
912
|
+
return current.filter(isRecord4);
|
|
743
913
|
}
|
|
744
914
|
function resolveContext(options) {
|
|
745
915
|
const env = readEnvSettings();
|
|
746
916
|
const space = options.space ?? env.space;
|
|
747
917
|
const environment = options.environment ?? env.environment ?? "master";
|
|
748
918
|
const preview = options.preview ?? false;
|
|
749
|
-
const token = preview ? options.previewToken ??
|
|
919
|
+
const token = preview ? options.previewToken ?? env.previewToken : options.deliveryToken ?? env.deliveryToken;
|
|
750
920
|
if (!space || !token) {
|
|
751
921
|
const missing = [];
|
|
752
922
|
if (!space) {
|
|
@@ -780,8 +950,13 @@ function resolveContext(options) {
|
|
|
780
950
|
fetch: fetchImpl,
|
|
781
951
|
retry,
|
|
782
952
|
autoSplitNestedCollections: options.autoSplitNestedCollections ?? true,
|
|
783
|
-
splitBatchSize: options.splitBatchSize ?? DEFAULT_SPLIT_BATCH_SIZE
|
|
953
|
+
splitBatchSize: options.splitBatchSize ?? DEFAULT_SPLIT_BATCH_SIZE,
|
|
954
|
+
annotateQueryOnError: options.annotateQueryOnError ?? true,
|
|
955
|
+
unresolvableLinks: options.unresolvableLinks ?? "omit"
|
|
784
956
|
};
|
|
957
|
+
if (options.onUnresolvableLink) {
|
|
958
|
+
context.onUnresolvableLink = options.onUnresolvableLink;
|
|
959
|
+
}
|
|
785
960
|
if (options.signal) context.signal = options.signal;
|
|
786
961
|
if (options.next) context.next = options.next;
|
|
787
962
|
if (options.cache) context.cache = options.cache;
|
|
@@ -799,7 +974,7 @@ async function resolvePlan(plan, data, variables, context) {
|
|
|
799
974
|
for (const parent of relevant) {
|
|
800
975
|
const typename = parent[TYPENAME_ALIAS];
|
|
801
976
|
const sys = parent[SYS_ID_ALIAS];
|
|
802
|
-
if (!
|
|
977
|
+
if (!isRecord4(sys) || typeof sys["id"] !== "string") {
|
|
803
978
|
throw new FetchContentfulError(
|
|
804
979
|
`Cannot split "${plan.responseKey}": a parent entry is missing its sys id.`,
|
|
805
980
|
{ code: "STITCH" }
|
|
@@ -822,19 +997,14 @@ async function resolvePlan(plan, data, variables, context) {
|
|
|
822
997
|
];
|
|
823
998
|
await Promise.all(
|
|
824
999
|
chunk(ids, context.splitBatchSize).map(async (idBatch) => {
|
|
825
|
-
const { document, rootKey } = buildSubquery(
|
|
826
|
-
plan,
|
|
827
|
-
typename,
|
|
828
|
-
idBatch.length,
|
|
829
|
-
context
|
|
830
|
-
);
|
|
1000
|
+
const { document, rootKey } = buildSubquery(plan, typename, idBatch.length, context);
|
|
831
1001
|
const subData = await executeDocument(
|
|
832
1002
|
document,
|
|
833
1003
|
{ ...variables, _splitIds: idBatch },
|
|
834
1004
|
context
|
|
835
1005
|
);
|
|
836
1006
|
const collection = subData[rootKey];
|
|
837
|
-
const items =
|
|
1007
|
+
const items = isRecord4(collection) && Array.isArray(collection["items"]) ? collection["items"] : void 0;
|
|
838
1008
|
if (!items) {
|
|
839
1009
|
throw new FetchContentfulError(
|
|
840
1010
|
`Split subquery for "${typename}" returned no "${rootKey}.items". Check that the content type follows Contentful naming conventions.`,
|
|
@@ -842,9 +1012,9 @@ async function resolvePlan(plan, data, variables, context) {
|
|
|
842
1012
|
);
|
|
843
1013
|
}
|
|
844
1014
|
for (const item of items) {
|
|
845
|
-
if (
|
|
1015
|
+
if (isRecord4(item)) {
|
|
846
1016
|
const sys = item["sys"];
|
|
847
|
-
if (
|
|
1017
|
+
if (isRecord4(sys) && typeof sys["id"] === "string") {
|
|
848
1018
|
resolved.set(`${typename}:${sys["id"]}`, item[plan.responseKey]);
|
|
849
1019
|
}
|
|
850
1020
|
}
|
|
@@ -877,9 +1047,7 @@ function cleanupMarkers(data, plans) {
|
|
|
877
1047
|
async function executeDocument(document, variables, context) {
|
|
878
1048
|
const { document: outer, plans } = planSplits(document, context);
|
|
879
1049
|
const data = await rawRequest(outer, variables, context);
|
|
880
|
-
await Promise.all(
|
|
881
|
-
plans.map((plan) => resolvePlan(plan, data, variables, context))
|
|
882
|
-
);
|
|
1050
|
+
await Promise.all(plans.map((plan) => resolvePlan(plan, data, variables, context)));
|
|
883
1051
|
cleanupMarkers(data, plans);
|
|
884
1052
|
return data;
|
|
885
1053
|
}
|
|
@@ -893,10 +1061,10 @@ function toDocument(query) {
|
|
|
893
1061
|
try {
|
|
894
1062
|
return graphql.parse(typeof query === "string" ? query : String(query));
|
|
895
1063
|
} catch (cause) {
|
|
896
|
-
throw new FetchContentfulError(
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
);
|
|
1064
|
+
throw new FetchContentfulError(`Failed to parse GraphQL query: ${String(cause)}`, {
|
|
1065
|
+
code: "CONFIG",
|
|
1066
|
+
cause
|
|
1067
|
+
});
|
|
900
1068
|
}
|
|
901
1069
|
}
|
|
902
1070
|
function withAutoVariables(document, variables, context) {
|
|
@@ -932,13 +1100,10 @@ async function runFetchContentful(query, options) {
|
|
|
932
1100
|
);
|
|
933
1101
|
}
|
|
934
1102
|
}
|
|
935
|
-
const variables = withAutoVariables(
|
|
936
|
-
document,
|
|
937
|
-
options.variables ?? {},
|
|
938
|
-
context
|
|
939
|
-
);
|
|
1103
|
+
const variables = withAutoVariables(document, options.variables ?? {}, context);
|
|
940
1104
|
const data = await executeDocument(document, variables, context);
|
|
941
|
-
const
|
|
1105
|
+
const resolved = context.unresolvableLinks === "null" ? data : omitUnresolvedLinks(data);
|
|
1106
|
+
const result = options.shapeResponseData === false ? resolved : shapeData(resolved);
|
|
942
1107
|
if (options.unwrapRootField === false) {
|
|
943
1108
|
return result;
|
|
944
1109
|
}
|
|
@@ -959,6 +1124,8 @@ function createFetchContentful(defaults = {}) {
|
|
|
959
1124
|
var index_default = fetchContentful;
|
|
960
1125
|
|
|
961
1126
|
exports.FetchContentfulError = FetchContentfulError;
|
|
1127
|
+
exports.UNRESOLVABLE_LINK_CODE = UNRESOLVABLE_LINK_CODE;
|
|
1128
|
+
exports.annotateQuery = annotateQuery;
|
|
962
1129
|
exports.clearLocaleCache = clearLocaleCache;
|
|
963
1130
|
exports.collectAtPath = collectAtPath;
|
|
964
1131
|
exports.createFetchContentful = createFetchContentful;
|
|
@@ -968,6 +1135,9 @@ exports.getLocales = getLocales;
|
|
|
968
1135
|
exports.injectRootArgs = injectRootArgs;
|
|
969
1136
|
exports.inlineFragments = inlineFragments;
|
|
970
1137
|
exports.isFetchContentfulError = isFetchContentfulError;
|
|
1138
|
+
exports.isUnresolvableLinkError = isUnresolvableLinkError;
|
|
1139
|
+
exports.omitUnresolvedLinks = omitUnresolvedLinks;
|
|
1140
|
+
exports.partitionUnresolvableLinks = partitionUnresolvableLinks;
|
|
971
1141
|
exports.readEnvSettings = readEnvSettings;
|
|
972
1142
|
exports.shapeData = shapeData;
|
|
973
1143
|
exports.unwrapSingleRoot = unwrapSingleRoot;
|