@murumets-ee/content 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.mts +5 -0
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +1 -1
- package/dist/client.mjs.map +1 -1
- package/package.json +2 -2
package/dist/client.d.mts
CHANGED
|
@@ -177,6 +177,11 @@ declare class ContentClient {
|
|
|
177
177
|
* Quick existence check for list badges — does a draft exist?
|
|
178
178
|
*/
|
|
179
179
|
hasDraft(entityId: string, locale?: string): Promise<boolean>;
|
|
180
|
+
/**
|
|
181
|
+
* Count drafts for this entity type, optionally filtered by locale.
|
|
182
|
+
* Single indexed COUNT query — used by dashboard widgets.
|
|
183
|
+
*/
|
|
184
|
+
countDrafts(locale?: string): Promise<number>;
|
|
180
185
|
/**
|
|
181
186
|
* Check draft existence for multiple entities in one query.
|
|
182
187
|
* Returns a set of entity IDs that have drafts.
|
package/dist/client.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;;;;;;;;KA4BK,gBAAA,GAAmB,SAAA,CAAU,WAAA;AAAA,UAEjB,mBAAA;EACf,MAAA,EAAQ,MAAA;EACR,EAAA,EAAI,kBAAA;EACJ,MAAA,GAAS,MAAA;AAAA;AAAA,cAGE,aAAA;EAAA,QACH,MAAA;EAAA,QACA,EAAA;EAAA,QACA,MAAA;EAAA,QACA,KAAA;EAAA,QACA,kBAAA;cAEI,MAAA,EAAQ,mBAAA;EAAA,QAON,QAAA;EAgBR,MAAA,CAAO,IAAA,EAAM,MAAA,SAAe,gBAAA,IAAiB,OAAA;;;;;;;;;EAY7C,QAAA,CAAS,EAAA,UAAY,OAAA;IAAY,MAAA;EAAA,IAAiB,OAAA;;;;;;;;;EAKlD,QAAA,CAAS,OAAA,GAAU,eAAA,GAAe,OAAA;;;;;;;;;EAKlC,MAAA,CAAO,EAAA,UAAY,IAAA,EAAM,MAAA,SAAe,gBAAA,IAAiB,OAAA;;;;;;;;;EAgCzD,MAAA,CAAO,EAAA,WAAU,OAAA;EASjB,eAAA,CAAgB,QAAA,UAAkB,MAAA,UAAgB,YAAA,EAAc,MAAA,oBAAuB,OAAA;EAKvF,iBAAA,CAAkB,QAAA,UAAkB,MAAA,YAAe,OAAA;EA+JtD;;;EAnJG,UAAA,CAAW,QAAA,UAAkB,OAAA;IAAW,IAAA;IAAc,EAAA;EAAA,IAAuB,OAAA;EAiPrC;;;;EA9LxC,WAAA,CACJ,QAAA,UACA,OAAA;IAAY,KAAA;IAAgB,MAAA;IAAiB,MAAA;EAAA,IAC5C,OAAA,CAAQ,YAAA;EAoc2B;;;EAvahC,UAAA,CAAW,QAAA,UAAkB,OAAA,UAAiB,MAAA,YAAe,OAAA,CAAQ,YAAA;EAgdnE;;;;;;EApbF,eAAA,CACJ,QAAA,UACA,EAAA,UACA,EAAA,UACA,MAAA,YACC,OAAA;IAAU,KAAA,EAAO,SAAA;IAAa,WAAA,EAAa,YAAA;IAAc,SAAA,EAAW,YAAA;EAAA;
|
|
1
|
+
{"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":"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"}
|
package/dist/client.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{o as e,r as t,t as n}from"./block-diff-H1AttKiz.mjs";import"server-only";var r=class{entity;db;logger;admin=null;versionableOptions;constructor(t){this.entity=t.entity,this.db=t.db,this.logger=t.logger,this.versionableOptions=e(t.entity.behaviors)}async getAdmin(){if(!this.admin){let{AdminClient:e}=await import(`@murumets-ee/entity/admin`);this.admin=new e({entity:this.entity,db:this.db,logger:this.logger})}return this.admin}async create(e){let t=await(await this.getAdmin()).create(e);return this.versionableOptions&&await this.saveVersionSnapshot(t.id,t,null,!1),t}async findById(e,t){return(await this.getAdmin()).findById(e,t)}async findMany(e){return(await this.getAdmin()).findMany(e)}async update(e,t){let n=await this.getAdmin(),r=null;this.versionableOptions&&(r=await n.findById(e));let i=await n.update(e,t);if(this.versionableOptions&&r){let t=this.calculateDelta(r,i),n=await this.saveVersionSnapshot(e,i,t,!1);if(n!==void 0){let{schemaRegistry:t}=await import(`@murumets-ee/db`),{eq:r}=await import(`drizzle-orm`),a=t.get(this.entity.name);a&&(await this.db.update(a).set({_version:n}).where(r(a.id,e)),i._version=n)}await this.pruneVersions(e)}return i}async delete(e){return(await this.getAdmin()).delete(e)}async saveTranslation(e,t,n){return(await this.getAdmin()).saveTranslation(e,t,n)}async deleteTranslation(e,t){return(await this.getAdmin()).deleteTranslation(e,t)}async copyLayout(e,t){let{schemaRegistry:n}=await import(`@murumets-ee/db`),{eq:r,and:i}=await import(`drizzle-orm`),a=n.get(`${this.entity.name}_layout`);if(!a)return;let o=Array.isArray(t.to)?t.to:[t.to],s=await this.db.select().from(a).where(i(r(a.entityId,e),r(a.locale,t.from))).orderBy(a.sortOrder);if(s.length!==0){for(let t of o){await this.db.delete(a).where(i(r(a.entityId,e),r(a.locale,t)));for(let e of s)await this.db.insert(a).values({entityId:e.entityId,fieldName:e.fieldName,blockType:e.blockType,sortOrder:e.sortOrder,data:e.data,locale:t})}this.logger?.debug({entityId:e,from:t.from,to:o},`Layout copied between locales`)}}async getVersions(e,t){let{schemaRegistry:n}=await import(`@murumets-ee/db`),{eq:r,and:i,desc:a}=await import(`drizzle-orm`),o=n.get(`${this.entity.name}_versions`);if(!o)return[];let s=t?.locale??`_`,c=this.db.select().from(o).where(i(r(o.entityId,e),r(o.locale,s))).orderBy(a(o.version)).$dynamic();return t?.limit&&(c=c.limit(t.limit)),t?.offset&&(c=c.offset(t.offset)),c}async getVersion(e,t,n=`_`){let{schemaRegistry:r}=await import(`@murumets-ee/db`),{eq:i,and:a}=await import(`drizzle-orm`),o=r.get(`${this.entity.name}_versions`);return o?(await this.db.select().from(o).where(a(i(o.entityId,e),i(o.version,t),i(o.locale,n))).limit(1))[0]??null:null}async compareVersions(e,r,i,a=`_`){let[o,s]=await Promise.all([this.getVersion(e,r,a),this.getVersion(e,i,a)]);if(!o||!s)return null;let c=o.data,l=s.data,u=this.entity.allFields,d=t(c,l,u);for(let e of d)e.type===`blocks`&&Array.isArray(e.from)&&Array.isArray(e.to)&&(e.blockDiffs=n(e.from,e.to));return{diffs:d,fromVersion:o,toVersion:s}}async compareDraftWithCurrent(e,r=`_`){let i=await this.getDraft(e,r);if(!i)return null;let a=await this.getAdmin(),{getContentConfig:o}=await import(`./plugin.mjs`),s=o().defaultLocale,c=r===`_`?{defaultLocale:s}:{locale:r,defaultLocale:s},l=await a.findById(e,c);if(!l)return null;let u=this.entity.allFields,d={};for(let e of Object.keys(i.data))d[e]=l[e];let f=t(d,i.data,u);for(let e of f)e.type===`blocks`&&Array.isArray(e.from)&&Array.isArray(e.to)&&(e.blockDiffs=n(e.from,e.to));return{diffs:f,current:l,draft:i}}async restoreVersion(e,t){let n=await this.getVersion(e,t);if(!n)throw Error(`Version ${t} not found for entity ${e}`);let{id:r,createdAt:i,createdBy:a,_version:o,_versionedAt:s,...c}=n.data;return(await this.getAdmin()).update(e,c)}async pruneVersions(e,t=`_`){if(!this.versionableOptions)return;let{schemaRegistry:n}=await import(`@murumets-ee/db`),{eq:r,and:i,desc:a,sql:o}=await import(`drizzle-orm`),s=n.get(`${this.entity.name}_versions`);if(!s)return;let c=this.versionableOptions.maxPerDoc,l=await this.db.select({id:s.id}).from(s).where(i(r(s.entityId,e),r(s.locale,t))).orderBy(a(s.version)).limit(c);if(l.length<c)return;let u=l.map(e=>e.id);await this.db.delete(s).where(o`${s.entityId} = ${e} AND ${s.locale} = ${t} AND ${s.id} NOT IN (${o.join(u.map(e=>o`${e}`),o`, `)})`)}async getTranslationStatus(e){let{getContentConfig:t}=await import(`./plugin.mjs`),n=t(),r=Object.entries(this.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e);if(r.length===0)return{};let{schemaRegistry:i}=await import(`@murumets-ee/db`),{eq:a}=await import(`drizzle-orm`),o=i.get(`${this.entity.name}_translations`);if(!o)return{};let s=await this.db.select().from(o).where(a(o.entityId,e)),c=new Map;for(let e of s){let t=e,n={};for(let e of r)n[e]=t[e];c.set(t.locale,n)}let l={};for(let e of n.locales){if(e.code===n.defaultLocale)continue;let t=c.get(e.code)??{},i=[];for(let e of r)(t[e]===void 0||t[e]===null)&&i.push(e);let a=r.length-i.length;l[e.code]={complete:i.length===0,total:r.length,translated:a,missing:i}}return l}async getTranslationStatusBatch(e){let t=new Map;if(e.length===0)return t;let{getContentConfig:n}=await import(`./plugin.mjs`),r=n(),i=Object.entries(this.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e);if(i.length===0)return t;let{schemaRegistry:a}=await import(`@murumets-ee/db`),{inArray:o}=await import(`drizzle-orm`),s=a.get(`${this.entity.name}_translations`);if(!s)return t;let c=await this.db.select().from(s).where(o(s.entityId,e)),l=new Map;for(let e of c){let t=e,n=t.entityId,r=t.locale,a={};for(let e of i)a[e]=t[e];l.has(n)||l.set(n,new Map),l.get(n).set(r,a)}for(let n of e){let e=l.get(n),a={};for(let t of r.locales){if(t.code===r.defaultLocale)continue;let n=e?.get(t.code)??{},o=[];for(let e of i)(n[e]===void 0||n[e]===null)&&o.push(e);let s=i.length-o.length;a[t.code]={complete:o.length===0,total:i.length,translated:s,missing:o}}t.set(n,a)}return t}async getTranslationReport(e){let{getContentConfig:t}=await import(`./plugin.mjs`),n=t(),r=e?.locale??n.locales.find(e=>e.code!==n.defaultLocale)?.code;if(!r)return{total:0,complete:0,partial:0,missing:0};let i=await(await this.getAdmin()).findMany(),a=0,o=0,s=0;for(let e of i){let t=(await this.getTranslationStatus(e.id))[r];!t||t.translated===0?s++:t.complete?a++:o++}return{total:i.length,complete:a,partial:o,missing:s}}async getLocaleStatus(e){let{schemaRegistry:t}=await import(`@murumets-ee/db`),{eq:n}=await import(`drizzle-orm`),r=t.get(`${this.entity.name}_locale_status`);return r?(await this.db.select().from(r).where(n(r.entityId,e))).map(e=>({locale:e.locale,status:e.status,publishedAt:e.publishedAt??null})):[]}async setLocaleStatus(e,t,n){let{getContentConfig:r}=await import(`./plugin.mjs`);if(t===r().defaultLocale){let t=await this.getAdmin(),r={status:n};n===`published`&&(r.publishedAt=new Date),await t.update(e,r);return}let{schemaRegistry:i}=await import(`@murumets-ee/db`),{eq:a,and:o}=await import(`drizzle-orm`),s=i.get(`${this.entity.name}_locale_status`);if(!s)return;let c=n===`published`?new Date:null,[l]=await this.db.select({id:s.id}).from(s).where(o(a(s.entityId,e),a(s.locale,t))).limit(1);l?await this.db.update(s).set({status:n,publishedAt:c}).where(a(s.id,l.id)):await this.db.insert(s).values({entityId:e,locale:t,status:n,publishedAt:c})}async getLocaleStatusBatch(e){let t=new Map;if(e.length===0)return t;let{schemaRegistry:n}=await import(`@murumets-ee/db`),{inArray:r}=await import(`drizzle-orm`),i=n.get(`${this.entity.name}_locale_status`);if(!i)return t;let a=await this.db.select().from(i).where(r(i.entityId,e));for(let e of a){let n=e.entityId,r=e.locale;t.has(n)||t.set(n,{}),t.get(n)[r]={locale:r,status:e.status,publishedAt:e.publishedAt??null}}return t}async saveDraft(e,t,n=`_`,r){let{schemaRegistry:i}=await import(`@murumets-ee/db`),{eq:a,and:o}=await import(`drizzle-orm`),s=i.get(`${this.entity.name}_drafts`);if(!s)throw Error(`Drafts table not found for entity "${this.entity.name}". Is the content plugin initialized?`);let c=new Date,l=r?.id??``,u=r?.name??null,[d]=await this.db.select().from(s).where(o(a(s.entityId,e),a(s.locale,n))).limit(1);if(d){let[e]=await this.db.update(s).set({data:t,updatedAt:c}).where(a(s.id,d.id)).returning();return this.toDraftEntry(e)}let[f]=await this.db.insert(s).values({entityId:e,locale:n,data:t,createdBy:l,createdByName:u,createdAt:c,updatedAt:c}).returning();return this.toDraftEntry(f)}async getDraft(e,t=`_`){let{schemaRegistry:n}=await import(`@murumets-ee/db`),{eq:r,and:i}=await import(`drizzle-orm`),a=n.get(`${this.entity.name}_drafts`);if(!a)return null;let[o]=await this.db.select().from(a).where(i(r(a.entityId,e),r(a.locale,t))).limit(1);return o?this.toDraftEntry(o):null}async deleteDraft(e,t=`_`){let{schemaRegistry:n}=await import(`@murumets-ee/db`),{eq:r,and:i}=await import(`drizzle-orm`),a=n.get(`${this.entity.name}_drafts`);return a?(await this.db.delete(a).where(i(r(a.entityId,e),r(a.locale,t))).returning({id:a.id})).length>0:!1}async publishDraft(e,t=`_`,n){let r=await this.getDraft(e,t);if(!r)throw Error(`No draft found for entity ${e} locale ${t}`);let i=await this.getAdmin(),a=null;this.versionableOptions&&(a=await i.findById(e));let{id:o,createdAt:s,createdBy:c,_version:l,_versionedAt:u,...d}=r.data,f=await i.update(e,d);return await this.deleteDraft(e,t),this.versionableOptions&&await this.createVersionSnapshot(e,f,a,t,n),f}async hasDraft(e,t=`_`){let{schemaRegistry:n}=await import(`@murumets-ee/db`),{eq:r,and:i}=await import(`drizzle-orm`),a=n.get(`${this.entity.name}_drafts`);if(!a)return!1;let[o]=await this.db.select({id:a.id}).from(a).where(i(r(a.entityId,e),r(a.locale,t))).limit(1);return!!o}async hasDraftBatch(e,t=`_`){let n=new Set;if(e.length===0)return n;let{schemaRegistry:r}=await import(`@murumets-ee/db`),{inArray:i,eq:a,and:o}=await import(`drizzle-orm`),s=r.get(`${this.entity.name}_drafts`);if(!s)return n;let c=await this.db.select({entityId:s.entityId}).from(s).where(o(i(s.entityId,e),a(s.locale,t)));for(let e of c)n.add(e.entityId);return n}async createVersionSnapshot(e,t,n,r,i){if(!this.versionableOptions)return;let a=r??`_`,o=n?this.calculateDelta(n,t):null,s=await this.saveVersionSnapshot(e,t,o,!1,a,i);if(s!==void 0&&a===`_`){let{schemaRegistry:t}=await import(`@murumets-ee/db`),{eq:n}=await import(`drizzle-orm`),r=t.get(this.entity.name);r&&await this.db.update(r).set({_version:s}).where(n(r.id,e))}await this.pruneVersions(e,a)}async saveVersionSnapshot(e,t,n,r,i=`_`,a){let{schemaRegistry:o}=await import(`@murumets-ee/db`),{eq:s,and:c,sql:l}=await import(`drizzle-orm`),u=o.get(`${this.entity.name}_versions`);if(!u)return;let[d]=await this.db.select({maxVersion:l`coalesce(max(${u.version}), 0)`}).from(u).where(c(s(u.entityId,e),s(u.locale,i))),f=(d?.maxVersion??0)+1,p=t.status??null;if(r){let[r]=await this.db.select().from(u).where(c(s(u.entityId,e),s(u.isAutosave,!0),s(u.version,f),s(u.locale,i))).limit(1);if(r)return await this.db.update(u).set({data:t,delta:n,status:p,createdAt:new Date}).where(s(u.id,r.id)),f}return await this.db.insert(u).values({entityId:e,version:f,locale:i,data:t,delta:n,status:p,createdBy:a?.id??t.updatedBy??t.createdBy??null,createdByName:a?.name??null,isAutosave:r}),f}toDraftEntry(e){return{id:e.id,entityId:e.entityId,locale:e.locale,data:e.data,createdBy:e.createdBy,createdByName:e.createdByName??null,createdAt:e.createdAt,updatedAt:e.updatedAt}}calculateDelta(e,t){let n={};for(let r of Object.keys(t))r===`id`||r.startsWith(`_`)||JSON.stringify(e[r])!==JSON.stringify(t[r])&&(n[r]=t[r]);return n}};async function i(e){let{getApp:t}=await import(`@murumets-ee/core`),n=t();return new r({entity:e,db:n.db.readWrite,logger:n.logger.child({content:e.name})})}export{r as ContentClient,i as createContentClient};
|
|
1
|
+
import{o as e,r as t,t as n}from"./block-diff-H1AttKiz.mjs";import"server-only";var r=class{entity;db;logger;admin=null;versionableOptions;constructor(t){this.entity=t.entity,this.db=t.db,this.logger=t.logger,this.versionableOptions=e(t.entity.behaviors)}async getAdmin(){if(!this.admin){let{AdminClient:e}=await import(`@murumets-ee/entity/admin`);this.admin=new e({entity:this.entity,db:this.db,logger:this.logger})}return this.admin}async create(e){let t=await(await this.getAdmin()).create(e);return this.versionableOptions&&await this.saveVersionSnapshot(t.id,t,null,!1),t}async findById(e,t){return(await this.getAdmin()).findById(e,t)}async findMany(e){return(await this.getAdmin()).findMany(e)}async update(e,t){let n=await this.getAdmin(),r=null;this.versionableOptions&&(r=await n.findById(e));let i=await n.update(e,t);if(this.versionableOptions&&r){let t=this.calculateDelta(r,i),n=await this.saveVersionSnapshot(e,i,t,!1);if(n!==void 0){let{schemaRegistry:t}=await import(`@murumets-ee/db`),{eq:r}=await import(`drizzle-orm`),a=t.get(this.entity.name);a&&(await this.db.update(a).set({_version:n}).where(r(a.id,e)),i._version=n)}await this.pruneVersions(e)}return i}async delete(e){return(await this.getAdmin()).delete(e)}async saveTranslation(e,t,n){return(await this.getAdmin()).saveTranslation(e,t,n)}async deleteTranslation(e,t){return(await this.getAdmin()).deleteTranslation(e,t)}async copyLayout(e,t){let{schemaRegistry:n}=await import(`@murumets-ee/db`),{eq:r,and:i}=await import(`drizzle-orm`),a=n.get(`${this.entity.name}_layout`);if(!a)return;let o=Array.isArray(t.to)?t.to:[t.to],s=await this.db.select().from(a).where(i(r(a.entityId,e),r(a.locale,t.from))).orderBy(a.sortOrder);if(s.length!==0){for(let t of o){await this.db.delete(a).where(i(r(a.entityId,e),r(a.locale,t)));for(let e of s)await this.db.insert(a).values({entityId:e.entityId,fieldName:e.fieldName,blockType:e.blockType,sortOrder:e.sortOrder,data:e.data,locale:t})}this.logger?.debug({entityId:e,from:t.from,to:o},`Layout copied between locales`)}}async getVersions(e,t){let{schemaRegistry:n}=await import(`@murumets-ee/db`),{eq:r,and:i,desc:a}=await import(`drizzle-orm`),o=n.get(`${this.entity.name}_versions`);if(!o)return[];let s=t?.locale??`_`,c=this.db.select().from(o).where(i(r(o.entityId,e),r(o.locale,s))).orderBy(a(o.version)).$dynamic();return t?.limit&&(c=c.limit(t.limit)),t?.offset&&(c=c.offset(t.offset)),c}async getVersion(e,t,n=`_`){let{schemaRegistry:r}=await import(`@murumets-ee/db`),{eq:i,and:a}=await import(`drizzle-orm`),o=r.get(`${this.entity.name}_versions`);return o?(await this.db.select().from(o).where(a(i(o.entityId,e),i(o.version,t),i(o.locale,n))).limit(1))[0]??null:null}async compareVersions(e,r,i,a=`_`){let[o,s]=await Promise.all([this.getVersion(e,r,a),this.getVersion(e,i,a)]);if(!o||!s)return null;let c=o.data,l=s.data,u=this.entity.allFields,d=t(c,l,u);for(let e of d)e.type===`blocks`&&Array.isArray(e.from)&&Array.isArray(e.to)&&(e.blockDiffs=n(e.from,e.to));return{diffs:d,fromVersion:o,toVersion:s}}async compareDraftWithCurrent(e,r=`_`){let i=await this.getDraft(e,r);if(!i)return null;let a=await this.getAdmin(),{getContentConfig:o}=await import(`./plugin.mjs`),s=o().defaultLocale,c=r===`_`?{defaultLocale:s}:{locale:r,defaultLocale:s},l=await a.findById(e,c);if(!l)return null;let u=this.entity.allFields,d={};for(let e of Object.keys(i.data))d[e]=l[e];let f=t(d,i.data,u);for(let e of f)e.type===`blocks`&&Array.isArray(e.from)&&Array.isArray(e.to)&&(e.blockDiffs=n(e.from,e.to));return{diffs:f,current:l,draft:i}}async restoreVersion(e,t){let n=await this.getVersion(e,t);if(!n)throw Error(`Version ${t} not found for entity ${e}`);let{id:r,createdAt:i,createdBy:a,_version:o,_versionedAt:s,...c}=n.data;return(await this.getAdmin()).update(e,c)}async pruneVersions(e,t=`_`){if(!this.versionableOptions)return;let{schemaRegistry:n}=await import(`@murumets-ee/db`),{eq:r,and:i,desc:a,sql:o}=await import(`drizzle-orm`),s=n.get(`${this.entity.name}_versions`);if(!s)return;let c=this.versionableOptions.maxPerDoc,l=await this.db.select({id:s.id}).from(s).where(i(r(s.entityId,e),r(s.locale,t))).orderBy(a(s.version)).limit(c);if(l.length<c)return;let u=l.map(e=>e.id);await this.db.delete(s).where(o`${s.entityId} = ${e} AND ${s.locale} = ${t} AND ${s.id} NOT IN (${o.join(u.map(e=>o`${e}`),o`, `)})`)}async getTranslationStatus(e){let{getContentConfig:t}=await import(`./plugin.mjs`),n=t(),r=Object.entries(this.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e);if(r.length===0)return{};let{schemaRegistry:i}=await import(`@murumets-ee/db`),{eq:a}=await import(`drizzle-orm`),o=i.get(`${this.entity.name}_translations`);if(!o)return{};let s=await this.db.select().from(o).where(a(o.entityId,e)),c=new Map;for(let e of s){let t=e,n={};for(let e of r)n[e]=t[e];c.set(t.locale,n)}let l={};for(let e of n.locales){if(e.code===n.defaultLocale)continue;let t=c.get(e.code)??{},i=[];for(let e of r)(t[e]===void 0||t[e]===null)&&i.push(e);let a=r.length-i.length;l[e.code]={complete:i.length===0,total:r.length,translated:a,missing:i}}return l}async getTranslationStatusBatch(e){let t=new Map;if(e.length===0)return t;let{getContentConfig:n}=await import(`./plugin.mjs`),r=n(),i=Object.entries(this.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e);if(i.length===0)return t;let{schemaRegistry:a}=await import(`@murumets-ee/db`),{inArray:o}=await import(`drizzle-orm`),s=a.get(`${this.entity.name}_translations`);if(!s)return t;let c=await this.db.select().from(s).where(o(s.entityId,e)),l=new Map;for(let e of c){let t=e,n=t.entityId,r=t.locale,a={};for(let e of i)a[e]=t[e];l.has(n)||l.set(n,new Map),l.get(n).set(r,a)}for(let n of e){let e=l.get(n),a={};for(let t of r.locales){if(t.code===r.defaultLocale)continue;let n=e?.get(t.code)??{},o=[];for(let e of i)(n[e]===void 0||n[e]===null)&&o.push(e);let s=i.length-o.length;a[t.code]={complete:o.length===0,total:i.length,translated:s,missing:o}}t.set(n,a)}return t}async getTranslationReport(e){let{getContentConfig:t}=await import(`./plugin.mjs`),n=t(),r=e?.locale??n.locales.find(e=>e.code!==n.defaultLocale)?.code;if(!r)return{total:0,complete:0,partial:0,missing:0};let i=await(await this.getAdmin()).findMany(),a=0,o=0,s=0;for(let e of i){let t=(await this.getTranslationStatus(e.id))[r];!t||t.translated===0?s++:t.complete?a++:o++}return{total:i.length,complete:a,partial:o,missing:s}}async getLocaleStatus(e){let{schemaRegistry:t}=await import(`@murumets-ee/db`),{eq:n}=await import(`drizzle-orm`),r=t.get(`${this.entity.name}_locale_status`);return r?(await this.db.select().from(r).where(n(r.entityId,e))).map(e=>({locale:e.locale,status:e.status,publishedAt:e.publishedAt??null})):[]}async setLocaleStatus(e,t,n){let{getContentConfig:r}=await import(`./plugin.mjs`);if(t===r().defaultLocale){let t=await this.getAdmin(),r={status:n};n===`published`&&(r.publishedAt=new Date),await t.update(e,r);return}let{schemaRegistry:i}=await import(`@murumets-ee/db`),{eq:a,and:o}=await import(`drizzle-orm`),s=i.get(`${this.entity.name}_locale_status`);if(!s)return;let c=n===`published`?new Date:null,[l]=await this.db.select({id:s.id}).from(s).where(o(a(s.entityId,e),a(s.locale,t))).limit(1);l?await this.db.update(s).set({status:n,publishedAt:c}).where(a(s.id,l.id)):await this.db.insert(s).values({entityId:e,locale:t,status:n,publishedAt:c})}async getLocaleStatusBatch(e){let t=new Map;if(e.length===0)return t;let{schemaRegistry:n}=await import(`@murumets-ee/db`),{inArray:r}=await import(`drizzle-orm`),i=n.get(`${this.entity.name}_locale_status`);if(!i)return t;let a=await this.db.select().from(i).where(r(i.entityId,e));for(let e of a){let n=e.entityId,r=e.locale;t.has(n)||t.set(n,{}),t.get(n)[r]={locale:r,status:e.status,publishedAt:e.publishedAt??null}}return t}async saveDraft(e,t,n=`_`,r){let{schemaRegistry:i}=await import(`@murumets-ee/db`),{eq:a,and:o}=await import(`drizzle-orm`),s=i.get(`${this.entity.name}_drafts`);if(!s)throw Error(`Drafts table not found for entity "${this.entity.name}". Is the content plugin initialized?`);let c=new Date,l=r?.id??``,u=r?.name??null,[d]=await this.db.select().from(s).where(o(a(s.entityId,e),a(s.locale,n))).limit(1);if(d){let[e]=await this.db.update(s).set({data:t,updatedAt:c}).where(a(s.id,d.id)).returning();return this.toDraftEntry(e)}let[f]=await this.db.insert(s).values({entityId:e,locale:n,data:t,createdBy:l,createdByName:u,createdAt:c,updatedAt:c}).returning();return this.toDraftEntry(f)}async getDraft(e,t=`_`){let{schemaRegistry:n}=await import(`@murumets-ee/db`),{eq:r,and:i}=await import(`drizzle-orm`),a=n.get(`${this.entity.name}_drafts`);if(!a)return null;let[o]=await this.db.select().from(a).where(i(r(a.entityId,e),r(a.locale,t))).limit(1);return o?this.toDraftEntry(o):null}async deleteDraft(e,t=`_`){let{schemaRegistry:n}=await import(`@murumets-ee/db`),{eq:r,and:i}=await import(`drizzle-orm`),a=n.get(`${this.entity.name}_drafts`);return a?(await this.db.delete(a).where(i(r(a.entityId,e),r(a.locale,t))).returning({id:a.id})).length>0:!1}async publishDraft(e,t=`_`,n){let r=await this.getDraft(e,t);if(!r)throw Error(`No draft found for entity ${e} locale ${t}`);let i=await this.getAdmin(),a=null;this.versionableOptions&&(a=await i.findById(e));let{id:o,createdAt:s,createdBy:c,_version:l,_versionedAt:u,...d}=r.data,f=await i.update(e,d);return await this.deleteDraft(e,t),this.versionableOptions&&await this.createVersionSnapshot(e,f,a,t,n),f}async hasDraft(e,t=`_`){let{schemaRegistry:n}=await import(`@murumets-ee/db`),{eq:r,and:i}=await import(`drizzle-orm`),a=n.get(`${this.entity.name}_drafts`);if(!a)return!1;let[o]=await this.db.select({id:a.id}).from(a).where(i(r(a.entityId,e),r(a.locale,t))).limit(1);return!!o}async countDrafts(e=`_`){let{schemaRegistry:t}=await import(`@murumets-ee/db`),{eq:n,sql:r}=await import(`drizzle-orm`),i=t.get(`${this.entity.name}_drafts`);if(!i)return 0;let[a]=await this.db.select({count:r`count(*)::int`}).from(i).where(n(i.locale,e));return a?.count??0}async hasDraftBatch(e,t=`_`){let n=new Set;if(e.length===0)return n;let{schemaRegistry:r}=await import(`@murumets-ee/db`),{inArray:i,eq:a,and:o}=await import(`drizzle-orm`),s=r.get(`${this.entity.name}_drafts`);if(!s)return n;let c=await this.db.select({entityId:s.entityId}).from(s).where(o(i(s.entityId,e),a(s.locale,t)));for(let e of c)n.add(e.entityId);return n}async createVersionSnapshot(e,t,n,r,i){if(!this.versionableOptions)return;let a=r??`_`,o=n?this.calculateDelta(n,t):null,s=await this.saveVersionSnapshot(e,t,o,!1,a,i);if(s!==void 0&&a===`_`){let{schemaRegistry:t}=await import(`@murumets-ee/db`),{eq:n}=await import(`drizzle-orm`),r=t.get(this.entity.name);r&&await this.db.update(r).set({_version:s}).where(n(r.id,e))}await this.pruneVersions(e,a)}async saveVersionSnapshot(e,t,n,r,i=`_`,a){let{schemaRegistry:o}=await import(`@murumets-ee/db`),{eq:s,and:c,sql:l}=await import(`drizzle-orm`),u=o.get(`${this.entity.name}_versions`);if(!u)return;let[d]=await this.db.select({maxVersion:l`coalesce(max(${u.version}), 0)`}).from(u).where(c(s(u.entityId,e),s(u.locale,i))),f=(d?.maxVersion??0)+1,p=t.status??null;if(r){let[r]=await this.db.select().from(u).where(c(s(u.entityId,e),s(u.isAutosave,!0),s(u.version,f),s(u.locale,i))).limit(1);if(r)return await this.db.update(u).set({data:t,delta:n,status:p,createdAt:new Date}).where(s(u.id,r.id)),f}return await this.db.insert(u).values({entityId:e,version:f,locale:i,data:t,delta:n,status:p,createdBy:a?.id??t.updatedBy??t.createdBy??null,createdByName:a?.name??null,isAutosave:r}),f}toDraftEntry(e){return{id:e.id,entityId:e.entityId,locale:e.locale,data:e.data,createdBy:e.createdBy,createdByName:e.createdByName??null,createdAt:e.createdAt,updatedAt:e.updatedAt}}calculateDelta(e,t){let n={};for(let r of Object.keys(t))r===`id`||r.startsWith(`_`)||JSON.stringify(e[r])!==JSON.stringify(t[r])&&(n[r]=t[r]);return n}};async function i(e){let{getApp:t}=await import(`@murumets-ee/core`),n=t();return new r({entity:e,db:n.db.readWrite,logger:n.logger.child({content:e.name})})}export{r as ContentClient,i as createContentClient};
|
|
2
2
|
//# sourceMappingURL=client.mjs.map
|
package/dist/client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["/**\n * ContentClient — wraps AdminClient with version history and translation status.\n *\n * Usage:\n * import { createContentClient } from '@murumets-ee/content/client'\n * const pages = createContentClient(Page)\n * await pages.create({ title: 'Hello', layout: [...] })\n */\n\nimport 'server-only'\n\nimport type { Logger } from '@murumets-ee/core'\nimport type { Entity, FieldConfig, FieldToTS } from '@murumets-ee/entity'\nimport type { AdminClient, FindManyOptions } from '@murumets-ee/entity/admin'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport { getVersionableOptions } from './behaviors/versionable.js'\nimport { diffBlocks } from './diff/block-diff.js'\nimport { diffFields } from './diff/field-diff.js'\nimport type { FieldDiff } from './diff/types.js'\nimport type {\n DraftEntry,\n LocalePublishStatus,\n TranslationReport,\n TranslationStatus,\n VersionEntry,\n} from './types.js'\n\n/** Value type that any entity field can hold */\ntype EntityFieldValue = FieldToTS<FieldConfig> | null | undefined\n\nexport interface ContentClientConfig {\n entity: Entity\n db: PostgresJsDatabase\n logger?: Logger\n}\n\nexport class ContentClient {\n private entity: Entity\n private db: PostgresJsDatabase\n private logger?: Logger\n private admin: AdminClient | null = null\n private versionableOptions: ReturnType<typeof getVersionableOptions>\n\n constructor(config: ContentClientConfig) {\n this.entity = config.entity\n this.db = config.db\n this.logger = config.logger\n this.versionableOptions = getVersionableOptions(config.entity.behaviors)\n }\n\n private async getAdmin() {\n if (!this.admin) {\n const { AdminClient } = await import('@murumets-ee/entity/admin')\n this.admin = new AdminClient({\n entity: this.entity,\n db: this.db,\n logger: this.logger,\n })\n }\n return this.admin\n }\n\n // ---------------------------------------------------------------\n // Standard CRUD (delegated to AdminClient)\n // ---------------------------------------------------------------\n\n async create(data: Record<string, EntityFieldValue>) {\n const admin = await this.getAdmin()\n const result = await admin.create(data)\n\n // Save initial version snapshot\n if (this.versionableOptions) {\n await this.saveVersionSnapshot(result.id, result, null, false)\n }\n\n return result\n }\n\n async findById(id: string, options?: { locale?: string }) {\n const admin = await this.getAdmin()\n return admin.findById(id, options)\n }\n\n async findMany(options?: FindManyOptions) {\n const admin = await this.getAdmin()\n return admin.findMany(options)\n }\n\n async update(id: string, data: Record<string, EntityFieldValue>) {\n const admin = await this.getAdmin()\n\n // Get previous state for delta calculation\n let previousState: Record<string, unknown> | null = null\n if (this.versionableOptions) {\n previousState = await admin.findById(id)\n }\n\n const result = await admin.update(id, data)\n\n // Save version snapshot and update _version on the entity\n if (this.versionableOptions && previousState) {\n const delta = this.calculateDelta(previousState, result)\n const version = await this.saveVersionSnapshot(id, result, delta, false)\n\n if (version !== undefined) {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq } = await import('drizzle-orm')\n const table = schemaRegistry.get(this.entity.name)\n if (table) {\n await this.db.update(table).set({ _version: version }).where(eq(table.id, id))\n ;(result as Record<string, unknown>)._version = version\n }\n }\n\n await this.pruneVersions(id)\n }\n\n return result\n }\n\n async delete(id: string) {\n const admin = await this.getAdmin()\n return admin.delete(id)\n }\n\n // ---------------------------------------------------------------\n // Translation delegation\n // ---------------------------------------------------------------\n\n async saveTranslation(entityId: string, locale: string, translations: Record<string, unknown>) {\n const admin = await this.getAdmin()\n return admin.saveTranslation(entityId, locale, translations)\n }\n\n async deleteTranslation(entityId: string, locale?: string) {\n const admin = await this.getAdmin()\n return admin.deleteTranslation(entityId, locale)\n }\n\n // ---------------------------------------------------------------\n // Block operations\n // ---------------------------------------------------------------\n\n /**\n * Copy layout from one locale to another (for localized: true blocks).\n */\n async copyLayout(entityId: string, options: { from: string; to: string | string[] }) {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const layoutTable = schemaRegistry.get(`${this.entity.name}_layout`)\n if (!layoutTable) return\n\n const targetLocales = Array.isArray(options.to) ? options.to : [options.to]\n\n // Get source layout rows\n const sourceRows = await this.db\n .select()\n .from(layoutTable)\n .where(and(eq(layoutTable.entityId, entityId), eq(layoutTable.locale, options.from)))\n .orderBy(layoutTable.sortOrder)\n\n if (sourceRows.length === 0) return\n\n for (const targetLocale of targetLocales) {\n // Delete existing target layout\n await this.db\n .delete(layoutTable)\n .where(and(eq(layoutTable.entityId, entityId), eq(layoutTable.locale, targetLocale)))\n\n // Copy source rows with new locale\n for (const row of sourceRows) {\n await this.db.insert(layoutTable).values({\n entityId: row.entityId,\n fieldName: row.fieldName,\n blockType: row.blockType,\n sortOrder: row.sortOrder,\n data: row.data,\n locale: targetLocale,\n })\n }\n }\n\n this.logger?.debug(\n { entityId, from: options.from, to: targetLocales },\n 'Layout copied between locales',\n )\n }\n\n // ---------------------------------------------------------------\n // Version operations\n // ---------------------------------------------------------------\n\n /**\n * List versions for an entity (newest first), filtered by locale.\n * Uses '_' as the locale value for default-locale snapshots.\n */\n async getVersions(\n entityId: string,\n options?: { limit?: number; offset?: number; locale?: string },\n ): Promise<VersionEntry[]> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and, desc } = await import('drizzle-orm')\n\n const versionsTable = schemaRegistry.get(`${this.entity.name}_versions`)\n if (!versionsTable) return []\n\n const snapshotLocale = options?.locale ?? '_'\n\n let query = this.db\n .select()\n .from(versionsTable)\n .where(and(eq(versionsTable.entityId, entityId), eq(versionsTable.locale, snapshotLocale)))\n .orderBy(desc(versionsTable.version))\n .$dynamic()\n\n if (options?.limit) {\n query = query.limit(options.limit)\n }\n if (options?.offset) {\n query = query.offset(options.offset)\n }\n\n return query as unknown as VersionEntry[]\n }\n\n /**\n * Get a specific version snapshot.\n */\n async getVersion(entityId: string, version: number, locale = '_'): Promise<VersionEntry | null> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const versionsTable = schemaRegistry.get(`${this.entity.name}_versions`)\n if (!versionsTable) return null\n\n const rows = await this.db\n .select()\n .from(versionsTable)\n .where(\n and(\n eq(versionsTable.entityId, entityId),\n eq(versionsTable.version, version),\n eq(versionsTable.locale, locale),\n ),\n )\n .limit(1)\n\n return (rows[0] as unknown as VersionEntry) ?? null\n }\n\n /**\n * Compare two versions and return field-level diff.\n *\n * Returns structured diffs with word-level segments for text fields\n * and structural block diffs for blocks fields.\n */\n async compareVersions(\n entityId: string,\n v1: number,\n v2: number,\n locale = '_',\n ): Promise<{ diffs: FieldDiff[]; fromVersion: VersionEntry; toVersion: VersionEntry } | null> {\n const [version1, version2] = await Promise.all([\n this.getVersion(entityId, v1, locale),\n this.getVersion(entityId, v2, locale),\n ])\n\n if (!version1 || !version2) return null\n\n const data1 = version1.data as Record<string, unknown>\n const data2 = version2.data as Record<string, unknown>\n\n const fieldConfigs = this.entity.allFields as Record<string, FieldConfig> | undefined\n const diffs = diffFields(data1, data2, fieldConfigs)\n\n // Enhance blocks fields with structural diff\n for (const diff of diffs) {\n if (diff.type === 'blocks' && Array.isArray(diff.from) && Array.isArray(diff.to)) {\n diff.blockDiffs = diffBlocks(diff.from, diff.to)\n }\n }\n\n return { diffs, fromVersion: version1, toVersion: version2 }\n }\n\n /**\n * Compare the current draft with the live entity (or a specific version).\n * Returns field-level diffs between the current published data and the draft.\n */\n async compareDraftWithCurrent(\n entityId: string,\n locale = '_',\n ): Promise<{ diffs: FieldDiff[]; current: Record<string, unknown>; draft: DraftEntry } | null> {\n const draft = await this.getDraft(entityId, locale)\n if (!draft) return null\n\n const admin = await this.getAdmin()\n\n // Fetch the live entity with the same locale context as the draft\n const { getContentConfig } = await import('./plugin.js')\n const config = getContentConfig()\n const defaultLocale = config.defaultLocale\n const findOptions = locale === '_' ? { defaultLocale } : { locale, defaultLocale }\n const current = (await admin.findById(entityId, findOptions)) as Record<string, unknown> | null\n if (!current) return null\n\n const fieldConfigs = this.entity.allFields as Record<string, FieldConfig> | undefined\n\n // Only compare fields present in the draft (draft stores a partial snapshot\n // of user-edited fields — missing fields are NOT \"changed to empty\")\n const currentSubset: Record<string, unknown> = {}\n for (const key of Object.keys(draft.data)) {\n currentSubset[key] = current[key]\n }\n const diffs = diffFields(currentSubset, draft.data, fieldConfigs)\n\n // Enhance blocks fields with structural diff\n for (const diff of diffs) {\n if (diff.type === 'blocks' && Array.isArray(diff.from) && Array.isArray(diff.to)) {\n diff.blockDiffs = diffBlocks(diff.from, diff.to)\n }\n }\n\n return { diffs, current, draft }\n }\n\n /**\n * Restore an entity to a previous version.\n */\n async restoreVersion(entityId: string, version: number) {\n const versionData = await this.getVersion(entityId, version)\n if (!versionData) {\n throw new Error(`Version ${version} not found for entity ${entityId}`)\n }\n\n const data = versionData.data as Record<string, EntityFieldValue>\n\n // Remove auto-generated fields before update\n const { id, createdAt, createdBy, _version, _versionedAt, ...updateData } = data\n\n const admin = await this.getAdmin()\n return admin.update(entityId, updateData)\n }\n\n /**\n * Enforce maxPerDoc retention by deleting oldest versions for a locale.\n */\n async pruneVersions(entityId: string, locale = '_') {\n if (!this.versionableOptions) return\n\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and, desc, sql } = await import('drizzle-orm')\n\n const versionsTable = schemaRegistry.get(`${this.entity.name}_versions`)\n if (!versionsTable) return\n\n const maxPerDoc = this.versionableOptions.maxPerDoc\n\n // Get IDs of versions to keep (newest N for this locale)\n const keepVersions = await this.db\n .select({ id: versionsTable.id })\n .from(versionsTable)\n .where(and(eq(versionsTable.entityId, entityId), eq(versionsTable.locale, locale)))\n .orderBy(desc(versionsTable.version))\n .limit(maxPerDoc)\n\n if (keepVersions.length < maxPerDoc) return // Under limit\n\n const keepIds = keepVersions.map((v: { id: string }) => v.id)\n\n // Delete everything not in keep list (scoped to this locale)\n await this.db.delete(versionsTable).where(\n sql`${versionsTable.entityId} = ${entityId} AND ${versionsTable.locale} = ${locale} AND ${versionsTable.id} NOT IN (${sql.join(\n keepIds.map((id: string) => sql`${id}`),\n sql`, `,\n )})`,\n )\n }\n\n // ---------------------------------------------------------------\n // Translation status\n // ---------------------------------------------------------------\n\n /**\n * Get translation status for an entity — per-locale completion report.\n */\n async getTranslationStatus(entityId: string): Promise<TranslationStatus> {\n const { getContentConfig } = await import('./plugin.js')\n const config = getContentConfig()\n\n // Find all translatable fields\n const translatableFields = Object.entries(this.entity.allFields as Record<string, FieldConfig>)\n .filter(([_, f]) => f.translatable)\n .map(([name]) => name)\n\n if (translatableFields.length === 0) return {}\n\n // Get existing translations\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq } = await import('drizzle-orm')\n\n const translationTable = schemaRegistry.get(`${this.entity.name}_translations`)\n if (!translationTable) return {}\n\n const translations = await this.db\n .select()\n .from(translationTable)\n .where(eq(translationTable.entityId, entityId))\n\n // Build lookup: locale → { fieldName: value } from real columns\n const translationMap = new Map<string, Record<string, unknown>>()\n for (const t of translations) {\n const row = t as Record<string, unknown>\n const fieldValues: Record<string, unknown> = {}\n for (const fieldName of translatableFields) {\n fieldValues[fieldName] = row[fieldName]\n }\n translationMap.set(row.locale as string, fieldValues)\n }\n\n const status: TranslationStatus = {}\n\n for (const locale of config.locales) {\n if (locale.code === config.defaultLocale) continue // Skip default locale\n\n const translated = translationMap.get(locale.code) ?? {}\n const missing: string[] = []\n\n for (const fieldName of translatableFields) {\n if (translated[fieldName] === undefined || translated[fieldName] === null) {\n missing.push(fieldName)\n }\n }\n\n const translatedCount = translatableFields.length - missing.length\n\n status[locale.code] = {\n complete: missing.length === 0,\n total: translatableFields.length,\n translated: translatedCount,\n missing,\n }\n }\n\n return status\n }\n\n /**\n * Get translation status for multiple entities in a single query.\n * Returns a map of entityId → TranslationStatus (O(1) DB roundtrips).\n */\n async getTranslationStatusBatch(entityIds: string[]): Promise<Map<string, TranslationStatus>> {\n const result = new Map<string, TranslationStatus>()\n if (entityIds.length === 0) return result\n\n const { getContentConfig } = await import('./plugin.js')\n const config = getContentConfig()\n\n // Find all translatable fields\n const translatableFields = Object.entries(this.entity.allFields as Record<string, FieldConfig>)\n .filter(([_, f]) => f.translatable)\n .map(([name]) => name)\n\n if (translatableFields.length === 0) return result\n\n // Get existing translations for all entity IDs in one query\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { inArray } = await import('drizzle-orm')\n\n const translationTable = schemaRegistry.get(`${this.entity.name}_translations`)\n if (!translationTable) return result\n\n const translations = await this.db\n .select()\n .from(translationTable)\n .where(inArray(translationTable.entityId, entityIds))\n\n // Group translations by entityId → locale → { fieldName: value } from real columns\n const translationsByEntity = new Map<string, Map<string, Record<string, unknown>>>()\n for (const t of translations) {\n const row = t as Record<string, unknown>\n const entityId = row.entityId as string\n const locale = row.locale as string\n const fieldValues: Record<string, unknown> = {}\n for (const fieldName of translatableFields) {\n fieldValues[fieldName] = row[fieldName]\n }\n if (!translationsByEntity.has(entityId)) {\n translationsByEntity.set(entityId, new Map())\n }\n translationsByEntity.get(entityId)!.set(locale, fieldValues)\n }\n\n // Compute status for each entity\n for (const entityId of entityIds) {\n const localeMap = translationsByEntity.get(entityId)\n const status: TranslationStatus = {}\n\n for (const locale of config.locales) {\n if (locale.code === config.defaultLocale) continue\n\n const translated = localeMap?.get(locale.code) ?? {}\n const missing: string[] = []\n\n for (const fieldName of translatableFields) {\n if (translated[fieldName] === undefined || translated[fieldName] === null) {\n missing.push(fieldName)\n }\n }\n\n const translatedCount = translatableFields.length - missing.length\n\n status[locale.code] = {\n complete: missing.length === 0,\n total: translatableFields.length,\n translated: translatedCount,\n missing,\n }\n }\n\n result.set(entityId, status)\n }\n\n return result\n }\n\n /**\n * Get translation report across all entities of this type.\n */\n async getTranslationReport(options?: { locale?: string }): Promise<TranslationReport> {\n const { getContentConfig } = await import('./plugin.js')\n const config = getContentConfig()\n\n const targetLocale =\n options?.locale ?? config.locales.find((l) => l.code !== config.defaultLocale)?.code\n\n if (!targetLocale) return { total: 0, complete: 0, partial: 0, missing: 0 }\n\n // Get all entities\n const admin = await this.getAdmin()\n const entities = await admin.findMany()\n\n let complete = 0\n let partial = 0\n let missing = 0\n\n for (const entity of entities) {\n const status = await this.getTranslationStatus(entity.id)\n const localeStatus = status[targetLocale]\n\n if (!localeStatus || localeStatus.translated === 0) {\n missing++\n } else if (localeStatus.complete) {\n complete++\n } else {\n partial++\n }\n }\n\n return {\n total: entities.length,\n complete,\n partial,\n missing,\n }\n }\n\n // ---------------------------------------------------------------\n // Per-locale publish status\n // ---------------------------------------------------------------\n\n /**\n * Get per-locale publish status for an entity.\n * Returns status for all non-default locales that have explicit overrides.\n * Missing locale = inherits base entity status.\n */\n async getLocaleStatus(entityId: string): Promise<LocalePublishStatus[]> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq } = await import('drizzle-orm')\n\n const statusTable = schemaRegistry.get(`${this.entity.name}_locale_status`)\n if (!statusTable) return []\n\n const rows = await this.db.select().from(statusTable).where(eq(statusTable.entityId, entityId))\n\n return rows.map((r) => ({\n locale: r.locale as string,\n status: r.status as 'draft' | 'published',\n publishedAt: (r.publishedAt as Date | null) ?? null,\n }))\n }\n\n /**\n * Set per-locale publish status. Upserts into locale_status table.\n * When locale === defaultLocale, updates the base entity status instead.\n */\n async setLocaleStatus(\n entityId: string,\n locale: string,\n status: 'draft' | 'published',\n ): Promise<void> {\n const { getContentConfig } = await import('./plugin.js')\n const config = getContentConfig()\n\n // Default locale → update base entity status column\n if (locale === config.defaultLocale) {\n const admin = await this.getAdmin()\n const data: Record<string, EntityFieldValue> = { status }\n if (status === 'published') {\n data.publishedAt = new Date()\n }\n await admin.update(entityId, data)\n return\n }\n\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const statusTable = schemaRegistry.get(`${this.entity.name}_locale_status`)\n if (!statusTable) return\n\n const publishedAt = status === 'published' ? new Date() : null\n\n // Upsert: check if row exists\n const [existing] = await this.db\n .select({ id: statusTable.id })\n .from(statusTable)\n .where(and(eq(statusTable.entityId, entityId), eq(statusTable.locale, locale)))\n .limit(1)\n\n if (existing) {\n await this.db\n .update(statusTable)\n .set({ status, publishedAt })\n .where(eq(statusTable.id, existing.id))\n } else {\n await this.db.insert(statusTable).values({\n entityId,\n locale,\n status,\n publishedAt,\n })\n }\n }\n\n /**\n * Get per-locale publish status for multiple entities in a single query.\n * Returns a map of entityId → locale → LocalePublishStatus.\n */\n async getLocaleStatusBatch(\n entityIds: string[],\n ): Promise<Map<string, Record<string, LocalePublishStatus>>> {\n const result = new Map<string, Record<string, LocalePublishStatus>>()\n if (entityIds.length === 0) return result\n\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { inArray } = await import('drizzle-orm')\n\n const statusTable = schemaRegistry.get(`${this.entity.name}_locale_status`)\n if (!statusTable) return result\n\n const rows = await this.db\n .select()\n .from(statusTable)\n .where(inArray(statusTable.entityId, entityIds))\n\n for (const r of rows) {\n const eid = r.entityId as string\n const locale = r.locale as string\n if (!result.has(eid)) {\n result.set(eid, {})\n }\n result.get(eid)![locale] = {\n locale,\n status: r.status as 'draft' | 'published',\n publishedAt: (r.publishedAt as Date | null) ?? null,\n }\n }\n\n return result\n }\n\n // ---------------------------------------------------------------\n // Draft operations\n // ---------------------------------------------------------------\n\n /**\n * Save (upsert) a draft for an entity+locale.\n * One draft per entity+locale — overwrites any existing draft.\n */\n async saveDraft(\n entityId: string,\n data: Record<string, unknown>,\n locale = '_',\n user?: { id: string; name?: string },\n ): Promise<DraftEntry> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const draftsTable = schemaRegistry.get(`${this.entity.name}_drafts`)\n if (!draftsTable) {\n throw new Error(\n `Drafts table not found for entity \"${this.entity.name}\". Is the content plugin initialized?`,\n )\n }\n\n const now = new Date()\n const createdBy = user?.id ?? ''\n const createdByName = user?.name ?? null\n\n // Check for existing draft\n const [existing] = await this.db\n .select()\n .from(draftsTable)\n .where(and(eq(draftsTable.entityId, entityId), eq(draftsTable.locale, locale)))\n .limit(1)\n\n if (existing) {\n const [updated] = await this.db\n .update(draftsTable)\n .set({ data, updatedAt: now })\n .where(eq(draftsTable.id, existing.id))\n .returning()\n\n return this.toDraftEntry(updated)\n }\n\n const [inserted] = await this.db\n .insert(draftsTable)\n .values({\n entityId,\n locale,\n data,\n createdBy,\n createdByName,\n createdAt: now,\n updatedAt: now,\n })\n .returning()\n\n return this.toDraftEntry(inserted)\n }\n\n /**\n * Get the draft for an entity+locale, or null if none exists.\n */\n async getDraft(entityId: string, locale = '_'): Promise<DraftEntry | null> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const draftsTable = schemaRegistry.get(`${this.entity.name}_drafts`)\n if (!draftsTable) return null\n\n const [row] = await this.db\n .select()\n .from(draftsTable)\n .where(and(eq(draftsTable.entityId, entityId), eq(draftsTable.locale, locale)))\n .limit(1)\n\n return row ? this.toDraftEntry(row) : null\n }\n\n /**\n * Delete (discard) a draft for an entity+locale.\n */\n async deleteDraft(entityId: string, locale = '_'): Promise<boolean> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const draftsTable = schemaRegistry.get(`${this.entity.name}_drafts`)\n if (!draftsTable) return false\n\n const result = await this.db\n .delete(draftsTable)\n .where(and(eq(draftsTable.entityId, entityId), eq(draftsTable.locale, locale)))\n .returning({ id: draftsTable.id })\n\n return result.length > 0\n }\n\n /**\n * Publish a draft: apply draft data to the live entity, then delete the draft.\n * Runs in a transaction to ensure atomicity.\n */\n async publishDraft(\n entityId: string,\n locale = '_',\n user?: { id: string; name?: string },\n ): Promise<Record<string, unknown>> {\n const draft = await this.getDraft(entityId, locale)\n if (!draft) {\n throw new Error(`No draft found for entity ${entityId} locale ${locale}`)\n }\n\n const admin = await this.getAdmin()\n\n // Get previous state for version delta\n let previousState: Record<string, unknown> | null = null\n if (this.versionableOptions) {\n previousState = await admin.findById(entityId)\n }\n\n // Apply draft data to entity\n const draftData = draft.data as Record<string, EntityFieldValue>\n const { id, createdAt, createdBy, _version, _versionedAt, ...updateData } = draftData\n const result = await admin.update(entityId, updateData)\n\n // Delete the draft\n await this.deleteDraft(entityId, locale)\n\n // Create version snapshot\n if (this.versionableOptions) {\n await this.createVersionSnapshot(entityId, result, previousState, locale, user)\n }\n\n return result\n }\n\n /**\n * Quick existence check for list badges — does a draft exist?\n */\n async hasDraft(entityId: string, locale = '_'): Promise<boolean> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const draftsTable = schemaRegistry.get(`${this.entity.name}_drafts`)\n if (!draftsTable) return false\n\n const [row] = await this.db\n .select({ id: draftsTable.id })\n .from(draftsTable)\n .where(and(eq(draftsTable.entityId, entityId), eq(draftsTable.locale, locale)))\n .limit(1)\n\n return !!row\n }\n\n /**\n * Check draft existence for multiple entities in one query.\n * Returns a set of entity IDs that have drafts.\n */\n async hasDraftBatch(entityIds: string[], locale = '_'): Promise<Set<string>> {\n const result = new Set<string>()\n if (entityIds.length === 0) return result\n\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { inArray, eq, and } = await import('drizzle-orm')\n\n const draftsTable = schemaRegistry.get(`${this.entity.name}_drafts`)\n if (!draftsTable) return result\n\n const rows = await this.db\n .select({ entityId: draftsTable.entityId })\n .from(draftsTable)\n .where(and(inArray(draftsTable.entityId, entityIds), eq(draftsTable.locale, locale)))\n\n for (const row of rows) {\n result.add(row.entityId as string)\n }\n\n return result\n }\n\n /**\n * Create a version snapshot for an entity after an external update.\n * Use this when the update was performed by AdminClient directly\n * (e.g., the admin API handler) rather than through ContentClient.update().\n *\n * @param locale - The locale being edited. Use undefined or '_' for the default locale.\n * @param user - The user who made the change (id + name for display).\n */\n async createVersionSnapshot(\n entityId: string,\n currentData: Record<string, unknown>,\n previousData?: Record<string, unknown> | null,\n locale?: string,\n user?: { id: string; name?: string },\n ) {\n if (!this.versionableOptions) return\n const snapshotLocale = locale ?? '_'\n const delta = previousData ? this.calculateDelta(previousData, currentData) : null\n const version = await this.saveVersionSnapshot(\n entityId,\n currentData,\n delta,\n false,\n snapshotLocale,\n user,\n )\n\n // Update _version on the entity to match the snapshot version (default locale only)\n if (version !== undefined && snapshotLocale === '_') {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq } = await import('drizzle-orm')\n const table = schemaRegistry.get(this.entity.name)\n if (table) {\n await this.db.update(table).set({ _version: version }).where(eq(table.id, entityId))\n }\n }\n\n await this.pruneVersions(entityId, snapshotLocale)\n }\n\n // ---------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------\n\n private async saveVersionSnapshot(\n entityId: string,\n data: Record<string, unknown>,\n delta: Record<string, unknown> | null,\n isAutosave: boolean,\n locale = '_',\n user?: { id: string; name?: string },\n ): Promise<number | undefined> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and, sql } = await import('drizzle-orm')\n\n const versionsTable = schemaRegistry.get(`${this.entity.name}_versions`)\n if (!versionsTable) return\n\n // Get the next version number for this locale (max + 1)\n const [maxRow] = await this.db\n .select({ maxVersion: sql<number>`coalesce(max(${versionsTable.version}), 0)` })\n .from(versionsTable)\n .where(and(eq(versionsTable.entityId, entityId), eq(versionsTable.locale, locale)))\n\n const version = (maxRow?.maxVersion ?? 0) + 1\n const status = (data.status as string) ?? null\n\n // For autosave: update existing autosave version instead of creating new one\n if (isAutosave) {\n const [existing] = await this.db\n .select()\n .from(versionsTable)\n .where(\n and(\n eq(versionsTable.entityId, entityId),\n eq(versionsTable.isAutosave, true),\n eq(versionsTable.version, version),\n eq(versionsTable.locale, locale),\n ),\n )\n .limit(1)\n\n if (existing) {\n await this.db\n .update(versionsTable)\n .set({ data, delta, status, createdAt: new Date() })\n .where(eq(versionsTable.id, existing.id))\n return version\n }\n }\n\n await this.db.insert(versionsTable).values({\n entityId,\n version,\n locale,\n data,\n delta,\n status,\n createdBy: user?.id ?? (data.updatedBy as string) ?? (data.createdBy as string) ?? null,\n createdByName: user?.name ?? null,\n isAutosave,\n })\n return version\n }\n\n private toDraftEntry(row: Record<string, unknown>): DraftEntry {\n return {\n id: row.id as string,\n entityId: row.entityId as string,\n locale: row.locale as string,\n data: row.data as Record<string, unknown>,\n createdBy: row.createdBy as string,\n createdByName: (row.createdByName as string) ?? null,\n createdAt: row.createdAt as Date,\n updatedAt: row.updatedAt as Date,\n }\n }\n\n private calculateDelta(\n previous: Record<string, unknown>,\n current: Record<string, unknown>,\n ): Record<string, unknown> {\n const delta: Record<string, unknown> = {}\n\n for (const key of Object.keys(current)) {\n if (key === 'id' || key.startsWith('_')) continue\n if (JSON.stringify(previous[key]) !== JSON.stringify(current[key])) {\n delta[key] = current[key]\n }\n }\n\n return delta\n }\n}\n\n/**\n * Factory — creates a ContentClient for an entity.\n * Must be called after createApp().\n */\nexport async function createContentClient(entity: Entity): Promise<ContentClient> {\n const { getApp } = await import('@murumets-ee/core')\n const app = getApp()\n return new ContentClient({\n entity,\n db: app.db.readWrite,\n logger: app.logger.child({ content: entity.name }),\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["/**\n * ContentClient — wraps AdminClient with version history and translation status.\n *\n * Usage:\n * import { createContentClient } from '@murumets-ee/content/client'\n * const pages = createContentClient(Page)\n * await pages.create({ title: 'Hello', layout: [...] })\n */\n\nimport 'server-only'\n\nimport type { Logger } from '@murumets-ee/core'\nimport type { Entity, FieldConfig, FieldToTS } from '@murumets-ee/entity'\nimport type { AdminClient, FindManyOptions } from '@murumets-ee/entity/admin'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport { getVersionableOptions } from './behaviors/versionable.js'\nimport { diffBlocks } from './diff/block-diff.js'\nimport { diffFields } from './diff/field-diff.js'\nimport type { FieldDiff } from './diff/types.js'\nimport type {\n DraftEntry,\n LocalePublishStatus,\n TranslationReport,\n TranslationStatus,\n VersionEntry,\n} from './types.js'\n\n/** Value type that any entity field can hold */\ntype EntityFieldValue = FieldToTS<FieldConfig> | null | undefined\n\nexport interface ContentClientConfig {\n entity: Entity\n db: PostgresJsDatabase\n logger?: Logger\n}\n\nexport class ContentClient {\n private entity: Entity\n private db: PostgresJsDatabase\n private logger?: Logger\n private admin: AdminClient | null = null\n private versionableOptions: ReturnType<typeof getVersionableOptions>\n\n constructor(config: ContentClientConfig) {\n this.entity = config.entity\n this.db = config.db\n this.logger = config.logger\n this.versionableOptions = getVersionableOptions(config.entity.behaviors)\n }\n\n private async getAdmin() {\n if (!this.admin) {\n const { AdminClient } = await import('@murumets-ee/entity/admin')\n this.admin = new AdminClient({\n entity: this.entity,\n db: this.db,\n logger: this.logger,\n })\n }\n return this.admin\n }\n\n // ---------------------------------------------------------------\n // Standard CRUD (delegated to AdminClient)\n // ---------------------------------------------------------------\n\n async create(data: Record<string, EntityFieldValue>) {\n const admin = await this.getAdmin()\n const result = await admin.create(data)\n\n // Save initial version snapshot\n if (this.versionableOptions) {\n await this.saveVersionSnapshot(result.id, result, null, false)\n }\n\n return result\n }\n\n async findById(id: string, options?: { locale?: string }) {\n const admin = await this.getAdmin()\n return admin.findById(id, options)\n }\n\n async findMany(options?: FindManyOptions) {\n const admin = await this.getAdmin()\n return admin.findMany(options)\n }\n\n async update(id: string, data: Record<string, EntityFieldValue>) {\n const admin = await this.getAdmin()\n\n // Get previous state for delta calculation\n let previousState: Record<string, unknown> | null = null\n if (this.versionableOptions) {\n previousState = await admin.findById(id)\n }\n\n const result = await admin.update(id, data)\n\n // Save version snapshot and update _version on the entity\n if (this.versionableOptions && previousState) {\n const delta = this.calculateDelta(previousState, result)\n const version = await this.saveVersionSnapshot(id, result, delta, false)\n\n if (version !== undefined) {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq } = await import('drizzle-orm')\n const table = schemaRegistry.get(this.entity.name)\n if (table) {\n await this.db.update(table).set({ _version: version }).where(eq(table.id, id))\n ;(result as Record<string, unknown>)._version = version\n }\n }\n\n await this.pruneVersions(id)\n }\n\n return result\n }\n\n async delete(id: string) {\n const admin = await this.getAdmin()\n return admin.delete(id)\n }\n\n // ---------------------------------------------------------------\n // Translation delegation\n // ---------------------------------------------------------------\n\n async saveTranslation(entityId: string, locale: string, translations: Record<string, unknown>) {\n const admin = await this.getAdmin()\n return admin.saveTranslation(entityId, locale, translations)\n }\n\n async deleteTranslation(entityId: string, locale?: string) {\n const admin = await this.getAdmin()\n return admin.deleteTranslation(entityId, locale)\n }\n\n // ---------------------------------------------------------------\n // Block operations\n // ---------------------------------------------------------------\n\n /**\n * Copy layout from one locale to another (for localized: true blocks).\n */\n async copyLayout(entityId: string, options: { from: string; to: string | string[] }) {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const layoutTable = schemaRegistry.get(`${this.entity.name}_layout`)\n if (!layoutTable) return\n\n const targetLocales = Array.isArray(options.to) ? options.to : [options.to]\n\n // Get source layout rows\n const sourceRows = await this.db\n .select()\n .from(layoutTable)\n .where(and(eq(layoutTable.entityId, entityId), eq(layoutTable.locale, options.from)))\n .orderBy(layoutTable.sortOrder)\n\n if (sourceRows.length === 0) return\n\n for (const targetLocale of targetLocales) {\n // Delete existing target layout\n await this.db\n .delete(layoutTable)\n .where(and(eq(layoutTable.entityId, entityId), eq(layoutTable.locale, targetLocale)))\n\n // Copy source rows with new locale\n for (const row of sourceRows) {\n await this.db.insert(layoutTable).values({\n entityId: row.entityId,\n fieldName: row.fieldName,\n blockType: row.blockType,\n sortOrder: row.sortOrder,\n data: row.data,\n locale: targetLocale,\n })\n }\n }\n\n this.logger?.debug(\n { entityId, from: options.from, to: targetLocales },\n 'Layout copied between locales',\n )\n }\n\n // ---------------------------------------------------------------\n // Version operations\n // ---------------------------------------------------------------\n\n /**\n * List versions for an entity (newest first), filtered by locale.\n * Uses '_' as the locale value for default-locale snapshots.\n */\n async getVersions(\n entityId: string,\n options?: { limit?: number; offset?: number; locale?: string },\n ): Promise<VersionEntry[]> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and, desc } = await import('drizzle-orm')\n\n const versionsTable = schemaRegistry.get(`${this.entity.name}_versions`)\n if (!versionsTable) return []\n\n const snapshotLocale = options?.locale ?? '_'\n\n let query = this.db\n .select()\n .from(versionsTable)\n .where(and(eq(versionsTable.entityId, entityId), eq(versionsTable.locale, snapshotLocale)))\n .orderBy(desc(versionsTable.version))\n .$dynamic()\n\n if (options?.limit) {\n query = query.limit(options.limit)\n }\n if (options?.offset) {\n query = query.offset(options.offset)\n }\n\n return query as unknown as VersionEntry[]\n }\n\n /**\n * Get a specific version snapshot.\n */\n async getVersion(entityId: string, version: number, locale = '_'): Promise<VersionEntry | null> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const versionsTable = schemaRegistry.get(`${this.entity.name}_versions`)\n if (!versionsTable) return null\n\n const rows = await this.db\n .select()\n .from(versionsTable)\n .where(\n and(\n eq(versionsTable.entityId, entityId),\n eq(versionsTable.version, version),\n eq(versionsTable.locale, locale),\n ),\n )\n .limit(1)\n\n return (rows[0] as unknown as VersionEntry) ?? null\n }\n\n /**\n * Compare two versions and return field-level diff.\n *\n * Returns structured diffs with word-level segments for text fields\n * and structural block diffs for blocks fields.\n */\n async compareVersions(\n entityId: string,\n v1: number,\n v2: number,\n locale = '_',\n ): Promise<{ diffs: FieldDiff[]; fromVersion: VersionEntry; toVersion: VersionEntry } | null> {\n const [version1, version2] = await Promise.all([\n this.getVersion(entityId, v1, locale),\n this.getVersion(entityId, v2, locale),\n ])\n\n if (!version1 || !version2) return null\n\n const data1 = version1.data as Record<string, unknown>\n const data2 = version2.data as Record<string, unknown>\n\n const fieldConfigs = this.entity.allFields as Record<string, FieldConfig> | undefined\n const diffs = diffFields(data1, data2, fieldConfigs)\n\n // Enhance blocks fields with structural diff\n for (const diff of diffs) {\n if (diff.type === 'blocks' && Array.isArray(diff.from) && Array.isArray(diff.to)) {\n diff.blockDiffs = diffBlocks(diff.from, diff.to)\n }\n }\n\n return { diffs, fromVersion: version1, toVersion: version2 }\n }\n\n /**\n * Compare the current draft with the live entity (or a specific version).\n * Returns field-level diffs between the current published data and the draft.\n */\n async compareDraftWithCurrent(\n entityId: string,\n locale = '_',\n ): Promise<{ diffs: FieldDiff[]; current: Record<string, unknown>; draft: DraftEntry } | null> {\n const draft = await this.getDraft(entityId, locale)\n if (!draft) return null\n\n const admin = await this.getAdmin()\n\n // Fetch the live entity with the same locale context as the draft\n const { getContentConfig } = await import('./plugin.js')\n const config = getContentConfig()\n const defaultLocale = config.defaultLocale\n const findOptions = locale === '_' ? { defaultLocale } : { locale, defaultLocale }\n const current = (await admin.findById(entityId, findOptions)) as Record<string, unknown> | null\n if (!current) return null\n\n const fieldConfigs = this.entity.allFields as Record<string, FieldConfig> | undefined\n\n // Only compare fields present in the draft (draft stores a partial snapshot\n // of user-edited fields — missing fields are NOT \"changed to empty\")\n const currentSubset: Record<string, unknown> = {}\n for (const key of Object.keys(draft.data)) {\n currentSubset[key] = current[key]\n }\n const diffs = diffFields(currentSubset, draft.data, fieldConfigs)\n\n // Enhance blocks fields with structural diff\n for (const diff of diffs) {\n if (diff.type === 'blocks' && Array.isArray(diff.from) && Array.isArray(diff.to)) {\n diff.blockDiffs = diffBlocks(diff.from, diff.to)\n }\n }\n\n return { diffs, current, draft }\n }\n\n /**\n * Restore an entity to a previous version.\n */\n async restoreVersion(entityId: string, version: number) {\n const versionData = await this.getVersion(entityId, version)\n if (!versionData) {\n throw new Error(`Version ${version} not found for entity ${entityId}`)\n }\n\n const data = versionData.data as Record<string, EntityFieldValue>\n\n // Remove auto-generated fields before update\n const { id, createdAt, createdBy, _version, _versionedAt, ...updateData } = data\n\n const admin = await this.getAdmin()\n return admin.update(entityId, updateData)\n }\n\n /**\n * Enforce maxPerDoc retention by deleting oldest versions for a locale.\n */\n async pruneVersions(entityId: string, locale = '_') {\n if (!this.versionableOptions) return\n\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and, desc, sql } = await import('drizzle-orm')\n\n const versionsTable = schemaRegistry.get(`${this.entity.name}_versions`)\n if (!versionsTable) return\n\n const maxPerDoc = this.versionableOptions.maxPerDoc\n\n // Get IDs of versions to keep (newest N for this locale)\n const keepVersions = await this.db\n .select({ id: versionsTable.id })\n .from(versionsTable)\n .where(and(eq(versionsTable.entityId, entityId), eq(versionsTable.locale, locale)))\n .orderBy(desc(versionsTable.version))\n .limit(maxPerDoc)\n\n if (keepVersions.length < maxPerDoc) return // Under limit\n\n const keepIds = keepVersions.map((v: { id: string }) => v.id)\n\n // Delete everything not in keep list (scoped to this locale)\n await this.db.delete(versionsTable).where(\n sql`${versionsTable.entityId} = ${entityId} AND ${versionsTable.locale} = ${locale} AND ${versionsTable.id} NOT IN (${sql.join(\n keepIds.map((id: string) => sql`${id}`),\n sql`, `,\n )})`,\n )\n }\n\n // ---------------------------------------------------------------\n // Translation status\n // ---------------------------------------------------------------\n\n /**\n * Get translation status for an entity — per-locale completion report.\n */\n async getTranslationStatus(entityId: string): Promise<TranslationStatus> {\n const { getContentConfig } = await import('./plugin.js')\n const config = getContentConfig()\n\n // Find all translatable fields\n const translatableFields = Object.entries(this.entity.allFields as Record<string, FieldConfig>)\n .filter(([_, f]) => f.translatable)\n .map(([name]) => name)\n\n if (translatableFields.length === 0) return {}\n\n // Get existing translations\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq } = await import('drizzle-orm')\n\n const translationTable = schemaRegistry.get(`${this.entity.name}_translations`)\n if (!translationTable) return {}\n\n const translations = await this.db\n .select()\n .from(translationTable)\n .where(eq(translationTable.entityId, entityId))\n\n // Build lookup: locale → { fieldName: value } from real columns\n const translationMap = new Map<string, Record<string, unknown>>()\n for (const t of translations) {\n const row = t as Record<string, unknown>\n const fieldValues: Record<string, unknown> = {}\n for (const fieldName of translatableFields) {\n fieldValues[fieldName] = row[fieldName]\n }\n translationMap.set(row.locale as string, fieldValues)\n }\n\n const status: TranslationStatus = {}\n\n for (const locale of config.locales) {\n if (locale.code === config.defaultLocale) continue // Skip default locale\n\n const translated = translationMap.get(locale.code) ?? {}\n const missing: string[] = []\n\n for (const fieldName of translatableFields) {\n if (translated[fieldName] === undefined || translated[fieldName] === null) {\n missing.push(fieldName)\n }\n }\n\n const translatedCount = translatableFields.length - missing.length\n\n status[locale.code] = {\n complete: missing.length === 0,\n total: translatableFields.length,\n translated: translatedCount,\n missing,\n }\n }\n\n return status\n }\n\n /**\n * Get translation status for multiple entities in a single query.\n * Returns a map of entityId → TranslationStatus (O(1) DB roundtrips).\n */\n async getTranslationStatusBatch(entityIds: string[]): Promise<Map<string, TranslationStatus>> {\n const result = new Map<string, TranslationStatus>()\n if (entityIds.length === 0) return result\n\n const { getContentConfig } = await import('./plugin.js')\n const config = getContentConfig()\n\n // Find all translatable fields\n const translatableFields = Object.entries(this.entity.allFields as Record<string, FieldConfig>)\n .filter(([_, f]) => f.translatable)\n .map(([name]) => name)\n\n if (translatableFields.length === 0) return result\n\n // Get existing translations for all entity IDs in one query\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { inArray } = await import('drizzle-orm')\n\n const translationTable = schemaRegistry.get(`${this.entity.name}_translations`)\n if (!translationTable) return result\n\n const translations = await this.db\n .select()\n .from(translationTable)\n .where(inArray(translationTable.entityId, entityIds))\n\n // Group translations by entityId → locale → { fieldName: value } from real columns\n const translationsByEntity = new Map<string, Map<string, Record<string, unknown>>>()\n for (const t of translations) {\n const row = t as Record<string, unknown>\n const entityId = row.entityId as string\n const locale = row.locale as string\n const fieldValues: Record<string, unknown> = {}\n for (const fieldName of translatableFields) {\n fieldValues[fieldName] = row[fieldName]\n }\n if (!translationsByEntity.has(entityId)) {\n translationsByEntity.set(entityId, new Map())\n }\n translationsByEntity.get(entityId)!.set(locale, fieldValues)\n }\n\n // Compute status for each entity\n for (const entityId of entityIds) {\n const localeMap = translationsByEntity.get(entityId)\n const status: TranslationStatus = {}\n\n for (const locale of config.locales) {\n if (locale.code === config.defaultLocale) continue\n\n const translated = localeMap?.get(locale.code) ?? {}\n const missing: string[] = []\n\n for (const fieldName of translatableFields) {\n if (translated[fieldName] === undefined || translated[fieldName] === null) {\n missing.push(fieldName)\n }\n }\n\n const translatedCount = translatableFields.length - missing.length\n\n status[locale.code] = {\n complete: missing.length === 0,\n total: translatableFields.length,\n translated: translatedCount,\n missing,\n }\n }\n\n result.set(entityId, status)\n }\n\n return result\n }\n\n /**\n * Get translation report across all entities of this type.\n */\n async getTranslationReport(options?: { locale?: string }): Promise<TranslationReport> {\n const { getContentConfig } = await import('./plugin.js')\n const config = getContentConfig()\n\n const targetLocale =\n options?.locale ?? config.locales.find((l) => l.code !== config.defaultLocale)?.code\n\n if (!targetLocale) return { total: 0, complete: 0, partial: 0, missing: 0 }\n\n // Get all entities\n const admin = await this.getAdmin()\n const entities = await admin.findMany()\n\n let complete = 0\n let partial = 0\n let missing = 0\n\n for (const entity of entities) {\n const status = await this.getTranslationStatus(entity.id)\n const localeStatus = status[targetLocale]\n\n if (!localeStatus || localeStatus.translated === 0) {\n missing++\n } else if (localeStatus.complete) {\n complete++\n } else {\n partial++\n }\n }\n\n return {\n total: entities.length,\n complete,\n partial,\n missing,\n }\n }\n\n // ---------------------------------------------------------------\n // Per-locale publish status\n // ---------------------------------------------------------------\n\n /**\n * Get per-locale publish status for an entity.\n * Returns status for all non-default locales that have explicit overrides.\n * Missing locale = inherits base entity status.\n */\n async getLocaleStatus(entityId: string): Promise<LocalePublishStatus[]> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq } = await import('drizzle-orm')\n\n const statusTable = schemaRegistry.get(`${this.entity.name}_locale_status`)\n if (!statusTable) return []\n\n const rows = await this.db.select().from(statusTable).where(eq(statusTable.entityId, entityId))\n\n return rows.map((r) => ({\n locale: r.locale as string,\n status: r.status as 'draft' | 'published',\n publishedAt: (r.publishedAt as Date | null) ?? null,\n }))\n }\n\n /**\n * Set per-locale publish status. Upserts into locale_status table.\n * When locale === defaultLocale, updates the base entity status instead.\n */\n async setLocaleStatus(\n entityId: string,\n locale: string,\n status: 'draft' | 'published',\n ): Promise<void> {\n const { getContentConfig } = await import('./plugin.js')\n const config = getContentConfig()\n\n // Default locale → update base entity status column\n if (locale === config.defaultLocale) {\n const admin = await this.getAdmin()\n const data: Record<string, EntityFieldValue> = { status }\n if (status === 'published') {\n data.publishedAt = new Date()\n }\n await admin.update(entityId, data)\n return\n }\n\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const statusTable = schemaRegistry.get(`${this.entity.name}_locale_status`)\n if (!statusTable) return\n\n const publishedAt = status === 'published' ? new Date() : null\n\n // Upsert: check if row exists\n const [existing] = await this.db\n .select({ id: statusTable.id })\n .from(statusTable)\n .where(and(eq(statusTable.entityId, entityId), eq(statusTable.locale, locale)))\n .limit(1)\n\n if (existing) {\n await this.db\n .update(statusTable)\n .set({ status, publishedAt })\n .where(eq(statusTable.id, existing.id))\n } else {\n await this.db.insert(statusTable).values({\n entityId,\n locale,\n status,\n publishedAt,\n })\n }\n }\n\n /**\n * Get per-locale publish status for multiple entities in a single query.\n * Returns a map of entityId → locale → LocalePublishStatus.\n */\n async getLocaleStatusBatch(\n entityIds: string[],\n ): Promise<Map<string, Record<string, LocalePublishStatus>>> {\n const result = new Map<string, Record<string, LocalePublishStatus>>()\n if (entityIds.length === 0) return result\n\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { inArray } = await import('drizzle-orm')\n\n const statusTable = schemaRegistry.get(`${this.entity.name}_locale_status`)\n if (!statusTable) return result\n\n const rows = await this.db\n .select()\n .from(statusTable)\n .where(inArray(statusTable.entityId, entityIds))\n\n for (const r of rows) {\n const eid = r.entityId as string\n const locale = r.locale as string\n if (!result.has(eid)) {\n result.set(eid, {})\n }\n result.get(eid)![locale] = {\n locale,\n status: r.status as 'draft' | 'published',\n publishedAt: (r.publishedAt as Date | null) ?? null,\n }\n }\n\n return result\n }\n\n // ---------------------------------------------------------------\n // Draft operations\n // ---------------------------------------------------------------\n\n /**\n * Save (upsert) a draft for an entity+locale.\n * One draft per entity+locale — overwrites any existing draft.\n */\n async saveDraft(\n entityId: string,\n data: Record<string, unknown>,\n locale = '_',\n user?: { id: string; name?: string },\n ): Promise<DraftEntry> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const draftsTable = schemaRegistry.get(`${this.entity.name}_drafts`)\n if (!draftsTable) {\n throw new Error(\n `Drafts table not found for entity \"${this.entity.name}\". Is the content plugin initialized?`,\n )\n }\n\n const now = new Date()\n const createdBy = user?.id ?? ''\n const createdByName = user?.name ?? null\n\n // Check for existing draft\n const [existing] = await this.db\n .select()\n .from(draftsTable)\n .where(and(eq(draftsTable.entityId, entityId), eq(draftsTable.locale, locale)))\n .limit(1)\n\n if (existing) {\n const [updated] = await this.db\n .update(draftsTable)\n .set({ data, updatedAt: now })\n .where(eq(draftsTable.id, existing.id))\n .returning()\n\n return this.toDraftEntry(updated)\n }\n\n const [inserted] = await this.db\n .insert(draftsTable)\n .values({\n entityId,\n locale,\n data,\n createdBy,\n createdByName,\n createdAt: now,\n updatedAt: now,\n })\n .returning()\n\n return this.toDraftEntry(inserted)\n }\n\n /**\n * Get the draft for an entity+locale, or null if none exists.\n */\n async getDraft(entityId: string, locale = '_'): Promise<DraftEntry | null> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const draftsTable = schemaRegistry.get(`${this.entity.name}_drafts`)\n if (!draftsTable) return null\n\n const [row] = await this.db\n .select()\n .from(draftsTable)\n .where(and(eq(draftsTable.entityId, entityId), eq(draftsTable.locale, locale)))\n .limit(1)\n\n return row ? this.toDraftEntry(row) : null\n }\n\n /**\n * Delete (discard) a draft for an entity+locale.\n */\n async deleteDraft(entityId: string, locale = '_'): Promise<boolean> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const draftsTable = schemaRegistry.get(`${this.entity.name}_drafts`)\n if (!draftsTable) return false\n\n const result = await this.db\n .delete(draftsTable)\n .where(and(eq(draftsTable.entityId, entityId), eq(draftsTable.locale, locale)))\n .returning({ id: draftsTable.id })\n\n return result.length > 0\n }\n\n /**\n * Publish a draft: apply draft data to the live entity, then delete the draft.\n * Runs in a transaction to ensure atomicity.\n */\n async publishDraft(\n entityId: string,\n locale = '_',\n user?: { id: string; name?: string },\n ): Promise<Record<string, unknown>> {\n const draft = await this.getDraft(entityId, locale)\n if (!draft) {\n throw new Error(`No draft found for entity ${entityId} locale ${locale}`)\n }\n\n const admin = await this.getAdmin()\n\n // Get previous state for version delta\n let previousState: Record<string, unknown> | null = null\n if (this.versionableOptions) {\n previousState = await admin.findById(entityId)\n }\n\n // Apply draft data to entity\n const draftData = draft.data as Record<string, EntityFieldValue>\n const { id, createdAt, createdBy, _version, _versionedAt, ...updateData } = draftData\n const result = await admin.update(entityId, updateData)\n\n // Delete the draft\n await this.deleteDraft(entityId, locale)\n\n // Create version snapshot\n if (this.versionableOptions) {\n await this.createVersionSnapshot(entityId, result, previousState, locale, user)\n }\n\n return result\n }\n\n /**\n * Quick existence check for list badges — does a draft exist?\n */\n async hasDraft(entityId: string, locale = '_'): Promise<boolean> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and } = await import('drizzle-orm')\n\n const draftsTable = schemaRegistry.get(`${this.entity.name}_drafts`)\n if (!draftsTable) return false\n\n const [row] = await this.db\n .select({ id: draftsTable.id })\n .from(draftsTable)\n .where(and(eq(draftsTable.entityId, entityId), eq(draftsTable.locale, locale)))\n .limit(1)\n\n return !!row\n }\n\n /**\n * Count drafts for this entity type, optionally filtered by locale.\n * Single indexed COUNT query — used by dashboard widgets.\n */\n async countDrafts(locale = '_'): Promise<number> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, sql } = await import('drizzle-orm')\n\n const draftsTable = schemaRegistry.get(`${this.entity.name}_drafts`)\n if (!draftsTable) return 0\n\n const [row] = await this.db\n .select({ count: sql<number>`count(*)::int` })\n .from(draftsTable)\n .where(eq(draftsTable.locale, locale))\n\n return row?.count ?? 0\n }\n\n /**\n * Check draft existence for multiple entities in one query.\n * Returns a set of entity IDs that have drafts.\n */\n async hasDraftBatch(entityIds: string[], locale = '_'): Promise<Set<string>> {\n const result = new Set<string>()\n if (entityIds.length === 0) return result\n\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { inArray, eq, and } = await import('drizzle-orm')\n\n const draftsTable = schemaRegistry.get(`${this.entity.name}_drafts`)\n if (!draftsTable) return result\n\n const rows = await this.db\n .select({ entityId: draftsTable.entityId })\n .from(draftsTable)\n .where(and(inArray(draftsTable.entityId, entityIds), eq(draftsTable.locale, locale)))\n\n for (const row of rows) {\n result.add(row.entityId as string)\n }\n\n return result\n }\n\n /**\n * Create a version snapshot for an entity after an external update.\n * Use this when the update was performed by AdminClient directly\n * (e.g., the admin API handler) rather than through ContentClient.update().\n *\n * @param locale - The locale being edited. Use undefined or '_' for the default locale.\n * @param user - The user who made the change (id + name for display).\n */\n async createVersionSnapshot(\n entityId: string,\n currentData: Record<string, unknown>,\n previousData?: Record<string, unknown> | null,\n locale?: string,\n user?: { id: string; name?: string },\n ) {\n if (!this.versionableOptions) return\n const snapshotLocale = locale ?? '_'\n const delta = previousData ? this.calculateDelta(previousData, currentData) : null\n const version = await this.saveVersionSnapshot(\n entityId,\n currentData,\n delta,\n false,\n snapshotLocale,\n user,\n )\n\n // Update _version on the entity to match the snapshot version (default locale only)\n if (version !== undefined && snapshotLocale === '_') {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq } = await import('drizzle-orm')\n const table = schemaRegistry.get(this.entity.name)\n if (table) {\n await this.db.update(table).set({ _version: version }).where(eq(table.id, entityId))\n }\n }\n\n await this.pruneVersions(entityId, snapshotLocale)\n }\n\n // ---------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------\n\n private async saveVersionSnapshot(\n entityId: string,\n data: Record<string, unknown>,\n delta: Record<string, unknown> | null,\n isAutosave: boolean,\n locale = '_',\n user?: { id: string; name?: string },\n ): Promise<number | undefined> {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, and, sql } = await import('drizzle-orm')\n\n const versionsTable = schemaRegistry.get(`${this.entity.name}_versions`)\n if (!versionsTable) return\n\n // Get the next version number for this locale (max + 1)\n const [maxRow] = await this.db\n .select({ maxVersion: sql<number>`coalesce(max(${versionsTable.version}), 0)` })\n .from(versionsTable)\n .where(and(eq(versionsTable.entityId, entityId), eq(versionsTable.locale, locale)))\n\n const version = (maxRow?.maxVersion ?? 0) + 1\n const status = (data.status as string) ?? null\n\n // For autosave: update existing autosave version instead of creating new one\n if (isAutosave) {\n const [existing] = await this.db\n .select()\n .from(versionsTable)\n .where(\n and(\n eq(versionsTable.entityId, entityId),\n eq(versionsTable.isAutosave, true),\n eq(versionsTable.version, version),\n eq(versionsTable.locale, locale),\n ),\n )\n .limit(1)\n\n if (existing) {\n await this.db\n .update(versionsTable)\n .set({ data, delta, status, createdAt: new Date() })\n .where(eq(versionsTable.id, existing.id))\n return version\n }\n }\n\n await this.db.insert(versionsTable).values({\n entityId,\n version,\n locale,\n data,\n delta,\n status,\n createdBy: user?.id ?? (data.updatedBy as string) ?? (data.createdBy as string) ?? null,\n createdByName: user?.name ?? null,\n isAutosave,\n })\n return version\n }\n\n private toDraftEntry(row: Record<string, unknown>): DraftEntry {\n return {\n id: row.id as string,\n entityId: row.entityId as string,\n locale: row.locale as string,\n data: row.data as Record<string, unknown>,\n createdBy: row.createdBy as string,\n createdByName: (row.createdByName as string) ?? null,\n createdAt: row.createdAt as Date,\n updatedAt: row.updatedAt as Date,\n }\n }\n\n private calculateDelta(\n previous: Record<string, unknown>,\n current: Record<string, unknown>,\n ): Record<string, unknown> {\n const delta: Record<string, unknown> = {}\n\n for (const key of Object.keys(current)) {\n if (key === 'id' || key.startsWith('_')) continue\n if (JSON.stringify(previous[key]) !== JSON.stringify(current[key])) {\n delta[key] = current[key]\n }\n }\n\n return delta\n }\n}\n\n/**\n * Factory — creates a ContentClient for an entity.\n * Must be called after createApp().\n */\nexport async function createContentClient(entity: Entity): Promise<ContentClient> {\n const { getApp } = await import('@murumets-ee/core')\n const app = getApp()\n return new ContentClient({\n entity,\n db: app.db.readWrite,\n logger: app.logger.child({ content: entity.name }),\n })\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@murumets-ee/content",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"server-only": "^0.0.1",
|
|
38
38
|
"zod": "^3.24.1",
|
|
39
39
|
"@murumets-ee/core": "0.1.6",
|
|
40
|
-
"@murumets-ee/entity": "0.1.5",
|
|
41
40
|
"@murumets-ee/logging": "0.1.6",
|
|
41
|
+
"@murumets-ee/entity": "0.2.0",
|
|
42
42
|
"@murumets-ee/db": "0.1.5"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|