import type { Slice } from "../types/value/slice" /** * Convert a value to a lazyily loaded module. This is useful when using functions like `() => * import("...")`. */ type LazyModule = () => Promise /** Mark a type as potentially lazy-loaded via a module. */ type MaybeLazyModule = T | LazyModule // oxlint-disable-next-line no-explicit-any type AnyFunction = (...args: any[]) => any /** * Returns the type of a `SliceLike` type. * * @typeParam Slice - The Slice from which the type will be extracted. */ type ExtractSliceType = TSlice extends SliceLikeRestV2 ? TSlice["slice_type"] : TSlice extends SliceLikeGraphQL ? TSlice["type"] : never /** * The minimum required properties to represent a Prismic slice from the Prismic Content API for the * `mapSliceZone()` helper. * * @typeParam SliceType - Type name of the slice. */ type SliceLikeRestV2 = Pick< Slice, "id" | "slice_type" > /** * The minimum required properties to represent a Prismic slice from the Prismic GraphQL API for the * `mapSliceZone()` helper. * * @typeParam SliceType - Type name of the slice. */ type SliceLikeGraphQL = { type: Slice["slice_type"] } /** * The minimum required properties to represent a Prismic slice for the `mapSliceZone()` helper. * * If using Prismic's Content API, use the `Slice` export from `@prismicio/client` for a full * interface. * * @typeParam SliceType - Type name of the slice. */ type SliceLike = | SliceLikeRestV2 | SliceLikeGraphQL /** * A looser version of the `SliceZone` type from `@prismicio/client` using `SliceLike`. * * If using Prismic's Content API, use the `SliceZone` export from `@prismicio/client` for the full * type. * * @typeParam TSlice - The type(s) of a slice in the slice zone. */ type SliceZoneLike = readonly TSlice[] /** * A set of properties that identify a Slice as having been mapped. Consumers of the mapped Slice * Zone can use these properties to detect and specially handle mapped Slices. */ type MappedSliceLike = { /** * If `true`, this Slice has been modified from its original value using a mapper. * * @internal */ __mapped: true } /** * Arguments for a function mapping content from a Prismic Slice using the `mapSliceZone()` helper. * * @typeParam TSlice - The Slice passed as a prop. * @typeParam TContext - Arbitrary data passed to `mapSliceZone()` and made available to all Slice * mappers. */ type SliceMapperArgs = { /** Slice data. */ slice: TSlice /** The index of the Slice in the Slice Zone. */ index: number /** All Slices from the Slice Zone to which the Slice belongs. */ // TODO: We have to keep this list of Slices general due to circular // reference limtiations. If we had another generic to determine the full // union of Slice types, it would include TSlice. This causes TypeScript to // throw a compilation error. slices: SliceZoneLike /** Arbitrary data passed to `mapSliceZone()` and made available to all Slice mappers. */ context: TContext } /** A record of mappers. */ type SliceMappers = { [P in ExtractSliceType]?: MaybeLazyModule< SliceMapper< Extract>, // oxlint-disable-next-line no-explicit-any any, TContext > > } /** * A function that maps a Slice and its metadata to a modified version. The return value will * replace the Slice in the Slice Zone. */ export type SliceMapper< TSlice extends SliceLike = SliceLike, TMappedSlice extends Record | undefined | void = | Record | undefined | void, TContext = unknown, > = (args: SliceMapperArgs) => TMappedSlice | Promise /** Unwraps a lazily loaded mapper module. */ type ResolveLazySliceMapperModule< // oxlint-disable-next-line no-explicit-any TSliceMapper extends SliceMapper | LazyModule, > = TSliceMapper extends LazyModule ? Awaited> extends { default: unknown } ? Awaited>["default"] : Awaited> : TSliceMapper /** Transforms a Slice into its mapped version. */ type MapSliceLike< // oxlint-disable-next-line no-explicit-any TSliceLike extends SliceLike, TSliceMappers extends SliceMappers< TSliceLike, // oxlint-disable-next-line no-explicit-any any >, > = TSliceLike extends Slice ? TSliceLike["slice_type"] extends keyof TSliceMappers ? TSliceMappers[TSliceLike["slice_type"]] extends AnyFunction ? SliceLikeRestV2 & MappedSliceLike & Awaited>> : TSliceLike : TSliceLike : TSliceLike extends SliceLikeGraphQL ? TSliceLike["type"] extends keyof TSliceMappers ? TSliceMappers[TSliceLike["type"]] extends AnyFunction ? SliceLikeGraphQL & MappedSliceLike & Awaited>> : TSliceLike : TSliceLike : never /** * Transforms a Slice Zone using a set of mapping functions, one for each type of Slice. Mapping * functions can be async. * * Whenever possible, use this function on the server to minimize client-side processing. * * @example * ;```typescript * const mappedSliceZone = await mapSliceZone(page.data.slices, { * code_block: ({ slice }) => ({ * codeHTML: await highlight(slice.primary.code), * }), * }); * ``` */ export function mapSliceZone< TSliceLike extends SliceLike, TSliceMappers extends SliceMappers, TContext = unknown, >( sliceZone: SliceZoneLike, mappers: TSliceMappers, context?: TContext, ): Promise[]> { return Promise.all( sliceZone.map(async (slice, index, slices) => { const isRestSliceType = "slice_type" in slice const sliceType = isRestSliceType ? slice.slice_type : slice.type const mapper = mappers[sliceType as keyof typeof mappers] if (!mapper) { return slice } const mapperArgs = { slice, slices, index, context } // `result` may be a mapper function OR a module // containing a mapper function. let result = await mapper( // @ts-expect-error - I don't know how to fix this type mapperArgs, ) // `result` is a module containing a mapper function, // we need to dig out the mapper function. `result` // will be reassigned with the mapper function's value. if ( // `mapper.length < 1` ensures the given // function is something of the form: // `() => import(...)` mapper.length < 1 && (typeof result === "function" || (typeof result === "object" && "default" in result)) ) { result = "default" in result ? result.default : result result = await result(mapperArgs) } if (isRestSliceType) { return { __mapped: true, id: slice.id, slice_type: sliceType, ...result, } } else { return { __mapped: true, type: sliceType, ...result, } } }), ) }