Skip to content
This repository has been archived by the owner on Mar 26, 2024. It is now read-only.

Commit

Permalink
feat: add query method (#21)
Browse files Browse the repository at this point in the history
* add query method; use it in both get and getAll

* typo
  • Loading branch information
ricokahler authored Dec 13, 2020
1 parent fcfa87d commit 4d5fe11
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 42 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ This will tell the codegen to remove the optional `?` modifier on the field.
### Client Usage

The client currently only contains 3 methods:
The client currently only contains 4 methods:

```ts
/**
Expand All @@ -133,6 +133,13 @@ function getAll<T>(type: string, filter?: string): Promise<T[]>;
* Calls the above `get` function internally
*/
function expand<T>(ref: SanityReference<T>): Promise<R>;

/**
* Passes a query along to sanity. If preview mode is active and a token is
* present, it will prefer drafts over the published versions. The type must be
* provided by you.
*/
function query<T = any>(query: string): Promise<T[]>;
```

The design behind the client is to fetch full documents and handle projections and transforms in code.
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sanity-codegen",
"version": "0.2.5",
"version": "0.3.0",
"private": true,
"description": "",
"repository": {
Expand Down
99 changes: 99 additions & 0 deletions src/client/create-client.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,61 @@
import createClient from './create-client';

describe('query', () => {
it('prefers draft documents in preview mode', async () => {
const postOne = {
_createdAt: '2020-10-24T03:00:29Z',
_id: 'post-one',
_rev: 'rev',
_type: 'post',
_updatedAt: '2020-10-24T03:04:54Z',
};
const postTwo = {
_createdAt: '2020-10-24T03:00:29Z',
_id: 'post-two',
_rev: 'rev',
_type: 'post',
_updatedAt: '2020-10-24T03:04:54Z',
};
const postTwoDraft = {
_createdAt: '2020-10-24T03:00:29Z',
_id: 'drafts.post-two',
_rev: 'rev',
_type: 'post',
_updatedAt: '2020-10-24T03:04:54Z',
};

const mockFetch: any = jest.fn(() => ({
ok: true,
json: () => Promise.resolve({ result: [postOne, postTwo, postTwoDraft] }),
}));

const sanity = createClient({
projectId: 'test-project-id',
dataset: 'test-dataset',
fetch: mockFetch,
previewMode: true,
token: 'test-token',
});

const docs = await sanity.query('*');

expect(docs).toEqual([postOne, postTwoDraft]);

expect(mockFetch).toHaveBeenCalledTimes(1);
expect(mockFetch.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"https://test-project-id.api.sanity.io/v1/data/query/test-dataset?query=*",
Object {
"headers": Object {
"Accept": "application/json",
"Authorization": "Bearer test-token",
},
},
]
`);
});
});

describe('get', () => {
it('gets one document from the sanity instance', async () => {
const mockDoc = {
Expand Down Expand Up @@ -137,6 +193,49 @@ describe('getAll', () => {
`);
});

it('passes the filter param', async () => {
const postOne = {
_createdAt: '2020-10-24T03:00:29Z',
_id: 'post-one',
_rev: 'rev',
_type: 'post',
_updatedAt: '2020-10-24T03:04:54Z',
};
const postTwo = {
_createdAt: '2020-10-24T03:00:29Z',
_id: 'post-two',
_rev: 'rev',
_type: 'post',
_updatedAt: '2020-10-24T03:04:54Z',
};

const mockFetch: any = jest.fn(() => ({
ok: true,
json: () => Promise.resolve({ result: [postOne, postTwo] }),
}));

const sanity = createClient({
projectId: 'test-project-id',
dataset: 'test-dataset',
fetch: mockFetch,
});
const docs = await sanity.getAll('post', 'name == "test"');

expect(docs).toEqual([postOne, postTwo]);

expect(mockFetch).toHaveBeenCalledTimes(1);
expect(mockFetch.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"https://test-project-id.api.sanity.io/v1/data/query/test-dataset?query=*+%5B_type+%3D%3D+%22post%22+%26%26+name+%3D%3D+%22test%22%5D",
Object {
"headers": Object {
"Accept": "application/json",
},
},
]
`);
});

it('prefers draft documents in preview mode', async () => {
const postOne = {
_createdAt: '2020-10-24T03:00:29Z',
Expand Down
70 changes: 31 additions & 39 deletions src/client/create-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,33 +45,16 @@ function createClient<Documents extends { _type: string; _id: string }>({
id: string
) {
type R = Documents & { _type: T };
const searchParams = new URLSearchParams();

const preview = previewMode && !!token;
const previewClause = preview
? // sanity creates a new document with an _id prefix of `drafts.`
// for when a document is edited without being published
`|| _id=="drafts.${id}"`
: '';

searchParams.set('query', `* [_id == "${id}" ${previewClause}]`);
const response = await jsonFetch<SanityResult<R>>(
`https://${projectId}.api.sanity.io/v1/data/query/${dataset}?${searchParams.toString()}`,
{
// conditionally add the authorization header if the token is present
...(token && { headers: { Authorization: `Bearer ${token}` } }),
}
);

// this will always be undefined in non-preview mode
const previewDoc = response.result.find((doc) =>
doc._id.startsWith('drafts.')
);

const publishedDoc = response.result.find(
(doc) => !doc._id.startsWith('drafts.')
);

return previewDoc || publishedDoc || null;
const result = await query<R>(`* [_id == "${id}" ${previewClause}]`);
return result[0];
}

/**
Expand All @@ -86,14 +69,34 @@ function createClient<Documents extends { _type: string; _id: string }>({
// TODO: might be a cleaner way to do this. this creates an ugly lookin type
type R = { _type: T } & Documents;

return query<R>(
`* [_type == "${type}"${filterClause ? ` && ${filterClause}` : ''}]`
);
}

/**
* If a sanity document refers to another sanity document, then you can use this
* function to expand that document, preserving the type
*/
async function expand<T extends Documents>(ref: SanityReference<T>) {
// this function is primarily for typescript
const response = await get<T['_type']>(null as any, ref._ref);
// since this is a ref, the response will be defined (unless weak reference)
return response!;
}

/**
* Passes a query along to sanity. If preview mode is active and a token is
* present, it will prefer drafts over the published versions.
*/
async function query<T extends { _id: string } = any>(
query: string
): Promise<T[]> {
const searchParams = new URLSearchParams();
const preview = previewMode && !!token;

searchParams.set(
'query',
`* [_type == "${type}"${filterClause ? ` && ${filterClause}` : ''}]`
);
const response = await jsonFetch<SanityResult<R>>(
searchParams.set('query', query);
const response = await jsonFetch<SanityResult<T>>(
`https://${projectId}.api.sanity.io/v1/data/query/${dataset}?${searchParams.toString()}`,
{
// conditionally add the authorization header if the token is present
Expand All @@ -113,14 +116,14 @@ function createClient<Documents extends { _type: string; _id: string }>({
// create a lookup of only draft docs
const draftDocs = response.result
.filter((doc) => doc._id.startsWith('drafts.'))
.reduce<{ [_id: string]: R }>((acc, next) => {
.reduce<{ [_id: string]: T }>((acc, next) => {
acc[removeDraftPrefix(next._id)] = next;
return acc;
}, {});

// in this dictionary, if there is draft doc, that will be preferred,
// otherwise it'll use the published version
const finalAcc = response.result.reduce<{ [_id: string]: R }>(
const finalAcc = response.result.reduce<{ [_id: string]: T }>(
(acc, next) => {
const id = removeDraftPrefix(next._id);
acc[id] = draftDocs[id] || next;
Expand All @@ -132,18 +135,7 @@ function createClient<Documents extends { _type: string; _id: string }>({
return Object.values(finalAcc);
}

/**
* If a sanity document refers to another sanity document, then you can use this
* function to expand that document, preserving the type
*/
async function expand<T extends Documents>(ref: SanityReference<T>) {
// this function is primarily for typescript
const response = await get<T['_type']>(null as any, ref._ref);
// since this is a ref, the response will be defined (unless weak reference)
return response!;
}

return { get, getAll, expand };
return { get, getAll, expand, query };
}

export default createClient;

0 comments on commit 4d5fe11

Please sign in to comment.