@murumets-ee/entity 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.
@@ -77,6 +77,23 @@ interface EntityAdminConfig {
77
77
  disableCreate?: boolean;
78
78
  /** Order within sidebar group. Default: 0 */
79
79
  sortOrder?: number;
80
+ /**
81
+ * Hide the entity from BOTH dashboard and sidebar.
82
+ * Use for system entities that should not appear in any UI.
83
+ * Shorthand for `hideFromMenu: true` + `hideFromDashboard: true`.
84
+ */
85
+ hidden?: boolean;
86
+ /**
87
+ * Hide from sidebar menu only. Entity remains accessible via dashboard, direct URL,
88
+ * and the entity API. Use for storage models accessed through specialized UI
89
+ * (e.g., Ticket entities accessed via the inbox/board, not as a CRUD table).
90
+ */
91
+ hideFromMenu?: boolean;
92
+ /**
93
+ * Hide from dashboard widget grid only. Entity still appears in sidebar.
94
+ * Use for entities that don't have meaningful counts/stats to show.
95
+ */
96
+ hideFromDashboard?: boolean;
80
97
  }
81
98
  //#endregion
82
99
  //#region src/fields/base.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/count-cache.ts","../../src/cursor.ts","../../src/admin-config.ts","../../src/fields/base.ts","../../src/behaviors/types.ts","../../src/define-entity.ts","../../src/types/infer.ts","../../src/types/logger.ts","../../src/admin/client.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/count-cache.ts","../../src/cursor.ts","../../src/admin-config.ts","../../src/fields/base.ts","../../src/behaviors/types.ts","../../src/define-entity.ts","../../src/types/infer.ts","../../src/types/logger.ts","../../src/admin/client.ts"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{schemaRegistry as e}from"@murumets-ee/db";import{and as t,eq as n,gt as r,inArray as i,isNull as a,lt as o,or as s,sql as c}from"drizzle-orm";import{index as l,pgTable as u,unique as d,uuid as f,varchar as p}from"drizzle-orm/pg-core";import{z as m}from"zod";function h(e,i){let a=e[i.field];if(!a)return null;let c=i.direction===`desc`?o:r,l=c(a,i.value);if(!i.id)return l;let u=e.id;if(!u)return l;let d=c(u,i.id);return s(l,t(n(a,i.value),d))}function g(e,t,n){if(!t)return null;let r={},i=n?.select||Object.keys(e.allFields);for(let a of i){if(!n?.includeInternal&&a.startsWith(`_`))continue;let i=e.allFields[a];i&&i.type!==`blocks`&&(r[a]=t[a])}return r}function _(e,t,n){return t.map(t=>g(e,t,n))}var v=class extends Error{entityName;entityId;usages;constructor(e,t,n){let r=n.length;super(`Cannot delete ${e} '${t}': referenced by ${r} other entit${r===1?`y`:`ies`}`),this.name=`ReferencedEntityError`,this.entityName=e,this.entityId=t,this.usages=n}};const y=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function b(e,t){let n=[],r=new Set;function i(e,t,i){if(!y.test(i))return;let a=`${e}|${t}|${i}`;r.has(a)||(r.add(a),n.push({sourceField:e,targetEntity:t,targetId:i}))}for(let[n,r]of Object.entries(e)){let e=t[n];if(e!=null)switch(r.type){case`media`:typeof e==`string`&&i(n,`media`,e);break;case`reference`:{let t=r;if(t.cardinality===`many`&&Array.isArray(e))for(let r of e)typeof r==`string`&&i(n,t.entity,r);else typeof e==`string`&&i(n,t.entity,e);break}case`blocks`:{let t=r;if(!Array.isArray(e))break;for(let r of e){let e=r,a=e._block;if(!a)continue;let o=t.blocks.find(e=>e.slug===a);if(o)for(let[t,r]of Object.entries(o.fields)){let a=e[t];if(a!=null&&(r.type===`media`&&typeof a==`string`&&i(n,`media`,a),r.type===`reference`)){let e=r;if(e.cardinality===`many`&&Array.isArray(a))for(let t of a)typeof t==`string`&&i(n,e.entity,t);else typeof a==`string`&&i(n,e.entity,a)}}}break}}}return n}const x=u(`entity_refs`,{sourceEntity:p(`source_entity`,{length:100}).notNull(),sourceId:f(`source_id`).notNull(),sourceField:p(`source_field`,{length:100}).notNull(),targetEntity:p(`target_entity`,{length:100}).notNull(),targetId:f(`target_id`).notNull()},e=>[d(`uq_entity_refs`).on(e.sourceEntity,e.sourceId,e.sourceField,e.targetEntity,e.targetId),l(`idx_entity_refs_target`).on(e.targetEntity,e.targetId),l(`idx_entity_refs_source`).on(e.sourceEntity,e.sourceId)]);async function S(e,r,i){return await i.select({sourceEntity:x.sourceEntity,sourceId:x.sourceId,sourceField:x.sourceField}).from(x).where(t(n(x.targetEntity,e),n(x.targetId,r)))}function C(e){let t;switch(e.type){case`id`:t=m.string().uuid();break;case`text`:t=m.string(),e.maxLength&&(t=t.max(e.maxLength)),e.minLength&&(t=t.min(e.minLength)),e.pattern&&(t=t.regex(e.pattern));break;case`number`:t=m.number(),e.integer&&(t=t.int()),e.min!==void 0&&(t=t.min(e.min)),e.max!==void 0&&(t=t.max(e.max));break;case`boolean`:t=m.boolean();break;case`date`:t=m.date().or(m.string().datetime());break;case`select`:t=m.enum(e.options);break;case`reference`:t=e.cardinality===`many`?m.array(m.string().uuid()):m.string().uuid();break;case`media`:t=m.string().uuid();break;case`richtext`:t=m.union([m.string(),m.array(m.record(m.unknown()))]);break;case`slug`:t=m.string().regex(/^[a-z0-9-]+$/);break;case`json`:t=m.record(m.unknown());break;case`blocks`:t=m.array(m.object({_block:m.string()}).passthrough());break;default:t=m.unknown()}return e.required||(t=t.nullable().optional()),e.default!==void 0&&(t=t.default(e.default)),t}function w(e){let t={},n=new Set([`id`,`createdAt`,`updatedAt`,`createdBy`,`_version`]);for(let[r,i]of Object.entries(e.allFields))n.has(r)||(t[r]=C(i));return m.object(t)}function T(e){let t={},n=new Set([`id`,`createdAt`,`createdBy`,`updatedAt`,`updatedBy`,`_version`]);for(let[r,i]of Object.entries(e.allFields))n.has(r)||(t[r]=C(i).optional());return m.object(t)}var E=class{entity;db;logger;createSchema;updateSchema;table;countCache;constructor(t){if(typeof window<`u`)throw Error(`AdminClient cannot be used in browser code. Use QueryClient for frontend data access.`);this.entity=t.entity,this.db=t.db,this.logger=t.logger,this.countCache=t.countCache,this.createSchema=w(t.entity),this.updateSchema=T(t.entity);let n=e.get(t.entity.name);if(!n)throw Error(`Schema for entity '${t.entity.name}' not found in registry. Ensure schemas are generated and registered before creating AdminClient.`);this.table=n}async create(e){this.logger?.info({entity:this.entity.name},`Creating entity`);let t=e;if(this.entity.hooks?.beforeCreate){let e=await this.entity.hooks.beforeCreate(t);t=e===void 0?t:e}t=this.createSchema.parse(t);let n=this.prepareDataForInsert(t),[r]=await this.db.insert(this.table).values(n).returning();await this.saveBlocks(r.id,t),await this.syncRefs(r.id,t,`create`),this.entity.hooks?.afterCreate&&await this.entity.hooks.afterCreate(r),this.invalidateCountCache();let i=g(this.entity,r);if(i&&this.getBlocksFields().length>0){let e=await this.loadBlocks([i.id]);this.attachBlocks([i],e)}return i}async findById(e,t){this.logger?.info({entity:this.entity.name,id:e},`Finding entity by ID`);let[r]=await this.db.select().from(this.table).where(n(this.table.id,e));if(!r)return null;let i=g(this.entity,r);if(!i)return null;if(this.getBlocksFields().length>0){let e=await this.loadBlocks([i.id],t?.locale,{defaultLocale:t?.defaultLocale});this.attachBlocks([i],e)}if(t?.locale){let[e]=await this.mergeTranslations([i],t.locale);return e}return i}async findMany(e){this.logger?.info({entity:this.entity.name,options:e},`Finding entities`);let n=this.db.select().from(this.table).$dynamic(),r=[];if(e?.where&&r.push(e.where),e?.cursor){let t=this.entity.allFields;if(!(e.cursor.field in t)&&e.cursor.field!==`id`)throw Error(`Invalid cursor field: '${e.cursor.field}' is not a field on '${this.entity.name}'`);let n=h(this.table,e.cursor);n&&r.push(n)}if(r.length>0&&(n=n.where(t(...r))),e?.limit&&(n=n.limit(e.limit)),e?.offset&&!e?.cursor&&(n=n.offset(e.offset)),e?.orderBy){let t=Array.isArray(e.orderBy)?e.orderBy:[e.orderBy];n=n.orderBy(...t)}let i=await n,a=_(this.entity,i).filter(e=>e!==null);if(this.getBlocksFields().length>0&&a.length>0){let t=a.map(e=>e.id),n=await this.loadBlocks(t,e?.locale,{defaultLocale:e?.defaultLocale});this.attachBlocks(a,n)}return e?.locale?await this.mergeTranslations(a,e.locale):a}async count(e){this.logger?.info({entity:this.entity.name,options:e},`Counting entities`);let t=this.buildCountCacheKey(e?.where);if(this.countCache){let e=this.countCache.get(t);if(e!==void 0)return this.logger?.debug?.({entity:this.entity.name,cached:e},`Count cache hit`),e}let n=this.db.select({count:c`count(*)`}).from(this.table).$dynamic();e?.where&&(n=n.where(e.where));let[r]=await n,i=Number(r.count);return this.countCache&&this.countCache.set(t,i),i}async update(e,t,r){this.logger?.info({entity:this.entity.name,id:e},`Updating entity`);let i=t;if(this.entity.hooks?.beforeUpdate){let t=await this.entity.hooks.beforeUpdate(e,i);i=t===void 0?i:t}i=this.updateSchema.parse(i);let a=this.prepareDataForUpdate(i),o;if(Object.keys(a).length>0){let[t]=await this.db.update(this.table).set(a).where(n(this.table.id,e)).returning();o=t}else{let[t]=await this.db.select().from(this.table).where(n(this.table.id,e));o=t}await this.saveBlocks(e,i,r),await this.syncRefs(e,i,`update`),this.entity.hooks?.afterUpdate&&await this.entity.hooks.afterUpdate(o);let s=g(this.entity,o);if(s&&this.getBlocksFields().length>0){let t=await this.loadBlocks([e]);this.attachBlocks([s],t)}return s}async delete(e){this.logger?.info({entity:this.entity.name,id:e},`Deleting entity`);let r=await S(this.entity.name,e,this.db);if(r.length>0)throw new v(this.entity.name,e,r);this.entity.hooks?.beforeDelete&&await this.entity.hooks.beforeDelete(e),await this.db.delete(this.table).where(n(this.table.id,e)),await this.db.delete(x).where(t(n(x.sourceEntity,this.entity.name),n(x.sourceId,e))),this.entity.hooks?.afterDelete&&await this.entity.hooks.afterDelete(e),this.invalidateCountCache()}prepareDataForInsert(e){let t={};for(let[n,r]of Object.entries(e)){let e=this.entity.allFields[n];e&&n!==`id`&&e.type!==`blocks`&&(t[n]=r)}return t}prepareDataForUpdate(e){let t={};for(let[n,r]of Object.entries(e)){let e=this.entity.allFields[n];e&&n!==`id`&&e.type!==`blocks`&&(t[n]=r)}return t}async mergeTranslations(r,a){if(!r.length)return r;let o=`${this.entity.name}_translations`,s=e.get(o);if(!s)return r;let c=r.map(e=>e.id),l=await this.db.select().from(s).where(t(i(s.entityId,c),n(s.locale,a))),u=Object.entries(this.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e),d=new Map;for(let e of l){let t={};for(let n of u){let r=e[n];r!=null&&(t[n]=r)}d.set(e.entityId,t)}return r.map(e=>{let t=d.get(e.id);return t?{...e,...t}:e})}async saveTranslation(t,n,r){this.logger?.info({entity:this.entity.name,entityId:t,locale:n},`Saving translation`);let i=`${this.entity.name}_translations`,a=e.get(i);if(!a)throw Error(`Translation table ${i} not found in schema registry`);let o=Object.entries(this.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e);if(o.length===0)throw Error(`Entity ${this.entity.name} has no translatable fields`);let s={entityId:t,locale:n},c={};for(let e of o)r[e]!==void 0&&(s[e]=r[e],c[e]=r[e]);await this.db.insert(a).values(s).onConflictDoUpdate({target:[a.entityId,a.locale],set:c}),this.logger?.info({entity:this.entity.name,entityId:t,locale:n},`Translation saved`)}async deleteTranslation(r,i){this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Deleting translation`);let a=`${this.entity.name}_translations`,o=e.get(a);if(!o)throw Error(`Translation table ${a} not found in schema registry`);i?(await this.db.delete(o).where(t(n(o.entityId,r),n(o.locale,i))),this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Translation deleted`)):(await this.db.delete(o).where(n(o.entityId,r)),this.logger?.info({entity:this.entity.name,entityId:r},`All translations deleted`))}async saveBlockTranslations(r,i,o){this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Saving block translations`);let s=this.getBlocksFields();if(s.length===0)return;let c=e.get(`${this.entity.name}_layout_translations`);if(!c)return;let l=e.get(`${this.entity.name}_layout`);if(l){for(let{name:e,config:u}of s){let s=o[e];if(!Array.isArray(s))continue;let d=u.blocks;if(!d)continue;let f=await this.db.select({id:l.id,blockType:l.blockType}).from(l).where(t(n(l.entityId,r),n(l.fieldName,e),a(l.locale))).orderBy(l.sortOrder);for(let e=0;e<s.length;e++){let t=s[e],n=t._block;if(!n)continue;let r=f[e];if(!r||r.blockType!==n)continue;let a=d.find(e=>e.slug===n);if(!a)continue;let o={};for(let[e,n]of Object.entries(a.fields))n.translatable&&t[e]!==void 0&&(o[e]=t[e]);Object.keys(o).length!==0&&await this.db.insert(c).values({layoutId:r.id,locale:i,fields:o}).onConflictDoUpdate({target:[c.layoutId,c.locale],set:{fields:o}})}}this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Block translations saved`)}}async saveLocalizedBlocks(e,t,n){this.logger?.info({entity:this.entity.name,entityId:e,locale:t},`Saving localized blocks`),await this.saveBlocks(e,n,{locale:t})}getBlocksFields(){return Object.entries(this.entity.allFields).filter(([e,t])=>t.type===`blocks`).map(([e,t])=>({name:e,config:t}))}async saveBlocks(r,i,o){let s=this.getBlocksFields();if(s.length===0)return;let c=e.get(`${this.entity.name}_layout`);if(c)for(let{name:e,config:l}of s){let s=i[e];if(!Array.isArray(s))continue;let u=`localized`in l&&l.localized===!0,d=u?o?.locale??null:null,f=[n(c.entityId,r),n(c.fieldName,e)];d?f.push(n(c.locale,d)):u||f.push(a(c.locale)),await this.db.delete(c).where(t(...f));for(let t=0;t<s.length;t++){let n=s[t],i=n._block;if(!i)continue;let{_block:a,_id:o,...l}=n;await this.db.insert(c).values({entityId:r,fieldName:e,blockType:i,sortOrder:t,data:l,locale:d})}}}async loadBlocks(r,o,c){let l=this.getBlocksFields();if(l.length===0||r.length===0)return new Map;let u=e.get(`${this.entity.name}_layout`);if(!u)return new Map;let d=l.filter(({config:e})=>!(`localized`in e&&e.localized)),f=l.filter(({config:e})=>`localized`in e&&e.localized),p=new Map;if(d.length>0){let s=await this.db.select().from(u).where(t(i(u.entityId,r),a(u.locale))).orderBy(u.sortOrder),c;if(o&&s.length>0){let r=e.get(`${this.entity.name}_layout_translations`);if(r){let e=s.map(e=>e.id),a=await this.db.select().from(r).where(t(i(r.layoutId,e),n(r.locale,o)));c=new Map;for(let e of a)c.set(e.layoutId,e.fields??{})}}let l=new Map;for(let{config:e}of d){let t=e.blocks;if(t)for(let e of t)l.set(e.slug,e.fields)}for(let e of s){let t=e.entityId,n=e.fieldName;p.has(t)||p.set(t,{});let r=p.get(t);r[n]||(r[n]=[]);let i=e.data??{},a=c?.get(e.id),s={_block:e.blockType,_id:e.id,...i,...a??{}};if(o){let t=e.blockType,n=l.get(t);if(n)for(let[e,t]of Object.entries(n))t.translatable&&!a?.[e]&&(s[e]=``)}r[n].push(s)}}if(f.length>0){let e=!o||o===c?.defaultLocale,l=o?e?s(n(u.locale,o),a(u.locale)):n(u.locale,o):a(u.locale),d=await this.db.select().from(u).where(t(i(u.entityId,r),l)).orderBy(u.sortOrder),f=new Map;for(let e of d){let t=`${e.entityId}::${e.fieldName}`;f.has(t)||f.set(t,{localeRows:[],nullRows:[]});let n=f.get(t);e.locale?n.localeRows.push(e):n.nullRows.push(e)}for(let[,{localeRows:e,nullRows:t}]of f){let n=e.length>0?e:t;for(let e of n){let t=e.entityId,n=e.fieldName;p.has(t)||p.set(t,{});let r=p.get(t);r[n]||(r[n]=[]);let i=e.data??{};r[n].push({_block:e.blockType,_id:e.id,...i})}}}return p}attachBlocks(e,t){let n=this.getBlocksFields();if(n.length!==0)for(let r of e){let e=r.id,i=t.get(e)??{};for(let{name:e}of n)r[e]=i[e]??[]}}async syncRefs(r,a,o){if(!e.has(`entity_refs`))return;let s=this.entity.allFields;if(o===`update`){let e=Object.keys(a).filter(e=>{let t=s[e];return t&&(t.type===`media`||t.type===`reference`||t.type===`blocks`)});e.length>0&&await this.db.delete(x).where(t(n(x.sourceEntity,this.entity.name),n(x.sourceId,r),i(x.sourceField,e)))}let c=b(s,a);c.length>0&&await this.db.insert(x).values(c.map(e=>({sourceEntity:this.entity.name,sourceId:r,sourceField:e.sourceField,targetEntity:e.targetEntity,targetId:e.targetId}))).onConflictDoNothing()}buildCountCacheKey(e){return e?`${this.entity.name}:${String(e)}`:this.entity.name}invalidateCountCache(){this.countCache?.invalidate(this.entity.name)}};export{E as AdminClient};
