@fourtwelvelabs/fetch-contentful 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { TypedDocumentNode } from '@graphql-typed-document-node/core';
1
2
  import { DocumentNode } from 'graphql';
2
3
 
3
4
  /**
@@ -51,8 +52,13 @@ interface FetchContentfulOptions<TVariables extends GraphQLVariables = GraphQLVa
51
52
  * query fields (see {@link FetchContentfulOptions.autoInjectArgs}),
52
53
  * auto-fills a `$locale: String` variable when your query declares one,
53
54
  * and is applied to generated subqueries.
55
+ *
56
+ * Defaults to `"en-US"`. Pass `null` to send no `locale` at all, which
57
+ * lets Contentful serve whichever locale the space itself defaults to —
58
+ * useful for a space whose default locale is not `en-US`, and the
59
+ * behavior this option had before a default existed.
54
60
  */
55
- locale?: string;
61
+ locale?: string | null;
56
62
  /**
57
63
  * Automatically add `preview: true` / `locale: "..."` arguments to the
58
64
  * root fields of your query based on the `preview` and `locale` options,
@@ -85,7 +91,16 @@ interface FetchContentfulOptions<TVariables extends GraphQLVariables = GraphQLVa
85
91
  * Automatically split nested reference collections (any `*Collection`
86
92
  * field that is not a root query field) into their own subqueries to stay
87
93
  * under Contentful's query complexity limits. Defaults to `true`.
88
- * One-to-one references can be split by annotating them with `@split`.
94
+ *
95
+ * This gates **automatic detection only**. The `@split` directive is an
96
+ * independent, explicit trigger: it always splits the field it annotates —
97
+ * a nested collection just as much as a one-to-one reference — whatever
98
+ * this option is set to. Setting it to `false` and annotating only the
99
+ * fields that actually exceed the limit gives you a manual mode in which
100
+ * every other query is a single round trip.
101
+ *
102
+ * `@split` on a root field throws a `CONFIG` error: there is no parent
103
+ * entry to stitch the result back onto.
89
104
  */
90
105
  autoSplitNestedCollections?: boolean;
91
106
  /** How many parent entry ids to resolve per subquery request. Defaults to `50`. */
@@ -114,6 +129,51 @@ interface FetchContentfulOptions<TVariables extends GraphQLVariables = GraphQLVa
114
129
  /** Standard fetch cache mode, forwarded to fetch. */
115
130
  cache?: RequestCache;
116
131
  }
132
+ /**
133
+ * Variables {@link FetchContentfulOptions.autoInjectArgs} fills in on the
134
+ * caller's behalf, so a typed document that declares `$preview` / `$locale`
135
+ * never forces the caller to pass them.
136
+ */
137
+ type AutoInjectedVariable = 'preview' | 'locale';
138
+ /** The variables a caller still has to supply for a typed document. */
139
+ type CallerVariables<TVariables> = Omit<TVariables, AutoInjectedVariable>;
140
+ /** `true` when nothing is left for the caller to pass. */
141
+ type VariablesAreOptional<TVariables> = Record<string, never> extends CallerVariables<TVariables> ? true : false;
142
+ /**
143
+ * Makes `variables` required exactly when the document declares one the
144
+ * caller must provide, and optional otherwise (including when every
145
+ * remaining variable is nullable, or when the only ones left are
146
+ * auto-injected).
147
+ *
148
+ * The conditional deliberately sits in *key* position rather than wrapping
149
+ * the whole object. TypeScript skips excess-property checks when a
150
+ * parameter's type is a conditional over a type argument it is still
151
+ * inferring, so `{ variables: { slgu: '…' } }` would slip through a
152
+ * `… ? { variables: T } : { variables?: T }` formulation. Selecting the key
153
+ * instead keeps the property's type a plain `TVariables`, and typos are
154
+ * caught.
155
+ */
156
+ type VariablesOption<TVariables> = {
157
+ [K in VariablesAreOptional<TVariables> extends true ? never : 'variables']: TVariables;
158
+ } & {
159
+ [K in VariablesAreOptional<TVariables> extends true ? 'variables' : never]?: TVariables;
160
+ };
161
+ /**
162
+ * {@link FetchContentfulOptions} for a call that passes a typed document:
163
+ * identical, except `variables` is typed by — and required by — the
164
+ * document itself.
165
+ */
166
+ type TypedFetchContentfulOptions<TVariables> = Omit<FetchContentfulOptions<GraphQLVariables>, 'variables'> & VariablesOption<TVariables>;
167
+ /**
168
+ * The result type a document carries, falling back to the historical
169
+ * default for a plain, untyped `DocumentNode`.
170
+ *
171
+ * `TypedDocumentNode`'s type brand is an optional property, so an untyped
172
+ * `DocumentNode` structurally satisfies it and infers `unknown`. Mapping
173
+ * that back to `Record<string, unknown>` keeps pre-existing
174
+ * `fetchContentful(parse(query))` calls typed exactly as they were.
175
+ */
176
+ type DocumentResult<TResult> = unknown extends TResult ? Record<string, unknown> : TResult;
117
177
  /** A single locale as configured in Contentful. */
