Skip to content

Commit

Permalink
Add API docs for the list items API
Browse files Browse the repository at this point in the history
  • Loading branch information
timleslie committed Apr 23, 2021
1 parent 9fd7cc6 commit fb18f36
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 52 deletions.
6 changes: 6 additions & 0 deletions .changeset/fair-shrimps-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@keystone-next/keystone': patch
'@keystone-next/types': patch
---

Updated the list item and db item APIs to include an empty default for `.findMany()` and `.count()`.
4 changes: 1 addition & 3 deletions docs/components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,7 @@ export function Navigation() {
<NavItem href="/apis/auth">Authentication API</NavItem>
<NavItem href="/apis/context">Context API</NavItem>
<NavItem href="/apis/graphql">GraphQL API</NavItem>
<NavItem href="/apis/list-items" isPlaceholder>
List Item API
</NavItem>
<NavItem href="/apis/list-items">List Item API</NavItem>
</Section>
</nav>
);
Expand Down
149 changes: 147 additions & 2 deletions docs/pages/apis/list-items.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,153 @@
import { Markdown } from '../../components/Page';
import { ComingSoon } from '../../components/ComingSoon';

# List Items API

<ComingSoon/>
The list items API provides a programmatic API for running CRUD operations against your GraphQL API.
For each list in your system the following API is available at `context.lists.<listName>`.

```
{
findOne({ where: { id }, query }),
findMany({ where, first, skip, sortBy, query }),
count({ where, first, skip }),
createOne({ data, query }),
createMany({ data, query }),
updateOne({ id, data, query }),
updateMany({ data, query }),
deleteOne({ id, query }),
deleteMany({ ids, query }),
}
```

The arguments to these functions closely correspond to their equivalent GraphQL APIs, making it easy to switch between the programmatic API and the GraphQL API.

The `query` argument, which defaults to `'id'` for all the functions, is a string which indicates which fields should be returned by the operation.
Unless otherwise specified, the other arguments to all functions are required.

The functions in the API all work by directly executing queries and mutations against your GraphQL API.

### findOne

```typescript
const user = await context.lists.User.findOne({
where: { id: '...' },
query: 'id name posts { id title }',
});
```

### findMany

All arguments are optional.

```typescript
const users = await context.lists.User.findMany({
where: { name_starts_with: 'A' },
first: 10,
skip: 20,
sortBy: ['name_ASC'],
query: 'id name posts { id title }',
});
```

### count

All arguments are optional.

```typescript
const count = await context.lists.User.count({
where: { name_starts_with: 'A' },
first: 10,
skip: 20,
});
```

### createOne

```typescript
const user = await context.lists.User.createOne({
data: {
name: 'Alice',
posts: { create: [{ title: 'My first post' }] },
},
query: 'id name posts { id title }',
});
```

### createMany

```typescript
const users = await context.lists.User.createOne({
data: [
{
data: {
name: 'Alice',
posts: [{ create: { title: 'Alices first post' } }],
},
},
{
data: {
name: 'Bob',
posts: [{ create: { title: 'Bobs first post' } }],
},
},
],
query: 'id name posts { id title }',
});
```

### updateOne

```typescript
const user = await context.lists.User.updateOne({
id: '...',
data: {
name: 'Alice',
posts: { create: [{ title: 'My first post' }] },
},
query: 'id name posts { id title }',
});
```

### updateMany

```typescript
const users = await context.lists.User.updateMany({
data: [
{
id: '...',
data: {
name: 'Alice',
posts: [{ create: { title: 'Alices first post' } }],
},
},
{
id: '...',
data: {
name: 'Bob',
posts: [{ create: { title: 'Bobs first post' } }],
},
},
],
query: 'id name posts { id title }',
});
```

### deleteOne

```typescript
const user = await context.lists.User.deleteOne({
id: '...',
query: 'id name posts { id title }',
});
```

### deleteMany

```typescript
const user = await context.lists.User.deleteMany({
ids: ['...', '...'],
query: 'id name posts { id title }',
});
```

