Skip to content

feat(soft-delete): implement soft delete query filter, helper, and route pattern - #103

Merged
PAMulligan merged 1 commit into
mainfrom
feat/soft-delete-implementation
May 31, 2026
Merged

feat(soft-delete): implement soft delete query filter, helper, and route pattern#103
PAMulligan merged 1 commit into
mainfrom
feat/soft-delete-implementation

Conversation

@PAMulligan

Copy link
Copy Markdown
Contributor

Why

pipeline.config.json sets database.softDeleteDefault: true, but no implementation existed. Generated projects got a deleted_at column with no filtering — every read query had to manually add WHERE 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 NULL filter
    • withNotDeleted(...) / softDeleteFilter(table, includeDeleted, ...) → compose the filter with query conditions
    • softDelete(db, table, id) → stamps deleted_at = now() instead of deleting; guards on IS NULL (idempotent), returns rows affected (0 = missing/already-deleted → 404)
    • includeDeletedQuerySchema → Zod schema for ?include_deleted=true
  • schema.ts — nullable deleted_at column added to the users template
  • soft-delete.test.ts — 13 integration tests asserting against real Drizzle SQL generation
  • setup-project.sh — copies the helper + test into generated projects

Skill updates

  • database-design — "Step 6a" generates deleted_at columns + indexes and the helper when softDeleteDefault is true; updated examples, migration SQL, validators, Output table, Design Decisions
  • route-generation — service delete() calls softDelete(); list/getById/update filter deleted rows; new optionalAuth middleware exposes deleted rows to admins only via ?include_deleted=true (non-admins always get live rows)
  • tdd-from-schema — soft-delete test cases (hidden from default list, visible to admins with the flag, idempotent double-delete → 404)

Acceptance criteria

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

Verification

  • tsc --noEmit passes for both Cloudflare and Node targets (matches CI's typecheck-templates job)
  • 13/13 new tests pass (vitest run)
  • bash -n scripts/setup-project.sh clean; no JSON/lockfile changes

🤖 Generated with Claude Code

…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>
@github-actions github-actions Bot added area: skills Claude Code skills area: scripts Automation scripts area: templates Starter templates labels May 31, 2026
@PAMulligan PAMulligan self-assigned this May 31, 2026
@PAMulligan
PAMulligan merged commit 04fac4d into main May 31, 2026
8 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: scripts Automation scripts area: skills Claude Code skills area: templates Starter templates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant