feat(soft-delete): implement soft delete query filter, helper, and route pattern - #103
Merged
Merged
Conversation
…ute pattern pipeline.config.json enabled database.softDeleteDefault by default, but no implementation existed — generated projects got a deleted_at column with no filtering, forcing manual WHERE deleted_at IS NULL in every query. Add the full pattern: - templates/snippets/shared/src/db/soft-delete.ts: notDeleted/withNotDeleted/ softDeleteFilter query filters, softDelete() mutation (stamps deleted_at, guards on IS NULL, returns rows affected), and includeDeletedQuerySchema for the ?include_deleted=true admin flag - add nullable deleted_at column to the users schema template - soft-delete.test.ts: 13 tests asserting against real Drizzle SQL generation - setup-project.sh copies the helper and test into generated projects - database-design skill: generate deleted_at columns + indexes and the helper when softDeleteDefault is true - route-generation skill: service.delete() calls softDelete(); reads filter deleted rows; optionalAuth middleware exposes deleted rows to admins only - tdd-from-schema skill: soft-delete test cases Verified: tsc passes for both Cloudflare and Node targets; 13/13 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PAMulligan
added a commit
that referenced
this pull request
Jun 7, 2026
softDelete()'s `db` parameter was typed `PgDatabase<PgQueryResultHKT>`, which pins the schema generics to their `Record<string, never>` defaults. Because that schema generic is invariant, passing a real, schema-typed Drizzle client (e.g. `PostgresJsDatabase<typeof schema>`) fails to compile with TS2379 — so the documented `await softDelete(db, table, id)` pattern could not actually be used in a generated project. Widen the signature to infer the query-result and schema generics, which accepts any concrete Drizzle client while staying fully backward compatible (broadening a constraint never breaks existing callers). Add templates/snippets/node/src/routes/users.ts: a typechecked Hono route that exercises softDelete(), softDeleteFilter(), and includeDeletedQuerySchema against the real db client, so the typecheck suite now covers an actual soft-delete invocation and this regression cannot return unnoticed. Refs #53 (soft-delete feature landed in #103). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PAMulligan
added a commit
that referenced
this pull request
Jun 7, 2026
…104) softDelete()'s `db` parameter was typed `PgDatabase<PgQueryResultHKT>`, which pins the schema generics to their `Record<string, never>` defaults. Because that schema generic is invariant, passing a real, schema-typed Drizzle client (e.g. `PostgresJsDatabase<typeof schema>`) fails to compile with TS2379 — so the documented `await softDelete(db, table, id)` pattern could not actually be used in a generated project. Widen the signature to infer the query-result and schema generics, which accepts any concrete Drizzle client while staying fully backward compatible (broadening a constraint never breaks existing callers). Add templates/snippets/node/src/routes/users.ts: a typechecked Hono route that exercises softDelete(), softDeleteFilter(), and includeDeletedQuerySchema against the real db client, so the typecheck suite now covers an actual soft-delete invocation and this regression cannot return unnoticed. Refs #53 (soft-delete feature landed in #103). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
pipeline.config.jsonsetsdatabase.softDeleteDefault: true, but no implementation existed. Generated projects got adeleted_atcolumn with no filtering — every read query had to manually addWHERE deleted_at IS NULL, and DELETE endpoints physically removed rows.What
Implements the soft-delete pattern end-to-end, in both the templates (code shipped into generated projects) and the skills (generation instructions).
New shipped code
templates/snippets/shared/src/db/soft-delete.ts— the reusable helper:notDeleted(table)→WHERE deleted_at IS NULLfilterwithNotDeleted(...)/softDeleteFilter(table, includeDeleted, ...)→ compose the filter with query conditionssoftDelete(db, table, id)→ stampsdeleted_at = now()instead of deleting; guards onIS NULL(idempotent), returns rows affected (0 = missing/already-deleted → 404)includeDeletedQuerySchema→ Zod schema for?include_deleted=trueschema.ts— nullabledeleted_atcolumn added to theuserstemplatesoft-delete.test.ts— 13 integration tests asserting against real Drizzle SQL generationsetup-project.sh— copies the helper + test into generated projectsSkill updates
deleted_atcolumns + indexes and the helper whensoftDeleteDefaultis true; updated examples, migration SQL, validators, Output table, Design Decisionsdelete()callssoftDelete();list/getById/updatefilter deleted rows; newoptionalAuthmiddleware exposes deleted rows to admins only via?include_deleted=true(non-admins always get live rows)Acceptance criteria
deleted_attimestamp column in the Drizzle schema templateWHERE deleted_at IS NULLsoftDelete()function that setsdeleted_atinstead of deletingsoftDelete()?include_deleted=truequery param for admin endpointsdatabase-designskill generates soft-delete columns when enabledVerification
tsc --noEmitpasses for both Cloudflare and Node targets (matches CI'stypecheck-templatesjob)vitest run)bash -n scripts/setup-project.shclean; no JSON/lockfile changes🤖 Generated with Claude Code