prisma-pothos-types
v1.0.0
Published
Zero-dependency utilities for Prisma + Pothos GraphQL — cursor/offset pagination, Relay connections, node IDs, filter builders, orderBy helpers
Maintainers
Readme
prisma-pothos-types
Zero-dependency utilities for Prisma + Pothos GraphQL stacks.
Cursor & offset pagination, Relay connections, global node IDs, filter input builders, orderBy helpers, field utilities, and Pothos type name conventions — all as plain functions with full TypeScript types.
npm install prisma-pothos-typesCursor encoding
import { encodeCursor, decodeCursor, isValidCursor } from 'prisma-pothos-types';
const cursor = encodeCursor('User', 42); // opaque base64url string
decodeCursor(cursor); // { type: 'User', id: '42' }
isValidCursor(cursor); // true
isValidCursor('garbage'); // falseRelay global node IDs
import { encodeNodeId, decodeNodeId, isNodeId } from 'prisma-pothos-types';
const id = encodeNodeId('Post', 7); // 'UG9zdDo3'
decodeNodeId(id); // { type: 'Post', id: '7' }
isNodeId(id); // trueCursor pagination (Relay spec)
import { toCursorArgs, buildConnection } from 'prisma-pothos-types';
// In a Pothos resolver:
const args = { first: 10, after: cursor };
const prismaArgs = toCursorArgs(args, 'id');
// { take: 10, skip: 1, cursor: { id: '42' } }
const users = await prisma.user.findMany(prismaArgs);
return buildConnection(users, args, { type: 'User', totalCount: 84 });
// {
// edges: [{ cursor: '...', node: { id, name, ... } }, ...],
// pageInfo: { hasNextPage, hasPreviousPage, startCursor, endCursor },
// totalCount: 84,
// }Offset pagination
import { toOffsetArgs, buildOffsetPage } from 'prisma-pothos-types';
const args = { page: 3, pageSize: 20 };
const { skip, take } = toOffsetArgs(args); // { skip: 40, take: 20 }
const [users, totalCount] = await Promise.all([
prisma.user.findMany({ skip, take }),
prisma.user.count(),
]);
return buildOffsetPage(users, args, totalCount);
// {
// nodes: [...],
// totalCount: 120,
// pageInfo: { page: 3, pageSize: 20, totalPages: 6, hasNextPage: true, hasPreviousPage: true }
// }Filter builders
import { stringFilter, numberFilter, dateFilter, boolFilter, buildWhereClause, toWhereInput } from 'prisma-pothos-types';
// Convert GraphQL scalar inputs to Prisma filter objects
stringFilter('john') // { contains: 'john', mode: 'insensitive' }
stringFilter(null) // { equals: null }
stringFilter(undefined) // undefined (key dropped)
numberFilter(42) // { equals: 42 }
numberFilter({ gte: 18 }) // { gte: 18 }
dateFilter(new Date()) // { equals: <Date> }
boolFilter(false) // { equals: false }
// Compose a where clause — undefined entries are dropped
const where = buildWhereClause([
toWhereInput({
name: stringFilter(input.name),
age: numberFilter(input.age),
isActive: boolFilter(input.isActive),
}),
notDeleted(),
]);
const users = await prisma.user.findMany({ where });OrderBy helpers
import { toOrderBy, buildOrderBy, parseSortString } from 'prisma-pothos-types';
toOrderBy({ field: 'name', direction: 'asc' }) // { name: 'asc' }
buildOrderBy([
{ field: 'lastName', direction: 'asc' },
{ field: 'createdAt', direction: 'desc' },
])
// [{ lastName: 'asc' }, { createdAt: 'desc' }]
parseSortString('createdAt_DESC') // { createdAt: 'desc' }
parseSortString('name_ASC') // { name: 'asc' }Field utilities
import { omitFields, pickFields, withTimestamps, withSoftDelete, notDeleted } from 'prisma-pothos-types';
omitFields(user, ['password', 'salt']) // user without sensitive fields
pickFields(user, ['id', 'email']) // only id and email
// Useful for seeding / test fixtures
withTimestamps({ id: 1, name: 'Alice' })
// { id: 1, name: 'Alice', createdAt: <now>, updatedAt: <now> }
withSoftDelete({ id: 2, name: 'Bob' })
// { id: 2, name: 'Bob', deletedAt: null }
// Prisma where fragment
prisma.user.findMany({ where: notDeleted() })
// { where: { deletedAt: null } }Pagination guards
import { clampPaginationArgs, PAGINATION_DEFAULTS } from 'prisma-pothos-types';
const safe = clampPaginationArgs({ first: 999 }, { maxFirst: 100 });
// { first: 100 }
PAGINATION_DEFAULTS // { first: 20, maxFirst: 100, maxLast: 100 }Pothos type name conventions
import {
edgeTypeName, connectionTypeName,
whereInputTypeName, orderByInputTypeName,
createInputTypeName, updateInputTypeName,
} from 'prisma-pothos-types';
edgeTypeName('User') // 'UserEdge'
connectionTypeName('User') // 'UserConnection'
whereInputTypeName('User') // 'UserWhereInput'
orderByInputTypeName('User') // 'UserOrderByInput'
createInputTypeName('User') // 'CreateUserInput'
updateInputTypeName('User') // 'UpdateUserInput'Full Pothos resolver example
import { toCursorArgs, buildConnection, stringFilter, buildWhereClause, toWhereInput, notDeleted } from 'prisma-pothos-types';
builder.queryField('users', t =>
t.connection({
type: UserType,
args: { search: t.arg.string() },
resolve: async (_root, args, ctx) => {
const where = buildWhereClause([
toWhereInput({ name: stringFilter(args.search) }),
notDeleted(),
]);
const prismaArgs = toCursorArgs(args, 'id');
const [users, totalCount] = await Promise.all([
ctx.prisma.user.findMany({ where, ...prismaArgs }),
ctx.prisma.user.count({ where }),
]);
return buildConnection(users, args, { type: 'User', totalCount });
},
})
);CommonJS
const { toCursorArgs, buildConnection, stringFilter } = require('prisma-pothos-types');License
MIT
