Skip to content

feat(database-design): add soft-delete query filters and helpers#104

Open
PAMulligan wants to merge 2 commits into
mainfrom
53-add-soft-delete-middleware-and-drizzle-query-filters
Open

feat(database-design): add soft-delete query filters and helpers#104
PAMulligan wants to merge 2 commits into
mainfrom
53-add-soft-delete-middleware-and-drizzle-query-filters

Conversation

@PAMulligan
Copy link
Copy Markdown
Contributor

Summary

pipeline.config.json enables soft deletes by default (database.softDeleteDefault: true), but no middleware or query-filter implementation existed — generated projects got a deleted_at column with no tooling to use it. This PR adds the full pattern to the templates and the database-design skill.

Changes

  • Schema column — nullable deletedAt timestamp added to the shared users schema template (null = active, timestamp = soft-deleted).
  • shared/src/db/soft-delete.ts (new) — reusable helpers:
    • notDeleted(table)WHERE deleted_at IS NULL
    • withSoftDeleteFilter(table, includeDeleted, extra?) → hides deleted rows unless ?include_deleted=true, AND-ing in an optional extra clause
    • softDelete(db, table, id) → stamps deletedAt instead of deleting
    • restore(db, table, id) → clears deletedAt
    • Generic over Drizzle's PgDatabase query-result and schema params, so it works with both the postgres-js (Node) and neon-http (Workers) clients with no casts on the query logic.
  • node/src/routes/users.ts (new) — route handler pattern: list/get with ?include_deleted=true, DELETE /:idsoftDelete() (404 via .returning()), and POST /:id/restore.
  • shared/tests/soft-delete.test.ts (new) — asserts the exact SQL/params each helper produces via Drizzle's .toSQL() (no live DB required).
  • database-design skill — new "Step 6.5 — Soft Delete Columns & Helpers" gated on softDeleteDefault; adds the column + index to the Users example, omits deletedAt from write validators, updates the Output table and Design Decisions.

Acceptance criteria

  • deleted_at column in the schema template
  • Query helper filtering WHERE deleted_at IS NULL
  • softDelete() that sets deleted_at instead of deleting
  • DELETE route handler pattern calling softDelete()
  • ?include_deleted=true admin query param
  • database-design skill generates soft-delete columns when enabled
  • Integration tests for soft-delete behavior
  • CI passes

Verification

  • tsc --noEmit passes for both tsconfig.node.json and tsconfig.cloudflare.json (the CI typecheck-templates job).
  • No JSON files changed; all tracked JSON still parses.
  • Test assertions were confirmed against the installed drizzle-orm@0.45.2 — e.g. softDelete emits update "users" set "deleted_at" = $1 where "users"."id" = $2 with [Date, id]; restore passes [null, id].

Closes #53

🤖 Generated with Claude Code

Implements the soft-delete pattern that pipeline.config.json enables by
default (database.softDeleteDefault: true) but was previously unimplemented.

- Add nullable deletedAt column to the shared users schema template
- Add shared db/soft-delete.ts with notDeleted, withSoftDeleteFilter,
  softDelete, and restore helpers (generic over postgres-js and neon-http)
- Add a node users route demonstrating DELETE -> softDelete and the
  ?include_deleted=true admin query param
- Add tests asserting generated SQL for each helper
- Document soft-delete column/helper/route generation in the
  database-design skill, gated on softDeleteDefault

Closes #53

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@PAMulligan PAMulligan linked an issue Jun 1, 2026 that may be closed by this pull request
8 tasks
@github-actions github-actions Bot added area: skills Claude Code skills area: templates Starter templates labels Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: skills Claude Code skills area: templates Starter templates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add soft delete middleware and Drizzle query filters

1 participant