118
178
  interface ContentfulLocale {
119
179
  code: string;
@@ -278,18 +338,33 @@ declare function collectAtPath(data: unknown, path: string[]): Record<string, un
278
338
  * query has succeeded.
279
339
  * - Shapes the response so every `fooCollection.items` becomes `foo`
280
340
  * (disable with `shapeResponseData: false` to receive the raw wire shape).
341
+ *
342
+ * Pass a typed document — from gql.tada, graphql-codegen's client preset, or
343
+ * anything else producing a `TypedDocumentNode` — and both the result and
344
+ * the variables are inferred, with no type arguments to write by hand.
281
345
  */
282
- declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options: FetchContentfulOptions<TVariables> & {
346
+ declare function fetchContentful<TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, options: TypedFetchContentfulOptions<TVariables> & {
347
+ shapeResponseData: false;
348
+ unwrapRootField: false;
349
+ }): Promise<DocumentResult<TResult>>;
350
+ declare function fetchContentful<TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, options: TypedFetchContentfulOptions<TVariables> & {
351
+ shapeResponseData: false;
352
+ }): Promise<UnwrapSingleRoot<DocumentResult<TResult>>>;
353
+ declare function fetchContentful<TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, options: TypedFetchContentfulOptions<TVariables> & {
354
+ unwrapRootField: false;
355
+ }): Promise<ShapeCollections<DocumentResult<TResult>>>;
356
+ declare function fetchContentful<TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, ...args: Record<string, never> extends CallerVariables<TVariables> ? [options?: TypedFetchContentfulOptions<TVariables>] : [options: TypedFetchContentfulOptions<TVariables>]): Promise<UnwrapSingleRoot<ShapeCollections<DocumentResult<TResult>>>>;
357
+ declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options: FetchContentfulOptions<TVariables> & {
283
358
  shapeResponseData: false;
284
359
  unwrapRootField: false;
285
360
  }): Promise<TData>;
286
- declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options: FetchContentfulOptions<TVariables> & {
361
+ declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options: FetchContentfulOptions<TVariables> & {
287
362
  shapeResponseData: false;
288
363
  }): Promise<UnwrapSingleRoot<TData>>;
289
- declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options: FetchContentfulOptions<TVariables> & {
364
+ declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options: FetchContentfulOptions<TVariables> & {
290
365
  unwrapRootField: false;
291
366
  }): Promise<ShapeCollections<TData>>;
292
- declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options?: FetchContentfulOptions<TVariables>): Promise<UnwrapSingleRoot<ShapeCollections<TData>>>;
367
+ declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options?: FetchContentfulOptions<TVariables>): Promise<UnwrapSingleRoot<ShapeCollections<TData>>>;
293
368
  /**
294
369
  * Creates a `fetchContentful` bound to default options — the recommended way
295
370
  * to configure the utility once per project:
@@ -298,7 +373,7 @@ declare function fetchContentful<TData = Record<string, unknown>, TVariables ext
298
373
  * // lib/contentful.ts
299
374
  * export const fetchContentful = createFetchContentful({
300
375
  * space: 'abc123',
301
- * locale: 'en-US',
376
+ * locale: 'de-DE', // overrides the 'en-US' default for the whole project
302
377
  * retries: 3,
303
378
  * });
304
379
  * ```
@@ -308,17 +383,28 @@ declare function fetchContentful<TData = Record<string, unknown>, TVariables ext
308
383
  * the call itself, so prefer setting it per call.
309
384
  */
