资讯动态

EmDash 插件内容 API 指南:Schema、翻译、发布与恢复的完整实现

发布时间:2026/9/24 15:36:49 来源:尧图企业网站定制
CMS后端前端插件系统【免费下载链接】emdashEmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress项目地址https://gitcode.com/gh_mirrors/emdas/emdash点击查看免费下载EmDash 是一款基于 Astro 的全栈 TypeScript CMSWordPress 的精神继任者其插件系统为第三方扩展提供了一套按能力capability门控的内容 API。本文围绕插件内容访问层的核心参考文档系统讲解schema:read、content:read、content:revisions:read、content:write、hooks.content-policy:register、content:publish与content:restore这七类能力对应的完整 API 面、底层实现原理与运行时测试方法。读完本文你将掌握在 EmDash 插件中安全地读取集合与字段定义、查询与翻译内容、创建多语言条目、注册发布前策略钩子、执行修订版revision感知的发布/恢复操作以及编写覆盖生产边界的测试用例。能力门控与执行模式一切 API 的前提插件内容 API 并非无条件开放而是遵循能力capability声明—授予—门控的三段式信任模型。核心参考文档明确指出内容 API 是能力门控的并且由三种执行模式共享——原生native插件、Cloudflare Worker Loader 以及 Node/workerd 沙箱执行。能力词汇表的权威定义位于共享契约包 packages/plugin-types/src/index.ts。其中与内容相关的当前能力名称为能力授予的访问隐含关系schema:read公开的集合与字段定义批量化无content:read内容身份、翻译、已发布公开 URL无content:revisions:read保留的修订数据隐含content:readcontent:write创建、更新、删除、翻译创建隐含content:readcontent:publish修订版围栏的发布、取消发布、调度、取消调度隐含content:readcontent:restore已删除trashed内容的修订版读取与恢复无hooks.content-policy:register发布前、调度前、取消发布前策略钩子无能力之间的蕴含关系在 packages/core/src/plugins/types.ts 中以PLUGIN_CAPABILITY_IMPLICATIONS常量显式声明如content:write→content:read、content:publish→content:read并通过normalizePluginCapabilities在运行时补全因此在声明content:write时无需重复声明content:read。值得注意的是能力词汇表中还保留了一批废弃别名如read:content、write:content。这些旧名称在CAPABILITY_RENAMES映射packages/plugin-types/src/index.ts中对应到新名称并在所有外部边界manifest 解析、definePlugin、沙箱适配器被静默归一化插件作者在bundle/validate时会收到警告publish时则硬性失败。新插件应只使用规范名称。能力的声明位置是插件清单emdash-plugin.jsonc由 skills/creating-plugins/SKILL.md 说明。清单本质上是身份与信任契约其capabilities、allowedHosts字段与结构化信任契约declaredAccesspackages/plugin-types/src/index.ts是同一信任模型的两种视图——capabilitiesToDeclaredAccess与declaredAccessToCapabilities负责在解析边界双向转换。新增能力、公开路由或 MCP 工具都需要管理员重新审批。Discovery and readsSchema 与内容读取schema:read批量获取集合与字段定义schema:read向插件暴露ctx.schema.listCollections()与ctx.schema.getCollection(slug)。两者都返回批量化batched的公开集合与字段定义——也就是说插件拿到的是完整的字段元数据类型、必填、唯一、默认值、校验规则、widget、可搜索、可索引、可翻译、排序权重而不是逐字段查询。底层实现位于 packages/core/src/plugins/context.ts 的createSchemaAccess它通过SchemaRegistry读取集合及字段再映射为插件侧的类型CollectionSchemaInfo。映射时对每个字段保留slug、label、type、required、unique、default、validation、widget、options、searchable、indexed、translatable、sortOrder等属性集合级信息还包含supports、hasSeo、titleField、dateField、urlPattern、routable与hidden——routable与urlPattern对后面getPublicUrl()的 URL 解析至关重要。content:read身份、翻译与公开 URLcontent:read暴露ctx.content.get()、list()、getTranslations()与getPublicUrl()。参考文档强调内容结果包含身份id/type/slug、状态status、locale、data、时间戳createdAt/updatedAt/publishedAt/scheduledAt、作者 ID、翻译组translationGroup、live 与 draft 修订指针liveRevisionId/draftRevisionId以及行版本version。这些字段在 packages/core/src/plugins/content-access.ts 的get()实现中可以一一对应——每个读取结果都会附带liveRevisionId与draftRevisionId两个指针这正是后续发布操作_rev围栏的源头。get()还会在集合启用 SEO 时附加seo数据通过SeoRepository读取list()content-access.ts支持limit默认 50、cursor分页、orderBy与where过滤返回{ items, cursor, hasMore }分页结构。getTranslations(collection, id)content-access.ts返回{ translationGroup, translations }若条目属于某个翻译组则列出该组全部翻译的id、locale、slug、status与updatedAt未加入任何组的条目则只有自身。getPublicUrl(collection, id)content-access.ts是内容读取中最具约束性的一个它只解析已发布、可路由routable的公开 URL绝不返回预览地址。实现中依次校验站点 URL 已配置、条目状态为published、slug 非空、集合routable再通过resolveLocalizedContentRoutePath依据集合urlPattern结合 slug、id、发布日期、locale 与trailingSlash设置拼出最终路径。任何条件不满足都返回null。content:revisions:read修订历史读取content:revisions:read在读取能力之上追加listRevisions()与getRevision()。实现位于 content-access.ts通过RevisionRepository.findVisibleByEntry/findVisibleById读取修订快照。参考文档点出两个关键语义修订快照可以保留后来被删除的字段值——即历史数据不会因字段被移除而丢失但快照省略修订作者身份author identity——实现中通过解构{ authorId: _authorId, ...revision }将authorId从返回对象剔除保证插件只能看到内容演化历史而不能借此收集用户行为数据。Writes and translationscontent:write的创建、更新、删除与翻译创建内容与翻译条目content:write追加create()、update()与delete()。参考文档给出了创建翻译的核心示例await ctx.content!.create(posts, data, { locale: fr, translationOf: sourceId });该调用的底层路径在 packages/core/src/plugins/context.ts 的createContentAccessWithWrite.create()中先通过resolveContentCreateLocale解析 locale随后在同一数据库事务内完成内容行创建如果data中携带保留的seo键还会在同一事务内写入 SEO 面板数据assertSeoEnabled会先校验集合是否启用 SEO未启用则抛错拒绝。参考文档强调的翻译语义在实现中有明确对应源条目必须是同一集合中的活跃条目active entry翻译行通过translationOf: sourceId加入translationGroup新行继承不可翻译字段、署名byline credits与分类taxonomy分配校验与保存钩子照常运行validation 与 save hooks 不因来源是插件而跳过对发起创建的插件有重入围栏re-entrancy fencing——防止同一插件在同一操作链上递归写回导致无限循环一个翻译组每个 locale 只允许一个活跃行one active row per locale。更新与删除update(collection, id, data)context.ts通过updateDraftAware写入同样支持seo键的路由实现还做了细节优化仅当存在字段更新时才调用仓库的 draft-aware 更新纯 SEO 更新不会误触updated_at/version的递增。delete(collection, id)context.ts在删除后主动释放该条目的编辑锁entry lock并标记内容-媒体使用关系缓存为陈旧CONTENT_USAGE_STALE确保引用该内容的媒体使用计数后续会重新计算——这与核心 REST API 的handleContentDelete行为镜像。稳定的失败码参考文档列出了content:write路径的稳定失败类型插件代码应对它们做显式处理失败码含义CONFLICT并发冲突如翻译组内同 locale 已存在活跃行、版本冲突NOT_FOUND集合、条目或源翻译不存在VALIDATION_ERROR字段校验失败、SEO 未启用等输入问题SAVE_REJECTED保存钩子拒绝了本次写入Publication policy不授予读写权的发布前策略hooks.content-policy:register是一类特殊能力它授予content:beforePublish、content:beforeSchedule与content:beforeUnpublish三个策略钩子的注册权但绝不授予内容读取、写入或发布动作本身。也就是说一个插件可以在不触碰任何内容数据的情况下对所有发布类动作实施闸门控制。钩子与能力的绑定关系在 packages/core/src/plugins/hooks.ts 中清晰可见三个策略钩子一律映射到hooks.content-policy:register。策略钩子通过返回{ cancel: true, reason }拒绝动作拒绝原因需要提供有界bounded的字符串方便管理员查阅。拒绝原因的约束在 packages/core/src/plugins/content-policy.ts 中实现MAX_CONTENT_POLICY_REASON_LENGTH 500原因按 Unicode 码点计数不得超过 500 字符禁止控制字符除 tab、LF、CR 外的小于 32 的码点以及 127 均被拒绝拒绝原因必须是去空白后非空的字符串inspectContentPolicyDecision会严格校验决策对象形状必须恰好是{ cancel: true, reason: string }两个键cancel必须严格为true否则判定为invalid。事件来源origin与调度执行参考文档指出发布事件会标识其来源——API、MCP、可视化编辑器visual-editor、插件plugin、调度器scheduler与系统system。经过认证的人类操作authenticated human actions还会附带操作者身份actor identity与来源这让策略钩子可以区分管理员手动发布与定时任务自动发布。对调度发布有一个特别值得注意的行为当调度器触发发布时发布策略会被再次执行若策略拒绝条目会被取消调度unscheduled并且有界的拒绝原因会被记录下来供管理员查看。记录机制同样位于 content-policy.ts以SCHEDULED_POLICY_REJECTION_PREFIX emdash:scheduled-policy-rejection:为前缀的 KV 键存储{ collection, id, pluginId, reason, rejectedAt }结构并附带版本_rev以支持后续的读取与清理。Publication and restore actions修订版围栏的发布与恢复content:publish先读后写_rev贯穿所有变更content:publish追加getVersioned()、publish()、unpublish()、schedule()与unschedule()。参考文档给出了最重要的使用纪律先读取Read first再把不透明的_rev传给每一个变更操作。这是典型的乐观并发控制optimistic concurrency模型。_rev是不透明opaque的修订令牌——调用方不得解析或推断其内部结构只需原样回传。ContentActionCallbacks接口packages/core/src/plugins/context.ts完整定义了这些操作publish与unpublish接收{ _rev }schedule额外接收{ scheduledAt, _rev }unschedule与restore同样要求_rev。如果本地持有的_rev与服务器当前版本不匹配操作会被拒绝从而避免覆盖他人已提交的变更。一次成功的发布动作会返回下一个修订版本并且照常触发完整的后续流水线正常策略policy、同步synchronization、媒体使用计数media-usage、缓存失效cache-invalidation以及 after 钩子行为。也就是说插件发起的发布与管理员在后台的发布走的是同一条生产路径而非旁路捷径。content:restore隔离的恢复能力content:restore单独追加getTrashedVersioned()与restore()而且不授予普通内容读取权——插件只能读取已删除条目的版本化数据不能借此窥探活跃内容。参考文档同时明确该能力不授予永久删除permanent deletion即恢复是单向的找回而非删除管线的一部分。从 context.ts 的实现可以看到这种隔离是如何构造的仅授予content:restore的插件会得到一个get/list直接抛Missing capability: content:read的对象再通过Object.assign附加getTrashedVersioned与restore两个方法。这是能力最小化在 API 面形状上的直接体现。插件动作来源标记与重入拒绝所有插件发起的动作都会上报{ source: plugin, pluginId }让策略钩子与审计链路能够精确识别动作发起者。参考文档还强调一个重入保护同一插件对同一 canonical 条目重复进入同一动作会被拒绝。结合create()路径的保存钩子重入围栏可以看出重入防护贯穿内容写路径与发布路径——这是防止插件钩子触发的递归写操作在生产环境失控的关键设计。能力门控的运行时装配上面所有 API 最终都由 packages/core/src/plugins/context.ts 的PluginContextFactory.createContext()按插件能力集合装配。其核心逻辑是content:write→ 构造createContentAccessWithWrite并传入beforeContentWrite回调与若同时声明revisions标志仅content:read→ 构造只读createContentAccesscontent:publish→ 在读取访问之上Object.assign五个发布方法仅在运行时提供了contentActions时可用content:restore→ 在抛错的占位访问之上附加恢复方法schema:read→ 构造createSchemaAccess未声明的能力对应的ctx属性保持undefined插件访问即得undefined不会静默降级为弱权限。这解释了参考文档第一句的含义内容 API 由能力门控且三种执行模式native、Cloudflare Worker Loader、Node/workerd共享同一套装配逻辑——沙箱边界两侧运行的是同一份契约。Runtime tests用运行时夹具与动作验证生产边界参考文档给出了测试内容插件的明确分层策略这与 skills/creating-plugins/SKILL.md 中两个测试宿主的分工一一对应createPluginTestHost()面向快速传输层测试——hook、route、manifest、能力、KV、settings、storage 的往返行为createPluginRuntimeTestHost()当测试必须触及真实内容动作时使用——包括插件激活、媒体、评论、重定向、调度、重启、授权、CSRF、缓存失效、Block Kit 校验与 saved-entry 扩展。针对内容 API 的具体测试建议直接来自参考文档用运行时夹具runtime fixtures准备初始状态初始条目、翻译、署名bylines与分类taxonomy分配都应通过夹具建立——关键点在于夹具建立状态但不触发钩子从而隔离数据准备与行为验证两个阶段用运行时动作runtime actions驱动发布路径发布、取消发布、调度、恢复必须走真实的生产边界验证策略钩子、同步、缓存失效的完整链路用检查器inspectors断言持久化状态读回数据库/存储中的实际状态确认动作的副作用落盘正确按需覆盖这些行为陈旧修订stale revision_rev过期、策略拒绝policy rejection{ cancel: true, reason }、重复 locale同一翻译组内同 locale 的活跃行冲突、重入re-entrancy、重启restart以及缓存失效cache invalidation——这些都是内容 API 最容易出现回归的边界。测试宿主的生命周期纪律是每次使用后必须 dispose 宿主避免跨测试的状态泄漏对于运行器敏感的行为如 Cloudflare Worker Loader 与 Node/workerd 的差异再补充双运行器一致性测试。总结内容 API 的安全护栏全景EmDash 插件内容 API 的设计可以概括为一组层层嵌套的安全护栏能力最小化读取、修订读取、写入、发布、恢复、策略注册是六个正交的能力面各自独立授予packages/plugin-types/src/index.ts隐式蕴含写/发布自动隐含读避免声明冗余packages/core/src/plugins/types.ts版本围栏发布与恢复操作强制_rev先读后写杜绝乐观并发下的覆盖packages/core/src/plugins/context.ts策略与执行的分离hooks.content-policy:register只给闸门不给钥匙拒绝原因有界且持久化packages/core/src/plugins/content-policy.ts重入防护与来源标记插件动作带{ source: plugin, pluginId }同插件对同条目的重复进入被拒绝只读边界getPublicUrl()永不返回预览地址修订快照剥离作者身份content:restore不授予普通读取。对于插件开发者而言最实用的心智模型是先声明最小能力集再以_rev作为所有写路径的通行证最后用运行时测试宿主把发布、恢复、翻译与策略拒绝全部跑成自动化用例。相关扩展资料可继续阅读 skills/creating-plugins/references/publishing.md发布流程专题与 skills/creating-plugins/references/sandbox-boundaries.md沙箱边界专题。赞分享CMS后端前端插件系统【免费下载链接】emdashEmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress项目地址https://gitcode.com/gh_mirrors/emdas/emdash点击查看免费下载相关推荐EmDash 插件内容 API 实战Schema 读取、多语言翻译、发布策略与版本化恢复EmDash 插件内容 API 实战Schema 读取、多语言翻译、发布策略与版本化恢复 EmDash全栈 TypeScript CMSAstro 生态CMS后端前端插件系统三步接入 Surface Pen 压感用 windows-rs 让 Rust 应用听懂笔尖轻重三步接入 Surface Pen 压感用 windows rs 让 Rust 应用听懂笔尖轻重 在绘画应用里写字如果笔触粗细永远是固定值画出来的线条像 PCMS后端前端插件系统EmDash CMS 插件内容 API 完全指南schema、翻译、发布策略与修订栅栏EmDash CMS 插件内容 API 完全指南schema、翻译、发布策略与修订栅栏 EmDash基于 Astro 的全栈 TypeScript CMSCMS后端前端插件系统上一篇ADK-Python 运行器报 SessionNotFoundError 时怎么排查先创建会话还是设置 auto_create_session下一篇DVWADamn Vulnerable Web Application完整安装配置与故障排查实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价