返回 Skill 列表
extension
分类: 开发与工程无需 API Key

zodipus-query-engine

构建类型安全、可组合的Prisma查询,并自动进行Zod验证。在使用createRegistry、查询构建器、选择/包含模式、经过验证的数据库查询、findMany验证或类型安全的Prisma结果时使用。

person作者: jakexiaohubgithub

Query Engine

Build validated, type-safe Prisma queries with automatic result validation.

When to Apply

  • User mentions "query engine" or "createRegistry"
  • User wants type-safe Prisma queries
  • User asks about select/include with validation
  • User needs to validate Prisma query results
  • User mentions "composable queries"
  • User asks about relation queries with Zod

Core Concept

The Query Engine creates composable query builders that:

  1. Generate Prisma query objects (select, include)
  2. Provide matching Zod schemas for validation
  3. Infer TypeScript types automatically
User Request → Query Builder → Prisma Query + Zod Schema → Validated Result

Setup

import { createRegistry } from 'zodipus/queryEngine';
import { models, modelRelations } from './generated/generated-index';

// Create registry with all models
const registry = createRegistry({
  models,
  relations: modelRelations,
});

// Create query builders for each model you need
const userQuery = registry.createQuery('user');
const postQuery = registry.createQuery('post');
const commentQuery = registry.createQuery('comment');

Basic Patterns

Pattern 1: Select Specific Fields

Select only the fields you need. Validation matches your selection.

const query = userQuery({
  select: { id: true, email: true, name: true }
});

// query.query = { select: { id: true, email: true, name: true } }
const user = await prisma.user.findFirst(query.query);

// Validates and types only selected fields
const validated = query.parse(user);
// Type: { id: string; email: string; name: string | null }

Pattern 2: Include Relations

Include related records with their own field selection.

const query = userQuery({
  select: { id: true, email: true },
  posts: {
    select: { id: true, title: true, published: true }
  }
});

const users = await prisma.user.findMany(query.query);
const validated = query.array().parse(users);
// Type: {
//   id: string;
//   email: string;
//   posts: { id: string; title: string; published: boolean }[]
// }[]

Pattern 3: Nested Relations

Chain relations as deeply as your relationDepth allows.

const query = userQuery({
  select: { id: true, name: true },
  posts: {
    select: { title: true },
    comments: {
      select: { content: true, createdAt: true },
      author: {
        select: { name: true, email: true }
      }
    }
  }
});

// 4 levels deep: User → posts → comments → author

Pattern 4: Array Results (findMany)

Use .array() for validating arrays of results.

const query = userQuery({
  select: { id: true, email: true }
});

const users = await prisma.user.findMany(query.query);

// Validate array
const validated = query.array().parse(users);
// Type: { id: string; email: string }[]

Pattern 5: Safe Parsing

Handle validation errors without throwing.

const result = query.safeParse(data);

if (result.success) {
  // result.data is fully typed
  console.log(result.data.email);
} else {
  // result.error is ZodError
  console.error(result.error.issues);
  console.error(result.error.format()); // Formatted errors
}

Pattern 6: Partial Validation

For PATCH endpoints or partial updates.

const query = userQuery({
  select: { id: true, email: true, name: true }
});

// Make all fields optional
const partialQuery = query.partial();

const updates = partialQuery.parse({ name: 'New Name' });
// Type: { id?: string; email?: string; name?: string | null }

Advanced Patterns

Combining with Prisma Where/OrderBy

The Query Engine generates select/include objects. Combine with your own conditions:

const query = userQuery({
  select: { id: true, email: true, name: true },
  posts: { select: { title: true } }
});

// Add where, orderBy, pagination
const users = await prisma.user.findMany({
  ...query.query,
  where: { role: 'ADMIN' },
  orderBy: { createdAt: 'desc' },
  take: 10,
  skip: 0,
});

const validated = query.array().parse(users);

Reusable Query Fragments

Create reusable query configurations:

// Define reusable configs
const minimalUser = { select: { id: true, email: true } } as const;
const fullUser = {
  select: { id: true, email: true, name: true, role: true },
  posts: { select: { id: true, title: true } }
} as const;

// Use them
const minimalQuery = userQuery(minimalUser);
const fullQuery = userQuery(fullUser);

Conditional Relations

Include relations conditionally:

function getUserQuery(includePosts: boolean) {
  const base = { select: { id: true, email: true, name: true } };

  if (includePosts) {
    return userQuery({
      ...base,
      posts: { select: { id: true, title: true } }
    });
  }

  return userQuery(base);
}

Type Extraction

Extract types from queries:

import { z } from 'zod';

const query = userQuery({
  select: { id: true, email: true },
  posts: { select: { title: true } }
});

// Extract the validated type
type UserWithPosts = z.infer<ReturnType<typeof query.parse>>;
// or
type UserWithPosts = z.output<typeof query>;

API Reference

createRegistry(config)

Creates a registry for building queries.

const registry = createRegistry({
  models,           // From generated-index.ts
  relations: modelRelations,  // From generated-index.ts
});

registry.createQuery(modelName)

Returns a query builder function for the specified model.

const userQuery = registry.createQuery('user');
const postQuery = registry.createQuery('post');

Query Builder Return Object

const query = userQuery({ select: { id: true } });

query.query       // Prisma query object: { select: { id: true } }
query.parse()     // Validates single result (throws on error)
query.safeParse() // Validates single result (returns result object)
query.array()     // Returns schema for validating arrays
query.partial()   // Returns schema with all fields optional

Priority Patterns

| Priority | Pattern | When to Use | |----------|---------|-------------| | Critical | query.parse(result) | Validate findFirst/findUnique results | | Critical | query.array().parse(results) | Validate findMany results | | High | query.safeParse(result) | When you need error handling | | High | Nested relations | Querying with related data | | Medium | query.partial() | PATCH endpoints, partial updates | | Medium | Combining with where/orderBy | Filtered queries | | Low | Type extraction | Advanced TypeScript usage |

Common Mistakes

Mistake 1: Forgetting .array() for findMany

// Wrong - will fail validation
const users = await prisma.user.findMany(query.query);
const validated = query.parse(users); // Error: expected object, got array

// Correct
const validated = query.array().parse(users);

Mistake 2: Using query.query as the whole argument

// Wrong - overwrites your conditions
const user = await prisma.user.findFirst(query.query); // OK
const user = await prisma.user.findFirst({
  query.query, // Syntax error!
  where: { id: '123' }
});

// Correct - spread the query
const user = await prisma.user.findFirst({
  ...query.query,
  where: { id: '123' }
});

Mistake 3: Accessing unselected fields

const query = userQuery({ select: { id: true } });
const user = query.parse(result);

// Wrong - 'email' wasn't selected
console.log(user.email); // TypeScript error

// Correct - add 'email' to select
const query = userQuery({ select: { id: true, email: true } });

For more patterns, see references/QUERY-PATTERNS.md.