1
+ import{schemaRegistry as e}from"@murumets-ee/db";import{and as t,eq as n,gt as r,inArray as i,isNull as a,lt as o,or as s,sql as c}from"drizzle-orm";import{index as l,pgTable as u,unique as d,uuid as f,varchar as p}from"drizzle-orm/pg-core";import{z as m}from"zod";function h(e,i){let a=e[i.field];if(!a)return null;let c=i.direction===`desc`?o:r,l=c(a,i.value);if(!i.id)return l;let u=e.id;if(!u)return l;let d=c(u,i.id);return s(l,t(n(a,i.value),d))}function g(e,t,n){if(!t)return null;let r={},i=n?.select||Object.keys(e.allFields);for(let a of i){if(!n?.includeInternal&&a.startsWith(`_`))continue;let i=e.allFields[a];i&&i.type!==`blocks`&&(r[a]=t[a])}return r}function _(e,t,n){return t.map(t=>g(e,t,n))}var v=class extends Error{entityName;entityId;usages;constructor(e,t,n){let r=n.length;super(`Cannot delete ${e} '${t}': referenced by ${r} other entit${r===1?`y`:`ies`}`),this.name=`ReferencedEntityError`,this.entityName=e,this.entityId=t,this.usages=n}};const y=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function b(e,t){let n=[],r=new Set;function i(e,t,i){if(!y.test(i))return;let a=`${e}|${t}|${i}`;r.has(a)||(r.add(a),n.push({sourceField:e,targetEntity:t,targetId:i}))}for(let[n,r]of Object.entries(e)){let e=t[n];if(e!=null)switch(r.type){case`media`:typeof e==`string`&&i(n,`media`,e);break;case`reference`:{let t=r;if(t.cardinality===`many`&&Array.isArray(e))for(let r of e)typeof r==`string`&&i(n,t.entity,r);else typeof e==`string`&&i(n,t.entity,e);break}case`blocks`:{let t=r;if(!Array.isArray(e))break;for(let r of e){let e=r,a=e._block;if(!a)continue;let o=t.blocks.find(e=>e.slug===a);if(o)for(let[t,r]of Object.entries(o.fields)){let a=e[t];if(a!=null&&(r.type===`media`&&typeof a==`string`&&i(n,`media`,a),r.type===`reference`)){let e=r;if(e.cardinality===`many`&&Array.isArray(a))for(let t of a)typeof t==`string`&&i(n,e.entity,t);else typeof a==`string`&&i(n,e.entity,a)}}}break}}}return n}const x=u(`entity_refs`,{sourceEntity:p(`source_entity`,{length:100}).notNull(),sourceId:f(`source_id`).notNull(),sourceField:p(`source_field`,{length:100}).notNull(),targetEntity:p(`target_entity`,{length:100}).notNull(),targetId:f(`target_id`).notNull()},e=>[d(`uq_entity_refs`).on(e.sourceEntity,e.sourceId,e.sourceField,e.targetEntity,e.targetId),l(`idx_entity_refs_target`).on(e.targetEntity,e.targetId),l(`idx_entity_refs_source`).on(e.sourceEntity,e.sourceId)]);async function S(e,r,i){return await i.select({sourceEntity:x.sourceEntity,sourceId:x.sourceId,sourceField:x.sourceField}).from(x).where(t(n(x.targetEntity,e),n(x.targetId,r)))}function C(e){let t;switch(e.type){case`id`:t=m.string().uuid();break;case`text`:t=m.string(),e.maxLength&&(t=t.max(e.maxLength)),e.minLength&&(t=t.min(e.minLength)),e.pattern&&(t=t.regex(e.pattern));break;case`number`:t=m.number(),e.integer&&(t=t.int()),e.min!==void 0&&(t=t.min(e.min)),e.max!==void 0&&(t=t.max(e.max));break;case`boolean`:t=m.boolean();break;case`date`:t=m.date().or(m.string().datetime());break;case`select`:t=m.enum(e.options);break;case`reference`:t=e.cardinality===`many`?m.array(m.string().uuid()):m.string().uuid();break;case`media`:t=m.string().uuid();break;case`richtext`:t=m.union([m.string(),m.array(m.record(m.unknown()))]);break;case`slug`:t=m.string().regex(/^[a-z0-9-]+$/);break;case`json`:t=m.record(m.unknown());break;case`blocks`:t=m.array(m.object({_block:m.string()}).passthrough());break;default:t=m.unknown()}return e.required||(t=t.nullable().optional()),e.default!==void 0&&(t=t.default(e.default)),t}function w(e){let t={},n=new Set([`id`,`createdAt`,`updatedAt`,`createdBy`,`_version`]);for(let[r,i]of Object.entries(e.allFields))n.has(r)||(t[r]=C(i));return m.object(t)}function T(e){let t={},n=new Set([`id`,`createdAt`,`createdBy`,`updatedAt`,`updatedBy`,`_version`]);for(let[r,i]of Object.entries(e.allFields))n.has(r)||(t[r]=C(i).optional());return m.object(t)}var E=class{entity;db;logger;createSchema;updateSchema;table;countCache;constructor(t){if(typeof window<`u`)throw Error(`AdminClient cannot be used in browser code. Use QueryClient for frontend data access.`);this.entity=t.entity,this.db=t.db,this.logger=t.logger,this.countCache=t.countCache,this.createSchema=w(t.entity),this.updateSchema=T(t.entity);let n=e.get(t.entity.name);if(!n)throw Error(`Schema for entity '${t.entity.name}' not found in registry. Ensure schemas are generated and registered before creating AdminClient.`);this.table=n}async create(e){this.logger?.info({entity:this.entity.name},`Creating entity`);let t=e;if(this.entity.hooks?.beforeCreate){let e=await this.entity.hooks.beforeCreate(t);t=e===void 0?t:e}let n={};for(let e of[`createdAt`,`updatedAt`,`createdBy`,`updatedBy`])t[e]!==void 0&&(n[e]=t[e]);t={...this.createSchema.parse(t),...n};let r=this.prepareDataForInsert(t),[i]=await this.db.insert(this.table).values(r).returning();await this.saveBlocks(i.id,t),await this.syncRefs(i.id,t,`create`),this.entity.hooks?.afterCreate&&await this.entity.hooks.afterCreate(i),this.invalidateCountCache();let a=g(this.entity,i);if(a&&this.getBlocksFields().length>0){let e=await this.loadBlocks([a.id]);this.attachBlocks([a],e)}return a}async findById(e,t){this.logger?.info({entity:this.entity.name,id:e},`Finding entity by ID`);let[r]=await this.db.select().from(this.table).where(n(this.table.id,e));if(!r)return null;let i=g(this.entity,r);if(!i)return null;if(this.getBlocksFields().length>0){let e=await this.loadBlocks([i.id],t?.locale,{defaultLocale:t?.defaultLocale});this.attachBlocks([i],e)}if(t?.locale){let[e]=await this.mergeTranslations([i],t.locale);return e}return i}async findMany(e){this.logger?.info({entity:this.entity.name,options:e},`Finding entities`);let n=this.db.select().from(this.table).$dynamic(),r=[];if(e?.where&&r.push(e.where),e?.cursor){let t=this.entity.allFields;if(!(e.cursor.field in t)&&e.cursor.field!==`id`)throw Error(`Invalid cursor field: '${e.cursor.field}' is not a field on '${this.entity.name}'`);let n=h(this.table,e.cursor);n&&r.push(n)}if(r.length>0&&(n=n.where(t(...r))),e?.limit&&(n=n.limit(e.limit)),e?.offset&&!e?.cursor&&(n=n.offset(e.offset)),e?.orderBy){let t=Array.isArray(e.orderBy)?e.orderBy:[e.orderBy];n=n.orderBy(...t)}let i=await n,a=_(this.entity,i).filter(e=>e!==null);if(this.getBlocksFields().length>0&&a.length>0){let t=a.map(e=>e.id),n=await this.loadBlocks(t,e?.locale,{defaultLocale:e?.defaultLocale});this.attachBlocks(a,n)}return e?.locale?await this.mergeTranslations(a,e.locale):a}async count(e){this.logger?.info({entity:this.entity.name,options:e},`Counting entities`);let t=this.buildCountCacheKey(e?.where);if(this.countCache){let e=this.countCache.get(t);if(e!==void 0)return this.logger?.debug?.({entity:this.entity.name,cached:e},`Count cache hit`),e}let n=this.db.select({count:c`count(*)`}).from(this.table).$dynamic();e?.where&&(n=n.where(e.where));let[r]=await n,i=Number(r.count);return this.countCache&&this.countCache.set(t,i),i}async update(e,t,r){this.logger?.info({entity:this.entity.name,id:e},`Updating entity`);let i=t;if(this.entity.hooks?.beforeUpdate){let t=await this.entity.hooks.beforeUpdate(e,i);i=t===void 0?i:t}i=this.updateSchema.parse(i);let a=this.prepareDataForUpdate(i),o;if(Object.keys(a).length>0){let[t]=await this.db.update(this.table).set(a).where(n(this.table.id,e)).returning();o=t}else{let[t]=await this.db.select().from(this.table).where(n(this.table.id,e));o=t}await this.saveBlocks(e,i,r),await this.syncRefs(e,i,`update`),this.entity.hooks?.afterUpdate&&await this.entity.hooks.afterUpdate(o);let s=g(this.entity,o);if(s&&this.getBlocksFields().length>0){let t=await this.loadBlocks([e]);this.attachBlocks([s],t)}return s}async delete(e){this.logger?.info({entity:this.entity.name,id:e},`Deleting entity`);let r=await S(this.entity.name,e,this.db);if(r.length>0)throw new v(this.entity.name,e,r);this.entity.hooks?.beforeDelete&&await this.entity.hooks.beforeDelete(e),await this.db.delete(this.table).where(n(this.table.id,e)),await this.db.delete(x).where(t(n(x.sourceEntity,this.entity.name),n(x.sourceId,e))),this.entity.hooks?.afterDelete&&await this.entity.hooks.afterDelete(e),this.invalidateCountCache()}prepareDataForInsert(e){let t={};for(let[n,r]of Object.entries(e)){let e=this.entity.allFields[n];e&&n!==`id`&&e.type!==`blocks`&&(t[n]=r)}return t}prepareDataForUpdate(e){let t={};for(let[n,r]of Object.entries(e)){let e=this.entity.allFields[n];e&&n!==`id`&&e.type!==`blocks`&&(t[n]=r)}return t}async mergeTranslations(r,a){if(!r.length)return r;let o=`${this.entity.name}_translations`,s=e.get(o);if(!s)return r;let c=r.map(e=>e.id),l=await this.db.select().from(s).where(t(i(s.entityId,c),n(s.locale,a))),u=Object.entries(this.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e),d=new Map;for(let e of l){let t={};for(let n of u){let r=e[n];r!=null&&(t[n]=r)}d.set(e.entityId,t)}return r.map(e=>{let t=d.get(e.id);return t?{...e,...t}:e})}async saveTranslation(t,n,r){this.logger?.info({entity:this.entity.name,entityId:t,locale:n},`Saving translation`);let i=`${this.entity.name}_translations`,a=e.get(i);if(!a)throw Error(`Translation table ${i} not found in schema registry`);let o=Object.entries(this.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e);if(o.length===0)throw Error(`Entity ${this.entity.name} has no translatable fields`);let s={entityId:t,locale:n},c={};for(let e of o)r[e]!==void 0&&(s[e]=r[e],c[e]=r[e]);await this.db.insert(a).values(s).onConflictDoUpdate({target:[a.entityId,a.locale],set:c}),this.logger?.info({entity:this.entity.name,entityId:t,locale:n},`Translation saved`)}async deleteTranslation(r,i){this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Deleting translation`);let a=`${this.entity.name}_translations`,o=e.get(a);if(!o)throw Error(`Translation table ${a} not found in schema registry`);i?(await this.db.delete(o).where(t(n(o.entityId,r),n(o.locale,i))),this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Translation deleted`)):(await this.db.delete(o).where(n(o.entityId,r)),this.logger?.info({entity:this.entity.name,entityId:r},`All translations deleted`))}async saveBlockTranslations(r,i,o){this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Saving block translations`);let s=this.getBlocksFields();if(s.length===0)return;let c=e.get(`${this.entity.name}_layout_translations`);if(!c)return;let l=e.get(`${this.entity.name}_layout`);if(l){for(let{name:e,config:u}of s){let s=o[e];if(!Array.isArray(s))continue;let d=u.blocks;if(!d)continue;let f=await this.db.select({id:l.id,blockType:l.blockType}).from(l).where(t(n(l.entityId,r),n(l.fieldName,e),a(l.locale))).orderBy(l.sortOrder);for(let e=0;e<s.length;e++){let t=s[e],n=t._block;if(!n)continue;let r=f[e];if(!r||r.blockType!==n)continue;let a=d.find(e=>e.slug===n);if(!a)continue;let o={};for(let[e,n]of Object.entries(a.fields))n.translatable&&t[e]!==void 0&&(o[e]=t[e]);Object.keys(o).length!==0&&await this.db.insert(c).values({layoutId:r.id,locale:i,fields:o}).onConflictDoUpdate({target:[c.layoutId,c.locale],set:{fields:o}})}}this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Block translations saved`)}}async saveLocalizedBlocks(e,t,n){this.logger?.info({entity:this.entity.name,entityId:e,locale:t},`Saving localized blocks`),await this.saveBlocks(e,n,{locale:t})}getBlocksFields(){return Object.entries(this.entity.allFields).filter(([e,t])=>t.type===`blocks`).map(([e,t])=>({name:e,config:t}))}async saveBlocks(r,i,o){let s=this.getBlocksFields();if(s.length===0)return;let c=e.get(`${this.entity.name}_layout`);if(c)for(let{name:e,config:l}of s){let s=i[e];if(!Array.isArray(s))continue;let u=`localized`in l&&l.localized===!0,d=u?o?.locale??null:null,f=[n(c.entityId,r),n(c.fieldName,e)];d?f.push(n(c.locale,d)):u||f.push(a(c.locale)),await this.db.delete(c).where(t(...f));for(let t=0;t<s.length;t++){let n=s[t],i=n._block;if(!i)continue;let{_block:a,_id:o,...l}=n;await this.db.insert(c).values({entityId:r,fieldName:e,blockType:i,sortOrder:t,data:l,locale:d})}}}async loadBlocks(r,o,c){let l=this.getBlocksFields();if(l.length===0||r.length===0)return new Map;let u=e.get(`${this.entity.name}_layout`);if(!u)return new Map;let d=l.filter(({config:e})=>!(`localized`in e&&e.localized)),f=l.filter(({config:e})=>`localized`in e&&e.localized),p=new Map;if(d.length>0){let s=await this.db.select().from(u).where(t(i(u.entityId,r),a(u.locale))).orderBy(u.sortOrder),c;if(o&&s.length>0){let r=e.get(`${this.entity.name}_layout_translations`);if(r){let e=s.map(e=>e.id),a=await this.db.select().from(r).where(t(i(r.layoutId,e),n(r.locale,o)));c=new Map;for(let e of a)c.set(e.layoutId,e.fields??{})}}let l=new Map;for(let{config:e}of d){let t=e.blocks;if(t)for(let e of t)l.set(e.slug,e.fields)}for(let e of s){let t=e.entityId,n=e.fieldName;p.has(t)||p.set(t,{});let r=p.get(t);r[n]||(r[n]=[]);let i=e.data??{},a=c?.get(e.id),s={_block:e.blockType,_id:e.id,...i,...a??{}};if(o){let t=e.blockType,n=l.get(t);if(n)for(let[e,t]of Object.entries(n))t.translatable&&!a?.[e]&&(s[e]=``)}r[n].push(s)}}if(f.length>0){let e=!o||o===c?.defaultLocale,l=o?e?s(n(u.locale,o),a(u.locale)):n(u.locale,o):a(u.locale),d=await this.db.select().from(u).where(t(i(u.entityId,r),l)).orderBy(u.sortOrder),f=new Map;for(let e of d){let t=`${e.entityId}::${e.fieldName}`;f.has(t)||f.set(t,{localeRows:[],nullRows:[]});let n=f.get(t);e.locale?n.localeRows.push(e):n.nullRows.push(e)}for(let[,{localeRows:e,nullRows:t}]of f){let n=e.length>0?e:t;for(let e of n){let t=e.entityId,n=e.fieldName;p.has(t)||p.set(t,{});let r=p.get(t);r[n]||(r[n]=[]);let i=e.data??{};r[n].push({_block:e.blockType,_id:e.id,...i})}}}return p}attachBlocks(e,t){let n=this.getBlocksFields();if(n.length!==0)for(let r of e){let e=r.id,i=t.get(e)??{};for(let{name:e}of n)r[e]=i[e]??[]}}async syncRefs(r,a,o){if(!e.has(`entity_refs`))return;let s=this.entity.allFields;if(o===`update`){let e=Object.keys(a).filter(e=>{let t=s[e];return t&&(t.type===`media`||t.type===`reference`||t.type===`blocks`)});e.length>0&&await this.db.delete(x).where(t(n(x.sourceEntity,this.entity.name),n(x.sourceId,r),i(x.sourceField,e)))}let c=b(s,a);c.length>0&&await this.db.insert(x).values(c.map(e=>({sourceEntity:this.entity.name,sourceId:r,sourceField:e.sourceField,targetEntity:e.targetEntity,targetId:e.targetId}))).onConflictDoNothing()}buildCountCacheKey(e){return e?`${this.entity.name}:${String(e)}`:this.entity.name}invalidateCountCache(){this.countCache?.invalidate(this.entity.name)}};export{E as AdminClient};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/cursor.ts","../../src/dto-shaper.ts","../../src/refs/errors.ts","../../src/refs/extract-refs.ts","../../src/refs/schema.ts","../../src/refs/find-usages.ts","../../src/validation.ts","../../src/admin/client.ts"],"sourcesContent":["/**\n * Cursor-based (keyset) pagination utilities.\n *\n * Cursor pagination avoids the performance cliff of OFFSET at scale (1M+ rows).\n * Instead of `OFFSET N`, it uses a WHERE condition:\n * `WHERE (sortField < lastValue) OR (sortField = lastValue AND id < lastId)`\n * which Postgres can serve from an index in constant time.\n *\n * The cursor is opaque to the client — base64-encoded JSON.\n *\n * Security:\n * - `field` must be whitelisted against the entity's actual fields\n * - `id` must be a valid UUID\n * - `value` is parameterized (never interpolated into SQL)\n * - Malformed cursors return null (caller returns 400)\n */\n\nimport { and, eq, gt, lt, or, type SQL } from 'drizzle-orm'\nimport type { PgTableWithColumns } from 'drizzle-orm/pg-core'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Cursor input for keyset pagination. */\nexport interface CursorInput {\n /** Sort field name (e.g. 'createdAt'). Must be a real column on the entity. */\n field: string\n /** Last seen value of the sort field. */\n value: string | number\n /** Sort direction — must match the ORDER BY direction. */\n direction: 'asc' | 'desc'\n /** Tie-breaker: last seen entity ID. Required for non-unique sort fields. */\n id?: string\n}\n\n/** Decoded cursor (internal, after validation). */\ninterface DecodedCursor {\n field: string\n value: string | number\n direction: 'asc' | 'desc'\n id?: string\n}\n\n// ---------------------------------------------------------------------------\n// UUID validation (same format used throughout the toolkit)\n// ---------------------------------------------------------------------------\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\n// ---------------------------------------------------------------------------\n// Encode / decode\n// ---------------------------------------------------------------------------\n\n/**\n * Encode a cursor for API transport (base64url).\n * Built from the last item in a result set.\n */\nexport function encodeCursor(\n item: Record<string, unknown>,\n sortField: string,\n direction: 'asc' | 'desc',\n): string {\n const payload: CursorInput = {\n field: sortField,\n value: item[sortField] as string | number,\n direction,\n id: item.id as string | undefined,\n }\n return btoa(JSON.stringify(payload))\n}\n\n/**\n * Decode and validate a cursor string from query params.\n * Returns null if the cursor is malformed, tampered, or invalid.\n *\n * Security: the `field` value is NOT validated here — the caller must\n * whitelist it against the entity's actual columns.\n */\nexport function decodeCursor(encoded: string): DecodedCursor | null {\n try {\n const json = atob(encoded)\n const parsed: unknown = JSON.parse(json)\n\n if (typeof parsed !== 'object' || parsed === null) return null\n const obj = parsed as Record<string, unknown>\n\n // Validate field\n if (typeof obj.field !== 'string' || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(obj.field)) {\n return null\n }\n\n // Validate value (string or number)\n if (typeof obj.value !== 'string' && typeof obj.value !== 'number') {\n return null\n }\n\n // Validate direction\n if (obj.direction !== 'asc' && obj.direction !== 'desc') {\n return null\n }\n\n // Validate id (optional, must be UUID if present)\n if (obj.id !== undefined) {\n if (typeof obj.id !== 'string' || !UUID_RE.test(obj.id)) {\n return null\n }\n }\n\n return {\n field: obj.field,\n value: obj.value,\n direction: obj.direction,\n id: obj.id as string | undefined,\n }\n } catch {\n return null\n }\n}\n\n// ---------------------------------------------------------------------------\n// SQL condition builder\n// ---------------------------------------------------------------------------\n\n/**\n * Build the keyset WHERE condition from a decoded cursor.\n *\n * For DESC: `WHERE (field < value) OR (field = value AND id < cursorId)`\n * For ASC: `WHERE (field > value) OR (field = value AND id > cursorId)`\n *\n * The caller must verify that `cursor.field` exists on the table before calling.\n *\n * @param table - Drizzle table with columns\n * @param cursor - Decoded and validated cursor\n * @returns SQL condition, or null if the field doesn't exist on the table\n */\nexport function buildCursorCondition(\n // biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any> for property access\n table: PgTableWithColumns<any>,\n cursor: DecodedCursor,\n): SQL | null {\n const column = table[cursor.field]\n if (!column) return null\n\n const isDesc = cursor.direction === 'desc'\n const compare = isDesc ? lt : gt\n\n // Primary condition: sort field passes the cursor value\n const fieldCondition = compare(column, cursor.value)\n\n // Without tie-breaker ID, use simple comparison\n if (!cursor.id) {\n return fieldCondition\n }\n\n // With tie-breaker: (field < value) OR (field = value AND id < cursorId)\n const idColumn = table.id\n if (!idColumn) return fieldCondition\n\n const idCondition = compare(idColumn, cursor.id)\n return or(fieldCondition, and(eq(column, cursor.value), idCondition))!\n}\n","/**\n * DTO Shaper\n * Transforms raw DB rows into clean DTOs.\n * All fields are real columns — just read from row directly.\n */\n\nimport type { Entity } from './define-entity.js'\nimport type { FieldConfig } from './fields/base.js'\nimport type { InferEntityDTO } from './types/infer.js'\n\nexport interface ShapeDtoOptions {\n select?: string[] // Explicit field selection\n includeInternal?: boolean // Include _version, _scopeId\n}\n\n/**\n * Shape a raw DB row into a typed DTO.\n * Every field is a real column — read directly from row.\n */\nexport function shapeDto<AllFields extends Record<string, FieldConfig>>(\n entity: Entity<AllFields>,\n row: Record<string, unknown>,\n options?: ShapeDtoOptions,\n): InferEntityDTO<AllFields> | null {\n if (!row) return null\n\n const result: Record<string, unknown> = {}\n const fieldsToInclude = options?.select || Object.keys(entity.allFields)\n\n for (const fieldName of fieldsToInclude) {\n if (!options?.includeInternal && fieldName.startsWith('_')) continue\n\n const fieldConfig = (entity.allFields as Record<string, FieldConfig>)[fieldName]\n if (!fieldConfig) continue\n\n // Blocks are loaded separately from layout tables\n if (fieldConfig.type === 'blocks') continue\n\n result[fieldName] = row[fieldName]\n }\n\n return result as InferEntityDTO<AllFields>\n}\n\n/**\n * Shape multiple rows\n */\nexport function shapeDtos<AllFields extends Record<string, FieldConfig>>(\n entity: Entity<AllFields>,\n rows: Record<string, unknown>[],\n options?: ShapeDtoOptions,\n): (InferEntityDTO<AllFields> | null)[] {\n return rows.map((row) => shapeDto(entity, row, options))\n}\n","/**\n * Error thrown when attempting to delete an entity that is still referenced.\n */\n\nimport type { EntityUsage } from './find-usages.js'\n\nexport class ReferencedEntityError extends Error {\n public readonly entityName: string\n public readonly entityId: string\n public readonly usages: EntityUsage[]\n\n constructor(entityName: string, entityId: string, usages: EntityUsage[]) {\n const count = usages.length\n super(\n `Cannot delete ${entityName} '${entityId}': referenced by ${count} other entit${count === 1 ? 'y' : 'ies'}`,\n )\n this.name = 'ReferencedEntityError'\n this.entityName = entityName\n this.entityId = entityId\n this.usages = usages\n }\n}\n","/**\n * Schema-aware reference extraction.\n *\n * Walks entity field definitions and data to find all outgoing references.\n * Handles:\n * - field.media() → target = 'media', single UUID\n * - field.reference() → target = config.entity, single UUID or UUID[]\n * - field.blocks() → inspects block instance data for media/reference fields\n */\n\nimport type { BlocksField, FieldConfig, ReferenceField } from '../fields/base.js'\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport interface ExtractedRef {\n sourceField: string\n targetEntity: string\n targetId: string\n}\n\n/**\n * Extract all outgoing references from entity data.\n *\n * @param allFields - The entity's complete field map (entity.allFields)\n * @param data - The entity data (full on create, partial on update)\n * @returns Deduplicated array of extracted references\n */\nexport function extractRefs(\n allFields: Record<string, FieldConfig>,\n data: Record<string, unknown>,\n): ExtractedRef[] {\n const refs: ExtractedRef[] = []\n const seen = new Set<string>()\n\n function add(sourceField: string, targetEntity: string, targetId: string) {\n if (!UUID_RE.test(targetId)) return\n const key = `${sourceField}|${targetEntity}|${targetId}`\n if (seen.has(key)) return\n seen.add(key)\n refs.push({ sourceField, targetEntity, targetId })\n }\n\n for (const [fieldName, config] of Object.entries(allFields)) {\n const value = data[fieldName]\n if (value == null) continue\n\n switch (config.type) {\n case 'media': {\n if (typeof value === 'string') {\n add(fieldName, 'media', value)\n }\n break\n }\n\n case 'reference': {\n const refConfig = config as ReferenceField\n if (refConfig.cardinality === 'many' && Array.isArray(value)) {\n for (const id of value) {\n if (typeof id === 'string') {\n add(fieldName, refConfig.entity, id)\n }\n }\n } else if (typeof value === 'string') {\n add(fieldName, refConfig.entity, value)\n }\n break\n }\n\n case 'blocks': {\n const blocksConfig = config as BlocksField\n if (!Array.isArray(value)) break\n\n for (const block of value) {\n const inst = block as Record<string, unknown>\n const blockType = inst._block as string | undefined\n if (!blockType) continue\n\n // Find the block definition to know its field types\n const blockDef = blocksConfig.blocks.find((b) => b.slug === blockType)\n if (!blockDef) continue\n\n // Scan block fields for media/reference values\n for (const [blockFieldName, blockFieldConfig] of Object.entries(blockDef.fields)) {\n const blockValue = inst[blockFieldName]\n if (blockValue == null) continue\n\n if (blockFieldConfig.type === 'media' && typeof blockValue === 'string') {\n add(fieldName, 'media', blockValue)\n }\n\n if (blockFieldConfig.type === 'reference') {\n const blockRefConfig = blockFieldConfig as ReferenceField\n if (blockRefConfig.cardinality === 'many' && Array.isArray(blockValue)) {\n for (const id of blockValue) {\n if (typeof id === 'string') {\n add(fieldName, blockRefConfig.entity, id)\n }\n }\n } else if (typeof blockValue === 'string') {\n add(fieldName, blockRefConfig.entity, blockValue)\n }\n }\n }\n }\n break\n }\n }\n }\n\n return refs\n}\n\n/**\n * Get the names of all ref-bearing fields in the entity.\n * Used to scope partial-update ref deletion.\n */\nexport function getRefBearingFields(allFields: Record<string, FieldConfig>): string[] {\n const names: string[] = []\n for (const [fieldName, config] of Object.entries(allFields)) {\n if (config.type === 'media' || config.type === 'reference' || config.type === 'blocks') {\n names.push(fieldName)\n }\n }\n return names\n}\n","/**\n * entity_refs — Universal reference tracking table.\n *\n * Every reference between entities (field.media(), field.reference(), block media fields)\n * gets a row here at write time. This enables:\n * - Instant \"where is this used?\" queries (one indexed lookup)\n * - Universal delete protection (can't delete referenced entities)\n * - Correct results (schema-aware, no LIKE text search)\n */\n\nimport { index, pgTable, unique, uuid, varchar } from 'drizzle-orm/pg-core'\n\nexport const entityRefs = pgTable(\n 'entity_refs',\n {\n sourceEntity: varchar('source_entity', { length: 100 }).notNull(),\n sourceId: uuid('source_id').notNull(),\n sourceField: varchar('source_field', { length: 100 }).notNull(),\n targetEntity: varchar('target_entity', { length: 100 }).notNull(),\n targetId: uuid('target_id').notNull(),\n },\n (t) => [\n unique('uq_entity_refs').on(\n t.sourceEntity,\n t.sourceId,\n t.sourceField,\n t.targetEntity,\n t.targetId,\n ),\n index('idx_entity_refs_target').on(t.targetEntity, t.targetId),\n index('idx_entity_refs_source').on(t.sourceEntity, t.sourceId),\n ],\n)\n","/**\n * Generic entity usage lookup — \"where is this entity referenced?\"\n *\n * One indexed query on entity_refs instead of scanning every table.\n */\n\nimport { and, eq } from 'drizzle-orm'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport { entityRefs } from './schema.js'\n\nexport interface EntityUsage {\n sourceEntity: string\n sourceId: string\n sourceField: string\n}\n\n/**\n * Find all entities that reference a given entity.\n *\n * @param targetEntity - Entity type name (e.g. 'media', 'category')\n * @param targetId - UUID of the referenced entity\n * @param db - Database connection\n * @returns Array of usage records (empty if unreferenced)\n */\nexport async function findEntityUsages(\n targetEntity: string,\n targetId: string,\n db: PostgresJsDatabase,\n): Promise<EntityUsage[]> {\n const rows = await db\n .select({\n sourceEntity: entityRefs.sourceEntity,\n sourceId: entityRefs.sourceId,\n sourceField: entityRefs.sourceField,\n })\n .from(entityRefs)\n .where(and(eq(entityRefs.targetEntity, targetEntity), eq(entityRefs.targetId, targetId)))\n\n return rows\n}\n","/**\n * Validation schema generator\n * Converts entity field definitions to Zod schemas for runtime validation\n */\n\nimport { z } from 'zod'\nimport type { Entity } from './define-entity.js'\nimport type { FieldConfig } from './fields/base.js'\n\n/**\n * Convert a field definition to a Zod schema\n */\nfunction fieldToZod(fieldConfig: FieldConfig): z.ZodType {\n let schema: z.ZodType\n\n switch (fieldConfig.type) {\n case 'id':\n schema = z.string().uuid()\n break\n\n case 'text':\n schema = z.string()\n if (fieldConfig.maxLength) {\n schema = (schema as z.ZodString).max(fieldConfig.maxLength)\n }\n if (fieldConfig.minLength) {\n schema = (schema as z.ZodString).min(fieldConfig.minLength)\n }\n if (fieldConfig.pattern) {\n schema = (schema as z.ZodString).regex(fieldConfig.pattern)\n }\n break\n\n case 'number':\n schema = z.number()\n if (fieldConfig.integer) {\n schema = (schema as z.ZodNumber).int()\n }\n if (fieldConfig.min !== undefined) {\n schema = (schema as z.ZodNumber).min(fieldConfig.min)\n }\n if (fieldConfig.max !== undefined) {\n schema = (schema as z.ZodNumber).max(fieldConfig.max)\n }\n break\n\n case 'boolean':\n schema = z.boolean()\n break\n\n case 'date':\n schema = z.date().or(z.string().datetime())\n // Allow either Date object or ISO string, coerce to Date\n break\n\n case 'select':\n schema = z.enum(fieldConfig.options as [string, ...string[]])\n break\n\n case 'reference':\n if (fieldConfig.cardinality === 'many') {\n schema = z.array(z.string().uuid())\n } else {\n schema = z.string().uuid()\n }\n break\n\n case 'media':\n schema = z.string().uuid() // Media entity ID\n break\n\n case 'richtext':\n // HTML string (TipTap/Puck) or Slate JSON array (Plate/entity forms)\n schema = z.union([z.string(), z.array(z.record(z.unknown()))])\n break\n\n case 'slug':\n schema = z.string().regex(/^[a-z0-9-]+$/)\n break\n\n case 'json':\n schema = z.record(z.unknown())\n break\n\n case 'blocks':\n // Array of block instances with _block discriminator.\n // .passthrough() is required because each block type has different dynamic props\n // (title, image, content, etc.) that can't be validated generically here.\n // Per-block-type validation happens in the block editor's converter layer.\n // Security: extra props are harmless at storage level since rendering maps to\n // known component definitions and ignores unknown fields.\n schema = z.array(\n z\n .object({\n _block: z.string(),\n })\n .passthrough(),\n )\n break\n\n default:\n schema = z.unknown()\n }\n\n // Apply required/optional — non-required fields accept both undefined and null\n if (!fieldConfig.required) {\n schema = schema.nullable().optional()\n }\n\n // Apply default value\n if (fieldConfig.default !== undefined) {\n schema = schema.default(fieldConfig.default)\n }\n\n return schema\n}\n\n/**\n * Generate complete validation schema for an entity\n */\nexport function generateValidationSchema(entity: Entity): z.AnyZodObject {\n const shape: Record<string, z.ZodType> = {}\n\n for (const [fieldName, fieldConfig] of Object.entries(entity.allFields)) {\n shape[fieldName] = fieldToZod(fieldConfig)\n }\n\n return z.object(shape)\n}\n\n/**\n * Generate schema for entity creation (omit auto-generated fields)\n */\nexport function generateCreateSchema(entity: Entity): z.AnyZodObject {\n const shape: Record<string, z.ZodType> = {}\n\n // Fields to omit from create schema (auto-generated + server-set behavior fields)\n const omitFields = new Set(['id', 'createdAt', 'updatedAt', 'createdBy', '_version'])\n\n for (const [fieldName, fieldConfig] of Object.entries(entity.allFields)) {\n if (!omitFields.has(fieldName)) {\n shape[fieldName] = fieldToZod(fieldConfig)\n }\n }\n\n return z.object(shape)\n}\n\n/**\n * Generate schema for entity updates (all fields optional, omit immutable fields)\n */\nexport function generateUpdateSchema(entity: Entity): z.AnyZodObject {\n const shape: Record<string, z.ZodType> = {}\n\n // Fields to omit from update schema (immutable + server-set behavior fields)\n const omitFields = new Set(['id', 'createdAt', 'createdBy', 'updatedAt', 'updatedBy', '_version'])\n\n for (const [fieldName, fieldConfig] of Object.entries(entity.allFields)) {\n if (!omitFields.has(fieldName)) {\n // All fields optional for updates\n const fieldSchema = fieldToZod(fieldConfig)\n shape[fieldName] = fieldSchema.optional()\n }\n }\n\n return z.object(shape)\n}\n","/**\n * AdminClient - Full CRUD with server-only enforcement\n * CRITICAL: This file MUST NOT be imported in client code\n */\n\n// NOTE: We use runtime check instead of 'server-only' import to allow CLI scripts\n// The 'server-only' package blocks ALL non-Next.js contexts (including Node.js scripts)\n// Layer 3: Runtime check (catches what bundlers miss, allows CLI usage)\n\nimport { schemaRegistry } from '@murumets-ee/db'\nimport { and, eq, inArray, isNull, or, type SQL, sql } from 'drizzle-orm'\nimport type { PgTableWithColumns } from 'drizzle-orm/pg-core'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport type { ZodType } from 'zod'\nimport type { CountCacheLike } from '../count-cache.js'\nimport type { CursorInput } from '../cursor.js'\nimport { buildCursorCondition } from '../cursor.js'\nimport type { Entity } from '../define-entity.js'\nimport { shapeDto, shapeDtos } from '../dto-shaper.js'\nimport type { FieldConfig } from '../fields/base.js'\nimport { ReferencedEntityError } from '../refs/errors.js'\nimport { extractRefs } from '../refs/extract-refs.js'\nimport { findEntityUsages } from '../refs/find-usages.js'\nimport { entityRefs } from '../refs/schema.js'\nimport type { InferCreateInput, InferEntityDTO, InferUpdateInput } from '../types/infer.js'\nimport type { Logger } from '../types/logger.js'\nimport { generateCreateSchema, generateUpdateSchema } from '../validation.js'\n\nexport interface AdminClientConfig<\n AllFields extends Record<string, FieldConfig> = Record<string, FieldConfig>,\n> {\n entity: Entity<AllFields>\n db: PostgresJsDatabase\n logger?: Logger\n /** Optional count cache for COUNT(*) query optimization. */\n countCache?: CountCacheLike\n}\n\nexport interface FindManyOptions {\n where?: SQL | undefined\n limit?: number\n offset?: number\n orderBy?: SQL | SQL[]\n locale?: string\n /** Default content locale. For localized blocks, NULL rows (from initial create)\n * are only returned as fallback when locale matches defaultLocale. */\n defaultLocale?: string\n /**\n * Cursor-based (keyset) pagination. When provided, replaces OFFSET with a\n * WHERE condition for O(1) page access at any depth. The `offset` option\n * is ignored when `cursor` is set.\n *\n * The cursor `field` must be a real column on the entity table.\n */\n cursor?: CursorInput\n}\n\nexport interface CountOptions {\n where?: SQL | undefined\n}\n\n/**\n * AdminClient - Full CRUD operations with security enforcement\n *\n * Security layers:\n * 1. Runtime check: typeof window !== 'undefined' → throw (allows CLI scripts)\n * 2. Uses read-write DB connection\n * 3. Validation with Zod before every write\n * 4. Hook execution (beforeCreate, afterCreate, etc.)\n *\n * Note: We don't use 'server-only' import because it blocks CLI scripts.\n * Next.js bundler protection comes from subpath exports (@murumets-ee/core/clients).\n *\n * Phase 1: Core CRUD + hooks + validation\n * Phase 2 (TODO): Access control, request context, scoping\n *\n * @typeParam AllFields - The entity's complete field map. Inferred automatically\n * when constructing via `createAdminClient(entity)`.\n */\nexport class AdminClient<\n AllFields extends Record<string, FieldConfig> = Record<string, FieldConfig>,\n> {\n private entity: Entity<AllFields>\n private db: PostgresJsDatabase\n private logger?: Logger\n private createSchema: ZodType\n private updateSchema: ZodType\n // biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any> for property access\n private table: PgTableWithColumns<any>\n private countCache?: CountCacheLike\n\n constructor(config: AdminClientConfig<AllFields>) {\n // Runtime enforcement: prevent usage in browser contexts\n if (typeof window !== 'undefined') {\n throw new Error(\n 'AdminClient cannot be used in browser code. ' +\n 'Use QueryClient for frontend data access.',\n )\n }\n\n this.entity = config.entity\n this.db = config.db\n this.logger = config.logger\n this.countCache = config.countCache\n\n // Pre-generate validation schemas for performance\n this.createSchema = generateCreateSchema(config.entity)\n this.updateSchema = generateUpdateSchema(config.entity)\n\n // Get table from schema registry\n const table = schemaRegistry.get(config.entity.name)\n if (!table) {\n throw new Error(\n `Schema for entity '${config.entity.name}' not found in registry. ` +\n 'Ensure schemas are generated and registered before creating AdminClient.',\n )\n }\n this.table = table\n }\n\n /**\n * Create a new entity\n *\n * Flow:\n * 1. Validate input with Zod\n * 2. Execute beforeCreate hooks\n * 3. Prepare data for insert\n * 4. Insert into database\n * 5. Execute afterCreate hooks\n * 6. Shape DTO and return\n */\n async create(data: InferCreateInput<AllFields>): Promise<InferEntityDTO<AllFields>> {\n this.logger?.info({ entity: this.entity.name }, 'Creating entity')\n\n // TODO (Phase 2): Access control check\n // const ctx = getRequestContext()\n // if (!hasAccess(ctx.user, this.entity.access.create)) {\n // throw new ForbiddenError('User not authorized to create this entity')\n // }\n\n // Execute beforeCreate hooks BEFORE validation — hooks may populate required fields\n let dataToInsert = data as Record<string, unknown>\n if (this.entity.hooks?.beforeCreate) {\n const hookResult = await this.entity.hooks.beforeCreate(dataToInsert)\n dataToInsert = hookResult !== undefined ? hookResult : dataToInsert\n }\n\n // Validate after hooks (hooks may add required fields like senderEmail)\n dataToInsert = this.createSchema.parse(dataToInsert)\n\n // TODO (Phase 2): Add scope if entity is scoped\n // if (this.entity.scope === 'team' || this.entity.scope === 'user') {\n // const ctx = getRequestContext()\n // dataToInsert._scopeId = ctx.scopeId\n // }\n\n // Prepare data for insert\n const columns = this.prepareDataForInsert(dataToInsert)\n\n // Insert into database\n const [row] = await this.db.insert(this.table).values(columns).returning()\n\n // Save blocks to layout table\n await this.saveBlocks(row.id, dataToInsert)\n\n // Track outgoing references (entity_refs)\n await this.syncRefs(row.id as string, dataToInsert, 'create')\n\n // Execute afterCreate hooks\n if (this.entity.hooks?.afterCreate) {\n await this.entity.hooks.afterCreate(row)\n }\n\n // Invalidate count cache after creation\n this.invalidateCountCache()\n\n // Shape DTO and attach blocks\n const shaped = shapeDto(this.entity, row) as Record<string, unknown>\n if (shaped && this.getBlocksFields().length > 0) {\n const blocksMap = await this.loadBlocks([shaped.id as string])\n this.attachBlocks([shaped], blocksMap)\n }\n return shaped as InferEntityDTO<AllFields>\n }\n\n /**\n * Find entity by ID\n */\n async findById(\n id: string,\n options?: { locale?: string; defaultLocale?: string },\n ): Promise<InferEntityDTO<AllFields> | null> {\n this.logger?.info({ entity: this.entity.name, id }, 'Finding entity by ID')\n\n // TODO (Phase 2): Access control check\n // const ctx = getRequestContext()\n // if (!hasAccess(ctx.user, this.entity.access.view)) {\n // throw new ForbiddenError('User not authorized to view this entity')\n // }\n\n // Query database\n const [row] = await this.db.select().from(this.table).where(eq(this.table.id, id))\n\n if (!row) return null\n\n // TODO (Phase 2): Scope check\n // if (this.entity.scope !== 'global') {\n // const ctx = getRequestContext()\n // if (row._scopeId !== ctx.scopeId) {\n // return null // Not in user's scope\n // }\n // }\n\n const shaped = shapeDto(this.entity, row) as Record<string, unknown>\n if (!shaped) return null\n\n // Attach blocks from layout table (with locale for block translations)\n if (this.getBlocksFields().length > 0) {\n const blocksMap = await this.loadBlocks([shaped.id as string], options?.locale, {\n defaultLocale: options?.defaultLocale,\n })\n this.attachBlocks([shaped], blocksMap)\n }\n\n if (options?.locale) {\n const [merged] = await this.mergeTranslations([shaped], options.locale)\n return merged as InferEntityDTO<AllFields>\n }\n return shaped as InferEntityDTO<AllFields>\n }\n\n /**\n * Find multiple entities\n */\n async findMany(options?: FindManyOptions): Promise<InferEntityDTO<AllFields>[]> {\n this.logger?.info({ entity: this.entity.name, options }, 'Finding entities')\n\n // TODO (Phase 2): Access control check\n // const ctx = getRequestContext()\n // if (!hasAccess(ctx.user, this.entity.access.view)) {\n // throw new ForbiddenError('User not authorized to view this entity')\n // }\n\n // Build and execute query\n // TODO (Phase 2): Add scope filter\n let query = this.db.select().from(this.table).$dynamic()\n\n // Collect WHERE conditions\n const conditions: SQL[] = []\n\n if (options?.where) {\n conditions.push(options.where)\n }\n\n // Cursor-based pagination: add keyset WHERE condition (replaces OFFSET)\n if (options?.cursor) {\n // Whitelist: cursor field must be a real column on the entity\n const allFields = this.entity.allFields as Record<string, FieldConfig>\n if (!(options.cursor.field in allFields) && options.cursor.field !== 'id') {\n throw new Error(\n `Invalid cursor field: '${options.cursor.field}' is not a field on '${this.entity.name}'`,\n )\n }\n const cursorCondition = buildCursorCondition(this.table, options.cursor)\n if (cursorCondition) {\n conditions.push(cursorCondition)\n }\n }\n\n if (conditions.length > 0) {\n query = query.where(and(...conditions))\n }\n\n if (options?.limit) {\n query = query.limit(options.limit)\n }\n // Cursor takes precedence over offset — skip offset when cursor is active\n if (options?.offset && !options?.cursor) {\n query = query.offset(options.offset)\n }\n if (options?.orderBy) {\n const cols = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy]\n query = query.orderBy(...cols)\n }\n\n const rows = await query\n\n const shaped = shapeDtos(this.entity, rows).filter(\n (e): e is NonNullable<typeof e> => e !== null,\n ) as Record<string, unknown>[]\n\n // Attach blocks from layout table (with locale for block translations)\n if (this.getBlocksFields().length > 0 && shaped.length > 0) {\n const entityIds = shaped.map((e) => e.id as string)\n const blocksMap = await this.loadBlocks(entityIds, options?.locale, {\n defaultLocale: options?.defaultLocale,\n })\n this.attachBlocks(shaped, blocksMap)\n }\n\n if (options?.locale) {\n return (await this.mergeTranslations(shaped, options.locale)) as InferEntityDTO<AllFields>[]\n }\n\n return shaped as InferEntityDTO<AllFields>[]\n }\n\n /**\n * Count entities matching optional conditions\n */\n async count(options?: CountOptions): Promise<number> {\n this.logger?.info({ entity: this.entity.name, options }, 'Counting entities')\n\n // Build cache key: entityName + serialized WHERE (or empty for unfiltered)\n const cacheKey = this.buildCountCacheKey(options?.where)\n if (this.countCache) {\n const cached = this.countCache.get(cacheKey)\n if (cached !== undefined) {\n this.logger?.debug?.({ entity: this.entity.name, cached }, 'Count cache hit')\n return cached\n }\n }\n\n let query = this.db.select({ count: sql<number>`count(*)` }).from(this.table).$dynamic()\n\n if (options?.where) {\n query = query.where(options.where)\n }\n\n const [result] = await query\n const count = Number(result.count)\n\n // Cache the result\n if (this.countCache) {\n this.countCache.set(cacheKey, count)\n }\n\n return count\n }\n\n /**\n * Update entity by ID\n *\n * Flow:\n * 1. Validate input with Zod\n * 2. Execute beforeUpdate hooks\n * 3. Prepare data for update\n * 4. Update in database\n * 5. Execute afterUpdate hooks\n * 6. Shape DTO and return\n */\n async update(\n id: string,\n data: InferUpdateInput<AllFields>,\n options?: { locale?: string },\n ): Promise<InferEntityDTO<AllFields>> {\n this.logger?.info({ entity: this.entity.name, id }, 'Updating entity')\n\n // TODO (Phase 2): Access control check\n // const ctx = getRequestContext()\n // if (!hasAccess(ctx.user, this.entity.access.update)) {\n // throw new ForbiddenError('User not authorized to update this entity')\n // }\n\n // Execute beforeUpdate hooks BEFORE validation — hooks may modify fields\n let dataToUpdate = data as Record<string, unknown>\n if (this.entity.hooks?.beforeUpdate) {\n const hookResult = await this.entity.hooks.beforeUpdate(id, dataToUpdate)\n dataToUpdate = hookResult !== undefined ? hookResult : dataToUpdate\n }\n\n // Validate after hooks\n dataToUpdate = this.updateSchema.parse(dataToUpdate)\n\n // Prepare data for update\n const columns = this.prepareDataForUpdate(dataToUpdate)\n\n // Update in database (only if there are non-blocks fields to update)\n let row: Record<string, unknown>\n if (Object.keys(columns).length > 0) {\n const [updated] = await this.db\n .update(this.table)\n .set(columns)\n .where(eq(this.table.id, id))\n .returning()\n row = updated\n } else {\n // Only blocks changed — fetch current row\n const [current] = await this.db.select().from(this.table).where(eq(this.table.id, id))\n row = current\n }\n\n // Update blocks in layout table (thread locale for per-locale blocks)\n await this.saveBlocks(id, dataToUpdate, options)\n\n // Update outgoing references (entity_refs) — only for changed fields\n await this.syncRefs(id, dataToUpdate, 'update')\n\n // Execute afterUpdate hooks\n if (this.entity.hooks?.afterUpdate) {\n await this.entity.hooks.afterUpdate(row)\n }\n\n // Shape DTO and attach blocks\n const shaped = shapeDto(this.entity, row) as Record<string, unknown>\n if (shaped && this.getBlocksFields().length > 0) {\n const blocksMap = await this.loadBlocks([id])\n this.attachBlocks([shaped], blocksMap)\n }\n return shaped as InferEntityDTO<AllFields>\n }\n\n /**\n * Delete entity by ID.\n *\n * Checks entity_refs for incoming references first — throws\n * ReferencedEntityError if this entity is still used somewhere.\n */\n async delete(id: string): Promise<void> {\n this.logger?.info({ entity: this.entity.name, id }, 'Deleting entity')\n\n // TODO (Phase 2): Access control check\n // const ctx = getRequestContext()\n // if (!hasAccess(ctx.user, this.entity.access.delete)) {\n // throw new ForbiddenError('User not authorized to delete this entity')\n // }\n\n // Check for incoming references — prevent deleting entities in use\n const usages = await findEntityUsages(this.entity.name, id, this.db)\n if (usages.length > 0) {\n throw new ReferencedEntityError(this.entity.name, id, usages)\n }\n\n // Execute beforeDelete hooks\n if (this.entity.hooks?.beforeDelete) {\n await this.entity.hooks.beforeDelete(id)\n }\n\n // Delete from database\n await this.db.delete(this.table).where(eq(this.table.id, id))\n\n // Clean up outgoing references from entity_refs\n await this.db\n .delete(entityRefs)\n .where(and(eq(entityRefs.sourceEntity, this.entity.name), eq(entityRefs.sourceId, id)))\n\n // Execute afterDelete hooks\n if (this.entity.hooks?.afterDelete) {\n await this.entity.hooks.afterDelete(id)\n }\n\n // Invalidate count cache after deletion\n this.invalidateCountCache()\n }\n\n /**\n * Prepare data for insert — every field is a real column.\n */\n private prepareDataForInsert(data: Record<string, unknown>): Record<string, unknown> {\n const columns: Record<string, unknown> = {}\n\n for (const [fieldName, value] of Object.entries(data)) {\n const fieldConfig = (this.entity.allFields as Record<string, FieldConfig>)[fieldName]\n if (!fieldConfig) continue\n if (fieldName === 'id') continue\n if (fieldConfig.type === 'blocks') continue\n columns[fieldName] = value\n }\n\n return columns\n }\n\n /**\n * Prepare data for update — every field is a real column.\n * Standard column SET, no JSONB merge needed.\n */\n private prepareDataForUpdate(data: Record<string, unknown>): Record<string, unknown> {\n const columns: Record<string, unknown> = {}\n\n for (const [fieldName, value] of Object.entries(data)) {\n const fieldConfig = (this.entity.allFields as Record<string, FieldConfig>)[fieldName]\n if (!fieldConfig) continue\n if (fieldName === 'id') continue\n if (fieldConfig.type === 'blocks') continue\n columns[fieldName] = value\n }\n\n return columns\n }\n\n /**\n * Merge translations into entities for the specified locale.\n * Reads translatable field values from real columns on the translation row.\n */\n private async mergeTranslations<T extends Record<string, unknown>>(\n entities: T[],\n locale: string,\n ): Promise<T[]> {\n if (!entities.length) return entities\n\n const translationTableName = `${this.entity.name}_translations`\n const translationTable = schemaRegistry.get(translationTableName)\n\n if (!translationTable) return entities\n\n const entityIds = entities.map((e) => e.id)\n\n const translations = await this.db\n .select()\n .from(translationTable)\n .where(\n and(inArray(translationTable.entityId, entityIds), eq(translationTable.locale, locale)),\n )\n\n // Get translatable field names\n const translatableFields = Object.entries(this.entity.allFields as Record<string, FieldConfig>)\n .filter(([_, config]) => config.translatable)\n .map(([name]) => name)\n\n const translationMap = new Map<unknown, Record<string, unknown>>()\n for (const translation of translations) {\n const translatedValues: Record<string, unknown> = {}\n for (const fieldName of translatableFields) {\n const value = (translation as Record<string, unknown>)[fieldName]\n if (value !== undefined && value !== null) {\n translatedValues[fieldName] = value\n }\n }\n translationMap.set(translation.entityId, translatedValues)\n }\n\n return entities.map((entity) => {\n const translatedFields = translationMap.get(entity.id)\n if (!translatedFields) return entity\n return { ...entity, ...translatedFields }\n })\n }\n\n /**\n * Save translation for an entity.\n * Each translatable field is a real column on the translation table.\n */\n async saveTranslation(\n entityId: string,\n locale: string,\n translations: Record<string, unknown>,\n ): Promise<void> {\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Saving translation')\n\n const translationTableName = `${this.entity.name}_translations`\n const translationTable = schemaRegistry.get(translationTableName)\n\n if (!translationTable) {\n throw new Error(`Translation table ${translationTableName} not found in schema registry`)\n }\n\n // Get translatable field names\n const translatableFields = Object.entries(this.entity.allFields as Record<string, FieldConfig>)\n .filter(([_, config]) => config.translatable)\n .map(([name]) => name)\n\n if (translatableFields.length === 0) {\n throw new Error(`Entity ${this.entity.name} has no translatable fields`)\n }\n\n // Extract only translatable fields — these are real columns on the translation table\n const translationData: Record<string, unknown> = { entityId, locale }\n const updateData: Record<string, unknown> = {}\n for (const fieldName of translatableFields) {\n if (translations[fieldName] !== undefined) {\n translationData[fieldName] = translations[fieldName]\n updateData[fieldName] = translations[fieldName]\n }\n }\n\n // Upsert: insert or update on conflict\n await this.db\n .insert(translationTable)\n .values(translationData)\n .onConflictDoUpdate({\n target: [translationTable.entityId, translationTable.locale],\n set: updateData,\n })\n\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Translation saved')\n }\n\n /**\n * PHASE 3: Delete translation(s) for an entity\n * If locale is provided, deletes specific locale; otherwise deletes all translations\n */\n async deleteTranslation(entityId: string, locale?: string): Promise<void> {\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Deleting translation')\n\n const translationTableName = `${this.entity.name}_translations`\n const translationTable = schemaRegistry.get(translationTableName)\n\n if (!translationTable) {\n throw new Error(`Translation table ${translationTableName} not found in schema registry`)\n }\n\n if (locale) {\n // Delete specific locale\n await this.db\n .delete(translationTable)\n .where(and(eq(translationTable.entityId, entityId), eq(translationTable.locale, locale)))\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Translation deleted')\n } else {\n // Delete all translations for entity\n await this.db.delete(translationTable).where(eq(translationTable.entityId, entityId))\n this.logger?.info({ entity: this.entity.name, entityId }, 'All translations deleted')\n }\n }\n\n /**\n * Save block-level translations for an entity.\n * For each block in each blocks field, extracts translatable field values\n * and upserts them into {entity}_layout_translations.\n *\n * Block _id must match an existing layout row ID.\n */\n async saveBlockTranslations(\n entityId: string,\n locale: string,\n data: Record<string, unknown>,\n ): Promise<void> {\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Saving block translations')\n\n const blocksFields = this.getBlocksFields()\n if (blocksFields.length === 0) return\n\n const layoutTransTable = schemaRegistry.get(`${this.entity.name}_layout_translations`)\n if (!layoutTransTable) return\n\n const layoutTable = schemaRegistry.get(`${this.entity.name}_layout`)\n if (!layoutTable) return\n\n for (const { name: fieldName, config } of blocksFields) {\n const blocks = data[fieldName]\n if (!Array.isArray(blocks)) continue\n\n // Get block definitions to identify translatable fields\n // biome-ignore lint/suspicious/noExplicitAny: blocks property exists on blocks-type FieldConfig but isn't in the base FieldConfig type\n const blockDefs = (config as any).blocks as\n | Array<{ slug: string; fields: Record<string, FieldConfig> }>\n | undefined\n if (!blockDefs) continue\n\n // Load actual layout row IDs from DB (ordered by sort_order to match request array order).\n // The _id from the request may be a Puck-generated ID (e.g. \"hero-abc123\") rather than\n // the real DB UUID, so we match by position instead.\n const layoutRows = await this.db\n .select({ id: layoutTable.id, blockType: layoutTable.blockType })\n .from(layoutTable)\n .where(\n and(\n eq(layoutTable.entityId, entityId),\n eq(layoutTable.fieldName, fieldName),\n isNull(layoutTable.locale),\n ),\n )\n .orderBy(layoutTable.sortOrder)\n\n for (let i = 0; i < (blocks as Record<string, unknown>[]).length; i++) {\n const block = (blocks as Record<string, unknown>[])[i]\n const blockType = block._block as string\n if (!blockType) continue\n\n // Match by position — layout rows and request blocks are in the same sort order\n const layoutRow = layoutRows[i]\n if (!layoutRow || layoutRow.blockType !== blockType) continue\n\n const blockDef = blockDefs.find((b) => b.slug === blockType)\n if (!blockDef) continue\n\n // Extract only translatable fields\n const translatedFields: Record<string, unknown> = {}\n for (const [fname, fconfig] of Object.entries(blockDef.fields)) {\n if (fconfig.translatable && block[fname] !== undefined) {\n translatedFields[fname] = block[fname]\n }\n }\n\n if (Object.keys(translatedFields).length === 0) continue\n\n // Upsert using the real DB layout row ID\n await this.db\n .insert(layoutTransTable)\n .values({ layoutId: layoutRow.id, locale, fields: translatedFields })\n .onConflictDoUpdate({\n target: [layoutTransTable.layoutId, layoutTransTable.locale],\n set: { fields: translatedFields },\n })\n }\n }\n\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Block translations saved')\n }\n\n /**\n * Save per-locale blocks for an entity.\n * Used for entities with `localized: true` on their blocks field — each locale gets\n * its own independent block layout rows in the layout table.\n */\n async saveLocalizedBlocks(\n entityId: string,\n locale: string,\n data: Record<string, unknown>,\n ): Promise<void> {\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Saving localized blocks')\n await this.saveBlocks(entityId, data, { locale })\n }\n\n // ---------------------------------------------------------------\n // Block CRUD (layout table operations)\n // ---------------------------------------------------------------\n\n /**\n * Get all blocks field names for this entity.\n */\n private getBlocksFields(): Array<{ name: string; config: FieldConfig }> {\n return Object.entries(this.entity.allFields as Record<string, FieldConfig>)\n .filter(([_, config]) => config.type === 'blocks')\n .map(([name, config]) => ({ name, config }))\n }\n\n /**\n * Save blocks for an entity after create/update.\n * For each blocks field, writes rows to {entity}_layout table.\n */\n private async saveBlocks(\n entityId: string,\n data: Record<string, unknown>,\n options?: { locale?: string },\n ): Promise<void> {\n const blocksFields = this.getBlocksFields()\n if (blocksFields.length === 0) return\n\n const layoutTable = schemaRegistry.get(`${this.entity.name}_layout`)\n if (!layoutTable) return\n\n for (const { name: fieldName, config } of blocksFields) {\n const blocks = data[fieldName]\n if (!Array.isArray(blocks)) continue\n\n const isLocalized = 'localized' in config && config.localized === true\n const locale = isLocalized ? (options?.locale ?? null) : null\n\n // Delete existing layout rows for this field + locale\n const deleteConditions = [\n eq(layoutTable.entityId, entityId),\n eq(layoutTable.fieldName, fieldName),\n ]\n if (locale) {\n deleteConditions.push(eq(layoutTable.locale, locale))\n } else if (!isLocalized) {\n // For shared layout, delete rows where locale IS NULL\n deleteConditions.push(isNull(layoutTable.locale))\n }\n await this.db.delete(layoutTable).where(and(...deleteConditions))\n\n // Insert new layout rows\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i] as Record<string, unknown>\n const blockType = block._block as string\n if (!blockType) continue\n\n // Separate _block and _id from block data\n const { _block, _id, ...blockData } = block\n\n await this.db.insert(layoutTable).values({\n entityId,\n fieldName,\n blockType,\n sortOrder: i,\n data: blockData,\n locale,\n })\n }\n }\n }\n\n /**\n * Load blocks for one or more entities from the layout table.\n *\n * Handles both block translation modes:\n * - Shared layout (localized: false): loads locale=NULL rows, merges translations,\n * clears untranslated translatable fields to '' (strict mode for admin editing)\n * - Per-locale layout (localized: true): loads rows matching the provided locale only\n *\n * @param entityIds - Entity IDs to load blocks for\n * @param locale - Content locale (omit for default locale / base data)\n * @param options.defaultLocale - Default locale; NULL rows fall back only for this locale\n */\n private async loadBlocks(\n entityIds: string[],\n locale?: string,\n options?: { defaultLocale?: string },\n ): Promise<Map<string, Record<string, unknown[]>>> {\n const blocksFields = this.getBlocksFields()\n if (blocksFields.length === 0 || entityIds.length === 0) {\n return new Map()\n }\n\n const layoutTable = schemaRegistry.get(`${this.entity.name}_layout`)\n if (!layoutTable) return new Map()\n\n // Determine block field modes\n const sharedFields = blocksFields.filter(\n ({ config }) => !('localized' in config && config.localized),\n )\n const localizedFields = blocksFields.filter(\n ({ config }) => 'localized' in config && config.localized,\n )\n\n const result = new Map<string, Record<string, unknown[]>>()\n\n // ---- Shared blocks: locale IS NULL, translations from layout_translations ----\n if (sharedFields.length > 0) {\n const rows = await this.db\n .select()\n .from(layoutTable)\n .where(and(inArray(layoutTable.entityId, entityIds), isNull(layoutTable.locale)))\n .orderBy(layoutTable.sortOrder)\n\n // Load translations if locale provided\n let blockTransMap: Map<string, Record<string, unknown>> | undefined\n if (locale && rows.length > 0) {\n const layoutTransTable = schemaRegistry.get(`${this.entity.name}_layout_translations`)\n if (layoutTransTable) {\n const layoutIds = rows.map((r) => r.id as string)\n const translations = await this.db\n .select()\n .from(layoutTransTable)\n .where(\n and(\n inArray(layoutTransTable.layoutId, layoutIds),\n eq(layoutTransTable.locale, locale),\n ),\n )\n\n blockTransMap = new Map()\n for (const t of translations) {\n blockTransMap.set(t.layoutId as string, (t.fields as Record<string, unknown>) ?? {})\n }\n }\n }\n\n // Build block definition lookup for translatable field clearing\n const blockDefMap = new Map<string, Record<string, FieldConfig>>()\n for (const { config } of sharedFields) {\n // biome-ignore lint/suspicious/noExplicitAny: blocks property exists on blocks-type FieldConfig\n const blockDefs = (config as any).blocks as\n | Array<{ slug: string; fields: Record<string, FieldConfig> }>\n | undefined\n if (blockDefs) {\n for (const def of blockDefs) {\n blockDefMap.set(def.slug, def.fields)\n }\n }\n }\n\n for (const row of rows) {\n const eid = row.entityId as string\n const fname = row.fieldName as string\n\n if (!result.has(eid)) result.set(eid, {})\n const entityBlocks = result.get(eid)!\n if (!entityBlocks[fname]) entityBlocks[fname] = []\n\n const blockData = (row.data as Record<string, unknown>) ?? {}\n const translatedFields = blockTransMap?.get(row.id as string)\n\n // Build the block object\n const block: Record<string, unknown> = {\n _block: row.blockType,\n _id: row.id,\n ...blockData,\n ...(translatedFields ?? {}),\n }\n\n // Strict mode: clear untranslated translatable fields when loading for non-default locale\n if (locale) {\n const blockType = row.blockType as string\n const fieldDefs = blockDefMap.get(blockType)\n if (fieldDefs) {\n for (const [fieldName, fieldConfig] of Object.entries(fieldDefs)) {\n if (fieldConfig.translatable && !translatedFields?.[fieldName]) {\n block[fieldName] = ''\n }\n }\n }\n }\n\n entityBlocks[fname].push(block)\n }\n }\n\n // ---- Localized blocks: filter by locale column ----\n // NULL rows (from initial create) only fall back for the default locale.\n // Non-default locales without locale-specific rows get an empty array.\n if (localizedFields.length > 0) {\n const isDefault = !locale || locale === options?.defaultLocale\n const localeCondition = locale\n ? isDefault\n ? or(eq(layoutTable.locale, locale), isNull(layoutTable.locale))!\n : eq(layoutTable.locale, locale)\n : isNull(layoutTable.locale)\n\n const rows = await this.db\n .select()\n .from(layoutTable)\n .where(and(inArray(layoutTable.entityId, entityIds), localeCondition))\n .orderBy(layoutTable.sortOrder)\n\n // Group by entityId + fieldName, prefer locale-specific rows over NULL\n const grouped = new Map<string, { localeRows: typeof rows; nullRows: typeof rows }>()\n for (const row of rows) {\n const key = `${row.entityId}::${row.fieldName}`\n if (!grouped.has(key)) grouped.set(key, { localeRows: [], nullRows: [] })\n const group = grouped.get(key)!\n if (row.locale) {\n group.localeRows.push(row)\n } else {\n group.nullRows.push(row)\n }\n }\n\n for (const [, { localeRows, nullRows }] of grouped) {\n // Use locale-specific rows if available, otherwise fall back to NULL\n const effective = localeRows.length > 0 ? localeRows : nullRows\n\n for (const row of effective) {\n const eid = row.entityId as string\n const fname = row.fieldName as string\n\n if (!result.has(eid)) result.set(eid, {})\n const entityBlocks = result.get(eid)!\n if (!entityBlocks[fname]) entityBlocks[fname] = []\n\n const blockData = (row.data as Record<string, unknown>) ?? {}\n\n entityBlocks[fname].push({\n _block: row.blockType,\n _id: row.id,\n ...blockData,\n })\n }\n }\n }\n\n return result\n }\n\n /**\n * Attach loaded blocks to shaped DTOs.\n */\n private attachBlocks(\n entities: Record<string, unknown>[],\n blocksMap: Map<string, Record<string, unknown[]>>,\n ): void {\n const blocksFields = this.getBlocksFields()\n if (blocksFields.length === 0) return\n\n for (const entity of entities) {\n const eid = entity.id as string\n const entityBlocks = blocksMap.get(eid) ?? {}\n\n for (const { name } of blocksFields) {\n entity[name] = entityBlocks[name] ?? []\n }\n }\n }\n\n // ---------------------------------------------------------------\n // Reference tracking (entity_refs)\n // ---------------------------------------------------------------\n\n /**\n * Sync outgoing references in entity_refs after create/update.\n *\n * - 'create': inserts all refs (entity is new, no existing refs)\n * - 'update': deletes refs for changed ref-bearing fields, then inserts new ones\n *\n * Gracefully skips if entity_refs table is not registered (e.g. before migration).\n */\n private async syncRefs(\n entityId: string,\n data: Record<string, unknown>,\n mode: 'create' | 'update',\n ): Promise<void> {\n // Graceful degradation: skip if entity_refs table doesn't exist yet\n if (!schemaRegistry.has('entity_refs')) return\n\n const allFields = this.entity.allFields as Record<string, FieldConfig>\n\n if (mode === 'update') {\n // Only delete refs for fields present in the update data\n const changedRefFields = Object.keys(data).filter((k) => {\n const config = allFields[k]\n return (\n config &&\n (config.type === 'media' || config.type === 'reference' || config.type === 'blocks')\n )\n })\n if (changedRefFields.length > 0) {\n await this.db\n .delete(entityRefs)\n .where(\n and(\n eq(entityRefs.sourceEntity, this.entity.name),\n eq(entityRefs.sourceId, entityId),\n inArray(entityRefs.sourceField, changedRefFields),\n ),\n )\n }\n }\n\n // Extract refs from the data and insert\n const refs = extractRefs(allFields, data)\n if (refs.length > 0) {\n await this.db\n .insert(entityRefs)\n .values(\n refs.map((ref) => ({\n sourceEntity: this.entity.name,\n sourceId: entityId,\n sourceField: ref.sourceField,\n targetEntity: ref.targetEntity,\n targetId: ref.targetId,\n })),\n )\n .onConflictDoNothing()\n }\n }\n\n // ---------------------------------------------------------------\n // Count cache helpers\n // ---------------------------------------------------------------\n\n /**\n * Build a cache key for a count query.\n * Key format: `entityName` for unfiltered, `entityName:where_sql` for filtered.\n */\n private buildCountCacheKey(where?: SQL): string {\n if (!where) return this.entity.name\n // Use SQL's toString() representation as a stable key.\n // This is a display string, not executed — safe for cache keying.\n return `${this.entity.name}:${String(where)}`\n }\n\n /**\n * Invalidate all count cache entries for this entity.\n */\n private invalidateCountCache(): void {\n this.countCache?.invalidate(this.entity.name)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/cursor.ts","../../src/dto-shaper.ts","../../src/refs/errors.ts","../../src/refs/extract-refs.ts","../../src/refs/schema.ts","../../src/refs/find-usages.ts","../../src/validation.ts","../../src/admin/client.ts"],"sourcesContent":["/**\n * Cursor-based (keyset) pagination utilities.\n *\n * Cursor pagination avoids the performance cliff of OFFSET at scale (1M+ rows).\n * Instead of `OFFSET N`, it uses a WHERE condition:\n * `WHERE (sortField < lastValue) OR (sortField = lastValue AND id < lastId)`\n * which Postgres can serve from an index in constant time.\n *\n * The cursor is opaque to the client — base64-encoded JSON.\n *\n * Security:\n * - `field` must be whitelisted against the entity's actual fields\n * - `id` must be a valid UUID\n * - `value` is parameterized (never interpolated into SQL)\n * - Malformed cursors return null (caller returns 400)\n */\n\nimport { and, eq, gt, lt, or, type SQL } from 'drizzle-orm'\nimport type { PgTableWithColumns } from 'drizzle-orm/pg-core'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Cursor input for keyset pagination. */\nexport interface CursorInput {\n /** Sort field name (e.g. 'createdAt'). Must be a real column on the entity. */\n field: string\n /** Last seen value of the sort field. */\n value: string | number\n /** Sort direction — must match the ORDER BY direction. */\n direction: 'asc' | 'desc'\n /** Tie-breaker: last seen entity ID. Required for non-unique sort fields. */\n id?: string\n}\n\n/** Decoded cursor (internal, after validation). */\ninterface DecodedCursor {\n field: string\n value: string | number\n direction: 'asc' | 'desc'\n id?: string\n}\n\n// ---------------------------------------------------------------------------\n// UUID validation (same format used throughout the toolkit)\n// ---------------------------------------------------------------------------\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\n// ---------------------------------------------------------------------------\n// Encode / decode\n// ---------------------------------------------------------------------------\n\n/**\n * Encode a cursor for API transport (base64url).\n * Built from the last item in a result set.\n */\nexport function encodeCursor(\n item: Record<string, unknown>,\n sortField: string,\n direction: 'asc' | 'desc',\n): string {\n const payload: CursorInput = {\n field: sortField,\n value: item[sortField] as string | number,\n direction,\n id: item.id as string | undefined,\n }\n return btoa(JSON.stringify(payload))\n}\n\n/**\n * Decode and validate a cursor string from query params.\n * Returns null if the cursor is malformed, tampered, or invalid.\n *\n * Security: the `field` value is NOT validated here — the caller must\n * whitelist it against the entity's actual columns.\n */\nexport function decodeCursor(encoded: string): DecodedCursor | null {\n try {\n const json = atob(encoded)\n const parsed: unknown = JSON.parse(json)\n\n if (typeof parsed !== 'object' || parsed === null) return null\n const obj = parsed as Record<string, unknown>\n\n // Validate field\n if (typeof obj.field !== 'string' || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(obj.field)) {\n return null\n }\n\n // Validate value (string or number)\n if (typeof obj.value !== 'string' && typeof obj.value !== 'number') {\n return null\n }\n\n // Validate direction\n if (obj.direction !== 'asc' && obj.direction !== 'desc') {\n return null\n }\n\n // Validate id (optional, must be UUID if present)\n if (obj.id !== undefined) {\n if (typeof obj.id !== 'string' || !UUID_RE.test(obj.id)) {\n return null\n }\n }\n\n return {\n field: obj.field,\n value: obj.value,\n direction: obj.direction,\n id: obj.id as string | undefined,\n }\n } catch {\n return null\n }\n}\n\n// ---------------------------------------------------------------------------\n// SQL condition builder\n// ---------------------------------------------------------------------------\n\n/**\n * Build the keyset WHERE condition from a decoded cursor.\n *\n * For DESC: `WHERE (field < value) OR (field = value AND id < cursorId)`\n * For ASC: `WHERE (field > value) OR (field = value AND id > cursorId)`\n *\n * The caller must verify that `cursor.field` exists on the table before calling.\n *\n * @param table - Drizzle table with columns\n * @param cursor - Decoded and validated cursor\n * @returns SQL condition, or null if the field doesn't exist on the table\n */\nexport function buildCursorCondition(\n // biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any> for property access\n table: PgTableWithColumns<any>,\n cursor: DecodedCursor,\n): SQL | null {\n const column = table[cursor.field]\n if (!column) return null\n\n const isDesc = cursor.direction === 'desc'\n const compare = isDesc ? lt : gt\n\n // Primary condition: sort field passes the cursor value\n const fieldCondition = compare(column, cursor.value)\n\n // Without tie-breaker ID, use simple comparison\n if (!cursor.id) {\n return fieldCondition\n }\n\n // With tie-breaker: (field < value) OR (field = value AND id < cursorId)\n const idColumn = table.id\n if (!idColumn) return fieldCondition\n\n const idCondition = compare(idColumn, cursor.id)\n return or(fieldCondition, and(eq(column, cursor.value), idCondition))!\n}\n","/**\n * DTO Shaper\n * Transforms raw DB rows into clean DTOs.\n * All fields are real columns — just read from row directly.\n */\n\nimport type { Entity } from './define-entity.js'\nimport type { FieldConfig } from './fields/base.js'\nimport type { InferEntityDTO } from './types/infer.js'\n\nexport interface ShapeDtoOptions {\n select?: string[] // Explicit field selection\n includeInternal?: boolean // Include _version, _scopeId\n}\n\n/**\n * Shape a raw DB row into a typed DTO.\n * Every field is a real column — read directly from row.\n */\nexport function shapeDto<AllFields extends Record<string, FieldConfig>>(\n entity: Entity<AllFields>,\n row: Record<string, unknown>,\n options?: ShapeDtoOptions,\n): InferEntityDTO<AllFields> | null {\n if (!row) return null\n\n const result: Record<string, unknown> = {}\n const fieldsToInclude = options?.select || Object.keys(entity.allFields)\n\n for (const fieldName of fieldsToInclude) {\n if (!options?.includeInternal && fieldName.startsWith('_')) continue\n\n const fieldConfig = (entity.allFields as Record<string, FieldConfig>)[fieldName]\n if (!fieldConfig) continue\n\n // Blocks are loaded separately from layout tables\n if (fieldConfig.type === 'blocks') continue\n\n result[fieldName] = row[fieldName]\n }\n\n return result as InferEntityDTO<AllFields>\n}\n\n/**\n * Shape multiple rows\n */\nexport function shapeDtos<AllFields extends Record<string, FieldConfig>>(\n entity: Entity<AllFields>,\n rows: Record<string, unknown>[],\n options?: ShapeDtoOptions,\n): (InferEntityDTO<AllFields> | null)[] {\n return rows.map((row) => shapeDto(entity, row, options))\n}\n","/**\n * Error thrown when attempting to delete an entity that is still referenced.\n */\n\nimport type { EntityUsage } from './find-usages.js'\n\nexport class ReferencedEntityError extends Error {\n public readonly entityName: string\n public readonly entityId: string\n public readonly usages: EntityUsage[]\n\n constructor(entityName: string, entityId: string, usages: EntityUsage[]) {\n const count = usages.length\n super(\n `Cannot delete ${entityName} '${entityId}': referenced by ${count} other entit${count === 1 ? 'y' : 'ies'}`,\n )\n this.name = 'ReferencedEntityError'\n this.entityName = entityName\n this.entityId = entityId\n this.usages = usages\n }\n}\n","/**\n * Schema-aware reference extraction.\n *\n * Walks entity field definitions and data to find all outgoing references.\n * Handles:\n * - field.media() → target = 'media', single UUID\n * - field.reference() → target = config.entity, single UUID or UUID[]\n * - field.blocks() → inspects block instance data for media/reference fields\n */\n\nimport type { BlocksField, FieldConfig, ReferenceField } from '../fields/base.js'\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport interface ExtractedRef {\n sourceField: string\n targetEntity: string\n targetId: string\n}\n\n/**\n * Extract all outgoing references from entity data.\n *\n * @param allFields - The entity's complete field map (entity.allFields)\n * @param data - The entity data (full on create, partial on update)\n * @returns Deduplicated array of extracted references\n */\nexport function extractRefs(\n allFields: Record<string, FieldConfig>,\n data: Record<string, unknown>,\n): ExtractedRef[] {\n const refs: ExtractedRef[] = []\n const seen = new Set<string>()\n\n function add(sourceField: string, targetEntity: string, targetId: string) {\n if (!UUID_RE.test(targetId)) return\n const key = `${sourceField}|${targetEntity}|${targetId}`\n if (seen.has(key)) return\n seen.add(key)\n refs.push({ sourceField, targetEntity, targetId })\n }\n\n for (const [fieldName, config] of Object.entries(allFields)) {\n const value = data[fieldName]\n if (value == null) continue\n\n switch (config.type) {\n case 'media': {\n if (typeof value === 'string') {\n add(fieldName, 'media', value)\n }\n break\n }\n\n case 'reference': {\n const refConfig = config as ReferenceField\n if (refConfig.cardinality === 'many' && Array.isArray(value)) {\n for (const id of value) {\n if (typeof id === 'string') {\n add(fieldName, refConfig.entity, id)\n }\n }\n } else if (typeof value === 'string') {\n add(fieldName, refConfig.entity, value)\n }\n break\n }\n\n case 'blocks': {\n const blocksConfig = config as BlocksField\n if (!Array.isArray(value)) break\n\n for (const block of value) {\n const inst = block as Record<string, unknown>\n const blockType = inst._block as string | undefined\n if (!blockType) continue\n\n // Find the block definition to know its field types\n const blockDef = blocksConfig.blocks.find((b) => b.slug === blockType)\n if (!blockDef) continue\n\n // Scan block fields for media/reference values\n for (const [blockFieldName, blockFieldConfig] of Object.entries(blockDef.fields)) {\n const blockValue = inst[blockFieldName]\n if (blockValue == null) continue\n\n if (blockFieldConfig.type === 'media' && typeof blockValue === 'string') {\n add(fieldName, 'media', blockValue)\n }\n\n if (blockFieldConfig.type === 'reference') {\n const blockRefConfig = blockFieldConfig as ReferenceField\n if (blockRefConfig.cardinality === 'many' && Array.isArray(blockValue)) {\n for (const id of blockValue) {\n if (typeof id === 'string') {\n add(fieldName, blockRefConfig.entity, id)\n }\n }\n } else if (typeof blockValue === 'string') {\n add(fieldName, blockRefConfig.entity, blockValue)\n }\n }\n }\n }\n break\n }\n }\n }\n\n return refs\n}\n\n/**\n * Get the names of all ref-bearing fields in the entity.\n * Used to scope partial-update ref deletion.\n */\nexport function getRefBearingFields(allFields: Record<string, FieldConfig>): string[] {\n const names: string[] = []\n for (const [fieldName, config] of Object.entries(allFields)) {\n if (config.type === 'media' || config.type === 'reference' || config.type === 'blocks') {\n names.push(fieldName)\n }\n }\n return names\n}\n","/**\n * entity_refs — Universal reference tracking table.\n *\n * Every reference between entities (field.media(), field.reference(), block media fields)\n * gets a row here at write time. This enables:\n * - Instant \"where is this used?\" queries (one indexed lookup)\n * - Universal delete protection (can't delete referenced entities)\n * - Correct results (schema-aware, no LIKE text search)\n */\n\nimport { index, pgTable, unique, uuid, varchar } from 'drizzle-orm/pg-core'\n\nexport const entityRefs = pgTable(\n 'entity_refs',\n {\n sourceEntity: varchar('source_entity', { length: 100 }).notNull(),\n sourceId: uuid('source_id').notNull(),\n sourceField: varchar('source_field', { length: 100 }).notNull(),\n targetEntity: varchar('target_entity', { length: 100 }).notNull(),\n targetId: uuid('target_id').notNull(),\n },\n (t) => [\n unique('uq_entity_refs').on(\n t.sourceEntity,\n t.sourceId,\n t.sourceField,\n t.targetEntity,\n t.targetId,\n ),\n index('idx_entity_refs_target').on(t.targetEntity, t.targetId),\n index('idx_entity_refs_source').on(t.sourceEntity, t.sourceId),\n ],\n)\n","/**\n * Generic entity usage lookup — \"where is this entity referenced?\"\n *\n * One indexed query on entity_refs instead of scanning every table.\n */\n\nimport { and, eq } from 'drizzle-orm'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport { entityRefs } from './schema.js'\n\nexport interface EntityUsage {\n sourceEntity: string\n sourceId: string\n sourceField: string\n}\n\n/**\n * Find all entities that reference a given entity.\n *\n * @param targetEntity - Entity type name (e.g. 'media', 'category')\n * @param targetId - UUID of the referenced entity\n * @param db - Database connection\n * @returns Array of usage records (empty if unreferenced)\n */\nexport async function findEntityUsages(\n targetEntity: string,\n targetId: string,\n db: PostgresJsDatabase,\n): Promise<EntityUsage[]> {\n const rows = await db\n .select({\n sourceEntity: entityRefs.sourceEntity,\n sourceId: entityRefs.sourceId,\n sourceField: entityRefs.sourceField,\n })\n .from(entityRefs)\n .where(and(eq(entityRefs.targetEntity, targetEntity), eq(entityRefs.targetId, targetId)))\n\n return rows\n}\n","/**\n * Validation schema generator\n * Converts entity field definitions to Zod schemas for runtime validation\n */\n\nimport { z } from 'zod'\nimport type { Entity } from './define-entity.js'\nimport type { FieldConfig } from './fields/base.js'\n\n/**\n * Convert a field definition to a Zod schema\n */\nfunction fieldToZod(fieldConfig: FieldConfig): z.ZodType {\n let schema: z.ZodType\n\n switch (fieldConfig.type) {\n case 'id':\n schema = z.string().uuid()\n break\n\n case 'text':\n schema = z.string()\n if (fieldConfig.maxLength) {\n schema = (schema as z.ZodString).max(fieldConfig.maxLength)\n }\n if (fieldConfig.minLength) {\n schema = (schema as z.ZodString).min(fieldConfig.minLength)\n }\n if (fieldConfig.pattern) {\n schema = (schema as z.ZodString).regex(fieldConfig.pattern)\n }\n break\n\n case 'number':\n schema = z.number()\n if (fieldConfig.integer) {\n schema = (schema as z.ZodNumber).int()\n }\n if (fieldConfig.min !== undefined) {\n schema = (schema as z.ZodNumber).min(fieldConfig.min)\n }\n if (fieldConfig.max !== undefined) {\n schema = (schema as z.ZodNumber).max(fieldConfig.max)\n }\n break\n\n case 'boolean':\n schema = z.boolean()\n break\n\n case 'date':\n schema = z.date().or(z.string().datetime())\n // Allow either Date object or ISO string, coerce to Date\n break\n\n case 'select':\n schema = z.enum(fieldConfig.options as [string, ...string[]])\n break\n\n case 'reference':\n if (fieldConfig.cardinality === 'many') {\n schema = z.array(z.string().uuid())\n } else {\n schema = z.string().uuid()\n }\n break\n\n case 'media':\n schema = z.string().uuid() // Media entity ID\n break\n\n case 'richtext':\n // HTML string (TipTap/Puck) or Slate JSON array (Plate/entity forms)\n schema = z.union([z.string(), z.array(z.record(z.unknown()))])\n break\n\n case 'slug':\n schema = z.string().regex(/^[a-z0-9-]+$/)\n break\n\n case 'json':\n schema = z.record(z.unknown())\n break\n\n case 'blocks':\n // Array of block instances with _block discriminator.\n // .passthrough() is required because each block type has different dynamic props\n // (title, image, content, etc.) that can't be validated generically here.\n // Per-block-type validation happens in the block editor's converter layer.\n // Security: extra props are harmless at storage level since rendering maps to\n // known component definitions and ignores unknown fields.\n schema = z.array(\n z\n .object({\n _block: z.string(),\n })\n .passthrough(),\n )\n break\n\n default:\n schema = z.unknown()\n }\n\n // Apply required/optional — non-required fields accept both undefined and null\n if (!fieldConfig.required) {\n schema = schema.nullable().optional()\n }\n\n // Apply default value\n if (fieldConfig.default !== undefined) {\n schema = schema.default(fieldConfig.default)\n }\n\n return schema\n}\n\n/**\n * Generate complete validation schema for an entity\n */\nexport function generateValidationSchema(entity: Entity): z.AnyZodObject {\n const shape: Record<string, z.ZodType> = {}\n\n for (const [fieldName, fieldConfig] of Object.entries(entity.allFields)) {\n shape[fieldName] = fieldToZod(fieldConfig)\n }\n\n return z.object(shape)\n}\n\n/**\n * Generate schema for entity creation (omit auto-generated fields)\n */\nexport function generateCreateSchema(entity: Entity): z.AnyZodObject {\n const shape: Record<string, z.ZodType> = {}\n\n // Fields to omit from create schema (auto-generated + server-set behavior fields)\n const omitFields = new Set(['id', 'createdAt', 'updatedAt', 'createdBy', '_version'])\n\n for (const [fieldName, fieldConfig] of Object.entries(entity.allFields)) {\n if (!omitFields.has(fieldName)) {\n shape[fieldName] = fieldToZod(fieldConfig)\n }\n }\n\n return z.object(shape)\n}\n\n/**\n * Generate schema for entity updates (all fields optional, omit immutable fields)\n */\nexport function generateUpdateSchema(entity: Entity): z.AnyZodObject {\n const shape: Record<string, z.ZodType> = {}\n\n // Fields to omit from update schema (immutable + server-set behavior fields)\n const omitFields = new Set(['id', 'createdAt', 'createdBy', 'updatedAt', 'updatedBy', '_version'])\n\n for (const [fieldName, fieldConfig] of Object.entries(entity.allFields)) {\n if (!omitFields.has(fieldName)) {\n // All fields optional for updates\n const fieldSchema = fieldToZod(fieldConfig)\n shape[fieldName] = fieldSchema.optional()\n }\n }\n\n return z.object(shape)\n}\n","/**\n * AdminClient - Full CRUD with server-only enforcement\n * CRITICAL: This file MUST NOT be imported in client code\n */\n\n// NOTE: We use runtime check instead of 'server-only' import to allow CLI scripts\n// The 'server-only' package blocks ALL non-Next.js contexts (including Node.js scripts)\n// Layer 3: Runtime check (catches what bundlers miss, allows CLI usage)\n\nimport { schemaRegistry } from '@murumets-ee/db'\nimport { and, eq, inArray, isNull, or, type SQL, sql } from 'drizzle-orm'\nimport type { PgTableWithColumns } from 'drizzle-orm/pg-core'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport type { ZodType } from 'zod'\nimport type { CountCacheLike } from '../count-cache.js'\nimport type { CursorInput } from '../cursor.js'\nimport { buildCursorCondition } from '../cursor.js'\nimport type { Entity } from '../define-entity.js'\nimport { shapeDto, shapeDtos } from '../dto-shaper.js'\nimport type { FieldConfig } from '../fields/base.js'\nimport { ReferencedEntityError } from '../refs/errors.js'\nimport { extractRefs } from '../refs/extract-refs.js'\nimport { findEntityUsages } from '../refs/find-usages.js'\nimport { entityRefs } from '../refs/schema.js'\nimport type { InferCreateInput, InferEntityDTO, InferUpdateInput } from '../types/infer.js'\nimport type { Logger } from '../types/logger.js'\nimport { generateCreateSchema, generateUpdateSchema } from '../validation.js'\n\nexport interface AdminClientConfig<\n AllFields extends Record<string, FieldConfig> = Record<string, FieldConfig>,\n> {\n entity: Entity<AllFields>\n db: PostgresJsDatabase\n logger?: Logger\n /** Optional count cache for COUNT(*) query optimization. */\n countCache?: CountCacheLike\n}\n\nexport interface FindManyOptions {\n where?: SQL | undefined\n limit?: number\n offset?: number\n orderBy?: SQL | SQL[]\n locale?: string\n /** Default content locale. For localized blocks, NULL rows (from initial create)\n * are only returned as fallback when locale matches defaultLocale. */\n defaultLocale?: string\n /**\n * Cursor-based (keyset) pagination. When provided, replaces OFFSET with a\n * WHERE condition for O(1) page access at any depth. The `offset` option\n * is ignored when `cursor` is set.\n *\n * The cursor `field` must be a real column on the entity table.\n */\n cursor?: CursorInput\n}\n\nexport interface CountOptions {\n where?: SQL | undefined\n}\n\n/**\n * AdminClient - Full CRUD operations with security enforcement\n *\n * Security layers:\n * 1. Runtime check: typeof window !== 'undefined' → throw (allows CLI scripts)\n * 2. Uses read-write DB connection\n * 3. Validation with Zod before every write\n * 4. Hook execution (beforeCreate, afterCreate, etc.)\n *\n * Note: We don't use 'server-only' import because it blocks CLI scripts.\n * Next.js bundler protection comes from subpath exports (@murumets-ee/core/clients).\n *\n * Phase 1: Core CRUD + hooks + validation\n * Phase 2 (TODO): Access control, request context, scoping\n *\n * @typeParam AllFields - The entity's complete field map. Inferred automatically\n * when constructing via `createAdminClient(entity)`.\n */\nexport class AdminClient<\n AllFields extends Record<string, FieldConfig> = Record<string, FieldConfig>,\n> {\n private entity: Entity<AllFields>\n private db: PostgresJsDatabase\n private logger?: Logger\n private createSchema: ZodType\n private updateSchema: ZodType\n // biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any> for property access\n private table: PgTableWithColumns<any>\n private countCache?: CountCacheLike\n\n constructor(config: AdminClientConfig<AllFields>) {\n // Runtime enforcement: prevent usage in browser contexts\n if (typeof window !== 'undefined') {\n throw new Error(\n 'AdminClient cannot be used in browser code. ' +\n 'Use QueryClient for frontend data access.',\n )\n }\n\n this.entity = config.entity\n this.db = config.db\n this.logger = config.logger\n this.countCache = config.countCache\n\n // Pre-generate validation schemas for performance\n this.createSchema = generateCreateSchema(config.entity)\n this.updateSchema = generateUpdateSchema(config.entity)\n\n // Get table from schema registry\n const table = schemaRegistry.get(config.entity.name)\n if (!table) {\n throw new Error(\n `Schema for entity '${config.entity.name}' not found in registry. ` +\n 'Ensure schemas are generated and registered before creating AdminClient.',\n )\n }\n this.table = table\n }\n\n /**\n * Create a new entity\n *\n * Flow:\n * 1. Validate input with Zod\n * 2. Execute beforeCreate hooks\n * 3. Prepare data for insert\n * 4. Insert into database\n * 5. Execute afterCreate hooks\n * 6. Shape DTO and return\n */\n async create(data: InferCreateInput<AllFields>): Promise<InferEntityDTO<AllFields>> {\n this.logger?.info({ entity: this.entity.name }, 'Creating entity')\n\n // TODO (Phase 2): Access control check\n // const ctx = getRequestContext()\n // if (!hasAccess(ctx.user, this.entity.access.create)) {\n // throw new ForbiddenError('User not authorized to create this entity')\n // }\n\n // Execute beforeCreate hooks BEFORE validation — hooks may populate required fields\n let dataToInsert = data as Record<string, unknown>\n if (this.entity.hooks?.beforeCreate) {\n const hookResult = await this.entity.hooks.beforeCreate(dataToInsert)\n dataToInsert = hookResult !== undefined ? hookResult : dataToInsert\n }\n\n // Preserve server-set fields that hooks added but createSchema omits\n // (e.g. createdAt/updatedAt from timestamped(), createdBy/updatedBy from auditable())\n const serverFields: Record<string, unknown> = {}\n for (const key of ['createdAt', 'updatedAt', 'createdBy', 'updatedBy']) {\n if (dataToInsert[key] !== undefined) serverFields[key] = dataToInsert[key]\n }\n\n // Validate after hooks (hooks may add required fields like senderEmail)\n dataToInsert = { ...this.createSchema.parse(dataToInsert), ...serverFields }\n\n // TODO (Phase 2): Add scope if entity is scoped\n // if (this.entity.scope === 'team' || this.entity.scope === 'user') {\n // const ctx = getRequestContext()\n // dataToInsert._scopeId = ctx.scopeId\n // }\n\n // Prepare data for insert\n const columns = this.prepareDataForInsert(dataToInsert)\n\n // Insert into database\n const [row] = await this.db.insert(this.table).values(columns).returning()\n\n // Save blocks to layout table\n await this.saveBlocks(row.id, dataToInsert)\n\n // Track outgoing references (entity_refs)\n await this.syncRefs(row.id as string, dataToInsert, 'create')\n\n // Execute afterCreate hooks\n if (this.entity.hooks?.afterCreate) {\n await this.entity.hooks.afterCreate(row)\n }\n\n // Invalidate count cache after creation\n this.invalidateCountCache()\n\n // Shape DTO and attach blocks\n const shaped = shapeDto(this.entity, row) as Record<string, unknown>\n if (shaped && this.getBlocksFields().length > 0) {\n const blocksMap = await this.loadBlocks([shaped.id as string])\n this.attachBlocks([shaped], blocksMap)\n }\n return shaped as InferEntityDTO<AllFields>\n }\n\n /**\n * Find entity by ID\n */\n async findById(\n id: string,\n options?: { locale?: string; defaultLocale?: string },\n ): Promise<InferEntityDTO<AllFields> | null> {\n this.logger?.info({ entity: this.entity.name, id }, 'Finding entity by ID')\n\n // TODO (Phase 2): Access control check\n // const ctx = getRequestContext()\n // if (!hasAccess(ctx.user, this.entity.access.view)) {\n // throw new ForbiddenError('User not authorized to view this entity')\n // }\n\n // Query database\n const [row] = await this.db.select().from(this.table).where(eq(this.table.id, id))\n\n if (!row) return null\n\n // TODO (Phase 2): Scope check\n // if (this.entity.scope !== 'global') {\n // const ctx = getRequestContext()\n // if (row._scopeId !== ctx.scopeId) {\n // return null // Not in user's scope\n // }\n // }\n\n const shaped = shapeDto(this.entity, row) as Record<string, unknown>\n if (!shaped) return null\n\n // Attach blocks from layout table (with locale for block translations)\n if (this.getBlocksFields().length > 0) {\n const blocksMap = await this.loadBlocks([shaped.id as string], options?.locale, {\n defaultLocale: options?.defaultLocale,\n })\n this.attachBlocks([shaped], blocksMap)\n }\n\n if (options?.locale) {\n const [merged] = await this.mergeTranslations([shaped], options.locale)\n return merged as InferEntityDTO<AllFields>\n }\n return shaped as InferEntityDTO<AllFields>\n }\n\n /**\n * Find multiple entities\n */\n async findMany(options?: FindManyOptions): Promise<InferEntityDTO<AllFields>[]> {\n this.logger?.info({ entity: this.entity.name, options }, 'Finding entities')\n\n // TODO (Phase 2): Access control check\n // const ctx = getRequestContext()\n // if (!hasAccess(ctx.user, this.entity.access.view)) {\n // throw new ForbiddenError('User not authorized to view this entity')\n // }\n\n // Build and execute query\n // TODO (Phase 2): Add scope filter\n let query = this.db.select().from(this.table).$dynamic()\n\n // Collect WHERE conditions\n const conditions: SQL[] = []\n\n if (options?.where) {\n conditions.push(options.where)\n }\n\n // Cursor-based pagination: add keyset WHERE condition (replaces OFFSET)\n if (options?.cursor) {\n // Whitelist: cursor field must be a real column on the entity\n const allFields = this.entity.allFields as Record<string, FieldConfig>\n if (!(options.cursor.field in allFields) && options.cursor.field !== 'id') {\n throw new Error(\n `Invalid cursor field: '${options.cursor.field}' is not a field on '${this.entity.name}'`,\n )\n }\n const cursorCondition = buildCursorCondition(this.table, options.cursor)\n if (cursorCondition) {\n conditions.push(cursorCondition)\n }\n }\n\n if (conditions.length > 0) {\n query = query.where(and(...conditions))\n }\n\n if (options?.limit) {\n query = query.limit(options.limit)\n }\n // Cursor takes precedence over offset — skip offset when cursor is active\n if (options?.offset && !options?.cursor) {\n query = query.offset(options.offset)\n }\n if (options?.orderBy) {\n const cols = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy]\n query = query.orderBy(...cols)\n }\n\n const rows = await query\n\n const shaped = shapeDtos(this.entity, rows).filter(\n (e): e is NonNullable<typeof e> => e !== null,\n ) as Record<string, unknown>[]\n\n // Attach blocks from layout table (with locale for block translations)\n if (this.getBlocksFields().length > 0 && shaped.length > 0) {\n const entityIds = shaped.map((e) => e.id as string)\n const blocksMap = await this.loadBlocks(entityIds, options?.locale, {\n defaultLocale: options?.defaultLocale,\n })\n this.attachBlocks(shaped, blocksMap)\n }\n\n if (options?.locale) {\n return (await this.mergeTranslations(shaped, options.locale)) as InferEntityDTO<AllFields>[]\n }\n\n return shaped as InferEntityDTO<AllFields>[]\n }\n\n /**\n * Count entities matching optional conditions\n */\n async count(options?: CountOptions): Promise<number> {\n this.logger?.info({ entity: this.entity.name, options }, 'Counting entities')\n\n // Build cache key: entityName + serialized WHERE (or empty for unfiltered)\n const cacheKey = this.buildCountCacheKey(options?.where)\n if (this.countCache) {\n const cached = this.countCache.get(cacheKey)\n if (cached !== undefined) {\n this.logger?.debug?.({ entity: this.entity.name, cached }, 'Count cache hit')\n return cached\n }\n }\n\n let query = this.db.select({ count: sql<number>`count(*)` }).from(this.table).$dynamic()\n\n if (options?.where) {\n query = query.where(options.where)\n }\n\n const [result] = await query\n const count = Number(result.count)\n\n // Cache the result\n if (this.countCache) {\n this.countCache.set(cacheKey, count)\n }\n\n return count\n }\n\n /**\n * Update entity by ID\n *\n * Flow:\n * 1. Validate input with Zod\n * 2. Execute beforeUpdate hooks\n * 3. Prepare data for update\n * 4. Update in database\n * 5. Execute afterUpdate hooks\n * 6. Shape DTO and return\n */\n async update(\n id: string,\n data: InferUpdateInput<AllFields>,\n options?: { locale?: string },\n ): Promise<InferEntityDTO<AllFields>> {\n this.logger?.info({ entity: this.entity.name, id }, 'Updating entity')\n\n // TODO (Phase 2): Access control check\n // const ctx = getRequestContext()\n // if (!hasAccess(ctx.user, this.entity.access.update)) {\n // throw new ForbiddenError('User not authorized to update this entity')\n // }\n\n // Execute beforeUpdate hooks BEFORE validation — hooks may modify fields\n let dataToUpdate = data as Record<string, unknown>\n if (this.entity.hooks?.beforeUpdate) {\n const hookResult = await this.entity.hooks.beforeUpdate(id, dataToUpdate)\n dataToUpdate = hookResult !== undefined ? hookResult : dataToUpdate\n }\n\n // Validate after hooks\n dataToUpdate = this.updateSchema.parse(dataToUpdate)\n\n // Prepare data for update\n const columns = this.prepareDataForUpdate(dataToUpdate)\n\n // Update in database (only if there are non-blocks fields to update)\n let row: Record<string, unknown>\n if (Object.keys(columns).length > 0) {\n const [updated] = await this.db\n .update(this.table)\n .set(columns)\n .where(eq(this.table.id, id))\n .returning()\n row = updated\n } else {\n // Only blocks changed — fetch current row\n const [current] = await this.db.select().from(this.table).where(eq(this.table.id, id))\n row = current\n }\n\n // Update blocks in layout table (thread locale for per-locale blocks)\n await this.saveBlocks(id, dataToUpdate, options)\n\n // Update outgoing references (entity_refs) — only for changed fields\n await this.syncRefs(id, dataToUpdate, 'update')\n\n // Execute afterUpdate hooks\n if (this.entity.hooks?.afterUpdate) {\n await this.entity.hooks.afterUpdate(row)\n }\n\n // Shape DTO and attach blocks\n const shaped = shapeDto(this.entity, row) as Record<string, unknown>\n if (shaped && this.getBlocksFields().length > 0) {\n const blocksMap = await this.loadBlocks([id])\n this.attachBlocks([shaped], blocksMap)\n }\n return shaped as InferEntityDTO<AllFields>\n }\n\n /**\n * Delete entity by ID.\n *\n * Checks entity_refs for incoming references first — throws\n * ReferencedEntityError if this entity is still used somewhere.\n */\n async delete(id: string): Promise<void> {\n this.logger?.info({ entity: this.entity.name, id }, 'Deleting entity')\n\n // TODO (Phase 2): Access control check\n // const ctx = getRequestContext()\n // if (!hasAccess(ctx.user, this.entity.access.delete)) {\n // throw new ForbiddenError('User not authorized to delete this entity')\n // }\n\n // Check for incoming references — prevent deleting entities in use\n const usages = await findEntityUsages(this.entity.name, id, this.db)\n if (usages.length > 0) {\n throw new ReferencedEntityError(this.entity.name, id, usages)\n }\n\n // Execute beforeDelete hooks\n if (this.entity.hooks?.beforeDelete) {\n await this.entity.hooks.beforeDelete(id)\n }\n\n // Delete from database\n await this.db.delete(this.table).where(eq(this.table.id, id))\n\n // Clean up outgoing references from entity_refs\n await this.db\n .delete(entityRefs)\n .where(and(eq(entityRefs.sourceEntity, this.entity.name), eq(entityRefs.sourceId, id)))\n\n // Execute afterDelete hooks\n if (this.entity.hooks?.afterDelete) {\n await this.entity.hooks.afterDelete(id)\n }\n\n // Invalidate count cache after deletion\n this.invalidateCountCache()\n }\n\n /**\n * Prepare data for insert — every field is a real column.\n */\n private prepareDataForInsert(data: Record<string, unknown>): Record<string, unknown> {\n const columns: Record<string, unknown> = {}\n\n for (const [fieldName, value] of Object.entries(data)) {\n const fieldConfig = (this.entity.allFields as Record<string, FieldConfig>)[fieldName]\n if (!fieldConfig) continue\n if (fieldName === 'id') continue\n if (fieldConfig.type === 'blocks') continue\n columns[fieldName] = value\n }\n\n return columns\n }\n\n /**\n * Prepare data for update — every field is a real column.\n * Standard column SET, no JSONB merge needed.\n */\n private prepareDataForUpdate(data: Record<string, unknown>): Record<string, unknown> {\n const columns: Record<string, unknown> = {}\n\n for (const [fieldName, value] of Object.entries(data)) {\n const fieldConfig = (this.entity.allFields as Record<string, FieldConfig>)[fieldName]\n if (!fieldConfig) continue\n if (fieldName === 'id') continue\n if (fieldConfig.type === 'blocks') continue\n columns[fieldName] = value\n }\n\n return columns\n }\n\n /**\n * Merge translations into entities for the specified locale.\n * Reads translatable field values from real columns on the translation row.\n */\n private async mergeTranslations<T extends Record<string, unknown>>(\n entities: T[],\n locale: string,\n ): Promise<T[]> {\n if (!entities.length) return entities\n\n const translationTableName = `${this.entity.name}_translations`\n const translationTable = schemaRegistry.get(translationTableName)\n\n if (!translationTable) return entities\n\n const entityIds = entities.map((e) => e.id)\n\n const translations = await this.db\n .select()\n .from(translationTable)\n .where(\n and(inArray(translationTable.entityId, entityIds), eq(translationTable.locale, locale)),\n )\n\n // Get translatable field names\n const translatableFields = Object.entries(this.entity.allFields as Record<string, FieldConfig>)\n .filter(([_, config]) => config.translatable)\n .map(([name]) => name)\n\n const translationMap = new Map<unknown, Record<string, unknown>>()\n for (const translation of translations) {\n const translatedValues: Record<string, unknown> = {}\n for (const fieldName of translatableFields) {\n const value = (translation as Record<string, unknown>)[fieldName]\n if (value !== undefined && value !== null) {\n translatedValues[fieldName] = value\n }\n }\n translationMap.set(translation.entityId, translatedValues)\n }\n\n return entities.map((entity) => {\n const translatedFields = translationMap.get(entity.id)\n if (!translatedFields) return entity\n return { ...entity, ...translatedFields }\n })\n }\n\n /**\n * Save translation for an entity.\n * Each translatable field is a real column on the translation table.\n */\n async saveTranslation(\n entityId: string,\n locale: string,\n translations: Record<string, unknown>,\n ): Promise<void> {\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Saving translation')\n\n const translationTableName = `${this.entity.name}_translations`\n const translationTable = schemaRegistry.get(translationTableName)\n\n if (!translationTable) {\n throw new Error(`Translation table ${translationTableName} not found in schema registry`)\n }\n\n // Get translatable field names\n const translatableFields = Object.entries(this.entity.allFields as Record<string, FieldConfig>)\n .filter(([_, config]) => config.translatable)\n .map(([name]) => name)\n\n if (translatableFields.length === 0) {\n throw new Error(`Entity ${this.entity.name} has no translatable fields`)\n }\n\n // Extract only translatable fields — these are real columns on the translation table\n const translationData: Record<string, unknown> = { entityId, locale }\n const updateData: Record<string, unknown> = {}\n for (const fieldName of translatableFields) {\n if (translations[fieldName] !== undefined) {\n translationData[fieldName] = translations[fieldName]\n updateData[fieldName] = translations[fieldName]\n }\n }\n\n // Upsert: insert or update on conflict\n await this.db\n .insert(translationTable)\n .values(translationData)\n .onConflictDoUpdate({\n target: [translationTable.entityId, translationTable.locale],\n set: updateData,\n })\n\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Translation saved')\n }\n\n /**\n * PHASE 3: Delete translation(s) for an entity\n * If locale is provided, deletes specific locale; otherwise deletes all translations\n */\n async deleteTranslation(entityId: string, locale?: string): Promise<void> {\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Deleting translation')\n\n const translationTableName = `${this.entity.name}_translations`\n const translationTable = schemaRegistry.get(translationTableName)\n\n if (!translationTable) {\n throw new Error(`Translation table ${translationTableName} not found in schema registry`)\n }\n\n if (locale) {\n // Delete specific locale\n await this.db\n .delete(translationTable)\n .where(and(eq(translationTable.entityId, entityId), eq(translationTable.locale, locale)))\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Translation deleted')\n } else {\n // Delete all translations for entity\n await this.db.delete(translationTable).where(eq(translationTable.entityId, entityId))\n this.logger?.info({ entity: this.entity.name, entityId }, 'All translations deleted')\n }\n }\n\n /**\n * Save block-level translations for an entity.\n * For each block in each blocks field, extracts translatable field values\n * and upserts them into {entity}_layout_translations.\n *\n * Block _id must match an existing layout row ID.\n */\n async saveBlockTranslations(\n entityId: string,\n locale: string,\n data: Record<string, unknown>,\n ): Promise<void> {\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Saving block translations')\n\n const blocksFields = this.getBlocksFields()\n if (blocksFields.length === 0) return\n\n const layoutTransTable = schemaRegistry.get(`${this.entity.name}_layout_translations`)\n if (!layoutTransTable) return\n\n const layoutTable = schemaRegistry.get(`${this.entity.name}_layout`)\n if (!layoutTable) return\n\n for (const { name: fieldName, config } of blocksFields) {\n const blocks = data[fieldName]\n if (!Array.isArray(blocks)) continue\n\n // Get block definitions to identify translatable fields\n // biome-ignore lint/suspicious/noExplicitAny: blocks property exists on blocks-type FieldConfig but isn't in the base FieldConfig type\n const blockDefs = (config as any).blocks as\n | Array<{ slug: string; fields: Record<string, FieldConfig> }>\n | undefined\n if (!blockDefs) continue\n\n // Load actual layout row IDs from DB (ordered by sort_order to match request array order).\n // The _id from the request may be a Puck-generated ID (e.g. \"hero-abc123\") rather than\n // the real DB UUID, so we match by position instead.\n const layoutRows = await this.db\n .select({ id: layoutTable.id, blockType: layoutTable.blockType })\n .from(layoutTable)\n .where(\n and(\n eq(layoutTable.entityId, entityId),\n eq(layoutTable.fieldName, fieldName),\n isNull(layoutTable.locale),\n ),\n )\n .orderBy(layoutTable.sortOrder)\n\n for (let i = 0; i < (blocks as Record<string, unknown>[]).length; i++) {\n const block = (blocks as Record<string, unknown>[])[i]\n const blockType = block._block as string\n if (!blockType) continue\n\n // Match by position — layout rows and request blocks are in the same sort order\n const layoutRow = layoutRows[i]\n if (!layoutRow || layoutRow.blockType !== blockType) continue\n\n const blockDef = blockDefs.find((b) => b.slug === blockType)\n if (!blockDef) continue\n\n // Extract only translatable fields\n const translatedFields: Record<string, unknown> = {}\n for (const [fname, fconfig] of Object.entries(blockDef.fields)) {\n if (fconfig.translatable && block[fname] !== undefined) {\n translatedFields[fname] = block[fname]\n }\n }\n\n if (Object.keys(translatedFields).length === 0) continue\n\n // Upsert using the real DB layout row ID\n await this.db\n .insert(layoutTransTable)\n .values({ layoutId: layoutRow.id, locale, fields: translatedFields })\n .onConflictDoUpdate({\n target: [layoutTransTable.layoutId, layoutTransTable.locale],\n set: { fields: translatedFields },\n })\n }\n }\n\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Block translations saved')\n }\n\n /**\n * Save per-locale blocks for an entity.\n * Used for entities with `localized: true` on their blocks field — each locale gets\n * its own independent block layout rows in the layout table.\n */\n async saveLocalizedBlocks(\n entityId: string,\n locale: string,\n data: Record<string, unknown>,\n ): Promise<void> {\n this.logger?.info({ entity: this.entity.name, entityId, locale }, 'Saving localized blocks')\n await this.saveBlocks(entityId, data, { locale })\n }\n\n // ---------------------------------------------------------------\n // Block CRUD (layout table operations)\n // ---------------------------------------------------------------\n\n /**\n * Get all blocks field names for this entity.\n */\n private getBlocksFields(): Array<{ name: string; config: FieldConfig }> {\n return Object.entries(this.entity.allFields as Record<string, FieldConfig>)\n .filter(([_, config]) => config.type === 'blocks')\n .map(([name, config]) => ({ name, config }))\n }\n\n /**\n * Save blocks for an entity after create/update.\n * For each blocks field, writes rows to {entity}_layout table.\n */\n private async saveBlocks(\n entityId: string,\n data: Record<string, unknown>,\n options?: { locale?: string },\n ): Promise<void> {\n const blocksFields = this.getBlocksFields()\n if (blocksFields.length === 0) return\n\n const layoutTable = schemaRegistry.get(`${this.entity.name}_layout`)\n if (!layoutTable) return\n\n for (const { name: fieldName, config } of blocksFields) {\n const blocks = data[fieldName]\n if (!Array.isArray(blocks)) continue\n\n const isLocalized = 'localized' in config && config.localized === true\n const locale = isLocalized ? (options?.locale ?? null) : null\n\n // Delete existing layout rows for this field + locale\n const deleteConditions = [\n eq(layoutTable.entityId, entityId),\n eq(layoutTable.fieldName, fieldName),\n ]\n if (locale) {\n deleteConditions.push(eq(layoutTable.locale, locale))\n } else if (!isLocalized) {\n // For shared layout, delete rows where locale IS NULL\n deleteConditions.push(isNull(layoutTable.locale))\n }\n await this.db.delete(layoutTable).where(and(...deleteConditions))\n\n // Insert new layout rows\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i] as Record<string, unknown>\n const blockType = block._block as string\n if (!blockType) continue\n\n // Separate _block and _id from block data\n const { _block, _id, ...blockData } = block\n\n await this.db.insert(layoutTable).values({\n entityId,\n fieldName,\n blockType,\n sortOrder: i,\n data: blockData,\n locale,\n })\n }\n }\n }\n\n /**\n * Load blocks for one or more entities from the layout table.\n *\n * Handles both block translation modes:\n * - Shared layout (localized: false): loads locale=NULL rows, merges translations,\n * clears untranslated translatable fields to '' (strict mode for admin editing)\n * - Per-locale layout (localized: true): loads rows matching the provided locale only\n *\n * @param entityIds - Entity IDs to load blocks for\n * @param locale - Content locale (omit for default locale / base data)\n * @param options.defaultLocale - Default locale; NULL rows fall back only for this locale\n */\n private async loadBlocks(\n entityIds: string[],\n locale?: string,\n options?: { defaultLocale?: string },\n ): Promise<Map<string, Record<string, unknown[]>>> {\n const blocksFields = this.getBlocksFields()\n if (blocksFields.length === 0 || entityIds.length === 0) {\n return new Map()\n }\n\n const layoutTable = schemaRegistry.get(`${this.entity.name}_layout`)\n if (!layoutTable) return new Map()\n\n // Determine block field modes\n const sharedFields = blocksFields.filter(\n ({ config }) => !('localized' in config && config.localized),\n )\n const localizedFields = blocksFields.filter(\n ({ config }) => 'localized' in config && config.localized,\n )\n\n const result = new Map<string, Record<string, unknown[]>>()\n\n // ---- Shared blocks: locale IS NULL, translations from layout_translations ----\n if (sharedFields.length > 0) {\n const rows = await this.db\n .select()\n .from(layoutTable)\n .where(and(inArray(layoutTable.entityId, entityIds), isNull(layoutTable.locale)))\n .orderBy(layoutTable.sortOrder)\n\n // Load translations if locale provided\n let blockTransMap: Map<string, Record<string, unknown>> | undefined\n if (locale && rows.length > 0) {\n const layoutTransTable = schemaRegistry.get(`${this.entity.name}_layout_translations`)\n if (layoutTransTable) {\n const layoutIds = rows.map((r) => r.id as string)\n const translations = await this.db\n .select()\n .from(layoutTransTable)\n .where(\n and(\n inArray(layoutTransTable.layoutId, layoutIds),\n eq(layoutTransTable.locale, locale),\n ),\n )\n\n blockTransMap = new Map()\n for (const t of translations) {\n blockTransMap.set(t.layoutId as string, (t.fields as Record<string, unknown>) ?? {})\n }\n }\n }\n\n // Build block definition lookup for translatable field clearing\n const blockDefMap = new Map<string, Record<string, FieldConfig>>()\n for (const { config } of sharedFields) {\n // biome-ignore lint/suspicious/noExplicitAny: blocks property exists on blocks-type FieldConfig\n const blockDefs = (config as any).blocks as\n | Array<{ slug: string; fields: Record<string, FieldConfig> }>\n | undefined\n if (blockDefs) {\n for (const def of blockDefs) {\n blockDefMap.set(def.slug, def.fields)\n }\n }\n }\n\n for (const row of rows) {\n const eid = row.entityId as string\n const fname = row.fieldName as string\n\n if (!result.has(eid)) result.set(eid, {})\n const entityBlocks = result.get(eid)!\n if (!entityBlocks[fname]) entityBlocks[fname] = []\n\n const blockData = (row.data as Record<string, unknown>) ?? {}\n const translatedFields = blockTransMap?.get(row.id as string)\n\n // Build the block object\n const block: Record<string, unknown> = {\n _block: row.blockType,\n _id: row.id,\n ...blockData,\n ...(translatedFields ?? {}),\n }\n\n // Strict mode: clear untranslated translatable fields when loading for non-default locale\n if (locale) {\n const blockType = row.blockType as string\n const fieldDefs = blockDefMap.get(blockType)\n if (fieldDefs) {\n for (const [fieldName, fieldConfig] of Object.entries(fieldDefs)) {\n if (fieldConfig.translatable && !translatedFields?.[fieldName]) {\n block[fieldName] = ''\n }\n }\n }\n }\n\n entityBlocks[fname].push(block)\n }\n }\n\n // ---- Localized blocks: filter by locale column ----\n // NULL rows (from initial create) only fall back for the default locale.\n // Non-default locales without locale-specific rows get an empty array.\n if (localizedFields.length > 0) {\n const isDefault = !locale || locale === options?.defaultLocale\n const localeCondition = locale\n ? isDefault\n ? or(eq(layoutTable.locale, locale), isNull(layoutTable.locale))!\n : eq(layoutTable.locale, locale)\n : isNull(layoutTable.locale)\n\n const rows = await this.db\n .select()\n .from(layoutTable)\n .where(and(inArray(layoutTable.entityId, entityIds), localeCondition))\n .orderBy(layoutTable.sortOrder)\n\n // Group by entityId + fieldName, prefer locale-specific rows over NULL\n const grouped = new Map<string, { localeRows: typeof rows; nullRows: typeof rows }>()\n for (const row of rows) {\n const key = `${row.entityId}::${row.fieldName}`\n if (!grouped.has(key)) grouped.set(key, { localeRows: [], nullRows: [] })\n const group = grouped.get(key)!\n if (row.locale) {\n group.localeRows.push(row)\n } else {\n group.nullRows.push(row)\n }\n }\n\n for (const [, { localeRows, nullRows }] of grouped) {\n // Use locale-specific rows if available, otherwise fall back to NULL\n const effective = localeRows.length > 0 ? localeRows : nullRows\n\n for (const row of effective) {\n const eid = row.entityId as string\n const fname = row.fieldName as string\n\n if (!result.has(eid)) result.set(eid, {})\n const entityBlocks = result.get(eid)!\n if (!entityBlocks[fname]) entityBlocks[fname] = []\n\n const blockData = (row.data as Record<string, unknown>) ?? {}\n\n entityBlocks[fname].push({\n _block: row.blockType,\n _id: row.id,\n ...blockData,\n })\n }\n }\n }\n\n return result\n }\n\n /**\n * Attach loaded blocks to shaped DTOs.\n */\n private attachBlocks(\n entities: Record<string, unknown>[],\n blocksMap: Map<string, Record<string, unknown[]>>,\n ): void {\n const blocksFields = this.getBlocksFields()\n if (blocksFields.length === 0) return\n\n for (const entity of entities) {\n const eid = entity.id as string\n const entityBlocks = blocksMap.get(eid) ?? {}\n\n for (const { name } of blocksFields) {\n entity[name] = entityBlocks[name] ?? []\n }\n }\n }\n\n // ---------------------------------------------------------------\n // Reference tracking (entity_refs)\n // ---------------------------------------------------------------\n\n /**\n * Sync outgoing references in entity_refs after create/update.\n *\n * - 'create': inserts all refs (entity is new, no existing refs)\n * - 'update': deletes refs for changed ref-bearing fields, then inserts new ones\n *\n * Gracefully skips if entity_refs table is not registered (e.g. before migration).\n */\n private async syncRefs(\n entityId: string,\n data: Record<string, unknown>,\n mode: 'create' | 'update',\n ): Promise<void> {\n // Graceful degradation: skip if entity_refs table doesn't exist yet\n if (!schemaRegistry.has('entity_refs')) return\n\n const allFields = this.entity.allFields as Record<string, FieldConfig>\n\n if (mode === 'update') {\n // Only delete refs for fields present in the update data\n const changedRefFields = Object.keys(data).filter((k) => {\n const config = allFields[k]\n return (\n config &&\n (config.type === 'media' || config.type === 'reference' || config.type === 'blocks')\n )\n })\n if (changedRefFields.length > 0) {\n await this.db\n .delete(entityRefs)\n .where(\n and(\n eq(entityRefs.sourceEntity, this.entity.name),\n eq(entityRefs.sourceId, entityId),\n inArray(entityRefs.sourceField, changedRefFields),\n ),\n )\n }\n }\n\n // Extract refs from the data and insert\n const refs = extractRefs(allFields, data)\n if (refs.length > 0) {\n await this.db\n .insert(entityRefs)\n .values(\n refs.map((ref) => ({\n sourceEntity: this.entity.name,\n sourceId: entityId,\n sourceField: ref.sourceField,\n targetEntity: ref.targetEntity,\n targetId: ref.targetId,\n })),\n )\n .onConflictDoNothing()\n }\n }\n\n // ---------------------------------------------------------------\n // Count cache helpers\n // ---------------------------------------------------------------\n\n /**\n * Build a cache key for a count query.\n * Key format: `entityName` for unfiltered, `entityName:where_sql` for filtered.\n */\n private buildCountCacheKey(where?: SQL): string {\n if (!where) return this.entity.name\n // Use SQL's toString() representation as a stable key.\n // This is a display string, not executed — safe for cache keying.\n return `${this.entity.name}:${String(where)}`\n }\n\n /**\n * Invalidate all count cache entries for this entity.\n */\n private invalidateCountCache(): void {\n this.countCache?.invalidate(this.entity.name)\n }\n}\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -44,6 +44,23 @@ interface EntityAdminConfig {
44
44
  disableCreate?: boolean;
