{
  "id": "postgres-search-app",
  "type": "context-packs",
  "category": "context",
  "locale": "zh",
  "url": "/zh/context/postgres-search-app",
  "title": "PostgreSQL 搜索应用 — 上下文包",
  "description": "可复制的上下文包，用于基于 PostgreSQL 和 pgvector 的全文与向量搜索应用，让您的 AI 代理从一开始就能编写正确的 SQL 和迁移代码。",
  "tools": [
    "Cursor",
    "Claude Code",
    "Codex",
    "Windsurf"
  ],
  "stack": [
    "Next.js",
    "PostgreSQL",
    "TypeScript"
  ],
  "tags": [
    "postgres",
    "search",
    "context-pack",
    "typescript",
    "nextjs"
  ],
  "difficulty": null,
  "updated": "2026-06-08",
  "markdown": "将此内容粘贴到任务开头，以便代理在接触任何查询代码之前理解 Postgres 模式、搜索策略和迁移工作流。\n\n## 项目背景\n\n```txt\nA content search application that combines PostgreSQL full-text search (tsvector\n+ tsquery) with vector similarity search via the pgvector extension. The backend\nis a Next.js 15 App Router API. Embeddings are generated by OpenAI's\ntext-embedding-3-small model and stored as vector(1536) columns. The database\nruns on Supabase (hosted Postgres 16) and is accessed via the Postgres.js driver.\n```\n\n## 技术栈\n\n```txt\nNext.js 15 (App Router, Route Handlers for search API)\nPostgreSQL 16 with extensions: pgvector, pg_trgm, unaccent\npostgres (Postgres.js) driver — NOT pg/node-postgres\nOpenAI SDK (text-embedding-3-small, 1536 dimensions)\nTypeScript (strict)\nTailwind CSS v4\ndb-migrate for schema migrations (SQL files, not ORM)\n```\n\n## 目录结构\n\n```txt\nsrc/\n  app/\n    api/\n      search/route.ts        # Unified search endpoint (FTS + vector)\n      embed/route.ts         # Embedding generation + upsert\n  lib/\n    db.ts                    # Postgres.js client singleton\n    search.ts                # ftsSearch(), vectorSearch(), hybridSearch()\n    embed.ts                 # OpenAI embedding helper\n    schema/                  # TypeScript types mirroring DB tables\n  components/\n    SearchBar.tsx\n    SearchResults.tsx\nmigrations/\n  001_initial.sql\n  002_add_pgvector.sql\n  003_add_trgm_index.sql\n  ...\nscripts/\n  seed.ts                    # Bulk embed + insert content\n```\n\n## 编码规范\n\n```txt\n- All SQL lives in src/lib/search.ts or in migrations/ — never inline SQL in\n  route handlers or components.\n- Use tagged template literals with Postgres.js (sql`...`) for all queries.\n  Never concatenate user input into SQL strings.\n- Full-text search uses a generated tsvector column with a GIN index.\n  Update triggers maintain the column automatically — do not compute tsvector\n  in application code.\n- Vector columns are vector(1536) for text-embedding-3-small. If the model\n  changes, the dimension must change too — this requires a migration.\n- Hybrid search combines FTS rank and cosine similarity with a weighted sum.\n  The weights live in src/lib/search.ts as named constants, not magic numbers.\n- Schema changes require a new numbered SQL file in migrations/ and must be\n  applied with: `npm run db:migrate` (which calls db-migrate up).\n- All migration files are append-only — never edit a migration that has been\n  applied to production.\n- Index creation uses CONCURRENTLY in production migrations to avoid table locks.\n```\n\n## AI 任务边界\n\n```txt\n- Do not switch to an ORM (Prisma, Drizzle) without explicit instruction.\n  Raw SQL with Postgres.js is intentional for fine-grained query control.\n- Do not call the OpenAI API in a request that also queries the DB within a\n  transaction — embeddings are generated before the transaction opens.\n- Do not use cosine similarity (<->) without a vector index (ivfflat or hnsw).\n  Always add the index in the same migration that adds the column.\n- Do not store user-supplied text as embeddings without sanitizing PII first.\n- Do not change the embedding model without a re-indexing migration that\n  updates all existing vector values and the column dimension.\n- pg_trgm GIN indexes are used for ILIKE fallback — do not drop them.\n- Supabase Row Level Security (RLS) is enabled on all tables. Any new table\n  must have RLS enabled and at least one policy added in its migration.\n```\n\n## llms.txt\n\n```txt\n# PostgreSQL Search App\nDB: Supabase (Postgres 16) — postgres.js driver (src/lib/db.ts)\nSearch: FTS (tsvector/tsquery) + vector (pgvector) hybrid (src/lib/search.ts)\nEmbeddings: OpenAI text-embedding-3-small, 1536 dims (src/lib/embed.ts)\nMigrations: db-migrate, SQL files in migrations/ — append-only\nRLS: enabled on all tables — new tables need policies\nKey indexes: GIN on tsvector column, hnsw on vector column\nDo NOT: inline SQL in routes, concatenate user input into SQL, skip RLS\n```"
}