Add after-restore hook - #1658
Conversation
🦋 Changeset detectedLatest commit: 9585274 The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
PR template validation failedPlease fix the following issues by editing your PR description:
See CONTRIBUTING.md for the full contribution policy. |
Query-count snapshot changes40 routes changed, total Δ +182 queries. SQLite
D1
Comparing snapshot files between base and head. Updated automatically on each push. |
Scope checkThis PR touches 26 files. PRs with a broad scope are harder to review. Please confirm the scope hasn't drifted beyond the intended change. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
| collection: string, | ||
| id: string, | ||
| ): Promise<ApiResult<{ restored: true }>> { | ||
| ): Promise<ApiResult<{ restored: true; item: ContentItem }>> { |
There was a problem hiding this comment.
kept restored: true for backwards compat
| const tableName = getTableName(type); | ||
|
|
||
| const result = await sql` | ||
| const result = await sql<Record<string, unknown>>` |
There was a problem hiding this comment.
Added returning the restored element instead of a true or false, to avoid extra queries and concurrency inconsistencies.
There was a problem hiding this comment.
Pull request overview
This PR adds a new content:afterRestore plugin hook that fires when trashed content is restored, and refactors the core restore path to return the restored ContentItem (avoiding an extra DB lookup). It also updates plugin/reference documentation and adjusts tests to match the new restore return shape.
Changes:
- Add
content:afterRestoreto the hook system (types, manifest validation, capability enforcement, pipeline execution, and runtime dispatch to trusted + sandboxed plugins). - Refactor content restore to return the restored item (
ContentItem | null) and thread that through the handler/runtime. - Update docs/templates and tests to document/validate the new hook and restore behavior.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| templates/starter/.agents/skills/creating-plugins/references/hooks.md | Document the new content:afterRestore hook in the starter template reference. |
| templates/starter-cloudflare/.agents/skills/creating-plugins/references/hooks.md | Document the new content:afterRestore hook in the Cloudflare starter template reference. |
| templates/portfolio/.agents/skills/creating-plugins/references/hooks.md | Document the new content:afterRestore hook in the portfolio template reference. |
| templates/portfolio-cloudflare/.agents/skills/creating-plugins/references/hooks.md | Document the new content:afterRestore hook in the Cloudflare portfolio template reference. |
| templates/marketing/.agents/skills/creating-plugins/references/hooks.md | Document the new content:afterRestore hook in the marketing template reference. |
| templates/marketing-cloudflare/.agents/skills/creating-plugins/references/hooks.md | Document the new content:afterRestore hook in the Cloudflare marketing template reference. |
| templates/blog/.agents/skills/creating-plugins/references/hooks.md | Document the new content:afterRestore hook in the blog template reference. |
| templates/blog-cloudflare/.agents/skills/creating-plugins/references/hooks.md | Document the new content:afterRestore hook in the Cloudflare blog template reference. |
| templates/blank/.agents/skills/creating-plugins/references/hooks.md | Document the new content:afterRestore hook in the blank template reference. |
| skills/creating-plugins/references/hooks.md | Update the canonical plugin-creation hook reference to include content:afterRestore. |
| packages/core/tests/unit/plugins/restore-hooks.test.ts | Add a unit test asserting content:afterRestore fires with the restored item payload. |
| packages/core/tests/unit/plugins/hooks.test.ts | Extend HookPipeline tests to include registration/capability enforcement for content:afterRestore. |
| packages/core/tests/integration/search/fts-corruption.test.ts | Update integration assertion to match restore() returning the restored row (not boolean). |
| packages/core/tests/integration/i18n/i18n.test.ts | Update i18n integration assertion to match restore() returning the restored row (not boolean). |
| packages/core/src/plugins/types.ts | Add ContentAfterRestoreHandler and hook typing for content:afterRestore. |
| packages/core/src/plugins/manifest-schema.ts | Allow content:afterRestore in plugin manifest hook-name validation. |
| packages/core/src/plugins/manager.ts | Add runContentAfterRestore() dispatch on the plugin manager. |
| packages/core/src/plugins/index.ts | Export the new ContentAfterRestoreHandler type from the plugin entrypoint. |
| packages/core/src/plugins/hooks.ts | Register/enforce/run the content:afterRestore hook in the HookPipeline. |
| packages/core/src/plugin-types.ts | Add content:afterRestore to the HookHandlers typing map. |
| packages/core/src/emdash-runtime.ts | Run content:afterRestore after a successful restore (deferred via after()), for trusted + sandboxed plugins. |
| packages/core/src/database/repositories/content.ts | Change restore() to UPDATE ... RETURNING * and return `ContentItem |
| packages/core/src/api/handlers/content.ts | Return { restored: true, item } from handleContentRestore() to propagate the restored item. |
| docs/src/content/docs/reference/hooks.mdx | Document content:afterRestore in the public hook reference. |
| docs/src/content/docs/plugins/creating-plugins/hooks.mdx | Document content:afterRestore in the plugin creation docs and hook summary table. |
| .changeset/content-restore-hook.md | Add a minor changeset announcing the new restore hook for plugins. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
There was a problem hiding this comment.
The approach is sound: content:afterRestore fills a clear gap in the content lifecycle hook surface, and refactoring ContentRepository.restore to return the restored item lets the runtime hand it to plugins without an extra round-trip.
I checked the restore handler (packages/core/src/api/handlers/content.ts), the repository update (packages/core/src/database/repositories/content.ts), runtime hook dispatch (packages/core/src/emdash-runtime.ts), hook-pipeline registration and capability gating (packages/core/src/plugins/hooks.ts), the plugin manager/types/manifest-schema exports, the route consumer, and the new/updated tests and docs.
Headline conclusion: the code is clean. The response widening from { restored: true } to { restored: true; item } is backwards-compatible, the RETURNING * update is safe and avoids the extra lookup, and the fire-and-forget dispatch matches the existing afterSave/afterPublish pattern. Tests cover the trusted-plugin path and existing .restore() callers were updated for the new return type.
One non-blocking process note: AGENTS.md/CONTRIBUTING.md require a maintainer-approved Discussion for new features. The PR description cites internal gchat discussion but does not link to a public approved Discussion. Worth confirming that sign-off exists before merging, though the feature itself is a natural fit.
* refactor(core): return restored content item * feat(core): add content restore hook * docs: add content restore hook docs
* refactor(core): return restored content item * feat(core): add content restore hook * docs: add content restore hook docs
refactor(core): return restored content item
Refactored the restore content call in core as to avoid extra db lookups, now "restore" returns the restored item.
feat(core): add content restore hook
docs: add content restore hook docs
What does this PR do?
Adds a hook so plugins can be notified when an item is restored from the bin, so plugins can keep up with what posts are visible.
Closes #
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain.AI-generated code disclosure
Screenshots / test output