export default ({ children }) => <Markdown>{children}</Markdown>;
8 changes: 4 additions & 4 deletions packages-next/keystone/src/lib/context/itemAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export function itemAPIForList(
return list.itemQuery(args, context);
}
},
findMany({ query, resolveFields, ...rawArgs }) {
findMany({ query, resolveFields, ...rawArgs } = {}) {
if (!getArgs.findMany) throw new Error('You do not have access to this resource');
const returnFields = defaultQueryParam(query, resolveFields);
if (returnFields) {
Expand All @@ -81,7 +81,7 @@ export function itemAPIForList(
return list.listQuery(args, context);
}
},
async count(rawArgs) {
async count(rawArgs = {}) {
if (!getArgs.count) throw new Error('You do not have access to this resource');
const args = getArgs.count(rawArgs!);
return (await list.listQueryMeta(args, context)).getCount();
Expand Down Expand Up @@ -166,12 +166,12 @@ export function itemDbAPIForList(
const args = getArgs.findOne(rawArgs) as { where: { id: string } };
return list.itemQuery(args, context);
},
findMany(rawArgs) {
findMany(rawArgs = {}) {
if (!getArgs.findMany) throw new Error('You do not have access to this resource');
const args = getArgs.findMany(rawArgs);
return list.listQuery(args, context);
},
async count(rawArgs) {
async count(rawArgs = {}) {
if (!getArgs.count) throw new Error('You do not have access to this resource');
const args = getArgs.count(rawArgs!);
return (await list.listQueryMeta(args, context)).getCount();
Expand Down
8 changes: 4 additions & 4 deletions packages-next/types/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ export type KeystoneListsAPI<
> = {
[Key in keyof KeystoneListsTypeInfo]: {
findMany(
args: KeystoneListsTypeInfo[Key]['args']['listQuery'] & ResolveFields
args?: KeystoneListsTypeInfo[Key]['args']['listQuery'] & ResolveFields
): Promise<readonly Record<string, any>[]>;
findOne(
args: { readonly where: { readonly id: string } } & ResolveFields
): Promise<Record<string, any>>;
count(args: KeystoneListsTypeInfo[Key]['args']['listQuery']): Promise<number>;
count(args?: KeystoneListsTypeInfo[Key]['args']['listQuery']): Promise<number>;
updateOne(
args: {
readonly id: string;
Expand Down Expand Up @@ -86,12 +86,12 @@ type ResolveFields = {
export type KeystoneDbAPI<KeystoneListsTypeInfo extends Record<string, BaseGeneratedListTypes>> = {
[Key in keyof KeystoneListsTypeInfo]: {
findMany(
args: KeystoneListsTypeInfo[Key]['args']['listQuery']
args?: KeystoneListsTypeInfo[Key]['args']['listQuery']
): Promise<readonly KeystoneListsTypeInfo[Key]['backing'][]>;
findOne(args: {
readonly where: { readonly id: string };
}): Promise<KeystoneListsTypeInfo[Key]['backing']>;
count(args: KeystoneListsTypeInfo[Key]['args']['listQuery']): Promise<number>;
count(args?: KeystoneListsTypeInfo[Key]['args']['listQuery']): Promise<number>;
updateOne(args: {
readonly id: string;
readonly data: KeystoneListsTypeInfo[Key]['inputs']['update'];
Expand Down
13 changes: 5 additions & 8 deletions tests/api-tests/access-control/authed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,8 @@ multiAdapterRunners().map(({ before, after, provider }) =>
const data = await context.exitSudo().graphql.run({ query });
expect(data[createMutationName]).not.toBe(null);
expect(data[createMutationName].id).not.toBe(null);
await deleteItem({
context,
listKey: nameFn[mode](access),
itemId: data[createMutationName].id,
await context.lists[nameFn[mode](access)].deleteOne({
id: data[createMutationName].id,
});
});
});
Expand Down Expand Up @@ -278,10 +276,9 @@ multiAdapterRunners().map(({ before, after, provider }) =>
const item = items[listKey][0];
const fieldName = getFieldName(access);
const singleQueryName = listKey;
await updateItem({
context,
listKey,
item: { id: item.id, data: { [fieldName]: 'hello' } },
await context.lists[listKey].updateOne({
id: item.id,
data: { [fieldName]: 'hello' },
});
const query = `query { ${singleQueryName}(where: { id: "${item.id}" }) { id ${fieldName} } }`;
const data = await context.exitSudo().graphql.run({ query });
Expand Down
6 changes: 2 additions & 4 deletions tests/api-tests/fields/crud.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,9 @@ multiAdapterRunners().map(({ runner, provider }) =>
context: KeystoneContext;
listKey: string;
}) => {
const items = await getItems({
context,
listKey,
returnFields,
const items = await context.lists[listKey].findMany({
sortBy: ['name_ASC'],
query: returnFields,
});
return wrappedFn({ context, listKey, items });
};
Expand Down
10 changes: 2 additions & 8 deletions tests/api-tests/relationships/crud-self-ref/one-to-one.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,8 @@ multiAdapterRunners().map(({ runner, provider }) =>
'Count',
runner(setupKeystone, async ({ context }) => {
await createInitialData(context);
const data = await context.graphql.run({
query: `
{
_allUsersMeta { count }
}
`,
});
expect(data._allUsersMeta.count).toEqual(3);
const count = await context.lists.User.count();
expect(count).toEqual(3);
})
);

Expand Down
8 changes: 3 additions & 5 deletions tests/api-tests/relationships/crud/one-to-one.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -591,11 +591,9 @@ multiAdapterRunners().map(({ runner, provider }) =>
});
expect(result).toHaveLength(1);

const result1 = await getItem({
context,
listKey: 'Company',
itemId: company1.id,
returnFields: 'id location { id }',
const result1 = await context.lists.Company.findOne({
where: { id: company1.id },
query: 'id location { id }',
});
expect(result1?.location).toBe(null);

Expand Down
6 changes: 2 additions & 4 deletions tests/api-tests/relationships/filtering/nested.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,8 @@ multiAdapterRunners().map(({ runner, provider }) =>
test(
'nested to-many relationship meta can be filtered',
runner(setupKeystone, async ({ context }) => {
const ids = await createItems({
context,
listKey: 'Post',
items: [
const ids = await context.lists.Post.createMany({
data: [
{ data: { content: 'Hello world' } },
{ data: { content: 'hi world' } },
{ data: { content: 'Hello? Or hi?' } },
Expand Down
10 changes: 4 additions & 6 deletions tests/api-tests/relationships/many-to-one-to-one.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { gen, sampleOne } from 'testcheck';
import { text, relationship } from '@keystone-next/fields';
import { createSchema, list } from '@keystone-next/keystone/schema';
import { multiAdapterRunners, setupFromConfig } from '@keystone-next/test-utils-legacy';
import { createItem, createItems } from '@keystone-next/server-side-graphql-client-legacy';
import { createItems } from '@keystone-next/server-side-graphql-client-legacy';

const alphanumGenerator = gen.alphaNumString.notEmpty();

Expand Down Expand Up @@ -54,10 +54,8 @@ const createCompanyAndLocation = async (context: KeystoneContext) => {
],
});

return createItem({
context,
listKey: 'Owner',
item: {
return context.lists.Owner.createOne({
data: {
name: sampleOne(alphanumGenerator),
companies: {
create: [
Expand Down Expand Up @@ -91,7 +89,7 @@ const createCompanyAndLocation = async (context: KeystoneContext) => {
],
},
},
returnFields: 'id name companies { id name location { id name custodians { id name } } }',
query: 'id name companies { id name location { id name custodians { id name } } }',
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,8 @@ multiAdapterRunners().map(({ runner, provider }) =>
item: { teachers: { connect: [{ id: teacher1.id }, { id: teacher2.id }] } },
});

await updateItems({
context,
listKey: 'Teacher',
items: [
await context.lists.Teacher.updateMany({
data: [
{
id: teacher1.id,
data: { students: { connect: [{ id: student1.id }, { id: student2.id }] } },
Expand Down

0 comments on commit fb18f36

Please sign in to comment.