diff --git a/.changeset/fair-shrimps-love.md b/.changeset/fair-shrimps-love.md
new file mode 100644
index 00000000000..78bddf88a6c
--- /dev/null
+++ b/.changeset/fair-shrimps-love.md
@@ -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()`.
diff --git a/docs/components/Navigation.tsx b/docs/components/Navigation.tsx
index bef03f365f8..933b63cc60b 100644
--- a/docs/components/Navigation.tsx
+++ b/docs/components/Navigation.tsx
@@ -98,9 +98,7 @@ export function Navigation() {
Authentication API
Context API
GraphQL API
-
- List Item API
-
+ List Item API
);
diff --git a/docs/pages/apis/list-items.mdx b/docs/pages/apis/list-items.mdx
index 78d60db63f1..579e2bc9537 100644
--- a/docs/pages/apis/list-items.mdx
+++ b/docs/pages/apis/list-items.mdx
@@ -1,8 +1,153 @@
import { Markdown } from '../../components/Page';
-import { ComingSoon } from '../../components/ComingSoon';
# List Items API
-
+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.`.
+
+```
+{
+ 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 }) => {children};
diff --git a/packages-next/keystone/src/lib/context/itemAPI.ts b/packages-next/keystone/src/lib/context/itemAPI.ts
index d27c73fe4eb..2fd0d05de30 100644
--- a/packages-next/keystone/src/lib/context/itemAPI.ts
+++ b/packages-next/keystone/src/lib/context/itemAPI.ts
@@ -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) {
@@ -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();
@@ -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();
diff --git a/packages-next/types/src/context.ts b/packages-next/types/src/context.ts
index 579f32b5da9..311a03ef00e 100644
--- a/packages-next/types/src/context.ts
+++ b/packages-next/types/src/context.ts
@@ -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[]>;
findOne(
args: { readonly where: { readonly id: string } } & ResolveFields
): Promise>;
- count(args: KeystoneListsTypeInfo[Key]['args']['listQuery']): Promise;
+ count(args?: KeystoneListsTypeInfo[Key]['args']['listQuery']): Promise;
updateOne(
args: {
readonly id: string;
@@ -86,12 +86,12 @@ type ResolveFields = {
export type KeystoneDbAPI> = {
[Key in keyof KeystoneListsTypeInfo]: {
findMany(
- args: KeystoneListsTypeInfo[Key]['args']['listQuery']
+ args?: KeystoneListsTypeInfo[Key]['args']['listQuery']
): Promise;
findOne(args: {
readonly where: { readonly id: string };
}): Promise;
- count(args: KeystoneListsTypeInfo[Key]['args']['listQuery']): Promise;
+ count(args?: KeystoneListsTypeInfo[Key]['args']['listQuery']): Promise;
updateOne(args: {
readonly id: string;
readonly data: KeystoneListsTypeInfo[Key]['inputs']['update'];
diff --git a/tests/api-tests/access-control/authed.test.ts b/tests/api-tests/access-control/authed.test.ts
index f051b1e49a4..69fcb517fb7 100644
--- a/tests/api-tests/access-control/authed.test.ts
+++ b/tests/api-tests/access-control/authed.test.ts
@@ -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 });
diff --git a/tests/api-tests/fields/crud.test.ts b/tests/api-tests/fields/crud.test.ts
index 17ed32d77ec..3a356c3e46f 100644
--- a/tests/api-tests/fields/crud.test.ts
+++ b/tests/api-tests/fields/crud.test.ts
@@ -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 });
};
diff --git a/tests/api-tests/relationships/crud-self-ref/one-to-one.test.ts b/tests/api-tests/relationships/crud-self-ref/one-to-one.test.ts
index cfef24eb44a..6da368a7361 100644
--- a/tests/api-tests/relationships/crud-self-ref/one-to-one.test.ts
+++ b/tests/api-tests/relationships/crud-self-ref/one-to-one.test.ts
@@ -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);
})
);
diff --git a/tests/api-tests/relationships/crud/one-to-one.test.ts b/tests/api-tests/relationships/crud/one-to-one.test.ts
index 9c4979283c4..db305580396 100644
--- a/tests/api-tests/relationships/crud/one-to-one.test.ts
+++ b/tests/api-tests/relationships/crud/one-to-one.test.ts
@@ -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);
diff --git a/tests/api-tests/relationships/filtering/nested.test.ts b/tests/api-tests/relationships/filtering/nested.test.ts
index 761ba683c80..6b4600256ad 100644
--- a/tests/api-tests/relationships/filtering/nested.test.ts
+++ b/tests/api-tests/relationships/filtering/nested.test.ts
@@ -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?' } },
diff --git a/tests/api-tests/relationships/many-to-one-to-one.test.ts b/tests/api-tests/relationships/many-to-one-to-one.test.ts
index e6d36ea5bba..1d733946417 100644
--- a/tests/api-tests/relationships/many-to-one-to-one.test.ts
+++ b/tests/api-tests/relationships/many-to-one-to-one.test.ts
@@ -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();
@@ -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: [
@@ -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 } } }',
});
};
diff --git a/tests/api-tests/relationships/nested-mutations/two-way-backreference/to-many.test.ts b/tests/api-tests/relationships/nested-mutations/two-way-backreference/to-many.test.ts
index 2d04a7c8a74..0eae55b5875 100644
--- a/tests/api-tests/relationships/nested-mutations/two-way-backreference/to-many.test.ts
+++ b/tests/api-tests/relationships/nested-mutations/two-way-backreference/to-many.test.ts
@@ -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 }] } },