RFC: @prismicio/client Refresh
See original GitHub issue@prismicio/client Refresh
While using @prismicio/client
during gatsby-source-prismic
’s development and testing direct usage in Next.js, I came across areas where the library could be improved. Functionally, the library works well and does everything I needed from it. Focusing on a simpler and more explicit API and taking advantage of the user’s environment (e.g. their browser or framework), however, could make for a better developer experience.
Some of the following notes have been explored in ts-prismic
, a library created to address issues I encountered.
While ts-prismic
focuses on providing a minimal interface to the REST API via URL builders and TypeScript types, the @prismicio/client
library would need to include more functionality. ts-prismic
can be used as a basis to rebuild most of the existing @prismicio/client
API with a lighter codebase. Code from ts-prismic
would be moved into the library, not installed as a dependency.
This refresh would need to have complete feature parity with the current API. If a function is removed, a straightforward replacement should be available which may be a direct function replacement or consist of a series of other functions.
Happy to get feedback on these ideas to see how we can make this library even easier to use and more connected to users’ codebases.
Goals
- Simpler API with less overlap between functions
- Strong TypeScript support
- Type-safety at compiler and run time
- Configurable network requests
- Smaller bundle
- Easy upgrade path
- Support modern environments
What the library does right
-
Easy to create a client and get started Users don’t need to know how the REST API works. Just create a client with
Prismic.client
and get a document withclient.getByID
. -
The standard API in JavaScript projects Documentation is plentiful as it can be found in several guides. Googling a problem will likely result in some kind of code example.
-
Abstracts common use cases Predicates can be created using easy-to-use functions with built-in JavaScript types like numbers and
Date
. Preview URLs can be resolved by providing just the preview token, document ID, and Link Resolver without needing to know what happens in the background.
Where the library could be cleaned-up
-
Bloaded API Overlap between methods can cause confusion.
// Which one am I supposed to use? const client = Prismic.client("..."); const api = Prismic.api("..."); const api2 = Prismic.getApi("...");
-
Platform-specific methods Core functions like
Prismic.client
contain options for Node.js-specific code, likereq
. Should a generic JavaScript library include Node.js-specific code? If yes, should it be more explicit about its integration? -
TypeScript support The library is written in TypeScript, but is not written or exported for library users.
import { Document } from "@prismicio/client/d.ts/documents"; import ResolvedApi from "@prismicio/client/d.ts/ResolvedApi"; // Compared to: import { Document, Client } from "@prismicio/client";
-
File size 6.9kb gzipped, partially due to its inclusion of
cross-fetch
.cross-fetch
might be unavoidable if we want a super simple setup for Node.js users, but we could probably reduce the library code size. -
Legacy API styles Promises are supported nearly everywhere. Usage with
async
/await
makes it even easier and can be transpiled down to earlier ES version if necessary. Callback-style functions could be dropped and Promise-based APIs embraced everywhere.
New features
-
Expose URL builders URL builders could be provided that create URLs that the client would use. This allows users to build URLs for cases where a full client may not be desired, such as when you are unable to pass around object references.
-
User-facing TypeScript types Export types to help users provide type-safety in their projects. This includes types like
Document
orRef
. These types may or may not be used internally by the library, but they can still be provided to users since their code may need it.These types should be flexible and extendable through generics and interfaces.
-
Runtime type guards Export type guard functions for common use cases like Documents and fields types (e.g.
isDocument
,isRichTextField
). -
Maybe: Runtime type guards based on Custom Type JSON schemas Runtime type guards could be created using Custom Type JSON schemas. The JSON file could generate a function that checks each field using something like
io-ts
. This would require the full JSON schemas to be available at runtime and could make for a very large bundle. This may not be worth the download cost if field-specific type guards are available (the feature described above). -
Provide the ability to have a custom fetcher When creating a client, a user could provide a function to perform network requests that the client would make. This function could hook into caching and use built-in functions depeneding on the user’s environment, like the browser’s
fetch
function. -
Documentation within the library via TSDoc Documentation for the library’s functions and types could be included within the library. As developers are using the library, they can view documentation (which may link to more in-depth docs) without needing to change contexts. Documentation for functions, parameters, and return types could be presented as users are typing.
What this library could look like
Creating a client and querying documents:
import * as prismic from '@prismicio/client'
import got from 'got'
const endpoint = prismic.defaultEndpoint('qwerty')
const client = prismic.createClient(endpoint, {
accessToken: process.env.PRISMIC_ACCESS_TOKEN,
fetcher: (url) => got.json(url),
})
const document = await client.getByID('abc123')
const response = await client.query([
prismic.predicate.at('document.type', 'blog-post'),
prismic.predicate.has('my.blog-post.title'),
])
Using a type guard at runtime and compile-time:
import * as prismic from '@prismicio/client'
const endpoint = prismic.defaultEndpoint('qwerty')
const client = prismic.createClient(endpoint, {
accessToken: process.env.PRISMIC_ACCESS_TOKEN,
fetcher: (url) => got.json(url),
})
const document = await client.getByID('abc123')
if (prismic.isRichTextField(document.data.description)) {
console.log('document.data.description is now typed as prismic.StructuredText')
}
User-facing TypeScript types:
import * as prismic from 'ts-prismic'
// Available as prismic.Document
export interface Document<Data = Record<string, unknown>> {
id: string
uid?: string
url?: string
type: string
href: string
tags: string[]
slugs: string[]
lang?: string
alternate_languages: AlternateLanguage[]
first_publication_date: string | null
last_publication_date: string | null
data: Data
}
// Available as prismic.Query
export interface Query<TResults extends Document[] = Document[]> {
page: number
results_per_page: number
results_size: number
total_results_size: number
total_pages: number
next_page: string | null
prev_page: string | null
results: TResults
}
Using URL builders directly without a client:
import * as prismic from 'ts-prismic'
import got from 'got'
// Get the repo's API endpoint
const endpoint = prismic.defaultEndpoint('qwerty')
// Get repo metadata
const repoUrl = prismic.buildRepositoryURL(endpoint)
const repo = await got(repoUrl).json<prismic.Response.Repository>()
// Get the master ref
const masterRef = repo.refs.find((ref) => ref.isMasterRef)
// Query all documents
const allDocsUrl = prismic.buildQueryURL(endpoint, masterRef.ref)
const allDocs = await got(allDocsUrl).json<prismic.Response.Query>()
Related issues
Issue Analytics
- State:
- Created 2 years ago
- Reactions:6
- Comments:6 (4 by maintainers)
Thanks @Duaner!
Regarding Node.js
req
support, that makes sense. I’ve yet to read into the codebase, but I assume this automatically reads the preview cookie from the req anytime a query is made.I agree that this process should be seamless without knowing how it’s working, but perhaps it could be a more transparent about what it’s doing.
Something like:
I’ll have to think more about the actual API and names. But overall the goal is to provide more clarity on how the
req
parameter is being used directly with the library without needing to read auxiliary documentation. Also to keep non-Node.js users in mind and making it as straightforward to use.Shipped 🎉