45
45
  /** Order within sidebar group. Default: 0 */
46
46
  sortOrder?: number;
47
+ /**
48
+ * Hide the entity from BOTH dashboard and sidebar.
49
+ * Use for system entities that should not appear in any UI.
50
+ * Shorthand for `hideFromMenu: true` + `hideFromDashboard: true`.
51
+ */
52
+ hidden?: boolean;
53
+ /**
54
+ * Hide from sidebar menu only. Entity remains accessible via dashboard, direct URL,
55
+ * and the entity API. Use for storage models accessed through specialized UI
56
+ * (e.g., Ticket entities accessed via the inbox/board, not as a CRUD table).
57
+ */
58
+ hideFromMenu?: boolean;
59
+ /**
60
+ * Hide from dashboard widget grid only. Entity still appears in sidebar.
61
+ * Use for entities that don't have meaningful counts/stats to show.
62
+ */
63
+ hideFromDashboard?: boolean;
47
64
  }
48
65
  //#endregion
49
66
  //#region src/fields/base.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/admin-config.ts","../src/fields/base.ts","../src/types/infer.ts","../src/behaviors/types.ts","../src/behaviors/auditable.ts","../src/behaviors/hierarchical.ts","../src/behaviors/publishable.ts","../src/behaviors/revisionable.ts","../src/behaviors/sluggable.ts","../src/fields/builders.ts","../src/behaviors/timestamped.ts","../src/behaviors/index.ts","../src/count-cache.ts","../src/count-estimate.ts","../src/cursor.ts","../src/define-entity.ts","../src/refs/find-usages.ts","../src/refs/errors.ts","../src/schema-generator.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/admin-config.ts","../src/fields/base.ts","../src/types/infer.ts","../src/behaviors/types.ts","../src/behaviors/auditable.ts","../src/behaviors/hierarchical.ts","../src/behaviors/publishable.ts","../src/behaviors/revisionable.ts","../src/behaviors/sluggable.ts","../src/fields/builders.ts","../src/behaviors/timestamped.ts","../src/behaviors/index.ts","../src/count-cache.ts","../src/count-estimate.ts","../src/cursor.ts","../src/define-entity.ts","../src/refs/find-usages.ts","../src/refs/errors.ts","../src/schema-generator.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import{and as e,eq as t,gt as n,lt as r,or as i,sql as a}from"drizzle-orm";import{boolean as o,doublePrecision as s,index as c,integer as l,jsonb as u,pgTable as d,text as f,timestamp as p,unique as m,uuid as h,varchar as g}from"drizzle-orm/pg-core";const _={id:e=>({type:`id`,required:!0,indexed:!0,...e}),text:e=>({type:`text`,...e}),number:e=>({type:`number`,...e}),boolean:e=>({type:`boolean`,default:!1,...e}),date:e=>({type:`date`,...e}),select:e=>({type:`select`,...e}),reference:e=>({type:`reference`,cardinality:`one`,onDelete:`set-null`,...e}),media:e=>({type:`media`,...e}),richtext:e=>({type:`richtext`,...e}),slug:e=>({type:`slug`,unique:!0,indexed:!0,...e}),json:e=>({type:`json`,...e}),blocks:e=>({type:`blocks`,...e})};async function v(){try{return(await import([`@murumets-ee`,`core`].join(`/`))).getCurrentUser()?.id}catch{return}}function y(){return{name:`auditable`,fields:{createdBy:_.text(),updatedBy:_.text(),createdAt:_.date({indexed:!0}),updatedAt:_.date({indexed:!0})},hooks:{beforeCreate:async e=>{e.createdAt=new Date,e.updatedAt=new Date;let t=await v();return t&&(e.createdBy=t,e.updatedBy=t),e},beforeUpdate:async(e,t)=>{t.updatedAt=new Date;let n=await v();return n&&(t.updatedBy=n),t}}}}function b(e){return{name:`hierarchical`,fields:{parentId:_.reference({entity:`_self`,required:!1}),path:_.text({indexed:!0,maxLength:2048}),depth:_.number({integer:!0,default:0,indexed:!0})},hooks:{beforeCreate:async e=>(e.parentId||(e.depth=0),e)}}}function x(){return{name:`publishable`,fields:{status:_.select({options:[`draft`,`published`],default:`draft`,indexed:!0}),publishedAt:_.date({indexed:!0})},hooks:{beforeUpdate:async(e,t)=>(t.status===`published`&&!t.publishedAt&&(t.publishedAt=new Date),t)}}}function S(){return{name:`revisionable`,fields:{_version:_.number({required:!0,default:1,integer:!0})},hooks:{beforeCreate:async e=>(e._version=1,e),beforeUpdate:async(e,t)=>(t._version=(Number(t._version)||0)+1,t)}}}function C(e){return e.toLowerCase().trim().replace(/[^\w\s-]/g,``).replace(/[\s_-]+/g,`-`).replace(/^-+|-+$/g,``)}function w(e,t){return{name:`sluggable`,fields:{slug:_.slug({from:e,...t?.translatable?{translatable:!0}:{}})},hooks:{beforeCreate:async t=>(!t.slug&&t[e]&&(t.slug=C(String(t[e]))),t),beforeUpdate:async(t,n)=>(n[e]&&!n.slug&&(n.slug=C(String(n[e]))),n)}}}function T(){return{name:`timestamped`,fields:{createdAt:_.date({indexed:!0}),updatedAt:_.date({indexed:!0})},hooks:{beforeCreate:async e=>(e.createdAt=new Date,e.updatedAt=new Date,e),beforeUpdate:async(e,t)=>(t.updatedAt=new Date,t)}}}const E={publishable:x,auditable:y,sluggable:w,revisionable:S,hierarchical:b,timestamped:T};var D=class{cache=new Map;ttlMs;constructor(e=5e3){this.ttlMs=e}get(e){let t=this.cache.get(e);if(!t||Date.now()>t.expiresAt){t&&this.cache.delete(e);return}return t.count}set(e,t){this.cache.set(e,{count:t,expiresAt:Date.now()+this.ttlMs})}invalidate(e){for(let t of this.cache.keys())t.startsWith(e)&&this.cache.delete(t)}prune(){let e=Date.now();for(let[t,n]of this.cache.entries())e>n.expiresAt&&this.cache.delete(t)}clear(){this.cache.clear()}get size(){return this.cache.size}};async function O(e,t){let n=await e.execute(a`SELECT reltuples::bigint AS estimate FROM pg_class WHERE relname = ${t}`),r=(Array.isArray(n)?n:n.rows??[])[0],i=Number(r?.estimate??0);return i>0?i:0}const k=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function A(e,t,n){let r={field:t,value:e[t],direction:n,id:e.id};return btoa(JSON.stringify(r))}function j(e){try{let t=atob(e),n=JSON.parse(t);if(typeof n!=`object`||!n)return null;let r=n;return typeof r.field!=`string`||!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(r.field)||typeof r.value!=`string`&&typeof r.value!=`number`||r.direction!==`asc`&&r.direction!==`desc`||r.id!==void 0&&(typeof r.id!=`string`||!k.test(r.id))?null:{field:r.field,value:r.value,direction:r.direction,id:r.id}}catch{return null}}function M(a,o){let s=a[o.field];if(!s)return null;let c=o.direction===`desc`?r:n,l=c(s,o.value);if(!o.id)return l;let u=a.id;if(!u)return l;let d=c(u,o.id);return i(l,e(t(s,o.value),d))}function N(e){let t={},n={};for(let r of e.behaviors||[]){if(r.fields)for(let[e,n]of Object.entries(r.fields)){if(t[e]){console.warn(`Field '${e}' from behavior '${r.name}' conflicts with existing field. Skipping.`);continue}t[e]=n}if(r.hooks)for(let[e,t]of Object.entries(r.hooks)){let r=e;if(t){let e=n[r];if(e){let i=e,a=t;n[r]=async(...e)=>{let t=await i(...e);if(t!==void 0){let n=[...e];return n[n.length-1]=t,await a(...n)}return await a(...e)}}else n[r]=t}}}let r={id:_.id(),...t,...e.fields};for(let[,e]of Object.entries(r))e.type===`slug`&&!e.translatable&&r[e.from]?.translatable&&(e.translatable=!0);return{...e,allFields:r,hooks:n}}var P=class extends Error{entityName;entityId;usages;constructor(e,t,n){let r=n.length;super(`Cannot delete ${e} '${t}': referenced by ${r} other entit${r===1?`y`:`ies`}`),this.name=`ReferencedEntityError`,this.entityName=e,this.entityId=t,this.usages=n}};function F(e,t,n){let r=n?.nullable??!1;switch(t.type){case`id`:return h(e).primaryKey().defaultRandom();case`text`:if(t.maxLength&&t.maxLength<=255){let n=g(e,{length:t.maxLength});return t.unique&&(n=n.unique()),!r&&t.required&&(n=n.notNull()),!r&&t.default!==void 0&&(n=n.default(t.default)),n}else{let n=f(e);return t.unique&&(n=n.unique()),!r&&t.required&&(n=n.notNull()),!r&&t.default!==void 0&&(n=n.default(t.default)),n}case`number`:{let n=t.integer?l(e):s(e);return!r&&t.required&&(n=n.notNull()),!r&&t.default!==void 0&&(n=n.default(t.default)),n}case`boolean`:{let n=o(e);if(!r&&t.required&&(n=n.notNull()),!r){let e=t.default===void 0?!1:t.default;n=n.default(e)}return n}case`date`:{let n=p(e,{withTimezone:!0});return!r&&t.required&&(n=n.notNull()),!r&&t.default!==void 0&&(n=n.default(t.default)),n}case`select`:{let n=g(e,{length:100});return!r&&t.required&&(n=n.notNull()),!r&&t.default!==void 0&&(n=n.default(t.default)),n}case`reference`:{if(t.cardinality===`many`)return h(e).array();let n=h(e);return!r&&t.required&&(n=n.notNull()),n}case`media`:{let n=h(e);return!r&&t.required&&(n=n.notNull()),n}case`slug`:{let n=g(e,{length:255});return t.unique&&!r&&(n=n.unique()),!r&&t.required&&(n=n.notNull()),n}case`richtext`:{let n=f(e);return!r&&t.required&&(n=n.notNull()),n}case`json`:return u(e);default:return f(e)}}function I(e,t,n){let r=n?.nullable??!1;switch(t.type){case`id`:return`id: uuid('id').primaryKey().defaultRandom()`;case`text`:{let n=t.maxLength||255,i=n<=255?`varchar('${e}', { length: ${n} })`:`text('${e}')`;return t.unique&&(i+=`.unique()`),!r&&t.required&&(i+=`.notNull()`),!r&&t.default&&(i+=`.default('${t.default}')`),`${e}: ${i}`}case`number`:{let n=t.integer?`integer('${e}')`:`doublePrecision('${e}')`;return!r&&t.required&&(n+=`.notNull()`),!r&&t.default!==void 0&&(n+=`.default(${t.default})`),`${e}: ${n}`}case`boolean`:return r?`${e}: boolean('${e}')`:`${e}: boolean('${e}').default(${t.default??!1})`;case`date`:{let n=`timestamp('${e}', { withTimezone: true })`;return!r&&t.required&&(n+=`.notNull()`),`${e}: ${n}`}case`reference`:{if(t.cardinality===`many`)return`${e}: uuid('${e}').array()`;let n=`uuid('${e}')`;return!r&&t.required&&(n+=`.notNull()`),`${e}: ${n}`}case`media`:{let n=`uuid('${e}')`;return!r&&t.required&&(n+=`.notNull()`),`${e}: ${n}`}case`slug`:{let n=`varchar('${e}', { length: 255 })`;return t.unique&&!r&&(n+=`.unique()`),!r&&t.required&&(n+=`.notNull()`),`${e}: ${n}`}case`select`:{let n=`varchar('${e}', { length: 100 })`;return!r&&t.required&&(n+=`.notNull()`),!r&&t.default&&(n+=`.default('${t.default}')`),`${e}: ${n}`}case`richtext`:{let n=`text('${e}')`;return!r&&t.required&&(n+=`.notNull()`),`${e}: ${n}`}case`json`:return`${e}: jsonb('${e}')`;default:return`${e}: text('${e}')`}}function L(e){let t=e.name,n={};for(let[t,r]of Object.entries(e.allFields))r.type!==`blocks`&&(n[t]=F(t,r));(e.scope===`team`||e.scope===`user`)&&(n._scopeId=h(`_scope_id`));let r=Object.entries(e.allFields).filter(([e,t])=>t.type!==`blocks`&&t.type!==`id`&&t.indexed&&!t.unique),i=e.behaviors?.some(e=>e.name===`publishable`)??!1;return r.length===0&&!i?d(t,n):d(t,n,e=>{let n={};for(let[i]of r)n[`idx_${t}_${i}`]=c(`idx_${t}_${i}`).on(e[i]);return i&&e.status&&e.createdAt&&(n[`idx_${t}_status_created`]=c(`idx_${t}_status_created`).on(e.status,e.createdAt)),n})}function R(e){let t=e.name,n=[],r=Object.entries(e.allFields).filter(([e,t])=>t.type!==`blocks`&&t.type!==`id`&&t.indexed&&!t.unique),i=e.behaviors?.some(e=>e.name===`publishable`)??!1,a=r.length>0||i;n.push(`export const ${t} = pgTable('${t}', {`);for(let[t,r]of Object.entries(e.allFields)){if(r.type===`blocks`)continue;let e=I(t,r);n.push(` ${e},`)}if((e.scope===`team`||e.scope===`user`)&&n.push(` _scopeId: uuid('_scope_id'),`),a){n.push(`}, (table) => ({`);for(let[e]of r)n.push(` idx_${t}_${e}: index('idx_${t}_${e}').on(table.${e}),`);i&&n.push(` idx_${t}_status_created: index('idx_${t}_status_created').on(table.status, table.createdAt),`),n.push(`}))`)}else n.push(`)`);return n.join(`
