@fourtwelvelabs/fetch-contentful 0.1.0 → 0.2.1

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
  /**
@@ -85,7 +86,16 @@ interface FetchContentfulOptions<TVariables extends GraphQLVariables = GraphQLVa
85
86
  * Automatically split nested reference collections (any `*Collection`
86
87
  * field that is not a root query field) into their own subqueries to stay
87
88
  * under Contentful's query complexity limits. Defaults to `true`.
88
- * One-to-one references can be split by annotating them with `@split`.
89
+ *
90
+ * This gates **automatic detection only**. The `@split` directive is an
91
+ * independent, explicit trigger: it always splits the field it annotates —
92
+ * a nested collection just as much as a one-to-one reference — whatever
93
+ * this option is set to. Setting it to `false` and annotating only the
94
+ * fields that actually exceed the limit gives you a manual mode in which
95
+ * every other query is a single round trip.
96
+ *
97
+ * `@split` on a root field throws a `CONFIG` error: there is no parent
98
+ * entry to stitch the result back onto.
89
99
  */
90
100
  autoSplitNestedCollections?: boolean;
91
101
  /** How many parent entry ids to resolve per subquery request. Defaults to `50`. */
@@ -114,6 +124,51 @@ interface FetchContentfulOptions<TVariables extends GraphQLVariables = GraphQLVa
114
124
  /** Standard fetch cache mode, forwarded to fetch. */
115
125
  cache?: RequestCache;
116
126
  }
127
+ /**
128
+ * Variables {@link FetchContentfulOptions.autoInjectArgs} fills in on the
129
+ * caller's behalf, so a typed document that declares `$preview` / `$locale`
130
+ * never forces the caller to pass them.
131
+ */
132
+ type AutoInjectedVariable = 'preview' | 'locale';
133
+ /** The variables a caller still has to supply for a typed document. */
134
+ type CallerVariables<TVariables> = Omit<TVariables, AutoInjectedVariable>;
135
+ /** `true` when nothing is left for the caller to pass. */
136
+ type VariablesAreOptional<TVariables> = Record<string, never> extends CallerVariables<TVariables> ? true : false;
137
+ /**
138
+ * Makes `variables` required exactly when the document declares one the
139
+ * caller must provide, and optional otherwise (including when every
140
+ * remaining variable is nullable, or when the only ones left are
141
+ * auto-injected).
142
+ *
143
+ * The conditional deliberately sits in *key* position rather than wrapping
144
+ * the whole object. TypeScript skips excess-property checks when a
145
+ * parameter's type is a conditional over a type argument it is still
146
+ * inferring, so `{ variables: { slgu: '…' } }` would slip through a
147
+ * `… ? { variables: T } : { variables?: T }` formulation. Selecting the key
148
+ * instead keeps the property's type a plain `TVariables`, and typos are
149
+ * caught.
150
+ */
151
+ type VariablesOption<TVariables> = {
152
+ [K in VariablesAreOptional<TVariables> extends true ? never : 'variables']: TVariables;
153
+ } & {
154
+ [K in VariablesAreOptional<TVariables> extends true ? 'variables' : never]?: TVariables;
155
+ };
156
+ /**
157
+ * {@link FetchContentfulOptions} for a call that passes a typed document:
158
+ * identical, except `variables` is typed by — and required by — the
159
+ * document itself.
160
+ */
161
+ type TypedFetchContentfulOptions<TVariables> = Omit<FetchContentfulOptions<GraphQLVariables>, 'variables'> & VariablesOption<TVariables>;
162
+ /**
163
+ * The result type a document carries, falling back to the historical
164
+ * default for a plain, untyped `DocumentNode`.
165
+ *
166
+ * `TypedDocumentNode`'s type brand is an optional property, so an untyped
167
+ * `DocumentNode` structurally satisfies it and infers `unknown`. Mapping
168
+ * that back to `Record<string, unknown>` keeps pre-existing
169
+ * `fetchContentful(parse(query))` calls typed exactly as they were.
170
+ */
171
+ type DocumentResult<TResult> = unknown extends TResult ? Record<string, unknown> : TResult;
117
172
  /** A single locale as configured in Contentful. */
118
173
  interface ContentfulLocale {
119
174
  code: string;
@@ -278,18 +333,33 @@ declare function collectAtPath(data: unknown, path: string[]): Record<string, un
278
333
  * query has succeeded.
279
334
  * - Shapes the response so every `fooCollection.items` becomes `foo`
280
335
  * (disable with `shapeResponseData: false` to receive the raw wire shape).
336
+ *
337
+ * Pass a typed document — from gql.tada, graphql-codegen's client preset, or
338
+ * anything else producing a `TypedDocumentNode` — and both the result and
339
+ * the variables are inferred, with no type arguments to write by hand.
281
340
  */
282
- declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options: FetchContentfulOptions<TVariables> & {
341
+ declare function fetchContentful<TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, options: TypedFetchContentfulOptions<TVariables> & {
342
+ shapeResponseData: false;
343
+ unwrapRootField: false;
344
+ }): Promise<DocumentResult<TResult>>;
345
+ declare function fetchContentful<TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, options: TypedFetchContentfulOptions<TVariables> & {
346
+ shapeResponseData: false;
347
+ }): Promise<UnwrapSingleRoot<DocumentResult<TResult>>>;
348
+ declare function fetchContentful<TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, options: TypedFetchContentfulOptions<TVariables> & {
349
+ unwrapRootField: false;
350
+ }): Promise<ShapeCollections<DocumentResult<TResult>>>;
351
+ 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>>>>;
352
+ declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options: FetchContentfulOptions<TVariables> & {
283
353
  shapeResponseData: false;
284
354
  unwrapRootField: false;
285
355
  }): Promise<TData>;
