@ 12- 应用程序/ 实体- 生命周期
通用、便携式实体寿命周期机械——设计用于任何 项目。 无框架核心,零运行时间依赖,通过适配器存储.
四个功能,每个插件和游戏每集:
| 特性 | 它给你什么 |
|---|---|
| 版本 | 基于 Diff 的历史( 仅存储已更改的字段), 恢复到任意版本, 保留政策( 最大计数/ 最大年龄) 有安全压缩 , publishedVersion 镜像 |
| 回收桶 | 使用依附者的树登记册软删除,恢复/永久删除 |
| 草稿 | 每个项目(或全新项目)一份未出版的工作副本,公布/丢弃;每集选入:新项目草案更新到位,由作者恢复,30天后被清理(ADOPTING.md QQ新项目草案) |
| 核准 | 由非核准人写作成为待处理的更改请求;核准人申请/拒绝 |
版本、草案和核准 每名租户可显示特征 通过a 三层门;一旦一个收藏被插入,循环箱总是被打开.
插头和游戏表面(12-17)
下面的核心是内核;主机通常采用整个地表 而不是布线:
@12-apps/entity-lifecycle/server—— 说createApiEntityLifecycle({ db, entities, directory }): 版本 / 恢复 / 草稿 / 再循环- bin / 每一登记藏品的GENERTED 每个声明,在软件包拥有的4个表格上.@12-apps/entity-lifecycle/hono—— 说entityLifecycleRouter({ ...config, resolveActor }),单调挂载(hono是一个可选的对等点).@12-apps/entity-lifecycle/react—— 说createWebEntityLifecycle({ apiBase, copy })数字 : lixeira + Aprovações 页面,加上VersionHistoryDialog财务报告和审定财务报表DraftBannera 主机滴入到自己的编辑器中.@12-apps/entity-lifecycle/mcp—— 说lifecycleMcpEndpoints({ collectionPath, noun, summaries })相同的八种能力 描述一个AGENT, 每集一通电话。 上面的路线已经来自一个 声明;他们的MCP描述是主机仍然写出来的部分 每个收藏一次。 形状是包的,摘要保持 东道主的 (@12-apps/mcp财务报告和审定财务报表zod是可选的同伴).prisma/entity-lifecycle.prisma+ 键prisma/migrations/*——型号 部分和迁移情况,通过prisma:sync编辑 主机的插件- 迁移同步 .
完全收养合同——配置表,终点清单,规则 和已拥有这些表格的主机的笔记——在 采用.md。 。 。 .
概念
抓图和 diffs
一个实体的版本为 Snapshot JSON 其顶级域图
(允许的去除值). 版本 1 存储 FULL 快照; 每个后期版本
仅存储已更改的顶级字段(data和已删除的字段
(单位:千美元)removedFields) (中文(简体) ). N版的状态通过重放行 1..N来重建
(单位:千美元)materializeVersion) (中文(简体) ). 保留修补折叠所覆盖的前缀
最古老的活生生的一行(被推广到一个完整的快照),所以链条总是
复有相会.
三层特性门
enabled = code (collection opted in)
&& plan (tenant is ENTITLED — what they pay for)
&& tenant (the tenant kept it ENABLED — never forced)
主办方按要求解决两个租户层,并通过
LifecycleContext图书馆从来不读租户州本身.
与 @12-apps/entitlements (FUT-278,一份有文件记载的决定):
resolveFeature 故意呆在这里,没有依赖和同步,
而不是迁移到 @12-apps/entitlements' resolveEntitlement 那个..
解析器是它的严格通缩(quotas, overs, a status level),带有
相同的图层顺序、理由字符串和未变换默认
布尔子集 。 迁移会买不到任何可观察到的行为 同时强迫
aync 引擎进入内核同步守护并翻转生命周期写入
从他们的403到引擎的402上升道的否定. 平等
使独立安全 由
apps/web/lib/entitlements/__tests__/resolver-drift.test.ts; 主机桥
两国在 apps/web/lib/entitlements/index.ts (FUT-326号)
合作赠款工会).
Schema 更改 vs 恢复( 回滚姿态)
版本存储了松散的JSON,故意与活生生的"计划"脱钩. 打开
恢复,HOST's applySnapshot 将存储的快照转换回它的
当前写入模式——丢出未知/存储字段,默认缺失字段,
并重新验证引用。 当前的方案总是获胜; 旧的快照
永远无法写出已经不存在的一栏。 从不指向迁移工具
这些桌子的有效载荷.
插入一个收藏
import {
createEntityLifecycle,
type EntityOps,
type LifecycleStores,
} from '@12-apps/entity-lifecycle';
const productOps: EntityOps = {
readSnapshot: async (tenantId, id) => /* live state or null */,
applySnapshot: async (tenantId, idOrNull, snapshot) => /* create/update; return id */,
archive: async (tenantId, id) => /* soft-hide; boolean */,
unarchive: async (tenantId, id) => /* undo archive; boolean */,
hardDelete: async (tenantId, id) => /* permanent */,
collectChildren: async (tenantId, id) => /* dependents for the bin tree */,
onVersionRecorded: async (tenantId, id, version) => /* mirror publishedVersion */,
};
export const products = createEntityLifecycle(
{
entityType: 'product',
features: { versioning: true, drafts: true, approvals: true },
featureDefaults: { approvals: false }, // entitled ⇒ on, except approvals
label: (s) => String(s.name ?? ''),
diff: { ignoreFields: ['updatedAt'] },
retention: { maxVersions: 50, maxAgeDays: 365 },
},
stores, // LifecycleStores: your DB-backed adapters (or the in-memory ones)
productOps,
);
每一次突变,然后通过服务漏斗:
const ctx = { tenantId, actorId, entitlements, settings, canApprove };
await products.create(ctx, snapshot); // → { status: 'applied' | 'pending-approval' }
await products.update(ctx, id, snapshot); // records a diff version
await products.softDelete(ctx, id); // archive + recycle-bin tree
await products.history(ctx, id); // newest-first summaries
await products.compareVersion(ctx, id, 3); // v3 beside previous / next / current
await products.restoreVersion(ctx, id, 3); // replay 1..3, apply, record RESTORE
await products.restoreDeleted(ctx, entryId); // un-archive from the bin
await products.purgeDeleted(ctx, entryId); // permanent delete
await products.saveDraft(ctx, id, data); // or (ctx, null, data) for a new item
await products.publishDraft(ctx, draftId);
await products.approveChange(ctx, requestId); // requires ctx.canApprove
canApprove 是东道主RBAC的决定(例如a) products:approve
允许)。 当批准有效时 canApprove !== true,创建 /
更新 / 删除 / 恢复 / 作为待决更改请求公布所有 park .
存储适配器
DB 辅助适配器飞船: 通过任何满意的客户端
结构 LifecycleDb 连接( Prisma 客户端直接进行) 到
createDbLifecycleStores(db) 从 @12-apps/entity-lifecycle/server - 或者让
createApiEntityLifecycle 为您建造它们. 普里斯玛模型及其
移民船也一样数字 : prisma/entity-lifecycle.prisma 是犬科动物
4个通用表格部分entity_versions, (中文(简体) ), recycle_bin_entries, (中文(简体) ),
entity_drafts, (中文(简体) ), change_requests(三); pnpm --filter @12-apps/entity-lifecycle prisma:sync 复制到一个多文件的 schema 文件夹( 带有 --check 漂移
和迁移情况 prisma/migrations/ 已发现
由主机的插件- 迁移同步结构 。 这部分是
宿主不可知:租赁是一个可分值 client_id scal (在您自己加个 FK
迁移( 如果您想要) 和关闭的套件是带有 CHECK 限制的字符串
在运入的迁移中。 对于自定义商店, 执行 VersionStore, (中文(简体) ),
RecycleBinStore, (中文(简体) ), DraftStore, (中文(简体) ), ApprovalStore; 模拟执行
用于试验/造型的船舶: createMemoryVersionStore() 包括:.
测试
pnpm --filter @12-apps/entity-lifecycle test