1
+ import{and as e,eq as t,gt as n,lt as r,or as i,sql as a}from"drizzle-orm";import{boolean as o,doublePrecision as s,index as c,integer as l,jsonb as u,pgTable as d,text as f,timestamp as p,unique as m,uuid as h,varchar as g}from"drizzle-orm/pg-core";const _={id:e=>({type:`id`,required:!0,indexed:!0,...e}),text:e=>({type:`text`,...e}),number:e=>({type:`number`,...e}),boolean:e=>({type:`boolean`,default:!1,...e}),date:e=>({type:`date`,...e}),select:e=>({type:`select`,...e}),reference:e=>({type:`reference`,cardinality:`one`,onDelete:`set-null`,...e}),media:e=>({type:`media`,...e}),richtext:e=>({type:`richtext`,...e}),slug:e=>({type:`slug`,unique:!0,indexed:!0,...e}),json:e=>({type:`json`,...e}),blocks:e=>({type:`blocks`,...e})};async function v(){try{return(await import([`@murumets-ee`,`core`].join(`/`))).getCurrentUser()?.id}catch{return}}function y(){return{name:`auditable`,fields:{createdBy:_.text(),updatedBy:_.text(),createdAt:_.date({indexed:!0}),updatedAt:_.date({indexed:!0})},hooks:{beforeCreate:async e=>{e.createdAt=new Date,e.updatedAt=new Date;let t=await v();return t&&(e.createdBy=t,e.updatedBy=t),e},beforeUpdate:async(e,t)=>{t.updatedAt=new Date;let n=await v();return n&&(t.updatedBy=n),t}}}}function b(e){return{name:`hierarchical`,fields:{parentId:_.reference({entity:`_self`,required:!1}),path:_.text({indexed:!0,maxLength:2048}),depth:_.number({integer:!0,default:0,indexed:!0})},hooks:{beforeCreate:async e=>(e.parentId||(e.depth=0),e)}}}function x(){return{name:`publishable`,fields:{status:_.select({options:[`draft`,`published`],default:`draft`,indexed:!0}),publishedAt:_.date({indexed:!0})},hooks:{beforeUpdate:async(e,t)=>(t.status===`published`&&!t.publishedAt&&(t.publishedAt=new Date),t)}}}function S(){return{name:`revisionable`,fields:{_version:_.number({required:!0,default:1,integer:!0})},hooks:{beforeCreate:async e=>(e._version=1,e),beforeUpdate:async(e,t)=>(t._version=(Number(t._version)||0)+1,t)}}}function C(e){return e.toLowerCase().trim().replace(/[^\w\s-]/g,``).replace(/[\s_-]+/g,`-`).replace(/^-+|-+$/g,``)}function w(e,t){return{name:`sluggable`,fields:{slug:_.slug({from:e,...t?.translatable?{translatable:!0}:{}})},hooks:{beforeCreate:async t=>(!t.slug&&t[e]&&(t.slug=C(String(t[e]))),t),beforeUpdate:async(t,n)=>(n[e]&&!n.slug&&(n.slug=C(String(n[e]))),n)}}}function T(){return{name:`timestamped`,fields:{createdAt:_.date({indexed:!0}),updatedAt:_.date({indexed:!0})},hooks:{beforeCreate:async e=>(e.createdAt=new Date,e.updatedAt=new Date,e),beforeUpdate:async(e,t)=>(t.updatedAt=new Date,t)}}}const E={publishable:x,auditable:y,sluggable:w,revisionable:S,hierarchical:b,timestamped:T};var D=class{cache=new Map;ttlMs;constructor(e=5e3){this.ttlMs=e}get(e){let t=this.cache.get(e);if(!t||Date.now()>t.expiresAt){t&&this.cache.delete(e);return}return t.count}set(e,t){this.cache.set(e,{count:t,expiresAt:Date.now()+this.ttlMs})}invalidate(e){for(let t of this.cache.keys())t.startsWith(e)&&this.cache.delete(t)}prune(){let e=Date.now();for(let[t,n]of this.cache.entries())e>n.expiresAt&&this.cache.delete(t)}clear(){this.cache.clear()}get size(){return this.cache.size}};async function O(e,t){let n=await e.execute(a`SELECT reltuples::bigint AS estimate FROM pg_class WHERE relname = ${t}`),r=(Array.isArray(n)?n:n.rows??[])[0],i=Number(r?.estimate??0);return i>0?i:0}const k=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function A(e,t,n){let r={field:t,value:e[t],direction:n,id:e.id};return btoa(JSON.stringify(r))}function j(e){try{let t=atob(e),n=JSON.parse(t);if(typeof n!=`object`||!n)return null;let r=n;return typeof r.field!=`string`||!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(r.field)||typeof r.value!=`string`&&typeof r.value!=`number`||r.direction!==`asc`&&r.direction!==`desc`||r.id!==void 0&&(typeof r.id!=`string`||!k.test(r.id))?null:{field:r.field,value:r.value,direction:r.direction,id:r.id}}catch{return null}}function M(a,o){let s=a[o.field];if(!s)return null;let c=o.direction===`desc`?r:n,l=c(s,o.value);if(!o.id)return l;let u=a.id;if(!u)return l;let d=c(u,o.id);return i(l,e(t(s,o.value),d))}function N(e){let t={},n={};for(let r of e.behaviors||[]){if(r.fields)for(let[e,n]of Object.entries(r.fields)){if(t[e]){console.warn(`Field '${e}' from behavior '${r.name}' conflicts with existing field. Skipping.`);continue}t[e]=n}if(r.hooks)for(let[e,t]of Object.entries(r.hooks)){let r=e;if(t){let e=n[r];if(e){let i=e,a=t;n[r]=async(...e)=>{let t=await i(...e);if(t!==void 0){let n=[...e];return n[n.length-1]=t,await a(...n)}return await a(...e)}}else n[r]=t}}}let r={id:_.id(),...t,...e.fields};for(let[,e]of Object.entries(r))e.type===`slug`&&!e.translatable&&r[e.from]?.translatable&&(e.translatable=!0);return{...e,allFields:r,hooks:n}}var P=class extends Error{entityName;entityId;usages;constructor(e,t,n){let r=n.length;super(`Cannot delete ${e} '${t}': referenced by ${r} other entit${r===1?`y`:`ies`}`),this.name=`ReferencedEntityError`,this.entityName=e,this.entityId=t,this.usages=n}};function F(e,t,n){let r=n?.nullable??!1;switch(t.type){case`id`:return h(e).primaryKey().defaultRandom();case`text`:if(t.maxLength&&t.maxLength<=255){let n=g(e,{length:t.maxLength});return t.unique&&(n=n.unique()),!r&&t.required&&(n=n.notNull()),!r&&t.default!==void 0&&(n=n.default(t.default)),n}else{let n=f(e);return t.unique&&(n=n.unique()),!r&&t.required&&(n=n.notNull()),!r&&t.default!==void 0&&(n=n.default(t.default)),n}case`number`:{let n=t.integer?l(e):s(e);return!r&&t.required&&(n=n.notNull()),!r&&t.default!==void 0&&(n=n.default(t.default)),n}case`boolean`:{let n=o(e);if(!r&&t.required&&(n=n.notNull()),!r){let e=t.default===void 0?!1:t.default;n=n.default(e)}return n}case`date`:{let n=p(e,{withTimezone:!0});return!r&&t.required&&(n=n.notNull()),!r&&t.default!==void 0&&(n=n.default(t.default)),n}case`select`:{let n=g(e,{length:100});return!r&&t.required&&(n=n.notNull()),!r&&t.default!==void 0&&(n=n.default(t.default)),n}case`reference`:{if(t.cardinality===`many`)return h(e).array();let n=h(e);return!r&&t.required&&(n=n.notNull()),n}case`media`:{let n=h(e);return!r&&t.required&&(n=n.notNull()),n}case`slug`:{let n=g(e,{length:255});return t.unique&&!r&&(n=n.unique()),!r&&t.required&&(n=n.notNull()),n}case`richtext`:{let n=f(e);return!r&&t.required&&(n=n.notNull()),n}case`json`:return u(e);default:return f(e)}}function I(e,t,n){let r=n?.nullable??!1;switch(t.type){case`id`:return`id: uuid('id').primaryKey().defaultRandom()`;case`text`:{let n=t.maxLength,i=n&&n<=255?`varchar('${e}', { length: ${n} })`:`text('${e}')`;return t.unique&&(i+=`.unique()`),!r&&t.required&&(i+=`.notNull()`),!r&&t.default&&(i+=`.default('${t.default}')`),`${e}: ${i}`}case`number`:{let n=t.integer?`integer('${e}')`:`doublePrecision('${e}')`;return!r&&t.required&&(n+=`.notNull()`),!r&&t.default!==void 0&&(n+=`.default(${t.default})`),`${e}: ${n}`}case`boolean`:return r?`${e}: boolean('${e}')`:`${e}: boolean('${e}').default(${t.default??!1})`;case`date`:{let n=`timestamp('${e}', { withTimezone: true })`;return!r&&t.required&&(n+=`.notNull()`),`${e}: ${n}`}case`reference`:{if(t.cardinality===`many`)return`${e}: uuid('${e}').array()`;let n=`uuid('${e}')`;return!r&&t.required&&(n+=`.notNull()`),`${e}: ${n}`}case`media`:{let n=`uuid('${e}')`;return!r&&t.required&&(n+=`.notNull()`),`${e}: ${n}`}case`slug`:{let n=`varchar('${e}', { length: 255 })`;return t.unique&&!r&&(n+=`.unique()`),!r&&t.required&&(n+=`.notNull()`),`${e}: ${n}`}case`select`:{let n=`varchar('${e}', { length: 100 })`;return!r&&t.required&&(n+=`.notNull()`),!r&&t.default&&(n+=`.default('${t.default}')`),`${e}: ${n}`}case`richtext`:{let n=`text('${e}')`;return!r&&t.required&&(n+=`.notNull()`),`${e}: ${n}`}case`json`:return`${e}: jsonb('${e}')`;default:return`${e}: text('${e}')`}}function L(e){let t=e.name,n={};for(let[t,r]of Object.entries(e.allFields))r.type!==`blocks`&&(n[t]=F(t,r));(e.scope===`team`||e.scope===`user`)&&(n._scopeId=h(`_scope_id`));let r=Object.entries(e.allFields).filter(([e,t])=>t.type!==`blocks`&&t.type!==`id`&&t.indexed&&!t.unique),i=e.behaviors?.some(e=>e.name===`publishable`)??!1;return r.length===0&&!i?d(t,n):d(t,n,e=>{let n={};for(let[i]of r)n[`idx_${t}_${i}`]=c(`idx_${t}_${i}`).on(e[i]);return i&&e.status&&e.createdAt&&(n[`idx_${t}_status_created`]=c(`idx_${t}_status_created`).on(e.status,e.createdAt)),n})}function R(e){let t=e.name,n=[],r=Object.entries(e.allFields).filter(([e,t])=>t.type!==`blocks`&&t.type!==`id`&&t.indexed&&!t.unique),i=e.behaviors?.some(e=>e.name===`publishable`)??!1,a=r.length>0||i;n.push(`export const ${t} = pgTable('${t}', {`);for(let[t,r]of Object.entries(e.allFields)){if(r.type===`blocks`)continue;let e=I(t,r);n.push(` ${e},`)}if((e.scope===`team`||e.scope===`user`)&&n.push(` _scopeId: uuid('_scope_id'),`),a){n.push(`}, (table) => ({`);for(let[e]of r)n.push(` idx_${t}_${e}: index('idx_${t}_${e}').on(table.${e}),`);i&&n.push(` idx_${t}_status_created: index('idx_${t}_status_created').on(table.status, table.createdAt),`),n.push(`}))`)}else n.push(`)`);return n.join(`
2
2
  `)}function z(e){let t=Object.entries(e.allFields).filter(([e,t])=>t.translatable);if(t.length===0)return null;let n=`${e.name}_translations`,r={id:h(`id`).primaryKey().defaultRandom(),entityId:h(`entity_id`).notNull(),locale:g(`locale`,{length:10}).notNull()},i=t.some(([e,t])=>t.type===`slug`);for(let[e,n]of t)r[e]=F(e,n,{nullable:!0});return d(n,r,e=>({uniqueEntityLocale:m().on(e.entityId,e.locale),...i&&e.slug?{uniqueSlugLocale:m().on(e.slug,e.locale)}:{}}))}function B(e){let t=Object.entries(e.allFields).filter(([e,t])=>t.translatable);if(t.length===0)return null;let n=`${e.name}_translations`,r=e.name,i=[];i.push(`export const ${n} = pgTable('${n}', {`),i.push(` id: uuid('id').primaryKey().defaultRandom(),`),i.push(` entityId: uuid('entity_id').notNull().references(() => ${r}.id, { onDelete: 'cascade' }),`),i.push(` locale: varchar('locale', { length: 10 }).notNull(),`);for(let[e,n]of t){let t=I(e,n,{nullable:!0});i.push(` ${t},`)}let a=t.some(([e,t])=>t.type===`slug`);return i.push(`}, (table) => ({`),i.push(` uniqueEntityLocale: unique().on(table.entityId, table.locale), // One translation per locale`),a&&i.push(` uniqueSlugLocale: unique().on(table.slug, table.locale), // Per-locale slug uniqueness`),i.push(`}))`),i.join(`
3
3
  `)}function V(e){return Object.values(e.allFields).some(e=>e.type===`blocks`)}function H(e){if(!V(e))return null;let t=`${e.name}_layout`;return d(t,{id:h(`id`).primaryKey().defaultRandom(),entityId:h(`entity_id`).notNull(),fieldName:g(`field_name`,{length:100}).notNull(),blockType:g(`block_type`,{length:100}).notNull(),sortOrder:l(`sort_order`).notNull().default(0),data:u(`data`),locale:g(`locale`,{length:10})},e=>({idx_entity_locale_sort:c(`idx_${t}_entity_locale_sort`).on(e.entityId,e.locale,e.sortOrder)}))}function U(e){return e.behaviors?.some(e=>e.name===`versionable`)??!1}function W(e){if(!(e.behaviors?.some(e=>e.name===`publishable`)??!1))return!1;let t=e.allFields;return Object.values(t).some(e=>e.translatable)}function G(e){let t=e.name,n=`${t}_locale_status`;return`export const ${n} = pgTable('${n}', {
4
4
  id: uuid('id').primaryKey().defaultRandom(),
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/fields/builders.ts","../src/behaviors/auditable.ts","../src/behaviors/hierarchical.ts","../src/behaviors/publishable.ts","../src/behaviors/revisionable.ts","../src/behaviors/sluggable.ts","../src/behaviors/timestamped.ts","../src/behaviors/index.ts","../src/count-cache.ts","../src/count-estimate.ts","../src/cursor.ts","../src/define-entity.ts","../src/refs/errors.ts","../src/schema-generator.ts"],"sourcesContent":["/**\n * Fluent API for building field definitions\n * Provides type-safe field builders with sensible defaults.\n *\n * Each builder uses a `const` generic parameter on the config to preserve\n * literal types (e.g., `required: true` stays `true`, not `boolean`).\n * This enables compile-time type inference in the entity system.\n */\n\nimport type {\n BlockDefinitionRef,\n BlocksField,\n BooleanField,\n DateField,\n IdField,\n JsonField,\n MediaField,\n NumberField,\n ReferenceField,\n RichTextField,\n SelectField,\n SlugField,\n TextField,\n} from './base.js'\n\nexport const field = {\n /**\n * ID field (auto-added to every entity)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n id: <const C extends Partial<IdField> = {}>(config?: C): IdField & C =>\n ({\n type: 'id',\n required: true,\n indexed: true,\n ...config,\n }) as IdField & C,\n\n /**\n * Text field\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n text: <const C extends Partial<TextField> = {}>(config?: C): TextField & C =>\n ({\n type: 'text',\n ...config,\n }) as TextField & C,\n\n /**\n * Number field\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n number: <const C extends Partial<NumberField> = {}>(config?: C): NumberField & C =>\n ({\n type: 'number',\n ...config,\n }) as NumberField & C,\n\n /**\n * Boolean field\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n boolean: <const C extends Partial<BooleanField> = {}>(config?: C): BooleanField & C =>\n ({\n type: 'boolean',\n default: false,\n ...config,\n }) as BooleanField & C,\n\n /**\n * Date field\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n date: <const C extends Partial<DateField> = {}>(config?: C): DateField & C =>\n ({\n type: 'date',\n ...config,\n }) as DateField & C,\n\n /**\n * Select field (enum)\n * Preserves literal option types for type inference.\n * e.g. field.select({ options: ['news', 'tutorial'] }) infers 'news' | 'tutorial'\n */\n select: <\n const O extends readonly string[],\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n const C extends Omit<Partial<SelectField>, 'options'> = {},\n >(\n config: { options: O } & C,\n ): SelectField & { options: O } & C =>\n ({\n type: 'select',\n ...config,\n }) as SelectField & { options: O } & C,\n\n /**\n * Reference field (foreign key to another entity)\n * Preserves literal cardinality to distinguish string vs string[] in inferred types.\n */\n reference: <\n C extends 'one' | 'many' = 'one',\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n const R extends Partial<Omit<ReferenceField, 'entity' | 'cardinality'>> = {},\n >(\n config: { entity: string; cardinality?: C } & R,\n ): ReferenceField & { cardinality: C } & R =>\n ({\n type: 'reference',\n cardinality: 'one' as C,\n onDelete: 'set-null',\n ...config,\n }) as ReferenceField & { cardinality: C } & R,\n\n /**\n * Media field (file upload)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n media: <const C extends Partial<MediaField> = {}>(config?: C): MediaField & C =>\n ({\n type: 'media',\n ...config,\n }) as MediaField & C,\n\n /**\n * Rich text field (WYSIWYG editor)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n richtext: <const C extends Partial<RichTextField> = {}>(config?: C): RichTextField & C =>\n ({\n type: 'richtext',\n ...config,\n }) as RichTextField & C,\n\n /**\n * Slug field (URL-safe string, auto-generated from source field)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n slug: <const C extends Partial<SlugField> = {}>(\n config: Pick<SlugField, 'from'> & C,\n ): SlugField & C =>\n ({\n type: 'slug',\n unique: true,\n indexed: true,\n ...config,\n }) as SlugField & C,\n\n /**\n * JSON field (arbitrary JSON data, stored as JSONB)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n json: <const C extends Partial<JsonField> = {}>(config?: C): JsonField & C =>\n ({\n type: 'json',\n ...config,\n }) as JsonField & C,\n\n /**\n * Blocks field — ordered array of typed content blocks\n * Preserves literal block tuple type for discriminated union inference.\n */\n blocks: <const B extends readonly BlockDefinitionRef[]>(config: {\n blocks: B\n min?: number\n max?: number\n localized?: boolean\n }): BlocksField & { blocks: B } =>\n ({\n type: 'blocks' as const,\n ...config,\n }) as BlocksField & { blocks: B },\n}\n","/**\n * Auditable behavior\n * Adds createdBy, updatedBy, createdAt, updatedAt fields + auto-set logic.\n *\n * When @murumets-ee/core is available (i.e. running inside createApp), the hooks\n * automatically populate createdBy/updatedBy from the RequestContext.\n */\n\nimport { field } from '../fields/builders.js'\nimport type { AuditableFields } from '../types/infer.js'\nimport type { Behavior } from './types.js'\n\n/**\n * Try to get the current user ID from @murumets-ee/core's RequestContext.\n * Returns undefined if @murumets-ee/core isn't loaded or no user is in context.\n *\n * Uses a dynamic import string indirection to avoid TypeScript resolving\n * @murumets-ee/core at compile time (which would create a circular dep: entity → core → entity).\n */\nasync function getCurrentUserId(): Promise<string | undefined> {\n try {\n // Module name built at runtime so TypeScript doesn't try to resolve it during DTS emit\n const moduleName = ['@murumets-ee', 'core'].join('/')\n const core = await (import(moduleName) as Promise<{\n getCurrentUser: () => { id: string } | undefined\n }>)\n return core.getCurrentUser()?.id\n } catch {\n return undefined\n }\n}\n\nexport function auditable(): Behavior<AuditableFields> {\n return {\n name: 'auditable',\n fields: {\n createdBy: field.text(),\n updatedBy: field.text(),\n createdAt: field.date({ indexed: true }),\n updatedAt: field.date({ indexed: true }),\n },\n hooks: {\n beforeCreate: async (data) => {\n data.createdAt = new Date()\n data.updatedAt = new Date()\n const userId = await getCurrentUserId()\n if (userId) {\n data.createdBy = userId\n data.updatedBy = userId\n }\n return data\n },\n beforeUpdate: async (_id, data) => {\n data.updatedAt = new Date()\n const userId = await getCurrentUserId()\n if (userId) {\n data.updatedBy = userId\n }\n return data\n },\n },\n }\n}\n","/**\n * Hierarchical behavior\n * Adds parentId, path, depth fields for materialized path tree structures.\n *\n * Path format: /uuid1/uuid2/uuid3\n * - Root items: /{ownId}\n * - Children: /{rootId}/.../{parentId}/{ownId}\n *\n * The behavior declares fields and sets safe defaults in hooks.\n * Heavy lifting (path computation, circular ref validation, descendant cascading)\n * is done by TaxonomyClient which has DB access.\n */\n\nimport { field } from '../fields/builders.js'\nimport type { HierarchicalFields } from '../types/infer.js'\nimport type { Behavior } from './types.js'\n\nexport interface HierarchicalOptions {\n /**\n * What happens when deleting a node that has children.\n * - 'restrict': Prevent deletion (default)\n * - 'cascade': Delete all descendants\n * - 'reparent': Move children to the deleted node's parent\n */\n onDelete?: 'restrict' | 'cascade' | 'reparent'\n}\n\nexport function hierarchical(_options?: HierarchicalOptions): Behavior<HierarchicalFields> {\n return {\n name: 'hierarchical',\n fields: {\n parentId: field.reference({ entity: '_self', required: false }),\n path: field.text({ indexed: true, maxLength: 2048 }),\n depth: field.number({ integer: true, default: 0, indexed: true }),\n },\n hooks: {\n beforeCreate: async (data) => {\n // Set defaults for root-level items.\n // TaxonomyClient overrides these after insert when it has the ID.\n if (!data.parentId) {\n data.depth = 0\n }\n return data\n },\n },\n }\n}\n","/**\n * Publishable behavior\n * Adds status (draft/published) and publishedAt fields + auto-set logic\n */\n\nimport { field } from '../fields/builders.js'\nimport type { PublishableFields } from '../types/infer.js'\nimport type { Behavior } from './types.js'\n\nexport function publishable(): Behavior<PublishableFields> {\n return {\n name: 'publishable',\n fields: {\n status: field.select({\n options: ['draft', 'published'] as const,\n default: 'draft',\n indexed: true,\n }),\n publishedAt: field.date({ indexed: true }),\n },\n hooks: {\n beforeUpdate: async (_id, data) => {\n // Auto-set publishedAt when status changes to published (if not already set)\n if (data.status === 'published' && !data.publishedAt) {\n data.publishedAt = new Date()\n }\n return data\n },\n },\n }\n}\n","/**\n * Revisionable behavior\n * Adds _version field + auto-increment on update\n */\n\nimport { field } from '../fields/builders.js'\nimport type { RevisionableFields } from '../types/infer.js'\nimport type { Behavior } from './types.js'\n\nexport function revisionable(): Behavior<RevisionableFields> {\n return {\n name: 'revisionable',\n fields: {\n _version: field.number({ required: true, default: 1, integer: true }),\n },\n hooks: {\n beforeCreate: async (data) => {\n data._version = 1\n return data\n },\n beforeUpdate: async (_id, data) => {\n // Increment version on every update\n data._version = (Number(data._version) || 0) + 1\n return data\n },\n },\n }\n}\n","/**\n * Sluggable behavior\n * Adds slug field + auto-generation from source field\n */\n\nimport { field } from '../fields/builders.js'\nimport type { SluggableFields } from '../types/infer.js'\nimport type { Behavior } from './types.js'\n\nexport interface SluggableOptions {\n /** Make the slug translatable — each locale gets its own slug. Default: false */\n translatable?: boolean\n}\n\n/**\n * Convert text to URL-safe slug\n */\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '') // Remove non-word chars except spaces and hyphens\n .replace(/[\\s_-]+/g, '-') // Replace spaces, underscores with single hyphen\n .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens\n}\n\nexport function sluggable(\n sourceField: string,\n options?: SluggableOptions,\n): Behavior<SluggableFields> {\n return {\n name: 'sluggable',\n fields: {\n slug: field.slug({\n from: sourceField,\n ...(options?.translatable ? { translatable: true } : {}),\n }),\n },\n hooks: {\n beforeCreate: async (data) => {\n // Auto-generate slug from source field if not provided\n if (!data.slug && data[sourceField]) {\n data.slug = slugify(String(data[sourceField]))\n }\n return data\n },\n beforeUpdate: async (_id, data) => {\n // Re-generate slug if source field changed and slug not manually set\n if (data[sourceField] && !data.slug) {\n data.slug = slugify(String(data[sourceField]))\n }\n return data\n },\n },\n }\n}\n","/**\n * Timestamped behavior\n * Adds createdAt and updatedAt fields with auto-set logic.\n * Lighter alternative to auditable() when you don't need createdBy/updatedBy tracking.\n */\n\nimport { field } from '../fields/builders.js'\nimport type { Behavior } from './types.js'\n\nexport type TimestampedFields = {\n createdAt: ReturnType<typeof field.date>\n updatedAt: ReturnType<typeof field.date>\n}\n\nexport function timestamped(): Behavior<TimestampedFields> {\n return {\n name: 'timestamped',\n fields: {\n createdAt: field.date({ indexed: true }),\n updatedAt: field.date({ indexed: true }),\n },\n hooks: {\n beforeCreate: async (data) => {\n data.createdAt = new Date()\n data.updatedAt = new Date()\n return data\n },\n beforeUpdate: async (_id, data) => {\n data.updatedAt = new Date()\n return data\n },\n },\n }\n}\n","/**\n * Behavior exports\n * All behaviors are exported under the `behavior` namespace\n */\n\nimport { auditable } from './auditable.js'\nimport { hierarchical } from './hierarchical.js'\nimport { publishable } from './publishable.js'\nimport { revisionable } from './revisionable.js'\nimport { sluggable } from './sluggable.js'\nimport { timestamped } from './timestamped.js'\n\nexport { publishable, auditable, sluggable, revisionable, hierarchical, timestamped }\nexport type { SluggableOptions } from './sluggable.js'\nexport { slugify } from './sluggable.js'\nexport type { Behavior, BehaviorFactory } from './types.js'\n\n/**\n * Behavior namespace for fluent API\n */\nexport const behavior = {\n publishable,\n auditable,\n sluggable,\n revisionable,\n hierarchical,\n timestamped,\n}\n","/**\n * In-memory TTL cache for COUNT(*) query results.\n *\n * Reduces database load on paginated list pages where the total count\n * is recalculated on every pagination/sort/search interaction. The cache\n * is per-process (not shared across workers) with a short TTL (default 5s)\n * so counts are at most a few seconds stale.\n *\n * Cache keys include the entity name + serialized WHERE clause, so filtered\n * and unfiltered counts are cached independently.\n */\n\n/**\n * Interface for count cache implementations.\n * Used in client configs to avoid TypeScript private-field structural incompatibility\n * across separate .d.ts files.\n */\nexport interface CountCacheLike {\n get(key: string): number | undefined\n set(key: string, count: number): void\n invalidate(prefix: string): void\n}\n\ninterface CountCacheEntry {\n count: number\n expiresAt: number\n}\n\nexport class CountCache implements CountCacheLike {\n private cache = new Map<string, CountCacheEntry>()\n private ttlMs: number\n\n constructor(ttlMs = 5000) {\n this.ttlMs = ttlMs\n }\n\n /**\n * Get a cached count. Returns undefined on miss or expired entry.\n */\n get(key: string): number | undefined {\n const entry = this.cache.get(key)\n if (!entry || Date.now() > entry.expiresAt) {\n if (entry) this.cache.delete(key)\n return undefined\n }\n return entry.count\n }\n\n /**\n * Cache a count result with the configured TTL.\n */\n set(key: string, count: number): void {\n this.cache.set(key, { count, expiresAt: Date.now() + this.ttlMs })\n }\n\n /**\n * Invalidate all cache entries whose key starts with the given prefix.\n * Typically called with the entity name after mutations (create/update/delete).\n */\n invalidate(prefix: string): void {\n for (const key of this.cache.keys()) {\n if (key.startsWith(prefix)) {\n this.cache.delete(key)\n }\n }\n }\n\n /**\n * Remove all expired entries. Useful for periodic cleanup in long-running processes.\n */\n prune(): void {\n const now = Date.now()\n for (const [key, entry] of this.cache.entries()) {\n if (now > entry.expiresAt) {\n this.cache.delete(key)\n }\n }\n }\n\n /**\n * Clear the entire cache.\n */\n clear(): void {\n this.cache.clear()\n }\n\n /**\n * Number of entries currently in the cache (including expired ones not yet pruned).\n */\n get size(): number {\n return this.cache.size\n }\n}\n","/**\n * Postgres row count estimation using pg_class statistics.\n *\n * For unfiltered counts on large tables, querying `pg_class.reltuples`\n * is effectively instant (no table scan) and returns a good approximation\n * that is updated by VACUUM and ANALYZE. This is suitable for pagination\n * totals where exact precision is not critical.\n *\n * SECURITY: The table name is NOT interpolated into SQL — it is passed as a\n * parameterized value to the `relname = $1` comparison. This prevents SQL injection.\n */\n\nimport { sql } from 'drizzle-orm'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\n\n/**\n * Estimate the total row count for a table using Postgres statistics.\n *\n * Returns the approximate row count from `pg_class.reltuples`, which is\n * updated by VACUUM/ANALYZE. Returns 0 if the table is not found or\n * statistics are not yet available (e.g., freshly created table).\n *\n * @param db - Drizzle Postgres database instance\n * @param tableName - The Postgres table name (without schema prefix)\n * @returns Estimated row count (non-negative integer)\n */\nexport async function estimateRowCount(db: PostgresJsDatabase, tableName: string): Promise<number> {\n const result = await db.execute(\n sql`SELECT reltuples::bigint AS estimate FROM pg_class WHERE relname = ${tableName}`,\n )\n\n const rows = Array.isArray(result) ? result : ((result as { rows?: unknown[] }).rows ?? [])\n const row = rows[0] as { estimate?: string | number } | undefined\n const estimate = Number(row?.estimate ?? 0)\n\n // reltuples can be -1 for tables that have never been analyzed\n return estimate > 0 ? estimate : 0\n}\n","/**\n * Cursor-based (keyset) pagination utilities.\n *\n * Cursor pagination avoids the performance cliff of OFFSET at scale (1M+ rows).\n * Instead of `OFFSET N`, it uses a WHERE condition:\n * `WHERE (sortField < lastValue) OR (sortField = lastValue AND id < lastId)`\n * which Postgres can serve from an index in constant time.\n *\n * The cursor is opaque to the client — base64-encoded JSON.\n *\n * Security:\n * - `field` must be whitelisted against the entity's actual fields\n * - `id` must be a valid UUID\n * - `value` is parameterized (never interpolated into SQL)\n * - Malformed cursors return null (caller returns 400)\n */\n\nimport { and, eq, gt, lt, or, type SQL } from 'drizzle-orm'\nimport type { PgTableWithColumns } from 'drizzle-orm/pg-core'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Cursor input for keyset pagination. */\nexport interface CursorInput {\n /** Sort field name (e.g. 'createdAt'). Must be a real column on the entity. */\n field: string\n /** Last seen value of the sort field. */\n value: string | number\n /** Sort direction — must match the ORDER BY direction. */\n direction: 'asc' | 'desc'\n /** Tie-breaker: last seen entity ID. Required for non-unique sort fields. */\n id?: string\n}\n\n/** Decoded cursor (internal, after validation). */\ninterface DecodedCursor {\n field: string\n value: string | number\n direction: 'asc' | 'desc'\n id?: string\n}\n\n// ---------------------------------------------------------------------------\n// UUID validation (same format used throughout the toolkit)\n// ---------------------------------------------------------------------------\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\n// ---------------------------------------------------------------------------\n// Encode / decode\n// ---------------------------------------------------------------------------\n\n/**\n * Encode a cursor for API transport (base64url).\n * Built from the last item in a result set.\n */\nexport function encodeCursor(\n item: Record<string, unknown>,\n sortField: string,\n direction: 'asc' | 'desc',\n): string {\n const payload: CursorInput = {\n field: sortField,\n value: item[sortField] as string | number,\n direction,\n id: item.id as string | undefined,\n }\n return btoa(JSON.stringify(payload))\n}\n\n/**\n * Decode and validate a cursor string from query params.\n * Returns null if the cursor is malformed, tampered, or invalid.\n *\n * Security: the `field` value is NOT validated here — the caller must\n * whitelist it against the entity's actual columns.\n */\nexport function decodeCursor(encoded: string): DecodedCursor | null {\n try {\n const json = atob(encoded)\n const parsed: unknown = JSON.parse(json)\n\n if (typeof parsed !== 'object' || parsed === null) return null\n const obj = parsed as Record<string, unknown>\n\n // Validate field\n if (typeof obj.field !== 'string' || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(obj.field)) {\n return null\n }\n\n // Validate value (string or number)\n if (typeof obj.value !== 'string' && typeof obj.value !== 'number') {\n return null\n }\n\n // Validate direction\n if (obj.direction !== 'asc' && obj.direction !== 'desc') {\n return null\n }\n\n // Validate id (optional, must be UUID if present)\n if (obj.id !== undefined) {\n if (typeof obj.id !== 'string' || !UUID_RE.test(obj.id)) {\n return null\n }\n }\n\n return {\n field: obj.field,\n value: obj.value,\n direction: obj.direction,\n id: obj.id as string | undefined,\n }\n } catch {\n return null\n }\n}\n\n// ---------------------------------------------------------------------------\n// SQL condition builder\n// ---------------------------------------------------------------------------\n\n/**\n * Build the keyset WHERE condition from a decoded cursor.\n *\n * For DESC: `WHERE (field < value) OR (field = value AND id < cursorId)`\n * For ASC: `WHERE (field > value) OR (field = value AND id > cursorId)`\n *\n * The caller must verify that `cursor.field` exists on the table before calling.\n *\n * @param table - Drizzle table with columns\n * @param cursor - Decoded and validated cursor\n * @returns SQL condition, or null if the field doesn't exist on the table\n */\nexport function buildCursorCondition(\n // biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any> for property access\n table: PgTableWithColumns<any>,\n cursor: DecodedCursor,\n): SQL | null {\n const column = table[cursor.field]\n if (!column) return null\n\n const isDesc = cursor.direction === 'desc'\n const compare = isDesc ? lt : gt\n\n // Primary condition: sort field passes the cursor value\n const fieldCondition = compare(column, cursor.value)\n\n // Without tie-breaker ID, use simple comparison\n if (!cursor.id) {\n return fieldCondition\n }\n\n // With tie-breaker: (field < value) OR (field = value AND id < cursorId)\n const idColumn = table.id\n if (!idColumn) return fieldCondition\n\n const idCondition = compare(idColumn, cursor.id)\n return or(fieldCondition, and(eq(column, cursor.value), idCondition))!\n}\n","/**\n * Entity definition API\n * The core function for defining entities with full type inference.\n *\n * The generic parameters are inferred from the call site:\n * - F is inferred from `definition.fields`\n * - B is inferred from `definition.behaviors` (as a tuple)\n *\n * The returned Entity carries the complete field map:\n * { id: IdField } & BehaviorFields & UserFields\n */\n\nimport type { EntityAdminConfig } from './admin-config.js'\nimport type { Behavior } from './behaviors/types.js'\nimport type { FieldConfig, IdField } from './fields/base.js'\nimport { field } from './fields/builders.js'\n\n/**\n * Entity definition input (without behaviors — behaviors are typed separately\n * on the `defineEntity` function to enable tuple inference).\n *\n * @typeParam F - The literal field map. Inferred automatically from the call site.\n */\nexport interface EntityDefinition<\n F extends Record<string, FieldConfig> = Record<string, FieldConfig>,\n> {\n name: string\n kind?: 'collection' | 'singleton'\n fields: F\n scope?: 'global' | 'team' | 'user'\n access?: {\n view?: string\n create?: string\n update?: string\n delete?: string\n }\n /** Admin UI configuration — controls sidebar, list, and form display */\n admin?: EntityAdminConfig\n}\n\n/**\n * Full input for defineEntity (fields + behaviors + other config).\n * Behaviors are typed as a tuple `B` for type extraction.\n */\nexport type EntityInput<\n F extends Record<string, FieldConfig> = Record<string, FieldConfig>,\n B extends Behavior[] = Behavior[],\n> = EntityDefinition<F> & { behaviors?: B }\n\n/**\n * A fully resolved entity with merged behavior fields.\n * @typeParam AllFields - The complete field map (id + behaviors + user fields).\n */\nexport interface Entity<\n AllFields extends Record<string, FieldConfig> = Record<string, FieldConfig>,\n> {\n name: string\n kind?: 'collection' | 'singleton'\n fields: Record<string, FieldConfig>\n behaviors?: Behavior[]\n scope?: 'global' | 'team' | 'user'\n access?: {\n view?: string\n create?: string\n update?: string\n delete?: string\n }\n /** Admin UI configuration — controls sidebar, list, and form display */\n admin?: EntityAdminConfig\n allFields: AllFields\n hooks: Behavior['hooks']\n}\n\n/**\n * Extract and intersect behavior field types from a behaviors tuple.\n * Walks the tuple recursively (depth bounded by number of behaviors, max ~5).\n */\ntype ExtractBehaviorFields<B extends Behavior[]> = B extends [\n Behavior<infer F1>,\n ...infer Rest extends Behavior[],\n]\n ? F1 & ExtractBehaviorFields<Rest>\n : // biome-ignore lint/complexity/noBannedTypes: empty object is correct for base case of intersection\n {}\n\n/**\n * Define an entity with full type inference.\n *\n * @example\n * const Article = defineEntity({\n * name: 'article',\n * fields: {\n * title: field.text({ required: true }),\n * featured: field.boolean(),\n * },\n * behaviors: [publishable(), auditable()],\n * })\n *\n * type ArticleDTO = InferEntity<typeof Article>\n * // { id: string; title: string; featured?: boolean | null; status?: ...; createdAt?: ...; }\n */\nexport function defineEntity<\n F extends Record<string, FieldConfig>,\n const B extends Behavior[] = [],\n>(\n definition: EntityDefinition<F> & { behaviors?: B },\n): Entity<{ id: IdField } & ExtractBehaviorFields<B> & F> {\n // Merge behavior fields into main fields\n const behaviorFields: Record<string, FieldConfig> = {}\n const hooks: NonNullable<Behavior['hooks']> = {}\n\n for (const behavior of definition.behaviors || []) {\n // Merge fields from behavior\n if (behavior.fields) {\n for (const [fieldName, fieldConfig] of Object.entries(behavior.fields)) {\n if (behaviorFields[fieldName]) {\n console.warn(\n `Field '${fieldName}' from behavior '${behavior.name}' conflicts with existing field. Skipping.`,\n )\n continue\n }\n behaviorFields[fieldName] = fieldConfig\n }\n }\n\n // Merge hooks from behavior\n if (behavior.hooks) {\n for (const [hookName, hookFn] of Object.entries(behavior.hooks)) {\n const typedHookName = hookName as keyof NonNullable<Behavior['hooks']>\n\n if (hookFn) {\n const existingHook = hooks[typedHookName]\n\n if (existingHook) {\n // Chain hooks: execute existing first, then new one\n const previousHook = existingHook as (...args: unknown[]) => Promise<unknown>\n const currentHook = hookFn as (...args: unknown[]) => Promise<unknown>\n\n const chainedHook = async (...args: unknown[]) => {\n const result = await previousHook(...args)\n if (result !== undefined) {\n // Hook returned modified data — replace last arg (the data param),\n // keep leading args (e.g. id) intact for hooks like beforeUpdate(id, data)\n const nextArgs = [...args]\n nextArgs[nextArgs.length - 1] = result\n return await currentHook(...nextArgs)\n }\n return await currentHook(...args)\n }\n // biome-ignore lint/suspicious/noExplicitAny: dynamic dispatch over heterogeneous hook signatures\n hooks[typedHookName] = chainedHook as any\n } else {\n // biome-ignore lint/suspicious/noExplicitAny: dynamic dispatch over heterogeneous hook signatures\n hooks[typedHookName] = hookFn as any\n }\n }\n }\n }\n }\n\n // Build complete field set\n // Order: id (always first) → behavior fields → user-defined fields\n const allFields: Record<string, FieldConfig> = {\n id: field.id(), // Every entity gets an ID\n ...behaviorFields,\n ...definition.fields,\n }\n\n // Auto-infer translatable slugs: if a slug field's source field is translatable,\n // the slug should be translatable too (each locale gets its own URL-safe slug).\n for (const [, fieldConfig] of Object.entries(allFields)) {\n if (\n fieldConfig.type === 'slug' &&\n !fieldConfig.translatable &&\n allFields[fieldConfig.from]?.translatable\n ) {\n fieldConfig.translatable = true\n }\n }\n\n // The cast through `unknown` is safe: runtime merge order (id + behaviors + user fields)\n // exactly mirrors the type-level intersection. TypeScript cannot verify imperative\n // Object.entries() loops produce the same result as a type-level intersection,\n // so we cast at this single auditable boundary.\n return {\n ...definition,\n allFields,\n hooks,\n } as unknown as Entity<{ id: IdField } & ExtractBehaviorFields<B> & F>\n}\n","/**\n * Error thrown when attempting to delete an entity that is still referenced.\n */\n\nimport type { EntityUsage } from './find-usages.js'\n\nexport class ReferencedEntityError extends Error {\n public readonly entityName: string\n public readonly entityId: string\n public readonly usages: EntityUsage[]\n\n constructor(entityName: string, entityId: string, usages: EntityUsage[]) {\n const count = usages.length\n super(\n `Cannot delete ${entityName} '${entityId}': referenced by ${count} other entit${count === 1 ? 'y' : 'ies'}`,\n )\n this.name = 'ReferencedEntityError'\n this.entityName = entityName\n this.entityId = entityId\n this.usages = usages\n }\n}\n","/**\n * Schema generator\n * Converts entity definitions to Drizzle schemas.\n * Every field gets its own Postgres column — no JSONB blobs.\n */\n\nimport type { PgColumnBuilderBase } from 'drizzle-orm/pg-core'\nimport {\n boolean,\n doublePrecision,\n index,\n integer,\n jsonb,\n pgTable,\n text,\n timestamp,\n unique,\n uuid,\n varchar,\n} from 'drizzle-orm/pg-core'\nimport type { Entity } from './define-entity.js'\nimport type { FieldConfig } from './fields/base.js'\n\n/**\n * Convert a field config to a Drizzle column.\n *\n * @param nullable - When true, skips notNull and default constraints.\n * Used for translation table columns (translations are partial overrides).\n */\nexport function fieldToColumn(\n fieldName: string,\n fieldConfig: FieldConfig,\n options?: { nullable?: boolean },\n): PgColumnBuilderBase {\n const nullable = options?.nullable ?? false\n\n switch (fieldConfig.type) {\n case 'id':\n return uuid(fieldName).primaryKey().defaultRandom()\n\n case 'text':\n if (fieldConfig.maxLength && fieldConfig.maxLength <= 255) {\n let column = varchar(fieldName, { length: fieldConfig.maxLength })\n if (fieldConfig.unique) column = column.unique()\n if (!nullable && fieldConfig.required) column = column.notNull()\n if (!nullable && fieldConfig.default !== undefined)\n column = column.default(fieldConfig.default as string)\n return column\n } else {\n let column = text(fieldName)\n if (fieldConfig.unique) column = column.unique()\n if (!nullable && fieldConfig.required) column = column.notNull()\n if (!nullable && fieldConfig.default !== undefined)\n column = column.default(fieldConfig.default as string)\n return column\n }\n\n case 'number': {\n let numColumn = fieldConfig.integer ? integer(fieldName) : doublePrecision(fieldName)\n if (!nullable && fieldConfig.required) numColumn = numColumn.notNull()\n if (!nullable && fieldConfig.default !== undefined)\n numColumn = numColumn.default(fieldConfig.default as number)\n return numColumn\n }\n\n case 'boolean': {\n let boolColumn = boolean(fieldName)\n if (!nullable && fieldConfig.required) boolColumn = boolColumn.notNull()\n if (!nullable) {\n const boolDefault =\n fieldConfig.default !== undefined ? (fieldConfig.default as boolean) : false\n boolColumn = boolColumn.default(boolDefault)\n }\n return boolColumn\n }\n\n case 'date': {\n let dateColumn = timestamp(fieldName, { withTimezone: true })\n if (!nullable && fieldConfig.required) dateColumn = dateColumn.notNull()\n if (!nullable && fieldConfig.default !== undefined)\n dateColumn = dateColumn.default(fieldConfig.default as Date)\n return dateColumn\n }\n\n case 'select': {\n let selectColumn = varchar(fieldName, { length: 100 })\n if (!nullable && fieldConfig.required) selectColumn = selectColumn.notNull()\n if (!nullable && fieldConfig.default !== undefined)\n selectColumn = selectColumn.default(fieldConfig.default as string)\n return selectColumn\n }\n\n case 'reference': {\n if (fieldConfig.cardinality === 'many') {\n return uuid(fieldName).array()\n }\n let refColumn = uuid(fieldName)\n if (!nullable && fieldConfig.required) refColumn = refColumn.notNull()\n return refColumn\n }\n\n case 'media': {\n let mediaColumn = uuid(fieldName)\n if (!nullable && fieldConfig.required) mediaColumn = mediaColumn.notNull()\n return mediaColumn\n }\n\n case 'slug': {\n let slugColumn = varchar(fieldName, { length: 255 })\n // For translation tables (nullable=true), skip per-column unique —\n // translatable slugs use composite UNIQUE(slug, locale) at table level instead.\n if (fieldConfig.unique && !nullable) slugColumn = slugColumn.unique()\n if (!nullable && fieldConfig.required) slugColumn = slugColumn.notNull()\n return slugColumn\n }\n\n case 'richtext': {\n // TipTap HTML strings stored as text\n let rtColumn = text(fieldName)\n if (!nullable && fieldConfig.required) rtColumn = rtColumn.notNull()\n return rtColumn\n }\n\n case 'json':\n return jsonb(fieldName)\n\n default:\n return text(fieldName)\n }\n}\n\n/**\n * Generate TypeScript code string for a single column.\n *\n * @param nullable - When true, skips notNull and default constraints.\n */\nfunction generateColumnCode(\n fieldName: string,\n fieldConfig: FieldConfig,\n options?: { nullable?: boolean },\n): string {\n const nullable = options?.nullable ?? false\n\n switch (fieldConfig.type) {\n case 'id':\n return `id: uuid('id').primaryKey().defaultRandom()`\n\n case 'text': {\n const maxLen = fieldConfig.maxLength || 255\n let textCol =\n maxLen <= 255 ? `varchar('${fieldName}', { length: ${maxLen} })` : `text('${fieldName}')`\n if (fieldConfig.unique) textCol += `.unique()`\n if (!nullable && fieldConfig.required) textCol += `.notNull()`\n if (!nullable && fieldConfig.default) textCol += `.default('${fieldConfig.default}')`\n return `${fieldName}: ${textCol}`\n }\n\n case 'number': {\n let numCol = fieldConfig.integer\n ? `integer('${fieldName}')`\n : `doublePrecision('${fieldName}')`\n if (!nullable && fieldConfig.required) numCol += `.notNull()`\n if (!nullable && fieldConfig.default !== undefined)\n numCol += `.default(${fieldConfig.default})`\n return `${fieldName}: ${numCol}`\n }\n\n case 'boolean': {\n if (nullable) {\n return `${fieldName}: boolean('${fieldName}')`\n }\n const boolDefault = fieldConfig.default ?? false\n return `${fieldName}: boolean('${fieldName}').default(${boolDefault})`\n }\n\n case 'date': {\n let dateCol = `timestamp('${fieldName}', { withTimezone: true })`\n if (!nullable && fieldConfig.required) dateCol += `.notNull()`\n return `${fieldName}: ${dateCol}`\n }\n\n case 'reference': {\n if (fieldConfig.cardinality === 'many') {\n return `${fieldName}: uuid('${fieldName}').array()`\n }\n let refCol = `uuid('${fieldName}')`\n if (!nullable && fieldConfig.required) refCol += `.notNull()`\n return `${fieldName}: ${refCol}`\n }\n\n case 'media': {\n let mediaCol = `uuid('${fieldName}')`\n if (!nullable && fieldConfig.required) mediaCol += `.notNull()`\n return `${fieldName}: ${mediaCol}`\n }\n\n case 'slug': {\n let slugCol = `varchar('${fieldName}', { length: 255 })`\n if (fieldConfig.unique && !nullable) slugCol += `.unique()`\n if (!nullable && fieldConfig.required) slugCol += `.notNull()`\n return `${fieldName}: ${slugCol}`\n }\n\n case 'select': {\n let selectCol = `varchar('${fieldName}', { length: 100 })`\n if (!nullable && fieldConfig.required) selectCol += `.notNull()`\n if (!nullable && fieldConfig.default) selectCol += `.default('${fieldConfig.default}')`\n return `${fieldName}: ${selectCol}`\n }\n\n case 'richtext': {\n let rtCol = `text('${fieldName}')`\n if (!nullable && fieldConfig.required) rtCol += `.notNull()`\n return `${fieldName}: ${rtCol}`\n }\n\n case 'json':\n return `${fieldName}: jsonb('${fieldName}')`\n\n default:\n return `${fieldName}: text('${fieldName}')`\n }\n}\n\n/**\n * Generate a runtime Drizzle schema from an entity definition.\n * Every field becomes its own column — no JSONB.\n */\nexport function generateSchema(entity: Entity) {\n const tableName = entity.name\n const columns: Record<string, PgColumnBuilderBase> = {}\n\n for (const [fieldName, fieldConfig] of Object.entries(entity.allFields)) {\n if (fieldConfig.type === 'blocks') continue // stored in layout table\n columns[fieldName] = fieldToColumn(fieldName, fieldConfig)\n }\n\n if (entity.scope === 'team' || entity.scope === 'user') {\n columns._scopeId = uuid('_scope_id')\n }\n\n // Collect fields that need indexes (skip id/unique — they already have btree indexes)\n const indexedFields = Object.entries(entity.allFields).filter(\n ([_, config]) =>\n config.type !== 'blocks' && config.type !== 'id' && config.indexed && !config.unique,\n )\n\n const isPublishableEntity = entity.behaviors?.some((b) => b.name === 'publishable') ?? false\n\n if (indexedFields.length === 0 && !isPublishableEntity) {\n return pgTable(tableName, columns)\n }\n\n return pgTable(tableName, columns, (table) => {\n // biome-ignore lint/suspicious/noExplicitAny: dynamic table columns from pgTable callback\n const constraints: Record<string, any> = {}\n\n for (const [fieldName] of indexedFields) {\n constraints[`idx_${tableName}_${fieldName}`] = index(`idx_${tableName}_${fieldName}`).on(\n table[fieldName],\n )\n }\n\n // Composite index for publishable entities: status + createdAt (most common list query)\n if (isPublishableEntity && table.status && table.createdAt) {\n constraints[`idx_${tableName}_status_created`] = index(`idx_${tableName}_status_created`).on(\n table.status,\n table.createdAt,\n )\n }\n\n return constraints\n })\n}\n\n/**\n * Generate TypeScript code string for an entity schema.\n * Every field becomes its own column — no JSONB.\n */\nexport function generateSchemaCode(entity: Entity): string {\n const tableName = entity.name\n const lines: string[] = []\n\n // Collect fields that need indexes (skip id/unique — they already have btree indexes)\n const indexedFields = Object.entries(entity.allFields).filter(\n ([_, config]) =>\n config.type !== 'blocks' && config.type !== 'id' && config.indexed && !config.unique,\n )\n\n const isPublishableEntity = entity.behaviors?.some((b) => b.name === 'publishable') ?? false\n const needsIndexes = indexedFields.length > 0 || isPublishableEntity\n\n lines.push(`export const ${tableName} = pgTable('${tableName}', {`)\n\n for (const [fieldName, fieldConfig] of Object.entries(entity.allFields)) {\n if (fieldConfig.type === 'blocks') continue // stored in layout table\n const columnCode = generateColumnCode(fieldName, fieldConfig)\n lines.push(` ${columnCode},`)\n }\n\n if (entity.scope === 'team' || entity.scope === 'user') {\n lines.push(` _scopeId: uuid('_scope_id'),`)\n }\n\n if (needsIndexes) {\n lines.push(`}, (table) => ({`)\n\n for (const [fieldName] of indexedFields) {\n lines.push(\n ` idx_${tableName}_${fieldName}: index('idx_${tableName}_${fieldName}').on(table.${fieldName}),`,\n )\n }\n\n // Composite index for publishable entities: status + createdAt\n if (isPublishableEntity) {\n lines.push(\n ` idx_${tableName}_status_created: index('idx_${tableName}_status_created').on(table.status, table.createdAt),`,\n )\n }\n\n lines.push(`}))`)\n } else {\n lines.push(`)`)\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Generate runtime translation table schema.\n * Each translatable field gets its own nullable column.\n */\nexport function generateTranslationSchema(entity: Entity) {\n const translatableFields = Object.entries(entity.allFields).filter(\n ([_, config]) => config.translatable,\n )\n\n if (translatableFields.length === 0) return null\n\n const tableName = `${entity.name}_translations`\n const columns: Record<string, PgColumnBuilderBase> = {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id').notNull(),\n locale: varchar('locale', { length: 10 }).notNull(),\n }\n\n const hasTranslatableSlug = translatableFields.some(([_, config]) => config.type === 'slug')\n\n for (const [fieldName, fieldConfig] of translatableFields) {\n columns[fieldName] = fieldToColumn(fieldName, fieldConfig, { nullable: true })\n }\n\n return pgTable(tableName, columns, (table) => ({\n uniqueEntityLocale: unique().on(table.entityId, table.locale),\n // Per-locale slug uniqueness: no two entities can share the same slug in the same locale\n ...(hasTranslatableSlug && table.slug\n ? { uniqueSlugLocale: unique().on(table.slug, table.locale) }\n : {}),\n }))\n}\n\n/**\n * Generate TypeScript code string for a translation table.\n * Each translatable field gets its own nullable column.\n */\nexport function generateTranslationSchemaCode(entity: Entity): string | null {\n const translatableFields = Object.entries(entity.allFields).filter(\n ([_, config]) => config.translatable,\n )\n\n if (translatableFields.length === 0) return null\n\n const tableName = `${entity.name}_translations`\n const entityTableName = entity.name\n const lines: string[] = []\n\n lines.push(`export const ${tableName} = pgTable('${tableName}', {`)\n lines.push(` id: uuid('id').primaryKey().defaultRandom(),`)\n lines.push(\n ` entityId: uuid('entity_id').notNull().references(() => ${entityTableName}.id, { onDelete: 'cascade' }),`,\n )\n lines.push(` locale: varchar('locale', { length: 10 }).notNull(),`)\n\n for (const [fieldName, fieldConfig] of translatableFields) {\n const columnCode = generateColumnCode(fieldName, fieldConfig, { nullable: true })\n lines.push(` ${columnCode},`)\n }\n\n const hasTranslatableSlug = translatableFields.some(([_, config]) => config.type === 'slug')\n\n lines.push(`}, (table) => ({`)\n lines.push(\n ` uniqueEntityLocale: unique().on(table.entityId, table.locale), // One translation per locale`,\n )\n if (hasTranslatableSlug) {\n lines.push(\n ` uniqueSlugLocale: unique().on(table.slug, table.locale), // Per-locale slug uniqueness`,\n )\n }\n lines.push(`}))`)\n\n return lines.join('\\n')\n}\n\n/**\n * Check if an entity has any blocks fields\n */\nexport function hasBlocksFields(entity: Entity): boolean {\n return Object.values(entity.allFields).some((f) => f.type === 'blocks')\n}\n\n/**\n * Generate layout table schema for entities with blocks fields.\n * Stores block instances in a separate table with ordering.\n *\n * When blocks field has localized: false (default), locale is NULL (shared layout).\n * When blocks field has localized: true, locale is set per-locale.\n */\nexport function generateLayoutSchema(entity: Entity) {\n if (!hasBlocksFields(entity)) return null\n\n const tableName = `${entity.name}_layout`\n\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id').notNull(),\n fieldName: varchar('field_name', { length: 100 }).notNull(),\n blockType: varchar('block_type', { length: 100 }).notNull(),\n sortOrder: integer('sort_order').notNull().default(0),\n data: jsonb('data'),\n locale: varchar('locale', { length: 10 }),\n },\n (table) => ({\n // Covers: WHERE entity_id IN (...) AND locale IS NULL ORDER BY sort_order\n idx_entity_locale_sort: index(`idx_${tableName}_entity_locale_sort`).on(\n table.entityId,\n table.locale,\n table.sortOrder,\n ),\n }),\n )\n}\n\n/**\n * Check if an entity has the versionable behavior\n */\nexport function isVersionable(entity: Entity): boolean {\n return entity.behaviors?.some((b) => b.name === 'versionable') ?? false\n}\n\n/**\n * Check if an entity needs a per-locale publish status table.\n * Requires both publishable() behavior AND at least one translatable field.\n */\nexport function needsLocaleStatus(entity: Entity): boolean {\n const isPublishable = entity.behaviors?.some((b) => b.name === 'publishable') ?? false\n if (!isPublishable) return false\n const allFields = entity.allFields as Record<string, { translatable?: boolean }>\n return Object.values(allFields).some((f) => f.translatable)\n}\n\n/**\n * Generate TypeScript code string for a per-locale publish status table.\n */\nexport function generateLocaleStatusCode(entity: Entity): string {\n const name = entity.name\n const varName = `${name}_locale_status`\n return `export const ${varName} = pgTable('${varName}', {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id').notNull().references(() => ${name}.id, { onDelete: 'cascade' }),\n locale: varchar('locale', { length: 10 }).notNull(),\n status: varchar('status', { length: 20 }).notNull().default('draft'),\n publishedAt: timestamp('published_at', { withTimezone: true }),\n}, (table) => ({\n uniqueEntityLocale: unique().on(table.entityId, table.locale),\n}))`\n}\n\n/**\n * Check if an entity is publishable (has publishable() behavior).\n */\nexport function isPublishable(entity: Entity): boolean {\n return entity.behaviors?.some((b) => b.name === 'publishable') ?? false\n}\n\n/**\n * Generate TypeScript code string for a drafts overlay table.\n */\nexport function generateDraftsCode(entity: Entity): string {\n const name = entity.name\n const varName = `${name}_drafts`\n return `export const ${varName} = pgTable('${varName}', {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id').notNull().references(() => ${name}.id, { onDelete: 'cascade' }),\n locale: varchar('locale', { length: 10 }).notNull().default('_'),\n data: jsonb('data').notNull(),\n createdBy: varchar('created_by', { length: 255 }).notNull(),\n createdByName: varchar('created_by_name', { length: 255 }),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n uniqueEntityLocale: unique().on(table.entityId, table.locale),\n}))`\n}\n\n/**\n * Generate TypeScript code string for the shared content locks table.\n */\nexport function generateContentLocksCode(): string {\n return `export const toolkit_content_locks = pgTable('toolkit_content_locks', {\n id: uuid('id').primaryKey().defaultRandom(),\n entityType: varchar('entity_type', { length: 100 }).notNull(),\n entityId: varchar('entity_id', { length: 255 }).notNull(),\n locale: varchar('locale', { length: 10 }).notNull().default('_'),\n lockedBy: varchar('locked_by', { length: 255 }).notNull(),\n lockedByName: varchar('locked_by_name', { length: 255 }),\n lockedAt: timestamp('locked_at', { withTimezone: true }).notNull().defaultNow(),\n expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),\n}, (table) => ({\n uniqueEntityLock: unique().on(table.entityType, table.entityId, table.locale),\n}))`\n}\n\n/**\n * Check if an entity has blocks fields with translatable block content (non-localized mode).\n */\nexport function hasTranslatableBlocks(entity: Entity): boolean {\n const allFields = entity.allFields as Record<\n string,\n {\n type: string\n localized?: boolean\n blocks?: Array<{ fields: Record<string, { translatable?: boolean }> }>\n }\n >\n return Object.values(allFields).some((f) => {\n if (f.type !== 'blocks') return false\n if (f.localized) return false\n return f.blocks?.some((block) => Object.values(block.fields).some((bf) => bf.translatable))\n })\n}\n\n/**\n * Generate TypeScript code string for a layout table (blocks storage).\n */\nexport function generateLayoutCode(entity: Entity): string {\n const name = entity.name\n const varName = `${name}_layout`\n return `export const ${varName} = pgTable('${varName}', {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id').notNull().references(() => ${name}.id, { onDelete: 'cascade' }),\n fieldName: varchar('field_name', { length: 100 }).notNull(),\n blockType: varchar('block_type', { length: 100 }).notNull(),\n sortOrder: integer('sort_order').notNull().default(0),\n data: jsonb('data'),\n locale: varchar('locale', { length: 10 }),\n}, (table) => ({\n idx_entity_locale_sort: index('idx_${varName}_entity_locale_sort').on(table.entityId, table.locale, table.sortOrder),\n}))`\n}\n\n/**\n * Generate TypeScript code string for a layout translation table.\n */\nexport function generateLayoutTranslationCode(entity: Entity): string {\n const name = entity.name\n const varName = `${name}_layout_translations`\n const layoutVar = `${name}_layout`\n return `export const ${varName} = pgTable('${varName}', {\n id: uuid('id').primaryKey().defaultRandom(),\n layoutId: uuid('layout_id').notNull().references(() => ${layoutVar}.id, { onDelete: 'cascade' }),\n locale: varchar('locale', { length: 10 }).notNull(),\n fields: jsonb('fields').notNull(),\n}, (table) => ({\n uniqueLayoutLocale: unique().on(table.layoutId, table.locale),\n}))`\n}\n\n/**\n * Generate TypeScript code string for a versions table.\n */\nexport function generateVersionsCode(entity: Entity): string {\n const name = entity.name\n const varName = `${name}_versions`\n return `export const ${varName} = pgTable('${varName}', {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id').notNull().references(() => ${name}.id, { onDelete: 'cascade' }),\n version: integer('version').notNull(),\n locale: varchar('locale', { length: 10 }).notNull().default('_'),\n data: jsonb('data').notNull(),\n delta: jsonb('delta'),\n status: varchar('status', { length: 20 }),\n createdBy: varchar('created_by', { length: 255 }),\n createdByName: varchar('created_by_name', { length: 255 }),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n isAutosave: boolean('is_autosave').notNull().default(false),\n}, (table) => ({\n uniqueEntityVersionLocale: unique().on(table.entityId, table.version, table.locale),\n}))`\n}\n\n/**\n * Generate layout translation table for non-localized blocks with translatable fields.\n */\nexport function generateLayoutTranslationSchema(entity: Entity) {\n const blocksFields = Object.values(entity.allFields).filter(\n (f) => f.type === 'blocks' && !('localized' in f && f.localized),\n )\n\n if (blocksFields.length === 0) return null\n\n const hasTranslatableBlockFields = blocksFields.some((bf) => {\n if (bf.type !== 'blocks') return false\n return bf.blocks.some((block) => Object.values(block.fields).some((f) => f.translatable))\n })\n\n if (!hasTranslatableBlockFields) return null\n\n const tableName = `${entity.name}_layout_translations`\n\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n layoutId: uuid('layout_id').notNull(),\n locale: varchar('locale', { length: 10 }).notNull(),\n fields: jsonb('fields').notNull(),\n },\n (table) => ({\n uniqueLayoutLocale: unique().on(table.layoutId, table.locale),\n }),\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/fields/builders.ts","../src/behaviors/auditable.ts","../src/behaviors/hierarchical.ts","../src/behaviors/publishable.ts","../src/behaviors/revisionable.ts","../src/behaviors/sluggable.ts","../src/behaviors/timestamped.ts","../src/behaviors/index.ts","../src/count-cache.ts","../src/count-estimate.ts","../src/cursor.ts","../src/define-entity.ts","../src/refs/errors.ts","../src/schema-generator.ts"],"sourcesContent":["/**\n * Fluent API for building field definitions\n * Provides type-safe field builders with sensible defaults.\n *\n * Each builder uses a `const` generic parameter on the config to preserve\n * literal types (e.g., `required: true` stays `true`, not `boolean`).\n * This enables compile-time type inference in the entity system.\n */\n\nimport type {\n BlockDefinitionRef,\n BlocksField,\n BooleanField,\n DateField,\n IdField,\n JsonField,\n MediaField,\n NumberField,\n ReferenceField,\n RichTextField,\n SelectField,\n SlugField,\n TextField,\n} from './base.js'\n\nexport const field = {\n /**\n * ID field (auto-added to every entity)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n id: <const C extends Partial<IdField> = {}>(config?: C): IdField & C =>\n ({\n type: 'id',\n required: true,\n indexed: true,\n ...config,\n }) as IdField & C,\n\n /**\n * Text field\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n text: <const C extends Partial<TextField> = {}>(config?: C): TextField & C =>\n ({\n type: 'text',\n ...config,\n }) as TextField & C,\n\n /**\n * Number field\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n number: <const C extends Partial<NumberField> = {}>(config?: C): NumberField & C =>\n ({\n type: 'number',\n ...config,\n }) as NumberField & C,\n\n /**\n * Boolean field\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n boolean: <const C extends Partial<BooleanField> = {}>(config?: C): BooleanField & C =>\n ({\n type: 'boolean',\n default: false,\n ...config,\n }) as BooleanField & C,\n\n /**\n * Date field\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n date: <const C extends Partial<DateField> = {}>(config?: C): DateField & C =>\n ({\n type: 'date',\n ...config,\n }) as DateField & C,\n\n /**\n * Select field (enum)\n * Preserves literal option types for type inference.\n * e.g. field.select({ options: ['news', 'tutorial'] }) infers 'news' | 'tutorial'\n */\n select: <\n const O extends readonly string[],\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n const C extends Omit<Partial<SelectField>, 'options'> = {},\n >(\n config: { options: O } & C,\n ): SelectField & { options: O } & C =>\n ({\n type: 'select',\n ...config,\n }) as SelectField & { options: O } & C,\n\n /**\n * Reference field (foreign key to another entity)\n * Preserves literal cardinality to distinguish string vs string[] in inferred types.\n */\n reference: <\n C extends 'one' | 'many' = 'one',\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n const R extends Partial<Omit<ReferenceField, 'entity' | 'cardinality'>> = {},\n >(\n config: { entity: string; cardinality?: C } & R,\n ): ReferenceField & { cardinality: C } & R =>\n ({\n type: 'reference',\n cardinality: 'one' as C,\n onDelete: 'set-null',\n ...config,\n }) as ReferenceField & { cardinality: C } & R,\n\n /**\n * Media field (file upload)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n media: <const C extends Partial<MediaField> = {}>(config?: C): MediaField & C =>\n ({\n type: 'media',\n ...config,\n }) as MediaField & C,\n\n /**\n * Rich text field (WYSIWYG editor)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n richtext: <const C extends Partial<RichTextField> = {}>(config?: C): RichTextField & C =>\n ({\n type: 'richtext',\n ...config,\n }) as RichTextField & C,\n\n /**\n * Slug field (URL-safe string, auto-generated from source field)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n slug: <const C extends Partial<SlugField> = {}>(\n config: Pick<SlugField, 'from'> & C,\n ): SlugField & C =>\n ({\n type: 'slug',\n unique: true,\n indexed: true,\n ...config,\n }) as SlugField & C,\n\n /**\n * JSON field (arbitrary JSON data, stored as JSONB)\n */\n // biome-ignore lint/complexity/noBannedTypes: {} is correct as empty generic default\n json: <const C extends Partial<JsonField> = {}>(config?: C): JsonField & C =>\n ({\n type: 'json',\n ...config,\n }) as JsonField & C,\n\n /**\n * Blocks field — ordered array of typed content blocks\n * Preserves literal block tuple type for discriminated union inference.\n */\n blocks: <const B extends readonly BlockDefinitionRef[]>(config: {\n blocks: B\n min?: number\n max?: number\n localized?: boolean\n }): BlocksField & { blocks: B } =>\n ({\n type: 'blocks' as const,\n ...config,\n }) as BlocksField & { blocks: B },\n}\n","/**\n * Auditable behavior\n * Adds createdBy, updatedBy, createdAt, updatedAt fields + auto-set logic.\n *\n * When @murumets-ee/core is available (i.e. running inside createApp), the hooks\n * automatically populate createdBy/updatedBy from the RequestContext.\n */\n\nimport { field } from '../fields/builders.js'\nimport type { AuditableFields } from '../types/infer.js'\nimport type { Behavior } from './types.js'\n\n/**\n * Try to get the current user ID from @murumets-ee/core's RequestContext.\n * Returns undefined if @murumets-ee/core isn't loaded or no user is in context.\n *\n * Uses a dynamic import string indirection to avoid TypeScript resolving\n * @murumets-ee/core at compile time (which would create a circular dep: entity → core → entity).\n */\nasync function getCurrentUserId(): Promise<string | undefined> {\n try {\n // Module name built at runtime so TypeScript doesn't try to resolve it during DTS emit\n const moduleName = ['@murumets-ee', 'core'].join('/')\n const core = await (import(moduleName) as Promise<{\n getCurrentUser: () => { id: string } | undefined\n }>)\n return core.getCurrentUser()?.id\n } catch {\n return undefined\n }\n}\n\nexport function auditable(): Behavior<AuditableFields> {\n return {\n name: 'auditable',\n fields: {\n createdBy: field.text(),\n updatedBy: field.text(),\n createdAt: field.date({ indexed: true }),\n updatedAt: field.date({ indexed: true }),\n },\n hooks: {\n beforeCreate: async (data) => {\n data.createdAt = new Date()\n data.updatedAt = new Date()\n const userId = await getCurrentUserId()\n if (userId) {\n data.createdBy = userId\n data.updatedBy = userId\n }\n return data\n },\n beforeUpdate: async (_id, data) => {\n data.updatedAt = new Date()\n const userId = await getCurrentUserId()\n if (userId) {\n data.updatedBy = userId\n }\n return data\n },\n },\n }\n}\n","/**\n * Hierarchical behavior\n * Adds parentId, path, depth fields for materialized path tree structures.\n *\n * Path format: /uuid1/uuid2/uuid3\n * - Root items: /{ownId}\n * - Children: /{rootId}/.../{parentId}/{ownId}\n *\n * The behavior declares fields and sets safe defaults in hooks.\n * Heavy lifting (path computation, circular ref validation, descendant cascading)\n * is done by TaxonomyClient which has DB access.\n */\n\nimport { field } from '../fields/builders.js'\nimport type { HierarchicalFields } from '../types/infer.js'\nimport type { Behavior } from './types.js'\n\nexport interface HierarchicalOptions {\n /**\n * What happens when deleting a node that has children.\n * - 'restrict': Prevent deletion (default)\n * - 'cascade': Delete all descendants\n * - 'reparent': Move children to the deleted node's parent\n */\n onDelete?: 'restrict' | 'cascade' | 'reparent'\n}\n\nexport function hierarchical(_options?: HierarchicalOptions): Behavior<HierarchicalFields> {\n return {\n name: 'hierarchical',\n fields: {\n parentId: field.reference({ entity: '_self', required: false }),\n path: field.text({ indexed: true, maxLength: 2048 }),\n depth: field.number({ integer: true, default: 0, indexed: true }),\n },\n hooks: {\n beforeCreate: async (data) => {\n // Set defaults for root-level items.\n // TaxonomyClient overrides these after insert when it has the ID.\n if (!data.parentId) {\n data.depth = 0\n }\n return data\n },\n },\n }\n}\n","/**\n * Publishable behavior\n * Adds status (draft/published) and publishedAt fields + auto-set logic\n */\n\nimport { field } from '../fields/builders.js'\nimport type { PublishableFields } from '../types/infer.js'\nimport type { Behavior } from './types.js'\n\nexport function publishable(): Behavior<PublishableFields> {\n return {\n name: 'publishable',\n fields: {\n status: field.select({\n options: ['draft', 'published'] as const,\n default: 'draft',\n indexed: true,\n }),\n publishedAt: field.date({ indexed: true }),\n },\n hooks: {\n beforeUpdate: async (_id, data) => {\n // Auto-set publishedAt when status changes to published (if not already set)\n if (data.status === 'published' && !data.publishedAt) {\n data.publishedAt = new Date()\n }\n return data\n },\n },\n }\n}\n","/**\n * Revisionable behavior\n * Adds _version field + auto-increment on update\n */\n\nimport { field } from '../fields/builders.js'\nimport type { RevisionableFields } from '../types/infer.js'\nimport type { Behavior } from './types.js'\n\nexport function revisionable(): Behavior<RevisionableFields> {\n return {\n name: 'revisionable',\n fields: {\n _version: field.number({ required: true, default: 1, integer: true }),\n },\n hooks: {\n beforeCreate: async (data) => {\n data._version = 1\n return data\n },\n beforeUpdate: async (_id, data) => {\n // Increment version on every update\n data._version = (Number(data._version) || 0) + 1\n return data\n },\n },\n }\n}\n","/**\n * Sluggable behavior\n * Adds slug field + auto-generation from source field\n */\n\nimport { field } from '../fields/builders.js'\nimport type { SluggableFields } from '../types/infer.js'\nimport type { Behavior } from './types.js'\n\nexport interface SluggableOptions {\n /** Make the slug translatable — each locale gets its own slug. Default: false */\n translatable?: boolean\n}\n\n/**\n * Convert text to URL-safe slug\n */\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '') // Remove non-word chars except spaces and hyphens\n .replace(/[\\s_-]+/g, '-') // Replace spaces, underscores with single hyphen\n .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens\n}\n\nexport function sluggable(\n sourceField: string,\n options?: SluggableOptions,\n): Behavior<SluggableFields> {\n return {\n name: 'sluggable',\n fields: {\n slug: field.slug({\n from: sourceField,\n ...(options?.translatable ? { translatable: true } : {}),\n }),\n },\n hooks: {\n beforeCreate: async (data) => {\n // Auto-generate slug from source field if not provided\n if (!data.slug && data[sourceField]) {\n data.slug = slugify(String(data[sourceField]))\n }\n return data\n },\n beforeUpdate: async (_id, data) => {\n // Re-generate slug if source field changed and slug not manually set\n if (data[sourceField] && !data.slug) {\n data.slug = slugify(String(data[sourceField]))\n }\n return data\n },\n },\n }\n}\n","/**\n * Timestamped behavior\n * Adds createdAt and updatedAt fields with auto-set logic.\n * Lighter alternative to auditable() when you don't need createdBy/updatedBy tracking.\n */\n\nimport { field } from '../fields/builders.js'\nimport type { Behavior } from './types.js'\n\nexport type TimestampedFields = {\n createdAt: ReturnType<typeof field.date>\n updatedAt: ReturnType<typeof field.date>\n}\n\nexport function timestamped(): Behavior<TimestampedFields> {\n return {\n name: 'timestamped',\n fields: {\n createdAt: field.date({ indexed: true }),\n updatedAt: field.date({ indexed: true }),\n },\n hooks: {\n beforeCreate: async (data) => {\n data.createdAt = new Date()\n data.updatedAt = new Date()\n return data\n },\n beforeUpdate: async (_id, data) => {\n data.updatedAt = new Date()\n return data\n },\n },\n }\n}\n","/**\n * Behavior exports\n * All behaviors are exported under the `behavior` namespace\n */\n\nimport { auditable } from './auditable.js'\nimport { hierarchical } from './hierarchical.js'\nimport { publishable } from './publishable.js'\nimport { revisionable } from './revisionable.js'\nimport { sluggable } from './sluggable.js'\nimport { timestamped } from './timestamped.js'\n\nexport { publishable, auditable, sluggable, revisionable, hierarchical, timestamped }\nexport type { SluggableOptions } from './sluggable.js'\nexport { slugify } from './sluggable.js'\nexport type { Behavior, BehaviorFactory } from './types.js'\n\n/**\n * Behavior namespace for fluent API\n */\nexport const behavior = {\n publishable,\n auditable,\n sluggable,\n revisionable,\n hierarchical,\n timestamped,\n}\n","/**\n * In-memory TTL cache for COUNT(*) query results.\n *\n * Reduces database load on paginated list pages where the total count\n * is recalculated on every pagination/sort/search interaction. The cache\n * is per-process (not shared across workers) with a short TTL (default 5s)\n * so counts are at most a few seconds stale.\n *\n * Cache keys include the entity name + serialized WHERE clause, so filtered\n * and unfiltered counts are cached independently.\n */\n\n/**\n * Interface for count cache implementations.\n * Used in client configs to avoid TypeScript private-field structural incompatibility\n * across separate .d.ts files.\n */\nexport interface CountCacheLike {\n get(key: string): number | undefined\n set(key: string, count: number): void\n invalidate(prefix: string): void\n}\n\ninterface CountCacheEntry {\n count: number\n expiresAt: number\n}\n\nexport class CountCache implements CountCacheLike {\n private cache = new Map<string, CountCacheEntry>()\n private ttlMs: number\n\n constructor(ttlMs = 5000) {\n this.ttlMs = ttlMs\n }\n\n /**\n * Get a cached count. Returns undefined on miss or expired entry.\n */\n get(key: string): number | undefined {\n const entry = this.cache.get(key)\n if (!entry || Date.now() > entry.expiresAt) {\n if (entry) this.cache.delete(key)\n return undefined\n }\n return entry.count\n }\n\n /**\n * Cache a count result with the configured TTL.\n */\n set(key: string, count: number): void {\n this.cache.set(key, { count, expiresAt: Date.now() + this.ttlMs })\n }\n\n /**\n * Invalidate all cache entries whose key starts with the given prefix.\n * Typically called with the entity name after mutations (create/update/delete).\n */\n invalidate(prefix: string): void {\n for (const key of this.cache.keys()) {\n if (key.startsWith(prefix)) {\n this.cache.delete(key)\n }\n }\n }\n\n /**\n * Remove all expired entries. Useful for periodic cleanup in long-running processes.\n */\n prune(): void {\n const now = Date.now()\n for (const [key, entry] of this.cache.entries()) {\n if (now > entry.expiresAt) {\n this.cache.delete(key)\n }\n }\n }\n\n /**\n * Clear the entire cache.\n */\n clear(): void {\n this.cache.clear()\n }\n\n /**\n * Number of entries currently in the cache (including expired ones not yet pruned).\n */\n get size(): number {\n return this.cache.size\n }\n}\n","/**\n * Postgres row count estimation using pg_class statistics.\n *\n * For unfiltered counts on large tables, querying `pg_class.reltuples`\n * is effectively instant (no table scan) and returns a good approximation\n * that is updated by VACUUM and ANALYZE. This is suitable for pagination\n * totals where exact precision is not critical.\n *\n * SECURITY: The table name is NOT interpolated into SQL — it is passed as a\n * parameterized value to the `relname = $1` comparison. This prevents SQL injection.\n */\n\nimport { sql } from 'drizzle-orm'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\n\n/**\n * Estimate the total row count for a table using Postgres statistics.\n *\n * Returns the approximate row count from `pg_class.reltuples`, which is\n * updated by VACUUM/ANALYZE. Returns 0 if the table is not found or\n * statistics are not yet available (e.g., freshly created table).\n *\n * @param db - Drizzle Postgres database instance\n * @param tableName - The Postgres table name (without schema prefix)\n * @returns Estimated row count (non-negative integer)\n */\nexport async function estimateRowCount(db: PostgresJsDatabase, tableName: string): Promise<number> {\n const result = await db.execute(\n sql`SELECT reltuples::bigint AS estimate FROM pg_class WHERE relname = ${tableName}`,\n )\n\n const rows = Array.isArray(result) ? result : ((result as { rows?: unknown[] }).rows ?? [])\n const row = rows[0] as { estimate?: string | number } | undefined\n const estimate = Number(row?.estimate ?? 0)\n\n // reltuples can be -1 for tables that have never been analyzed\n return estimate > 0 ? estimate : 0\n}\n","/**\n * Cursor-based (keyset) pagination utilities.\n *\n * Cursor pagination avoids the performance cliff of OFFSET at scale (1M+ rows).\n * Instead of `OFFSET N`, it uses a WHERE condition:\n * `WHERE (sortField < lastValue) OR (sortField = lastValue AND id < lastId)`\n * which Postgres can serve from an index in constant time.\n *\n * The cursor is opaque to the client — base64-encoded JSON.\n *\n * Security:\n * - `field` must be whitelisted against the entity's actual fields\n * - `id` must be a valid UUID\n * - `value` is parameterized (never interpolated into SQL)\n * - Malformed cursors return null (caller returns 400)\n */\n\nimport { and, eq, gt, lt, or, type SQL } from 'drizzle-orm'\nimport type { PgTableWithColumns } from 'drizzle-orm/pg-core'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Cursor input for keyset pagination. */\nexport interface CursorInput {\n /** Sort field name (e.g. 'createdAt'). Must be a real column on the entity. */\n field: string\n /** Last seen value of the sort field. */\n value: string | number\n /** Sort direction — must match the ORDER BY direction. */\n direction: 'asc' | 'desc'\n /** Tie-breaker: last seen entity ID. Required for non-unique sort fields. */\n id?: string\n}\n\n/** Decoded cursor (internal, after validation). */\ninterface DecodedCursor {\n field: string\n value: string | number\n direction: 'asc' | 'desc'\n id?: string\n}\n\n// ---------------------------------------------------------------------------\n// UUID validation (same format used throughout the toolkit)\n// ---------------------------------------------------------------------------\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\n// ---------------------------------------------------------------------------\n// Encode / decode\n// ---------------------------------------------------------------------------\n\n/**\n * Encode a cursor for API transport (base64url).\n * Built from the last item in a result set.\n */\nexport function encodeCursor(\n item: Record<string, unknown>,\n sortField: string,\n direction: 'asc' | 'desc',\n): string {\n const payload: CursorInput = {\n field: sortField,\n value: item[sortField] as string | number,\n direction,\n id: item.id as string | undefined,\n }\n return btoa(JSON.stringify(payload))\n}\n\n/**\n * Decode and validate a cursor string from query params.\n * Returns null if the cursor is malformed, tampered, or invalid.\n *\n * Security: the `field` value is NOT validated here — the caller must\n * whitelist it against the entity's actual columns.\n */\nexport function decodeCursor(encoded: string): DecodedCursor | null {\n try {\n const json = atob(encoded)\n const parsed: unknown = JSON.parse(json)\n\n if (typeof parsed !== 'object' || parsed === null) return null\n const obj = parsed as Record<string, unknown>\n\n // Validate field\n if (typeof obj.field !== 'string' || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(obj.field)) {\n return null\n }\n\n // Validate value (string or number)\n if (typeof obj.value !== 'string' && typeof obj.value !== 'number') {\n return null\n }\n\n // Validate direction\n if (obj.direction !== 'asc' && obj.direction !== 'desc') {\n return null\n }\n\n // Validate id (optional, must be UUID if present)\n if (obj.id !== undefined) {\n if (typeof obj.id !== 'string' || !UUID_RE.test(obj.id)) {\n return null\n }\n }\n\n return {\n field: obj.field,\n value: obj.value,\n direction: obj.direction,\n id: obj.id as string | undefined,\n }\n } catch {\n return null\n }\n}\n\n// ---------------------------------------------------------------------------\n// SQL condition builder\n// ---------------------------------------------------------------------------\n\n/**\n * Build the keyset WHERE condition from a decoded cursor.\n *\n * For DESC: `WHERE (field < value) OR (field = value AND id < cursorId)`\n * For ASC: `WHERE (field > value) OR (field = value AND id > cursorId)`\n *\n * The caller must verify that `cursor.field` exists on the table before calling.\n *\n * @param table - Drizzle table with columns\n * @param cursor - Decoded and validated cursor\n * @returns SQL condition, or null if the field doesn't exist on the table\n */\nexport function buildCursorCondition(\n // biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any> for property access\n table: PgTableWithColumns<any>,\n cursor: DecodedCursor,\n): SQL | null {\n const column = table[cursor.field]\n if (!column) return null\n\n const isDesc = cursor.direction === 'desc'\n const compare = isDesc ? lt : gt\n\n // Primary condition: sort field passes the cursor value\n const fieldCondition = compare(column, cursor.value)\n\n // Without tie-breaker ID, use simple comparison\n if (!cursor.id) {\n return fieldCondition\n }\n\n // With tie-breaker: (field < value) OR (field = value AND id < cursorId)\n const idColumn = table.id\n if (!idColumn) return fieldCondition\n\n const idCondition = compare(idColumn, cursor.id)\n return or(fieldCondition, and(eq(column, cursor.value), idCondition))!\n}\n","/**\n * Entity definition API\n * The core function for defining entities with full type inference.\n *\n * The generic parameters are inferred from the call site:\n * - F is inferred from `definition.fields`\n * - B is inferred from `definition.behaviors` (as a tuple)\n *\n * The returned Entity carries the complete field map:\n * { id: IdField } & BehaviorFields & UserFields\n */\n\nimport type { EntityAdminConfig } from './admin-config.js'\nimport type { Behavior } from './behaviors/types.js'\nimport type { FieldConfig, IdField } from './fields/base.js'\nimport { field } from './fields/builders.js'\n\n/**\n * Entity definition input (without behaviors — behaviors are typed separately\n * on the `defineEntity` function to enable tuple inference).\n *\n * @typeParam F - The literal field map. Inferred automatically from the call site.\n */\nexport interface EntityDefinition<\n F extends Record<string, FieldConfig> = Record<string, FieldConfig>,\n> {\n name: string\n kind?: 'collection' | 'singleton'\n fields: F\n scope?: 'global' | 'team' | 'user'\n access?: {\n view?: string\n create?: string\n update?: string\n delete?: string\n }\n /** Admin UI configuration — controls sidebar, list, and form display */\n admin?: EntityAdminConfig\n}\n\n/**\n * Full input for defineEntity (fields + behaviors + other config).\n * Behaviors are typed as a tuple `B` for type extraction.\n */\nexport type EntityInput<\n F extends Record<string, FieldConfig> = Record<string, FieldConfig>,\n B extends Behavior[] = Behavior[],\n> = EntityDefinition<F> & { behaviors?: B }\n\n/**\n * A fully resolved entity with merged behavior fields.\n * @typeParam AllFields - The complete field map (id + behaviors + user fields).\n */\nexport interface Entity<\n AllFields extends Record<string, FieldConfig> = Record<string, FieldConfig>,\n> {\n name: string\n kind?: 'collection' | 'singleton'\n fields: Record<string, FieldConfig>\n behaviors?: Behavior[]\n scope?: 'global' | 'team' | 'user'\n access?: {\n view?: string\n create?: string\n update?: string\n delete?: string\n }\n /** Admin UI configuration — controls sidebar, list, and form display */\n admin?: EntityAdminConfig\n allFields: AllFields\n hooks: Behavior['hooks']\n}\n\n/**\n * Extract and intersect behavior field types from a behaviors tuple.\n * Walks the tuple recursively (depth bounded by number of behaviors, max ~5).\n */\ntype ExtractBehaviorFields<B extends Behavior[]> = B extends [\n Behavior<infer F1>,\n ...infer Rest extends Behavior[],\n]\n ? F1 & ExtractBehaviorFields<Rest>\n : // biome-ignore lint/complexity/noBannedTypes: empty object is correct for base case of intersection\n {}\n\n/**\n * Define an entity with full type inference.\n *\n * @example\n * const Article = defineEntity({\n * name: 'article',\n * fields: {\n * title: field.text({ required: true }),\n * featured: field.boolean(),\n * },\n * behaviors: [publishable(), auditable()],\n * })\n *\n * type ArticleDTO = InferEntity<typeof Article>\n * // { id: string; title: string; featured?: boolean | null; status?: ...; createdAt?: ...; }\n */\nexport function defineEntity<\n F extends Record<string, FieldConfig>,\n const B extends Behavior[] = [],\n>(\n definition: EntityDefinition<F> & { behaviors?: B },\n): Entity<{ id: IdField } & ExtractBehaviorFields<B> & F> {\n // Merge behavior fields into main fields\n const behaviorFields: Record<string, FieldConfig> = {}\n const hooks: NonNullable<Behavior['hooks']> = {}\n\n for (const behavior of definition.behaviors || []) {\n // Merge fields from behavior\n if (behavior.fields) {\n for (const [fieldName, fieldConfig] of Object.entries(behavior.fields)) {\n if (behaviorFields[fieldName]) {\n console.warn(\n `Field '${fieldName}' from behavior '${behavior.name}' conflicts with existing field. Skipping.`,\n )\n continue\n }\n behaviorFields[fieldName] = fieldConfig\n }\n }\n\n // Merge hooks from behavior\n if (behavior.hooks) {\n for (const [hookName, hookFn] of Object.entries(behavior.hooks)) {\n const typedHookName = hookName as keyof NonNullable<Behavior['hooks']>\n\n if (hookFn) {\n const existingHook = hooks[typedHookName]\n\n if (existingHook) {\n // Chain hooks: execute existing first, then new one\n const previousHook = existingHook as (...args: unknown[]) => Promise<unknown>\n const currentHook = hookFn as (...args: unknown[]) => Promise<unknown>\n\n const chainedHook = async (...args: unknown[]) => {\n const result = await previousHook(...args)\n if (result !== undefined) {\n // Hook returned modified data — replace last arg (the data param),\n // keep leading args (e.g. id) intact for hooks like beforeUpdate(id, data)\n const nextArgs = [...args]\n nextArgs[nextArgs.length - 1] = result\n return await currentHook(...nextArgs)\n }\n return await currentHook(...args)\n }\n // biome-ignore lint/suspicious/noExplicitAny: dynamic dispatch over heterogeneous hook signatures\n hooks[typedHookName] = chainedHook as any\n } else {\n // biome-ignore lint/suspicious/noExplicitAny: dynamic dispatch over heterogeneous hook signatures\n hooks[typedHookName] = hookFn as any\n }\n }\n }\n }\n }\n\n // Build complete field set\n // Order: id (always first) → behavior fields → user-defined fields\n const allFields: Record<string, FieldConfig> = {\n id: field.id(), // Every entity gets an ID\n ...behaviorFields,\n ...definition.fields,\n }\n\n // Auto-infer translatable slugs: if a slug field's source field is translatable,\n // the slug should be translatable too (each locale gets its own URL-safe slug).\n for (const [, fieldConfig] of Object.entries(allFields)) {\n if (\n fieldConfig.type === 'slug' &&\n !fieldConfig.translatable &&\n allFields[fieldConfig.from]?.translatable\n ) {\n fieldConfig.translatable = true\n }\n }\n\n // The cast through `unknown` is safe: runtime merge order (id + behaviors + user fields)\n // exactly mirrors the type-level intersection. TypeScript cannot verify imperative\n // Object.entries() loops produce the same result as a type-level intersection,\n // so we cast at this single auditable boundary.\n return {\n ...definition,\n allFields,\n hooks,\n } as unknown as Entity<{ id: IdField } & ExtractBehaviorFields<B> & F>\n}\n","/**\n * Error thrown when attempting to delete an entity that is still referenced.\n */\n\nimport type { EntityUsage } from './find-usages.js'\n\nexport class ReferencedEntityError extends Error {\n public readonly entityName: string\n public readonly entityId: string\n public readonly usages: EntityUsage[]\n\n constructor(entityName: string, entityId: string, usages: EntityUsage[]) {\n const count = usages.length\n super(\n `Cannot delete ${entityName} '${entityId}': referenced by ${count} other entit${count === 1 ? 'y' : 'ies'}`,\n )\n this.name = 'ReferencedEntityError'\n this.entityName = entityName\n this.entityId = entityId\n this.usages = usages\n }\n}\n","/**\n * Schema generator\n * Converts entity definitions to Drizzle schemas.\n * Every field gets its own Postgres column — no JSONB blobs.\n */\n\nimport type { PgColumnBuilderBase } from 'drizzle-orm/pg-core'\nimport {\n boolean,\n doublePrecision,\n index,\n integer,\n jsonb,\n pgTable,\n text,\n timestamp,\n unique,\n uuid,\n varchar,\n} from 'drizzle-orm/pg-core'\nimport type { Entity } from './define-entity.js'\nimport type { FieldConfig } from './fields/base.js'\n\n/**\n * Convert a field config to a Drizzle column.\n *\n * @param nullable - When true, skips notNull and default constraints.\n * Used for translation table columns (translations are partial overrides).\n */\nexport function fieldToColumn(\n fieldName: string,\n fieldConfig: FieldConfig,\n options?: { nullable?: boolean },\n): PgColumnBuilderBase {\n const nullable = options?.nullable ?? false\n\n switch (fieldConfig.type) {\n case 'id':\n return uuid(fieldName).primaryKey().defaultRandom()\n\n case 'text':\n if (fieldConfig.maxLength && fieldConfig.maxLength <= 255) {\n let column = varchar(fieldName, { length: fieldConfig.maxLength })\n if (fieldConfig.unique) column = column.unique()\n if (!nullable && fieldConfig.required) column = column.notNull()\n if (!nullable && fieldConfig.default !== undefined)\n column = column.default(fieldConfig.default as string)\n return column\n } else {\n let column = text(fieldName)\n if (fieldConfig.unique) column = column.unique()\n if (!nullable && fieldConfig.required) column = column.notNull()\n if (!nullable && fieldConfig.default !== undefined)\n column = column.default(fieldConfig.default as string)\n return column\n }\n\n case 'number': {\n let numColumn = fieldConfig.integer ? integer(fieldName) : doublePrecision(fieldName)\n if (!nullable && fieldConfig.required) numColumn = numColumn.notNull()\n if (!nullable && fieldConfig.default !== undefined)\n numColumn = numColumn.default(fieldConfig.default as number)\n return numColumn\n }\n\n case 'boolean': {\n let boolColumn = boolean(fieldName)\n if (!nullable && fieldConfig.required) boolColumn = boolColumn.notNull()\n if (!nullable) {\n const boolDefault =\n fieldConfig.default !== undefined ? (fieldConfig.default as boolean) : false\n boolColumn = boolColumn.default(boolDefault)\n }\n return boolColumn\n }\n\n case 'date': {\n let dateColumn = timestamp(fieldName, { withTimezone: true })\n if (!nullable && fieldConfig.required) dateColumn = dateColumn.notNull()\n if (!nullable && fieldConfig.default !== undefined)\n dateColumn = dateColumn.default(fieldConfig.default as Date)\n return dateColumn\n }\n\n case 'select': {\n let selectColumn = varchar(fieldName, { length: 100 })\n if (!nullable && fieldConfig.required) selectColumn = selectColumn.notNull()\n if (!nullable && fieldConfig.default !== undefined)\n selectColumn = selectColumn.default(fieldConfig.default as string)\n return selectColumn\n }\n\n case 'reference': {\n if (fieldConfig.cardinality === 'many') {\n return uuid(fieldName).array()\n }\n let refColumn = uuid(fieldName)\n if (!nullable && fieldConfig.required) refColumn = refColumn.notNull()\n return refColumn\n }\n\n case 'media': {\n let mediaColumn = uuid(fieldName)\n if (!nullable && fieldConfig.required) mediaColumn = mediaColumn.notNull()\n return mediaColumn\n }\n\n case 'slug': {\n let slugColumn = varchar(fieldName, { length: 255 })\n // For translation tables (nullable=true), skip per-column unique —\n // translatable slugs use composite UNIQUE(slug, locale) at table level instead.\n if (fieldConfig.unique && !nullable) slugColumn = slugColumn.unique()\n if (!nullable && fieldConfig.required) slugColumn = slugColumn.notNull()\n return slugColumn\n }\n\n case 'richtext': {\n // TipTap HTML strings stored as text\n let rtColumn = text(fieldName)\n if (!nullable && fieldConfig.required) rtColumn = rtColumn.notNull()\n return rtColumn\n }\n\n case 'json':\n return jsonb(fieldName)\n\n default:\n return text(fieldName)\n }\n}\n\n/**\n * Generate TypeScript code string for a single column.\n *\n * @param nullable - When true, skips notNull and default constraints.\n */\nfunction generateColumnCode(\n fieldName: string,\n fieldConfig: FieldConfig,\n options?: { nullable?: boolean },\n): string {\n const nullable = options?.nullable ?? false\n\n switch (fieldConfig.type) {\n case 'id':\n return `id: uuid('id').primaryKey().defaultRandom()`\n\n case 'text': {\n const maxLen = fieldConfig.maxLength\n let textCol =\n maxLen && maxLen <= 255 ? `varchar('${fieldName}', { length: ${maxLen} })` : `text('${fieldName}')`\n if (fieldConfig.unique) textCol += `.unique()`\n if (!nullable && fieldConfig.required) textCol += `.notNull()`\n if (!nullable && fieldConfig.default) textCol += `.default('${fieldConfig.default}')`\n return `${fieldName}: ${textCol}`\n }\n\n case 'number': {\n let numCol = fieldConfig.integer\n ? `integer('${fieldName}')`\n : `doublePrecision('${fieldName}')`\n if (!nullable && fieldConfig.required) numCol += `.notNull()`\n if (!nullable && fieldConfig.default !== undefined)\n numCol += `.default(${fieldConfig.default})`\n return `${fieldName}: ${numCol}`\n }\n\n case 'boolean': {\n if (nullable) {\n return `${fieldName}: boolean('${fieldName}')`\n }\n const boolDefault = fieldConfig.default ?? false\n return `${fieldName}: boolean('${fieldName}').default(${boolDefault})`\n }\n\n case 'date': {\n let dateCol = `timestamp('${fieldName}', { withTimezone: true })`\n if (!nullable && fieldConfig.required) dateCol += `.notNull()`\n return `${fieldName}: ${dateCol}`\n }\n\n case 'reference': {\n if (fieldConfig.cardinality === 'many') {\n return `${fieldName}: uuid('${fieldName}').array()`\n }\n let refCol = `uuid('${fieldName}')`\n if (!nullable && fieldConfig.required) refCol += `.notNull()`\n return `${fieldName}: ${refCol}`\n }\n\n case 'media': {\n let mediaCol = `uuid('${fieldName}')`\n if (!nullable && fieldConfig.required) mediaCol += `.notNull()`\n return `${fieldName}: ${mediaCol}`\n }\n\n case 'slug': {\n let slugCol = `varchar('${fieldName}', { length: 255 })`\n if (fieldConfig.unique && !nullable) slugCol += `.unique()`\n if (!nullable && fieldConfig.required) slugCol += `.notNull()`\n return `${fieldName}: ${slugCol}`\n }\n\n case 'select': {\n let selectCol = `varchar('${fieldName}', { length: 100 })`\n if (!nullable && fieldConfig.required) selectCol += `.notNull()`\n if (!nullable && fieldConfig.default) selectCol += `.default('${fieldConfig.default}')`\n return `${fieldName}: ${selectCol}`\n }\n\n case 'richtext': {\n let rtCol = `text('${fieldName}')`\n if (!nullable && fieldConfig.required) rtCol += `.notNull()`\n return `${fieldName}: ${rtCol}`\n }\n\n case 'json':\n return `${fieldName}: jsonb('${fieldName}')`\n\n default:\n return `${fieldName}: text('${fieldName}')`\n }\n}\n\n/**\n * Generate a runtime Drizzle schema from an entity definition.\n * Every field becomes its own column — no JSONB.\n */\nexport function generateSchema(entity: Entity) {\n const tableName = entity.name\n const columns: Record<string, PgColumnBuilderBase> = {}\n\n for (const [fieldName, fieldConfig] of Object.entries(entity.allFields)) {\n if (fieldConfig.type === 'blocks') continue // stored in layout table\n columns[fieldName] = fieldToColumn(fieldName, fieldConfig)\n }\n\n if (entity.scope === 'team' || entity.scope === 'user') {\n columns._scopeId = uuid('_scope_id')\n }\n\n // Collect fields that need indexes (skip id/unique — they already have btree indexes)\n const indexedFields = Object.entries(entity.allFields).filter(\n ([_, config]) =>\n config.type !== 'blocks' && config.type !== 'id' && config.indexed && !config.unique,\n )\n\n const isPublishableEntity = entity.behaviors?.some((b) => b.name === 'publishable') ?? false\n\n if (indexedFields.length === 0 && !isPublishableEntity) {\n return pgTable(tableName, columns)\n }\n\n return pgTable(tableName, columns, (table) => {\n // biome-ignore lint/suspicious/noExplicitAny: dynamic table columns from pgTable callback\n const constraints: Record<string, any> = {}\n\n for (const [fieldName] of indexedFields) {\n constraints[`idx_${tableName}_${fieldName}`] = index(`idx_${tableName}_${fieldName}`).on(\n table[fieldName],\n )\n }\n\n // Composite index for publishable entities: status + createdAt (most common list query)\n if (isPublishableEntity && table.status && table.createdAt) {\n constraints[`idx_${tableName}_status_created`] = index(`idx_${tableName}_status_created`).on(\n table.status,\n table.createdAt,\n )\n }\n\n return constraints\n })\n}\n\n/**\n * Generate TypeScript code string for an entity schema.\n * Every field becomes its own column — no JSONB.\n */\nexport function generateSchemaCode(entity: Entity): string {\n const tableName = entity.name\n const lines: string[] = []\n\n // Collect fields that need indexes (skip id/unique — they already have btree indexes)\n const indexedFields = Object.entries(entity.allFields).filter(\n ([_, config]) =>\n config.type !== 'blocks' && config.type !== 'id' && config.indexed && !config.unique,\n )\n\n const isPublishableEntity = entity.behaviors?.some((b) => b.name === 'publishable') ?? false\n const needsIndexes = indexedFields.length > 0 || isPublishableEntity\n\n lines.push(`export const ${tableName} = pgTable('${tableName}', {`)\n\n for (const [fieldName, fieldConfig] of Object.entries(entity.allFields)) {\n if (fieldConfig.type === 'blocks') continue // stored in layout table\n const columnCode = generateColumnCode(fieldName, fieldConfig)\n lines.push(` ${columnCode},`)\n }\n\n if (entity.scope === 'team' || entity.scope === 'user') {\n lines.push(` _scopeId: uuid('_scope_id'),`)\n }\n\n if (needsIndexes) {\n lines.push(`}, (table) => ({`)\n\n for (const [fieldName] of indexedFields) {\n lines.push(\n ` idx_${tableName}_${fieldName}: index('idx_${tableName}_${fieldName}').on(table.${fieldName}),`,\n )\n }\n\n // Composite index for publishable entities: status + createdAt\n if (isPublishableEntity) {\n lines.push(\n ` idx_${tableName}_status_created: index('idx_${tableName}_status_created').on(table.status, table.createdAt),`,\n )\n }\n\n lines.push(`}))`)\n } else {\n lines.push(`)`)\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Generate runtime translation table schema.\n * Each translatable field gets its own nullable column.\n */\nexport function generateTranslationSchema(entity: Entity) {\n const translatableFields = Object.entries(entity.allFields).filter(\n ([_, config]) => config.translatable,\n )\n\n if (translatableFields.length === 0) return null\n\n const tableName = `${entity.name}_translations`\n const columns: Record<string, PgColumnBuilderBase> = {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id').notNull(),\n locale: varchar('locale', { length: 10 }).notNull(),\n }\n\n const hasTranslatableSlug = translatableFields.some(([_, config]) => config.type === 'slug')\n\n for (const [fieldName, fieldConfig] of translatableFields) {\n columns[fieldName] = fieldToColumn(fieldName, fieldConfig, { nullable: true })\n }\n\n return pgTable(tableName, columns, (table) => ({\n uniqueEntityLocale: unique().on(table.entityId, table.locale),\n // Per-locale slug uniqueness: no two entities can share the same slug in the same locale\n ...(hasTranslatableSlug && table.slug\n ? { uniqueSlugLocale: unique().on(table.slug, table.locale) }\n : {}),\n }))\n}\n\n/**\n * Generate TypeScript code string for a translation table.\n * Each translatable field gets its own nullable column.\n */\nexport function generateTranslationSchemaCode(entity: Entity): string | null {\n const translatableFields = Object.entries(entity.allFields).filter(\n ([_, config]) => config.translatable,\n )\n\n if (translatableFields.length === 0) return null\n\n const tableName = `${entity.name}_translations`\n const entityTableName = entity.name\n const lines: string[] = []\n\n lines.push(`export const ${tableName} = pgTable('${tableName}', {`)\n lines.push(` id: uuid('id').primaryKey().defaultRandom(),`)\n lines.push(\n ` entityId: uuid('entity_id').notNull().references(() => ${entityTableName}.id, { onDelete: 'cascade' }),`,\n )\n lines.push(` locale: varchar('locale', { length: 10 }).notNull(),`)\n\n for (const [fieldName, fieldConfig] of translatableFields) {\n const columnCode = generateColumnCode(fieldName, fieldConfig, { nullable: true })\n lines.push(` ${columnCode},`)\n }\n\n const hasTranslatableSlug = translatableFields.some(([_, config]) => config.type === 'slug')\n\n lines.push(`}, (table) => ({`)\n lines.push(\n ` uniqueEntityLocale: unique().on(table.entityId, table.locale), // One translation per locale`,\n )\n if (hasTranslatableSlug) {\n lines.push(\n ` uniqueSlugLocale: unique().on(table.slug, table.locale), // Per-locale slug uniqueness`,\n )\n }\n lines.push(`}))`)\n\n return lines.join('\\n')\n}\n\n/**\n * Check if an entity has any blocks fields\n */\nexport function hasBlocksFields(entity: Entity): boolean {\n return Object.values(entity.allFields).some((f) => f.type === 'blocks')\n}\n\n/**\n * Generate layout table schema for entities with blocks fields.\n * Stores block instances in a separate table with ordering.\n *\n * When blocks field has localized: false (default), locale is NULL (shared layout).\n * When blocks field has localized: true, locale is set per-locale.\n */\nexport function generateLayoutSchema(entity: Entity) {\n if (!hasBlocksFields(entity)) return null\n\n const tableName = `${entity.name}_layout`\n\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id').notNull(),\n fieldName: varchar('field_name', { length: 100 }).notNull(),\n blockType: varchar('block_type', { length: 100 }).notNull(),\n sortOrder: integer('sort_order').notNull().default(0),\n data: jsonb('data'),\n locale: varchar('locale', { length: 10 }),\n },\n (table) => ({\n // Covers: WHERE entity_id IN (...) AND locale IS NULL ORDER BY sort_order\n idx_entity_locale_sort: index(`idx_${tableName}_entity_locale_sort`).on(\n table.entityId,\n table.locale,\n table.sortOrder,\n ),\n }),\n )\n}\n\n/**\n * Check if an entity has the versionable behavior\n */\nexport function isVersionable(entity: Entity): boolean {\n return entity.behaviors?.some((b) => b.name === 'versionable') ?? false\n}\n\n/**\n * Check if an entity needs a per-locale publish status table.\n * Requires both publishable() behavior AND at least one translatable field.\n */\nexport function needsLocaleStatus(entity: Entity): boolean {\n const isPublishable = entity.behaviors?.some((b) => b.name === 'publishable') ?? false\n if (!isPublishable) return false\n const allFields = entity.allFields as Record<string, { translatable?: boolean }>\n return Object.values(allFields).some((f) => f.translatable)\n}\n\n/**\n * Generate TypeScript code string for a per-locale publish status table.\n */\nexport function generateLocaleStatusCode(entity: Entity): string {\n const name = entity.name\n const varName = `${name}_locale_status`\n return `export const ${varName} = pgTable('${varName}', {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id').notNull().references(() => ${name}.id, { onDelete: 'cascade' }),\n locale: varchar('locale', { length: 10 }).notNull(),\n status: varchar('status', { length: 20 }).notNull().default('draft'),\n publishedAt: timestamp('published_at', { withTimezone: true }),\n}, (table) => ({\n uniqueEntityLocale: unique().on(table.entityId, table.locale),\n}))`\n}\n\n/**\n * Check if an entity is publishable (has publishable() behavior).\n */\nexport function isPublishable(entity: Entity): boolean {\n return entity.behaviors?.some((b) => b.name === 'publishable') ?? false\n}\n\n/**\n * Generate TypeScript code string for a drafts overlay table.\n */\nexport function generateDraftsCode(entity: Entity): string {\n const name = entity.name\n const varName = `${name}_drafts`\n return `export const ${varName} = pgTable('${varName}', {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id').notNull().references(() => ${name}.id, { onDelete: 'cascade' }),\n locale: varchar('locale', { length: 10 }).notNull().default('_'),\n data: jsonb('data').notNull(),\n createdBy: varchar('created_by', { length: 255 }).notNull(),\n createdByName: varchar('created_by_name', { length: 255 }),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n uniqueEntityLocale: unique().on(table.entityId, table.locale),\n}))`\n}\n\n/**\n * Generate TypeScript code string for the shared content locks table.\n */\nexport function generateContentLocksCode(): string {\n return `export const toolkit_content_locks = pgTable('toolkit_content_locks', {\n id: uuid('id').primaryKey().defaultRandom(),\n entityType: varchar('entity_type', { length: 100 }).notNull(),\n entityId: varchar('entity_id', { length: 255 }).notNull(),\n locale: varchar('locale', { length: 10 }).notNull().default('_'),\n lockedBy: varchar('locked_by', { length: 255 }).notNull(),\n lockedByName: varchar('locked_by_name', { length: 255 }),\n lockedAt: timestamp('locked_at', { withTimezone: true }).notNull().defaultNow(),\n expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),\n}, (table) => ({\n uniqueEntityLock: unique().on(table.entityType, table.entityId, table.locale),\n}))`\n}\n\n/**\n * Check if an entity has blocks fields with translatable block content (non-localized mode).\n */\nexport function hasTranslatableBlocks(entity: Entity): boolean {\n const allFields = entity.allFields as Record<\n string,\n {\n type: string\n localized?: boolean\n blocks?: Array<{ fields: Record<string, { translatable?: boolean }> }>\n }\n >\n return Object.values(allFields).some((f) => {\n if (f.type !== 'blocks') return false\n if (f.localized) return false\n return f.blocks?.some((block) => Object.values(block.fields).some((bf) => bf.translatable))\n })\n}\n\n/**\n * Generate TypeScript code string for a layout table (blocks storage).\n */\nexport function generateLayoutCode(entity: Entity): string {\n const name = entity.name\n const varName = `${name}_layout`\n return `export const ${varName} = pgTable('${varName}', {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id').notNull().references(() => ${name}.id, { onDelete: 'cascade' }),\n fieldName: varchar('field_name', { length: 100 }).notNull(),\n blockType: varchar('block_type', { length: 100 }).notNull(),\n sortOrder: integer('sort_order').notNull().default(0),\n data: jsonb('data'),\n locale: varchar('locale', { length: 10 }),\n}, (table) => ({\n idx_entity_locale_sort: index('idx_${varName}_entity_locale_sort').on(table.entityId, table.locale, table.sortOrder),\n}))`\n}\n\n/**\n * Generate TypeScript code string for a layout translation table.\n */\nexport function generateLayoutTranslationCode(entity: Entity): string {\n const name = entity.name\n const varName = `${name}_layout_translations`\n const layoutVar = `${name}_layout`\n return `export const ${varName} = pgTable('${varName}', {\n id: uuid('id').primaryKey().defaultRandom(),\n layoutId: uuid('layout_id').notNull().references(() => ${layoutVar}.id, { onDelete: 'cascade' }),\n locale: varchar('locale', { length: 10 }).notNull(),\n fields: jsonb('fields').notNull(),\n}, (table) => ({\n uniqueLayoutLocale: unique().on(table.layoutId, table.locale),\n}))`\n}\n\n/**\n * Generate TypeScript code string for a versions table.\n */\nexport function generateVersionsCode(entity: Entity): string {\n const name = entity.name\n const varName = `${name}_versions`\n return `export const ${varName} = pgTable('${varName}', {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id').notNull().references(() => ${name}.id, { onDelete: 'cascade' }),\n version: integer('version').notNull(),\n locale: varchar('locale', { length: 10 }).notNull().default('_'),\n data: jsonb('data').notNull(),\n delta: jsonb('delta'),\n status: varchar('status', { length: 20 }),\n createdBy: varchar('created_by', { length: 255 }),\n createdByName: varchar('created_by_name', { length: 255 }),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n isAutosave: boolean('is_autosave').notNull().default(false),\n}, (table) => ({\n uniqueEntityVersionLocale: unique().on(table.entityId, table.version, table.locale),\n}))`\n}\n\n/**\n * Generate layout translation table for non-localized blocks with translatable fields.\n */\nexport function generateLayoutTranslationSchema(entity: Entity) {\n const blocksFields = Object.values(entity.allFields).filter(\n (f) => f.type === 'blocks' && !('localized' in f && f.localized),\n )\n\n if (blocksFields.length === 0) return null\n\n const hasTranslatableBlockFields = blocksFields.some((bf) => {\n if (bf.type !== 'blocks') return false\n return bf.blocks.some((block) => Object.values(block.fields).some((f) => f.translatable))\n })\n\n if (!hasTranslatableBlockFields) return null\n\n const tableName = `${entity.name}_layout_translations`\n\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n layoutId: uuid('layout_id').notNull(),\n locale: varchar('locale', { length: 10 }).notNull(),\n fields: jsonb('fields').notNull(),\n },\n (table) => ({\n uniqueLayoutLocale: unique().on(table.layoutId, table.locale),\n }),\n )\n}\n"],"mappings":"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"}
@@ -76,6 +76,23 @@ interface EntityAdminConfig {
76
76
  disableCreate?: boolean;
77
77
  /** Order within sidebar group. Default: 0 */
78
78
  sortOrder?: number;
79
+ /**
80
+ * Hide the entity from BOTH dashboard and sidebar.
81
+ * Use for system entities that should not appear in any UI.
82
+ * Shorthand for `hideFromMenu: true` + `hideFromDashboard: true`.
83
+ */
84
+ hidden?: boolean;
85
+ /**
86
+ * Hide from sidebar menu only. Entity remains accessible via dashboard, direct URL,
87
+ * and the entity API. Use for storage models accessed through specialized UI
88
+ * (e.g., Ticket entities accessed via the inbox/board, not as a CRUD table).
89
+ */
90
+ hideFromMenu?: boolean;
91
+ /**
92
+ * Hide from dashboard widget grid only. Entity still appears in sidebar.
93
+ * Use for entities that don't have meaningful counts/stats to show.
94
+ */
95
+ hideFromDashboard?: boolean;
79
96
  }
80
97
  //#endregion
81
98
  //#region src/fields/base.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/count-cache.ts","../../src/cursor.ts","../../src/admin-config.ts","../../src/fields/base.ts","../../src/behaviors/types.ts","../../src/define-entity.ts","../../src/types/infer.ts","../../src/types/logger.ts","../../src/query/client.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/count-cache.ts","../../src/cursor.ts","../../src/admin-config.ts","../../src/fields/base.ts","../../src/behaviors/types.ts","../../src/define-entity.ts","../../src/types/infer.ts","../../src/types/logger.ts","../../src/query/client.ts"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@murumets-ee/entity",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "exports": {