gatsby-source-prismic
Version:
Gatsby source plugin for building websites using Prismic as a data source
251 lines (245 loc) • 6.76 kB
text/typescript
import * as gatsby from "gatsby";
import * as prismicT from "@prismicio/types";
import * as RTE from "fp-ts/ReaderTaskEither";
import * as R from "fp-ts/Record";
import * as A from "fp-ts/Array";
import * as ReadonlyA from "fp-ts/ReadonlyArray";
import * as E from "fp-ts/Either";
import * as O from "fp-ts/Option";
import { pipe, flow, identity } from "fp-ts/function";
import { buildObjectType } from "../lib/buildObjectType";
import { buildSchemaRecordType } from "../lib/buildSchemaRecordType";
import { buildUnionType } from "../lib/buildUnionType";
import { getTypeName } from "../lib/getTypeName";
import { listTypeName } from "../lib/listTypeName";
import { createType } from "../lib/createType";
import { createTypes } from "../lib/createTypes";
import { requiredTypeName } from "../lib/requiredTypeName";
import { Dependencies, FieldConfigCreator, UnknownRecord } from "../types";
/**
* Builds a GraphQL field configuration object for a Slice zone's Slice. Both
* `non-repeat` and `repeat` schemas will be converted to GraphQL field
* configuration objects. The resulting type can be created using Gatsby's
* `createTypes` action.
*
* This function registers a typepath for the field.
*
* @param path - Path to the Slice zone.
* @param model - Schema definition for the Slice.
*
* @returns GraphQL object type.
*/
const buildSliceType = (
path: string[],
model: prismicT.CustomTypeModelSlice,
): RTE.ReaderTaskEither<Dependencies, Error, gatsby.GatsbyGraphQLObjectType> =>
pipe(
RTE.ask<Dependencies>(),
RTE.chain((deps) =>
pipe(
{} as Record<
"primary" | "items",
RTE.ReaderTaskEither<
Dependencies,
never,
gatsby.GatsbyGraphQLObjectType
>
>,
R.isEmpty(model["non-repeat"])
? identity
: R.upsertAt(
"primary",
buildSchemaRecordType([...path, "primary"], model["non-repeat"]),
),
R.isEmpty(model.repeat)
? identity
: R.upsertAt(
"items",
buildSchemaRecordType([...path, "items"], model.repeat, [
...path,
"item",
]),
),
R.sequence(RTE.ApplicativeSeq),
RTE.chainFirstW(
flow(
R.collect((_, type) => type),
createTypes,
),
),
RTE.map(
R.mapWithIndex((field, type) =>
field === "items"
? pipe(
type,
getTypeName,
requiredTypeName,
listTypeName,
requiredTypeName,
)
: pipe(type, getTypeName, requiredTypeName),
),
),
RTE.chainW((fields) =>
buildObjectType({
name: deps.nodeHelpers.createTypeName(path),
fields: {
...fields,
id: {
type: "ID!",
resolve: (source: UnknownRecord): string =>
deps.nodeHelpers.createNodeId([
...path,
deps.createContentDigest(source),
]),
},
slice_type: "String!",
slice_label: "String",
},
interfaces: [deps.globalNodeHelpers.createTypeName("SliceType")],
extensions: { infer: false },
}),
),
),
),
);
/**
* Builds GraphQL types for Slice Custom Type fields. The resulting types can be
* created using Gatsby's `createTypes` action.
*
* @param path - Path to the fields.
* @param choices - Record of Slice choices mapping a Slice API ID to its schema
* definition.
*
* @returns List of GraphQL types for the provided Slice schemas.
*/
const buildSliceTypes = (
path: string[],
choices: prismicT.CustomTypeModelSliceZoneField["config"]["choices"],
): RTE.ReaderTaskEither<Dependencies, Error, string[]> =>
pipe(
RTE.ask<Dependencies>(),
RTE.bindW("sliceModels", () =>
pipe(
choices,
R.filter(
(slice): slice is prismicT.CustomTypeModelSlice =>
slice.type === prismicT.CustomTypeModelSliceType.Slice,
),
RTE.right,
),
),
RTE.bindW("sliceTypeNames", (scope) =>
pipe(
scope.sliceModels,
R.mapWithIndex((sliceName, sliceModel) =>
buildSliceType([...path, sliceName], sliceModel),
),
R.sequence(RTE.ApplicativeSeq),
RTE.map(R.collect((_, type) => type)),
RTE.chainFirstW(createTypes),
RTE.map(A.map(getTypeName)),
),
),
RTE.bindW("sharedSliceModels", () =>
pipe(
choices,
R.filter(
(slice): slice is prismicT.CustomTypeModelSharedSlice =>
slice.type === prismicT.CustomTypeModelSliceType.SharedSlice,
),
RTE.right,
),
),
RTE.bindW("sharedSliceTypesNames", (scope) =>
pipe(
scope.sharedSliceModels,
R.keys,
A.map((sharedSliceId) =>
pipe(
scope.pluginOptions.sharedSliceModels,
A.findFirst(
(sharedSliceModel) => sharedSliceModel.id === sharedSliceId,
),
E.fromOption(
() =>
new Error(
`Could not find a Shared Slice model for a Shared Slice named "${sharedSliceId}"`,
),
),
),
),
A.sequence(E.Applicative),
RTE.fromEither,
RTE.map(
A.map((sharedSliceModel) =>
pipe(
sharedSliceModel.variations,
ReadonlyA.map((variation) =>
scope.nodeHelpers.createTypeName([
sharedSliceModel.id,
variation.id,
]),
),
),
),
),
RTE.map(ReadonlyA.flatten),
RTE.map(ReadonlyA.toArray),
),
),
RTE.map((scope) => [
...scope.sliceTypeNames,
...scope.sharedSliceTypesNames,
]),
);
/**
* Builds a GraphQL field configuration object for a Slices Custom Type field
* (also known as a Slice zone). The resulting configuration object can be used
* in a GraphQL type.
*
* This function registers a typepath for the field.
*
* @param path - Path to the field.
* @param schema - Schema definition for the field.
*
* @returns GraphQL field configuration object.
*/
export const buildSlicesFieldConfig: FieldConfigCreator<
prismicT.CustomTypeModelSliceZoneField
> = (path, schema) =>
pipe(
RTE.ask<Dependencies>(),
RTE.chain((deps) =>
pipe(
buildSliceTypes(path, schema.config.choices),
RTE.chainW((types) =>
buildUnionType({
name: deps.nodeHelpers.createTypeName([...path, "SlicesType"]),
types,
resolveType: (source: prismicT.Slice | prismicT.SharedSlice) =>
pipe(
source,
O.fromPredicate(
(source): source is prismicT.SharedSlice =>
"variation" in source,
),
O.map((source) =>
deps.nodeHelpers.createTypeName([
source.slice_type,
source.variation,
]),
),
O.getOrElse(() =>
deps.nodeHelpers.createTypeName([...path, source.slice_type]),
),
),
}),
),
RTE.chainFirstW(createType),
RTE.map(
flow(getTypeName, requiredTypeName, listTypeName, requiredTypeName),
),
),
),
);