Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API docs for the list items API #5518

Merged
merged 1 commit into from
Apr 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
7 changes: 3 additions & 4 deletions tests/api-tests/access-control/authed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,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 @@ -99,11 +99,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