310
385
  declare function createFetchContentful<TDefaultVariables extends GraphQLVariables = GraphQLVariables>(defaults?: FetchContentfulOptions<TDefaultVariables>): {
311
- <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options: FetchContentfulOptions<TVariables> & {
386
+ <TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, options: TypedFetchContentfulOptions<TVariables> & {
387
+ shapeResponseData: false;
388
+ unwrapRootField: false;
389
+ }): Promise<DocumentResult<TResult>>;
390
+ <TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, options: TypedFetchContentfulOptions<TVariables> & {
391
+ shapeResponseData: false;
392
+ }): Promise<UnwrapSingleRoot<DocumentResult<TResult>>>;
393
+ <TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, options: TypedFetchContentfulOptions<TVariables> & {
394
+ unwrapRootField: false;
395
+ }): Promise<ShapeCollections<DocumentResult<TResult>>>;
396
+ <TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, ...args: Record<string, never> extends CallerVariables<TVariables> ? [options?: TypedFetchContentfulOptions<TVariables>] : [options: TypedFetchContentfulOptions<TVariables>]): Promise<UnwrapSingleRoot<ShapeCollections<DocumentResult<TResult>>>>;
397
+ <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options: FetchContentfulOptions<TVariables> & {
312
398
  shapeResponseData: false;
313
399
  unwrapRootField: false;
314
400
  }): Promise<TData>;
315
- <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options: FetchContentfulOptions<TVariables> & {
401
+ <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options: FetchContentfulOptions<TVariables> & {
316
402
  shapeResponseData: false;
317
403
  }): Promise<UnwrapSingleRoot<TData>>;
318
- <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options: FetchContentfulOptions<TVariables> & {
404
+ <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options: FetchContentfulOptions<TVariables> & {
319
405
  unwrapRootField: false;
320
406
  }): Promise<ShapeCollections<TData>>;
321
- <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options?: FetchContentfulOptions<TVariables>): Promise<UnwrapSingleRoot<ShapeCollections<TData>>>;
407
+ <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options?: FetchContentfulOptions<TVariables>): Promise<UnwrapSingleRoot<ShapeCollections<TData>>>;
322
408
  };
323
409
 
324
- export { type ContentfulGraphQLError, type ContentfulLocale, FetchContentfulError, type FetchContentfulErrorCode, type FetchContentfulOptions, type GraphQLVariables, type NextFetchOptions, type ShapeCollections, type UnwrapSingleRoot, clearLocaleCache, collectAtPath, createFetchContentful, fetchContentful as default, fetchContentful, getLocales, injectRootArgs, inlineFragments, isFetchContentfulError, readEnvSettings, shapeData, unwrapSingleRoot };
410
+ export { type AutoInjectedVariable, type CallerVariables, type ContentfulGraphQLError, type ContentfulLocale, type DocumentResult, FetchContentfulError, type FetchContentfulErrorCode, type FetchContentfulOptions, type GraphQLVariables, type NextFetchOptions, type ShapeCollections, type TypedFetchContentfulOptions, type UnwrapSingleRoot, type VariablesOption, clearLocaleCache, collectAtPath, createFetchContentful, fetchContentful as default, fetchContentful, getLocales, injectRootArgs, inlineFragments, isFetchContentfulError, readEnvSettings, shapeData, unwrapSingleRoot };
package/dist/index.mjs CHANGED
@@ -228,7 +228,15 @@ function planSplits(document, options) {
228
228
  );
229
229
  }
230
230
  const field = selection;
