{
  "id": "postgres-search-app",
  "type": "context-packs",
  "category": "context",
  "locale": "fr",
  "url": "/fr/context/postgres-search-app",
  "title": "Application de recherche PostgreSQL — Pack de contexte",
  "description": "Pack de contexte copiable pour une application de recherche plein texte et vectorielle basée sur PostgreSQL avec pgvector, afin que votre agent IA écrive du code SQL et des migrations corrects dès le départ.",
  "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": "Collez ceci en haut d'une tâche afin que l'agent comprenne le schéma Postgres,\nla stratégie de recherche et le workflow de migration avant de toucher au code de requête.\n\n## Contexte du projet\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## Stack\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## Structure du répertoire\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## Conventions de codage\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## Limites des tâches IA\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```"
}