286
- declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options: FetchContentfulOptions<TVariables> & {
356
+ declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options: FetchContentfulOptions<TVariables> & {
287
357
  shapeResponseData: false;
288
358
  }): Promise<UnwrapSingleRoot<TData>>;
289
- declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options: FetchContentfulOptions<TVariables> & {
359
+ declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options: FetchContentfulOptions<TVariables> & {
290
360
  unwrapRootField: false;
291
361
  }): 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>>>;
362
+ declare function fetchContentful<TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options?: FetchContentfulOptions<TVariables>): Promise<UnwrapSingleRoot<ShapeCollections<TData>>>;
293
363
  /**
294
364
  * Creates a `fetchContentful` bound to default options — the recommended way
295
365
  * to configure the utility once per project:
@@ -308,17 +378,28 @@ declare function fetchContentful<TData = Record<string, unknown>, TVariables ext
308
378
  * the call itself, so prefer setting it per call.
309
379
  */
310
380
  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> & {
381
+ <TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, options: TypedFetchContentfulOptions<TVariables> & {
382
+ shapeResponseData: false;
383
+ unwrapRootField: false;
384
+ }): Promise<DocumentResult<TResult>>;
385
+ <TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, options: TypedFetchContentfulOptions<TVariables> & {
386
+ shapeResponseData: false;
387
+ }): Promise<UnwrapSingleRoot<DocumentResult<TResult>>>;
388
+ <TResult, TVariables = GraphQLVariables>(document: TypedDocumentNode<TResult, TVariables>, options: TypedFetchContentfulOptions<TVariables> & {
389
+ unwrapRootField: false;
390
+ }): Promise<ShapeCollections<DocumentResult<TResult>>>;
391
+ <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>>>>;
392
+ <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options: FetchContentfulOptions<TVariables> & {
312
393
  shapeResponseData: false;
313
394
  unwrapRootField: false;
314
395
  }): Promise<TData>;
315
- <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options: FetchContentfulOptions<TVariables> & {
396
+ <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options: FetchContentfulOptions<TVariables> & {
316
397
  shapeResponseData: false;
317
398
  }): Promise<UnwrapSingleRoot<TData>>;
318
- <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options: FetchContentfulOptions<TVariables> & {
399
+ <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options: FetchContentfulOptions<TVariables> & {
319
400
  unwrapRootField: false;
320
401
  }): Promise<ShapeCollections<TData>>;
321
- <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string | DocumentNode, options?: FetchContentfulOptions<TVariables>): Promise<UnwrapSingleRoot<ShapeCollections<TData>>>;
402
+ <TData = Record<string, unknown>, TVariables extends GraphQLVariables = GraphQLVariables>(query: string, options?: FetchContentfulOptions<TVariables>): Promise<UnwrapSingleRoot<ShapeCollections<TData>>>;
322
403
  };
323
404
 
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 };
405
+ 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),
@@ -867,12 +875,15 @@ async function executeDocument(document, variables, context) {
867
875
  cleanupMarkers(data, plans);
868
876
  return data;
869
877
  }
878
+ function isDocumentNode(query) {
879
+ return typeof query === "object" && query !== null && query.kind === Kind.DOCUMENT && Array.isArray(query.definitions);
880
+ }
870
881
  function toDocument(query) {
871
- if (typeof query !== "string") {
882
+ if (isDocumentNode(query)) {
872
883
  return query;
873
884
  }
874
885
  try {
875
- return parse(query);
886
+ return parse(typeof query === "string" ? query : String(query));
876
887
  } catch (cause) {
877
888
  throw new FetchContentfulError(
878
889
  `Failed to parse GraphQL query: ${String(cause)}`,
@@ -895,7 +906,7 @@ function withAutoVariables(document, variables, context) {
895
906
  }
896
907
  return merged;
897
908
  }
898
- async function fetchContentful(query, options = {}) {
909
+ async function runFetchContentful(query, options) {
899
910
  const context = resolveContext(options);
900
911
  let document = inlineFragments(toDocument(query));
901
912
  if (options.autoInjectArgs ?? true) {
@@ -925,32 +936,15 @@ async function fetchContentful(query, options = {}) {
925
936
  }
926
937
  return unwrapSingleRoot(result);
927
938
  }
939
+ async function fetchContentful(query, options = {}) {
940
+ return runFetchContentful(query, options);
941
+ }
928
942
  function createFetchContentful(defaults = {}) {
929
943
  function bound(query, options = {}) {
930
- const merged = {
944
+ return runFetchContentful(query, {
931
945
  ...defaults,
932
946
  ...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);
947
+ });
954
948
  }
955
949
  return bound;
956
950
  }