231
- const shouldSplit = depth > 0 && field.selectionSet !== void 0 && !hasDirective(field, NO_SPLIT_DIRECTIVE) && (hasDirective(field, SPLIT_DIRECTIVE) || options.autoSplitNestedCollections && isCollectionField(field));
231
+ const isExplicitSplit = hasDirective(field, SPLIT_DIRECTIVE);
232
+ const isAutoSplit = options.autoSplitNestedCollections && isCollectionField(field);
233
+ if (isExplicitSplit && depth === 0) {
234
+ throw new FetchContentfulError(
235
+ `Cannot split root field "${responseKeyOf(field)}": @split needs a parent entry to stitch the result back onto, and root fields have none. Move the directive to a nested field, or page through this field with its own \`limit\` and \`skip\` arguments.`,
236
+ { code: "CONFIG" }
237
+ );
238
+ }
239
+ const shouldSplit = depth > 0 && field.selectionSet !== void 0 && !hasDirective(field, NO_SPLIT_DIRECTIVE) && (isExplicitSplit || isAutoSplit);
232
240
  if (shouldSplit) {
233
241
  const planned = withDirective(
234
242
  withoutDirective(field, SPLIT_DIRECTIVE),
@@ -701,6 +709,7 @@ function unwrapSingleRoot(data) {
701
709
  }
702
710
 
703
711
  // src/index.ts
712
+ var DEFAULT_LOCALE = "en-US";
704
713
  var DEFAULT_RETRIES = 5;
705
714
  var DEFAULT_RETRY_DELAY_MS = 250;
706
715
  var DEFAULT_MAX_RETRY_DELAY_MS = 8e3;
@@ -760,7 +769,8 @@ function resolveContext(options) {
760
769
  environment,
761
770
  token,
762
771
  preview,
763
- locale: options.locale,
772
+ // `null` is the opt-out; `undefined` (or absent) takes the default.
773
+ locale: options.locale === null ? void 0 : options.locale ?? DEFAULT_LOCALE,
764
774
  fetch: fetchImpl,
765
775
  retry,
766
776
  autoSplitNestedCollections: options.autoSplitNestedCollections ?? true,
@@ -867,12 +877,15 @@ async function executeDocument(document, variables, context) {
867
877
  cleanupMarkers(data, plans);
868
878
  return data;
869
879
  }
880
+ function isDocumentNode(query) {
881
+ return typeof query === "object" && query !== null && query.kind === Kind.DOCUMENT && Array.isArray(query.definitions);
882
+ }
870
883
  function toDocument(query) {
871
- if (typeof query !== "string") {
884
+ if (isDocumentNode(query)) {
872
885
  return query;
873
886
  }
874
887
  try {
875
- return parse(query);
888
+ return parse(typeof query === "string" ? query : String(query));
876
889
  } catch (cause) {
877
890
  throw new FetchContentfulError(
878
891
  `Failed to parse GraphQL query: ${String(cause)}`,
@@ -895,7 +908,7 @@ function withAutoVariables(document, variables, context) {
895
908
  }
896
909
  return merged;
897
910
  }
898
- async function fetchContentful(query, options = {}) {
911
+ async function runFetchContentful(query, options) {
899
912
  const context = resolveContext(options);
900
913
  let document = inlineFragments(toDocument(query));
901
914
  if (options.autoInjectArgs ?? true) {
@@ -925,32 +938,15 @@ async function fetchContentful(query, options = {}) {
925
938
  }
926
939
  return unwrapSingleRoot(result);
927
940
  }
941
+ async function fetchContentful(query, options = {}) {
942
+ return runFetchContentful(query, options);
943
+ }
928
944
  function createFetchContentful(defaults = {}) {
929
945
  function bound(query, options = {}) {
930
- const merged = {
946
+ return runFetchContentful(query, {
931
947
  ...defaults,
932
948
  ...options
933
- };
934
- if (merged.shapeResponseData === false) {
935
- if (merged.unwrapRootField === false) {
936
- return fetchContentful(query, {
937
- ...merged,
938
- shapeResponseData: false,
939
- unwrapRootField: false
940
- });
941
- }
942
- return fetchContentful(query, {
943
- ...merged,
944
- shapeResponseData: false
945
- });
946
- }
947
- if (merged.unwrapRootField === false) {
948
- return fetchContentful(query, {
949
- ...merged,
950
- unwrapRootField: false
951
- });
952
- }
953
- return fetchContentful(query, merged);
949
+ });
954
950
  }
955
951
  return bound;
956
952
  }