@murumets-ee/entity 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/index.d.mts +44 -6
- package/dist/admin/index.d.mts.map +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/index.mjs.map +1 -1
- package/dist/index.d.mts +22 -7
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/query/index.d.mts +21 -6
- package/dist/query/index.d.mts.map +1 -1
- package/dist/query/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.mts.map
CHANGED
|
@@ -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/shared/entity-data-ops.ts","../src/refs/find-usages.ts","../src/refs/errors.ts","../src/schema-generator.ts"],"mappings":";;;;;;;;;;;UAIiB,iBAAA;;EAEf,KAAA;EAFe;EAIf,KAAA;;EAEA,aAAA;EAJA;EAMA,IAAA;EAFA;EAIA,WAAA;EAAA;EAEA,MAAA;EAEA;EAAA,YAAA;EAIA;EAFA,aAAA;EAEkC;EAAlC,cAAA,GAAiB,MAAA;IAAiB,KAAA;IAAgB,WAAA;IAAsB,WAAA;EAAA;EAQxE;EANA,WAAA;EAUA;EARA,oBAAA;EAoBA;EAlBA,QAAA;EAuBiB;EArBjB,UAAA;;EAEA,aAAA;;EAEA,SAAA;EC7B8B;;;;;EDmC9B,MAAA;EC/BA;;;;;EDqCA,YAAA;ECjCM;AAIR;;;EDkCE,iBAAA;AAAA;;;;;;;UC9Ce,eAAA;EACf,QAAA;EACA,OAAA;EACA,YAAA;EACA,OAAA;EACA,MAAA;EACA,MAAA;IACE,IAAA;IACA,IAAA;EAAA;AAAA;AAAA,UAIa,OAAA,SAAgB,eAAA;EAC/B,IAAA;AAAA;AAAA,UAGe,SAAA,SAAkB,eAAA;EACjC,IAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA,GAAU,MAAA;AAAA;AAAA,UAGK,WAAA,SAAoB,eAAA;EACnC,IAAA;EACA,GAAA;EACA,GAAA;EACA,OAAA;AAAA;AAAA,UAGe,YAAA,SAAqB,eAAA;EACpC,IAAA;AAAA;AAAA,UAGe,SAAA,SAAkB,eAAA;EACjC,IAAA;EACA,OAAA,GAAU,IAAA;EACV,OAAA,GAAU,IAAA;AAAA;AAAA,UAGK,WAAA,SAAoB,eAAA;EACnC,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,cAAA,SAAuB,eAAA;EACtC,IAAA;EACA,MAAA;EACA,WAAA;EACA,QAAA;AAAA;AAAA,UAGe,UAAA,SAAmB,eAAA;EAClC,IAAA;EACA,MAAA;EACA,OAAA;AAAA;AAAA,UAGe,aAAA,SAAsB,eAAA;EACrC,IAAA;EACA,MAAA;AAAA;AAAA,UAGe,SAAA,SAAkB,eAAA;EACjC,IAAA;EACA,IAAA;EACA,MAAA;AAAA;;;;;UAOe,kBAAA;EACf,IAAA;EACA,MAAA,EAAQ,MAAA,SAAe,WAAA;AAAA;;;;;;;;;UAWR,SAAA,SAAkB,eAAA;EACjC,IAAA;AAAA;AAAA,UAGe,WAAA,SAAoB,eAAA;EACnC,IAAA;EACA,MAAA,WAAiB,kBAAA;EACjB,GAAA;EACA,GAAA;EACA,SAAA;AAAA;AAAA,KAGU,WAAA,GACR,OAAA,GACA,SAAA,GACA,WAAA,GACA,YAAA,GACA,SAAA,GACA,WAAA,GACA,cAAA,GACA,UAAA,GACA,aAAA,GACA,SAAA,GACA,SAAA,GACA,WAAA;;;;;;;KCjFQ,SAAA,sCAKR,SAAA;EAAA,CACG,GAAA,WAAc,SAAA;AAAA;;;;;KAUT,SAAA,WAAoB,WAAA,IAAe,CAAA,SAAU,OAAA,YAErD,CAAA,SAAU,SAAA,YAER,CAAA,SAAU,WAAA,YAER,CAAA,SAAU,YAAA,aAER,CAAA,SAAU,SAAA,GACR,IAAA,YACA,CAAA,SAAU,WAAA,GACR,CAAA,sBACA,CAAA,SAAU,cAAA,GACR,CAAA,qDAGA,CAAA,SAAU,UAAA,YAER,CAAA,SAAU,aAAA,GACR,MAAA,sBACA,CAAA,SAAU,SAAA,YAER,CAAA,SAAU,SAAA,GACR,SAAA,GACA,CAAA,SAAU,WAAA,GACR,KAAA;EAAQ,MAAA;EAAgB,GAAA;EAAA,CAAc,GAAA;AAAA;;;;;;;;;KAsCpD,cAAA,gBAA8B,MAAA,SAAe,WAAA;EAAkB,EAAA;AAAA,kBAC7D,MAAA,IAAU,CAAA,wBAElB,MAAA,CAAO,CAAA,6BACL,CAAA,WACQ,SAAA,CAAU,MAAA,CAAO,CAAA,qBAEnB,MAAA,IAAU,MAAA,CAAO,CAAA,qCAAsC,CAAA,IAC/D,SAAA,CAAU,MAAA,CAAO,CAAA;;;;;;;;;ADtFvB;AAAA,KCkJY,iBAAA;EACV,MAAA,EAAQ,WAAA;IAAgB,OAAA;EAAA;EACxB,WAAA,EAAa,SAAA;AAAA;;KAIH,eAAA;EACV,SAAA,EAAW,SAAA;EACX,SAAA,EAAW,SAAA;EACX,SAAA,EAAW,SAAA;EACX,SAAA,EAAW,SAAA;AAAA;;KAID,eAAA;EACV,IAAA,EAAM,SAAA;AAAA;;KAII,kBAAA;EACV,QAAA,EAAU,WAAA;AAAA;;KAIA,kBAAA;EACV,QAAA,EAAU,cAAA;IAAmB,WAAA;EAAA;EAC7B,IAAA,EAAM,SAAA;EACN,KAAA,EAAO,WAAA;AAAA;AD9JT;;;;;;;AAAA,KC+KY,WAAA,MAAiB,CAAA;EAAY,SAAA,kBAA2B,MAAA,SAAe,WAAA;AAAA,IAC/E,cAAA,CAAe,CAAA
|
|
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/shared/entity-data-ops.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,2 +1,2 @@
|
|
|
1
|
-
import{and as e,eq as t,getTableColumns as n,gt as r,lt as i,or as a,sql as o}from"drizzle-orm";import"@murumets-ee/db";import{boolean as s,doublePrecision as c,index as l,integer as u,jsonb as d,pgTable as f,text as p,timestamp as m,unique as h,uuid as g,varchar as _}from"drizzle-orm/pg-core";const v={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 y(){try{return(await import([`@murumets-ee`,`core`].join(`/`))).getCurrentUser()?.id}catch{return}}function b(){return{name:`auditable`,fields:{createdBy:v.text(),updatedBy:v.text(),createdAt:v.date({indexed:!0}),updatedAt:v.date({indexed:!0})},hooks:{beforeCreate:async e=>{e.createdAt=new Date,e.updatedAt=new Date;let t=await y();return t&&(e.createdBy=t,e.updatedBy=t),e},beforeUpdate:async(e,t)=>{t.updatedAt=new Date;let n=await y();return n&&(t.updatedBy=n),t}}}}function x(e){return{name:`hierarchical`,fields:{parentId:v.reference({entity:`_self`,required:!1}),path:v.text({indexed:!0,maxLength:2048}),depth:v.number({integer:!0,default:0,indexed:!0})},hooks:{beforeCreate:async e=>(e.parentId||(e.depth=0),e)}}}function S(){return{name:`publishable`,fields:{status:v.select({options:[`draft`,`published`],default:`draft`,indexed:!0}),publishedAt:v.date({indexed:!0})},hooks:{beforeUpdate:async(e,t)=>(t.status===`published`&&!t.publishedAt&&(t.publishedAt=new Date),t)}}}function C(){return{name:`revisionable`,fields:{_version:v.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 w(e){return e.toLowerCase().trim().replace(/[^\w\s-]/g,``).replace(/[\s_-]+/g,`-`).replace(/^-+|-+$/g,``)}function T(e,t){return{name:`sluggable`,fields:{slug:v.slug({from:e,...t?.translatable?{translatable:!0}:{}})},hooks:{beforeCreate:async t=>(!t.slug&&t[e]&&(t.slug=w(String(t[e]))),t),beforeUpdate:async(t,n)=>(n[e]&&!n.slug&&(n.slug=w(String(n[e]))),n)}}}function E(){return{name:`timestamped`,fields:{createdAt:v.date({indexed:!0}),updatedAt:v.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 D={publishable:S,auditable:b,sluggable:T,revisionable:C,hierarchical:x,timestamped:E};var O=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 k(e,t){let n=await e.execute(o`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 A=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function j(e,t,n){let r={field:t,value:e[t],direction:n,id:e.id};return btoa(JSON.stringify(r))}function M(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`||!A.test(r.id))?null:{field:r.field,value:r.value,direction:r.direction,id:r.id}}catch{return null}}function N(o,s){let c=n(o),l=c[s.field];if(!l)return null;let u=s.direction===`desc`?i:r,d=u(l,s.value);if(!s.id)return d;let f=c.id;if(!f)return d;let p=u(f,s.id);return a(d,e(t(l,s.value),p))}function P(e){let t={};for(let n of e.behaviors||[])if(n.fields)for(let[e,r]of Object.entries(n.fields)){if(t[e]){console.warn(`Field '${e}' from behavior '${n.name}' conflicts with existing field. Skipping.`);continue}t[e]=r}let n={id:v.id(),...t,...e.fields};for(let[,e]of Object.entries(n))e.type===`slug`&&!e.translatable&&n[e.from]?.translatable&&(e.translatable=!0);return{...e,allFields:n}}var F=class extends Error{constructor(e){super(e),this.name=`ForbiddenError`}},I=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 L=f(`entity_refs`,{sourceEntity:_(`source_entity`,{length:100}).notNull(),sourceId:g(`source_id`).notNull(),sourceField:_(`source_field`,{length:100}).notNull(),targetEntity:_(`target_entity`,{length:100}).notNull(),targetId:g(`target_id`).notNull()},e=>[h(`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)]);function R(e,t,n){let r=n?.nullable??!1;switch(t.type){case`id`:return g(e).primaryKey().defaultRandom();case`text`:if(t.maxLength&&t.maxLength<=255){let n=_(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=p(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?u(e):c(e);return!r&&t.required&&(n=n.notNull()),!r&&t.default!==void 0&&(n=n.default(t.default)),n}case`boolean`:{let n=s(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=m(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=_(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 g(e).array();let n=g(e);return!r&&t.required&&(n=n.notNull()),n}case`media`:{let n=g(e);return!r&&t.required&&(n=n.notNull()),n}case`slug`:{let n=_(e,{length:255});return t.unique&&!r&&(n=n.unique()),!r&&t.required&&(n=n.notNull()),n}case`richtext`:{let n=p(e);return!r&&t.required&&(n=n.notNull()),n}case`json`:return d(e);default:return p(e)}}function z(e){let t=e.name,n={};for(let[t,r]of Object.entries(e.allFields))r.type!==`blocks`&&(n[t]=R(t,r));(e.scope===`team`||e.scope===`user`)&&(n._scopeId=g(`_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?f(t,n):f(t,n,e=>{let n={};for(let[i]of r)n[`idx_${t}_${i}`]=l(`idx_${t}_${i}`).on(e[i]);return i&&e.status&&e.createdAt&&(n[`idx_${t}_status_created`]=l(`idx_${t}_status_created`).on(e.status,e.createdAt)),n})}function B(e,t){let r=Object.entries(e.allFields).filter(([e,t])=>t.translatable);if(r.length===0)return null;let i=`${e.name}_translations`,a=t?g(`entity_id`).notNull().references(()=>n(t).id,{onDelete:`cascade`}):g(`entity_id`).notNull(),o={id:g(`id`).primaryKey().defaultRandom(),entityId:a,locale:_(`locale`,{length:10}).notNull()},s=r.some(([e,t])=>t.type===`slug`);for(let[e,t]of r)o[e]=R(e,t,{nullable:!0});return f(i,o,e=>({uniqueEntityLocale:h().on(e.entityId,e.locale),...s&&e.slug?{uniqueSlugLocale:h().on(e.slug,e.locale)}:{}}))}function V(e){return Object.values(e.allFields).some(e=>e.type===`blocks`)}function H(e,t){if(!V(e))return null;let r=`${e.name}_layout`,i=t?g(`entity_id`).notNull().references(()=>n(t).id,{onDelete:`cascade`}):g(`entity_id`).notNull();return f(r,{id:g(`id`).primaryKey().defaultRandom(),entityId:i,fieldName:_(`field_name`,{length:100}).notNull(),blockType:_(`block_type`,{length:100}).notNull(),sortOrder:u(`sort_order`).notNull().default(0),data:d(`data`),locale:_(`locale`,{length:10})},e=>({idx_entity_locale_sort:l(`idx_${r}_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){return e.behaviors?.some(e=>e.name===`publishable`)??!1}function K(e,t){return f(`${e.name}_locale_status`,{id:g(`id`).primaryKey().defaultRandom(),entityId:g(`entity_id`).notNull().references(()=>n(t).id,{onDelete:`cascade`}),locale:_(`locale`,{length:10}).notNull(),status:_(`status`,{length:20}).notNull().default(`draft`),publishedAt:m(`published_at`,{withTimezone:!0})},e=>({uniqueEntityLocale:h().on(e.entityId,e.locale)}))}function q(e,t){return f(`${e.name}_drafts`,{id:g(`id`).primaryKey().defaultRandom(),entityId:g(`entity_id`).notNull().references(()=>n(t).id,{onDelete:`cascade`}),locale:_(`locale`,{length:10}).notNull().default(`_`),data:d(`data`).notNull(),createdBy:_(`created_by`,{length:255}).notNull(),createdByName:_(`created_by_name`,{length:255}),createdAt:m(`created_at`,{withTimezone:!0}).notNull().defaultNow(),updatedAt:m(`updated_at`,{withTimezone:!0}).notNull().defaultNow()},e=>({uniqueEntityLocale:h().on(e.entityId,e.locale)}))}function J(e,t){return f(`${e.name}_versions`,{id:g(`id`).primaryKey().defaultRandom(),entityId:g(`entity_id`).notNull().references(()=>n(t).id,{onDelete:`cascade`}),version:u(`version`).notNull(),locale:_(`locale`,{length:10}).notNull().default(`_`),data:d(`data`).notNull(),delta:d(`delta`),status:_(`status`,{length:20}),createdBy:_(`created_by`,{length:255}),createdByName:_(`created_by_name`,{length:255}),createdAt:m(`created_at`,{withTimezone:!0}).notNull().defaultNow(),isAutosave:s(`is_autosave`).notNull().default(!1)},e=>({uniqueEntityVersionLocale:h().on(e.entityId,e.version,e.locale)}))}function Y(){return f(`toolkit_content_locks`,{id:g(`id`).primaryKey().defaultRandom(),entityType:_(`entity_type`,{length:100}).notNull(),entityId:_(`entity_id`,{length:255}).notNull(),locale:_(`locale`,{length:10}).notNull().default(`_`),lockedBy:_(`locked_by`,{length:255}).notNull(),lockedByName:_(`locked_by_name`,{length:255}),lockedAt:m(`locked_at`,{withTimezone:!0}).notNull().defaultNow(),expiresAt:m(`expires_at`,{withTimezone:!0}).notNull()},e=>({uniqueEntityLock:h().on(e.entityType,e.entityId,e.locale)}))}function X(e){let t=e.allFields;return Object.values(t).some(e=>e.type!==`blocks`||e.localized?!1:e.blocks?.some(e=>Object.values(e.fields).some(e=>e.translatable)))}function Z(e,t){let r=Object.values(e.allFields).filter(e=>e.type===`blocks`&&!(`localized`in e&&e.localized));if(r.length===0||!r.some(e=>e.type===`blocks`?e.blocks.some(e=>Object.values(e.fields).some(e=>e.translatable)):!1))return null;let i=`${e.name}_layout_translations`,a=t?g(`layout_id`).notNull().references(()=>n(t).id,{onDelete:`cascade`}):g(`layout_id`).notNull();return f(i,{id:g(`id`).primaryKey().defaultRandom(),layoutId:a,locale:_(`locale`,{length:10}).notNull(),fields:d(`fields`).notNull()},e=>({uniqueLayoutLocale:h().on(e.layoutId,e.locale)}))}function Q(e){let t={},n=!1;for(let r of e){let e=z(r);t[r.name]=e;let i=B(r,e);if(i&&(t[`${r.name}_translations`]=i),V(r)){let n=H(r,e);if(n){t[`${r.name}_layout`]=n;let e=Z(r,n);e&&(t[`${r.name}_layout_translations`]=e)}}U(r)&&(t[`${r.name}_versions`]=J(r,e)),W(r)&&(t[`${r.name}_locale_status`]=K(r,e)),G(r)&&(t[`${r.name}_drafts`]=q(r,e),n=!0)}return n&&(t.toolkit_content_locks=Y()),t.entity_refs=L,t}export{O as CountCache,F as ForbiddenError,I as ReferencedEntityError,b as auditable,D as behavior,N as buildCursorCondition,Q as buildEntitySchemaMap,M as decodeCursor,P as defineEntity,j as encodeCursor,k as estimateRowCount,v as field,Y as generateContentLocksSchema,q as generateDraftsSchema,H as generateLayoutSchema,Z as generateLayoutTranslationSchema,K as generateLocaleStatusSchema,z as generateSchema,B as generateTranslationSchema,J as generateVersionsSchema,V as hasBlocksFields,X as hasTranslatableBlocks,x as hierarchical,G as isPublishable,U as isVersionable,W as needsLocaleStatus,S as publishable,C as revisionable,T as sluggable,w as slugify,E as timestamped};
|
|
1
|
+
import{and as e,eq as t,getTableColumns as n,gt as r,lt as i,or as a,sql as o}from"drizzle-orm";import"@murumets-ee/db";import{boolean as s,doublePrecision as c,index as l,integer as u,jsonb as d,pgTable as f,text as p,timestamp as m,unique as h,uuid as g,varchar as _}from"drizzle-orm/pg-core";const v={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})};function y(){return{name:`auditable`,fields:{createdBy:v.text(),updatedBy:v.text(),createdAt:v.date({indexed:!0}),updatedAt:v.date({indexed:!0})},hooks:{beforeCreate:async(e,t)=>{e.createdAt=new Date,e.updatedAt=new Date;let n=t?.user?.id;return n&&(e.createdBy=n,e.updatedBy=n),e},beforeUpdate:async(e,t,n)=>{t.updatedAt=new Date;let r=n?.user?.id;return r&&(t.updatedBy=r),t}}}}function b(e){return{name:`hierarchical`,fields:{parentId:v.reference({entity:`_self`,required:!1}),path:v.text({indexed:!0,maxLength:2048}),depth:v.number({integer:!0,default:0,indexed:!0})},hooks:{beforeCreate:async e=>(e.parentId||(e.depth=0),e)}}}function x(){return{name:`publishable`,fields:{status:v.select({options:[`draft`,`published`],default:`draft`,indexed:!0}),publishedAt:v.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:v.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:v.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:v.date({indexed:!0}),updatedAt:v.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(o`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(o,s){let c=n(o),l=c[s.field];if(!l)return null;let u=s.direction===`desc`?i:r,d=u(l,s.value);if(!s.id)return d;let f=c.id;if(!f)return d;let p=u(f,s.id);return a(d,e(t(l,s.value),p))}function N(e){let t={};for(let n of e.behaviors||[])if(n.fields)for(let[e,r]of Object.entries(n.fields)){if(t[e]){console.warn(`Field '${e}' from behavior '${n.name}' conflicts with existing field. Skipping.`);continue}t[e]=r}let n={id:v.id(),...t,...e.fields};for(let[,e]of Object.entries(n))e.type===`slug`&&!e.translatable&&n[e.from]?.translatable&&(e.translatable=!0);return{...e,allFields:n}}var P=class extends Error{constructor(e){super(e),this.name=`ForbiddenError`}},F=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 I=f(`entity_refs`,{sourceEntity:_(`source_entity`,{length:100}).notNull(),sourceId:g(`source_id`).notNull(),sourceField:_(`source_field`,{length:100}).notNull(),targetEntity:_(`target_entity`,{length:100}).notNull(),targetId:g(`target_id`).notNull()},e=>[h(`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)]);function L(e,t,n){let r=n?.nullable??!1;switch(t.type){case`id`:return g(e).primaryKey().defaultRandom();case`text`:if(t.maxLength&&t.maxLength<=255){let n=_(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=p(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?u(e):c(e);return!r&&t.required&&(n=n.notNull()),!r&&t.default!==void 0&&(n=n.default(t.default)),n}case`boolean`:{let n=s(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=m(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=_(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 g(e).array();let n=g(e);return!r&&t.required&&(n=n.notNull()),n}case`media`:{let n=g(e);return!r&&t.required&&(n=n.notNull()),n}case`slug`:{let n=_(e,{length:255});return t.unique&&!r&&(n=n.unique()),!r&&t.required&&(n=n.notNull()),n}case`richtext`:{let n=p(e);return!r&&t.required&&(n=n.notNull()),n}case`json`:return d(e);default:return p(e)}}function R(e){let t=e.name,n={};for(let[t,r]of Object.entries(e.allFields))r.type!==`blocks`&&(n[t]=L(t,r));(e.scope===`team`||e.scope===`user`)&&(n._scopeId=g(`_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?f(t,n):f(t,n,e=>{let n={};for(let[i]of r)n[`idx_${t}_${i}`]=l(`idx_${t}_${i}`).on(e[i]);return i&&e.status&&e.createdAt&&(n[`idx_${t}_status_created`]=l(`idx_${t}_status_created`).on(e.status,e.createdAt)),n})}function z(e,t){let r=Object.entries(e.allFields).filter(([e,t])=>t.translatable);if(r.length===0)return null;let i=`${e.name}_translations`,a=t?g(`entity_id`).notNull().references(()=>n(t).id,{onDelete:`cascade`}):g(`entity_id`).notNull(),o={id:g(`id`).primaryKey().defaultRandom(),entityId:a,locale:_(`locale`,{length:10}).notNull()},s=r.some(([e,t])=>t.type===`slug`);for(let[e,t]of r)o[e]=L(e,t,{nullable:!0});return f(i,o,e=>({uniqueEntityLocale:h().on(e.entityId,e.locale),...s&&e.slug?{uniqueSlugLocale:h().on(e.slug,e.locale)}:{}}))}function B(e){return Object.values(e.allFields).some(e=>e.type===`blocks`)}function V(e,t){if(!B(e))return null;let r=`${e.name}_layout`,i=t?g(`entity_id`).notNull().references(()=>n(t).id,{onDelete:`cascade`}):g(`entity_id`).notNull();return f(r,{id:g(`id`).primaryKey().defaultRandom(),entityId:i,fieldName:_(`field_name`,{length:100}).notNull(),blockType:_(`block_type`,{length:100}).notNull(),sortOrder:u(`sort_order`).notNull().default(0),data:d(`data`),locale:_(`locale`,{length:10})},e=>({idx_entity_locale_sort:l(`idx_${r}_entity_locale_sort`).on(e.entityId,e.locale,e.sortOrder)}))}function H(e){return e.behaviors?.some(e=>e.name===`versionable`)??!1}function U(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 W(e){return e.behaviors?.some(e=>e.name===`publishable`)??!1}function G(e,t){return f(`${e.name}_locale_status`,{id:g(`id`).primaryKey().defaultRandom(),entityId:g(`entity_id`).notNull().references(()=>n(t).id,{onDelete:`cascade`}),locale:_(`locale`,{length:10}).notNull(),status:_(`status`,{length:20}).notNull().default(`draft`),publishedAt:m(`published_at`,{withTimezone:!0})},e=>({uniqueEntityLocale:h().on(e.entityId,e.locale)}))}function K(e,t){return f(`${e.name}_drafts`,{id:g(`id`).primaryKey().defaultRandom(),entityId:g(`entity_id`).notNull().references(()=>n(t).id,{onDelete:`cascade`}),locale:_(`locale`,{length:10}).notNull().default(`_`),data:d(`data`).notNull(),createdBy:_(`created_by`,{length:255}).notNull(),createdByName:_(`created_by_name`,{length:255}),createdAt:m(`created_at`,{withTimezone:!0}).notNull().defaultNow(),updatedAt:m(`updated_at`,{withTimezone:!0}).notNull().defaultNow()},e=>({uniqueEntityLocale:h().on(e.entityId,e.locale)}))}function q(e,t){return f(`${e.name}_versions`,{id:g(`id`).primaryKey().defaultRandom(),entityId:g(`entity_id`).notNull().references(()=>n(t).id,{onDelete:`cascade`}),version:u(`version`).notNull(),locale:_(`locale`,{length:10}).notNull().default(`_`),data:d(`data`).notNull(),delta:d(`delta`),status:_(`status`,{length:20}),createdBy:_(`created_by`,{length:255}),createdByName:_(`created_by_name`,{length:255}),createdAt:m(`created_at`,{withTimezone:!0}).notNull().defaultNow(),isAutosave:s(`is_autosave`).notNull().default(!1)},e=>({uniqueEntityVersionLocale:h().on(e.entityId,e.version,e.locale)}))}function J(){return f(`toolkit_content_locks`,{id:g(`id`).primaryKey().defaultRandom(),entityType:_(`entity_type`,{length:100}).notNull(),entityId:_(`entity_id`,{length:255}).notNull(),locale:_(`locale`,{length:10}).notNull().default(`_`),lockedBy:_(`locked_by`,{length:255}).notNull(),lockedByName:_(`locked_by_name`,{length:255}),lockedAt:m(`locked_at`,{withTimezone:!0}).notNull().defaultNow(),expiresAt:m(`expires_at`,{withTimezone:!0}).notNull()},e=>({uniqueEntityLock:h().on(e.entityType,e.entityId,e.locale)}))}function Y(e){let t=e.allFields;return Object.values(t).some(e=>e.type!==`blocks`||e.localized?!1:e.blocks?.some(e=>Object.values(e.fields).some(e=>e.translatable)))}function X(e,t){let r=Object.values(e.allFields).filter(e=>e.type===`blocks`&&!(`localized`in e&&e.localized));if(r.length===0||!r.some(e=>e.type===`blocks`?e.blocks.some(e=>Object.values(e.fields).some(e=>e.translatable)):!1))return null;let i=`${e.name}_layout_translations`,a=t?g(`layout_id`).notNull().references(()=>n(t).id,{onDelete:`cascade`}):g(`layout_id`).notNull();return f(i,{id:g(`id`).primaryKey().defaultRandom(),layoutId:a,locale:_(`locale`,{length:10}).notNull(),fields:d(`fields`).notNull()},e=>({uniqueLayoutLocale:h().on(e.layoutId,e.locale)}))}function Z(e){let t={},n=!1;for(let r of e){let e=R(r);t[r.name]=e;let i=z(r,e);if(i&&(t[`${r.name}_translations`]=i),B(r)){let n=V(r,e);if(n){t[`${r.name}_layout`]=n;let e=X(r,n);e&&(t[`${r.name}_layout_translations`]=e)}}H(r)&&(t[`${r.name}_versions`]=q(r,e)),U(r)&&(t[`${r.name}_locale_status`]=G(r,e)),W(r)&&(t[`${r.name}_drafts`]=K(r,e),n=!0)}return n&&(t.toolkit_content_locks=J()),t.entity_refs=I,t}export{D as CountCache,P as ForbiddenError,F as ReferencedEntityError,y as auditable,E as behavior,M as buildCursorCondition,Z as buildEntitySchemaMap,j as decodeCursor,N as defineEntity,A as encodeCursor,O as estimateRowCount,v as field,J as generateContentLocksSchema,K as generateDraftsSchema,V as generateLayoutSchema,X as generateLayoutTranslationSchema,G as generateLocaleStatusSchema,R as generateSchema,z as generateTranslationSchema,q as generateVersionsSchema,B as hasBlocksFields,Y as hasTranslatableBlocks,b as hierarchical,W as isPublishable,H as isVersionable,U as needsLocaleStatus,x as publishable,S as revisionable,w as sluggable,C as slugify,T as timestamped};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -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/shared/entity-data-ops.ts","../src/refs/errors.ts","../src/refs/schema.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 * String options → varchar column, number options → integer column.\n * e.g. field.select({ options: ['news', 'tutorial'] }) infers 'news' | 'tutorial'\n * e.g. field.select({ options: [0, 1, 2, 3] }) infers 0 | 1 | 2 | 3\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 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, getTableColumns, gt, lt, or, type SQL } from 'drizzle-orm'\nimport type { PgTable } 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 table: PgTable,\n cursor: DecodedCursor,\n): SQL | null {\n const cols = getTableColumns(table)\n const column = cols[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 = cols.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}\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 *\n * Inferring `F` via `Behavior<infer F1>` is unreliable for inline hook-only\n * behaviors: when the literal has no `fields` property, TS falls back to the\n * `Behavior` constraint bound (`Record<string, FieldConfig>`) rather than the\n * declared default. That bound would widen every `AllFields[K]` access to\n * `FieldConfig` and break per-field type inference downstream (e.g. reference\n * cardinality, select option literals). Instead, derive the field map from\n * the behavior's `fields` property directly — if absent/undefined, contribute\n * nothing (empty object) to the intersection.\n */\ntype BehaviorFieldsOf<B> = B extends { fields?: infer F }\n ? F extends Record<string, FieldConfig>\n ? F\n : // biome-ignore lint/complexity/noBannedTypes: empty object for hook-only behaviors (fields is undefined)\n {}\n : // biome-ignore lint/complexity/noBannedTypes: empty object for behaviors without `fields`\n {}\n\ntype ExtractBehaviorFields<B extends Behavior[]> = B extends [\n infer B1,\n ...infer Rest extends Behavior[],\n]\n ? BehaviorFieldsOf<B1> & 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\n for (const behavior of definition.behaviors || []) {\n if (behavior.fields) {\n for (const [fieldName, fieldConfig] of Object.entries(\n behavior.fields as Record<string, FieldConfig>,\n )) {\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\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 } as unknown as Entity<{ id: IdField } & ExtractBehaviorFields<B> & F>\n}\n","/**\n * Shared entity data operations.\n *\n * Extracted from AdminClient and QueryClient to eliminate duplication.\n * These are standalone functions that take an EntityContext — both clients\n * satisfy this interface via `this`.\n */\n\nimport { schemaRegistry } from '@murumets-ee/db'\nimport { and, eq, inArray, isNull, or, type SQL } from 'drizzle-orm'\nimport type { PgTableWithColumns } from 'drizzle-orm/pg-core'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport type { Entity } from '../define-entity.js'\nimport type { FieldConfig } from '../fields/base.js'\n\n// ---------------------------------------------------------------------------\n// Context interface — satisfied by both AdminClient and QueryClient via `this`\n// ---------------------------------------------------------------------------\n\n/**\n * Security context resolved per-request. Provided by the consumer\n * (e.g., via React.cache() in Next.js, or runAsCli for CLI).\n * The entity package has zero knowledge of how this is resolved.\n */\nexport interface SecurityContext {\n user: { id: string; groups: string[] }\n checker: (role: string, resource: string, action: string) => boolean\n scope?: { type: string; id: string }\n}\n\n/**\n * Function that resolves the current request's security context.\n * Injected at construction time — the entity package never imports\n * @murumets-ee/core or any framework-specific code.\n *\n * Returns undefined only when intentionally skipping enforcement\n * (should not happen in production — throw ForbiddenError instead).\n */\nexport type ContextResolver = () => SecurityContext | undefined | Promise<SecurityContext | undefined>\n\nexport interface EntityContext {\n entity: Entity\n db: PostgresJsDatabase\n // biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any>\n table: PgTableWithColumns<any>\n /** Resolves the current request's security context. */\n resolveContext?: ContextResolver\n}\n\n// ---------------------------------------------------------------------------\n// Field accessors\n// ---------------------------------------------------------------------------\n\n/** Get the entity's full field map with proper typing. Eliminates repeated casts. */\nexport function getAllFields(ctx: EntityContext): Record<string, FieldConfig> {\n return ctx.entity.allFields as Record<string, FieldConfig>\n}\n\n/** Get all blocks field definitions for this entity. */\nexport function getBlocksFields(ctx: EntityContext): Array<{ name: string; config: FieldConfig }> {\n return Object.entries(getAllFields(ctx))\n .filter(([_, config]) => config.type === 'blocks')\n .map(([name, config]) => ({ name, config }))\n}\n\n// ---------------------------------------------------------------------------\n// Translation merging\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 */\nexport async function mergeTranslations<T extends Record<string, unknown>>(\n ctx: EntityContext,\n entities: T[],\n locale: string,\n): Promise<T[]> {\n if (!entities.length) return entities\n\n const translationTableName = `${ctx.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 ctx.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(getAllFields(ctx))\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// Block loading\n// ---------------------------------------------------------------------------\n\nexport interface LoadBlocksOptions {\n defaultLocale?: string\n /**\n * When true (admin editing), clears untranslated translatable block fields\n * to empty string — signals incomplete translation in the editing UI.\n * When false (visitor-facing), preserves base data as fallback.\n */\n strictTranslations?: boolean\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 * - Per-locale layout (localized: true): loads rows matching the provided locale only\n */\nexport async function loadBlocks(\n ctx: EntityContext,\n entityIds: string[],\n locale?: string,\n options?: LoadBlocksOptions,\n): Promise<Map<string, Record<string, unknown[]>>> {\n const blocksFields = getBlocksFields(ctx)\n if (blocksFields.length === 0 || entityIds.length === 0) {\n return new Map()\n }\n\n const layoutTable = schemaRegistry.get(`${ctx.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 ctx.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(`${ctx.entity.name}_layout_translations`)\n if (layoutTransTable) {\n const layoutIds = rows.map((r) => r.id as string)\n const translations = await ctx.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 (strict mode only)\n let blockDefMap: Map<string, Record<string, FieldConfig>> | undefined\n if (options?.strictTranslations) {\n blockDefMap = new Map()\n for (const { config } of sharedFields) {\n if (config.type !== 'blocks') continue\n for (const def of config.blocks) {\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 && blockDefMap) {\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 ctx.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 */\nexport function attachBlocks(\n ctx: EntityContext,\n entities: Record<string, unknown>[],\n blocksMap: Map<string, Record<string, unknown[]>>,\n): void {\n const blocksFields = getBlocksFields(ctx)\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// Count cache key\n// ---------------------------------------------------------------------------\n\n/**\n * Build a cache key for a count query.\n * @param prefix - Optional namespace prefix (e.g. 'query' for QueryClient)\n * @param scopeId - Optional scope ID for multi-tenant isolation\n */\nexport function buildCountCacheKey(\n entityName: string,\n where?: SQL,\n prefix?: string,\n scopeId?: string,\n): string {\n let base = prefix ? `${prefix}:${entityName}` : entityName\n if (scopeId) base = `${base}@${scopeId}`\n if (!where) return base\n // Use SQL's toString() representation as a stable key.\n // This is a display string, not executed — safe for cache keying.\n return `${base}:${String(where)}`\n}\n\n// ---------------------------------------------------------------------------\n// ForbiddenError\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when a user lacks permission for an entity operation.\n * Consumers (api-handler) catch this and return HTTP 403.\n */\nexport class ForbiddenError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ForbiddenError'\n }\n}\n\n// ---------------------------------------------------------------------------\n// Context helpers — dynamic imports to avoid entity → core circular dep\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Context resolution — framework-agnostic\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the security context from the resolver on EntityContext.\n * Throws ForbiddenError if no resolver is configured or if it returns undefined.\n */\nasync function resolveSecurityContext(resolver: ContextResolver | undefined): Promise<SecurityContext> {\n if (!resolver) {\n throw new ForbiddenError(\n 'No context resolver configured on AdminClient/QueryClient. ' +\n 'Use createAdminClient() from @murumets-ee/core/clients, or pass a contextResolver in config.',\n )\n }\n\n const ctx = await resolver()\n\n if (!ctx) {\n throw new ForbiddenError(\n 'Context resolver returned no context. ' +\n 'Ensure auth is configured in your request handler, or use runAsCli() for CLI scripts.',\n )\n }\n\n return ctx\n}\n\n// ---------------------------------------------------------------------------\n// Permission enforcement\n// ---------------------------------------------------------------------------\n\n/**\n * Assert the current user has permission for the given action on the entity.\n * Uses the PermissionChecker from the resolved SecurityContext.\n */\nexport async function assertEntityAccess(\n entity: Entity,\n resolver: ContextResolver | undefined,\n action: 'view' | 'create' | 'update' | 'delete',\n): Promise<void> {\n const ctx = await resolveSecurityContext(resolver)\n\n const role = ctx.user.groups[0]\n if (!role) {\n throw new ForbiddenError(`User '${ctx.user.id}' has no role assigned.`)\n }\n\n if (!ctx.checker(role, entity.name, action)) {\n throw new ForbiddenError(\n `Forbidden: role '${role}' cannot ${action} '${entity.name}'`,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Scope filtering\n// ---------------------------------------------------------------------------\n\n/**\n * Get the current scope ID for a scoped entity.\n * Returns undefined for global entities.\n */\nexport async function getScopeId(\n entity: Entity,\n resolver: ContextResolver | undefined,\n): Promise<string | undefined> {\n if (!entity.scope || entity.scope === 'global') return undefined\n\n const ctx = await resolveSecurityContext(resolver)\n return ctx.scope?.id\n}\n\n/**\n * Build a WHERE condition for scope filtering.\n */\nexport function buildScopeCondition(\n ctx: EntityContext,\n scopeId: string,\n): SQL {\n return eq(ctx.table._scopeId, scopeId)\n}\n\n/**\n * Require scope for a scoped entity. Throws if no scope is in context.\n * Returns undefined for global entities.\n */\nexport async function requireScopeForEntity(\n entity: Entity,\n resolver: ContextResolver | undefined,\n): Promise<string | undefined> {\n if (!entity.scope || entity.scope === 'global') return undefined\n\n const ctx = await resolveSecurityContext(resolver)\n const scopeId = ctx.scope?.id\n\n if (!scopeId) {\n throw new ForbiddenError(\n `Entity '${entity.name}' requires scope '${entity.scope}' but no scope is set in context.`,\n )\n }\n\n return scopeId\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 * 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 * Schema generator\n * Converts entity definitions to Drizzle schemas.\n * Every field gets its own Postgres column — no JSONB blobs.\n */\n\nimport { getTableColumns } from 'drizzle-orm'\nimport type { PgColumnBuilderBase, PgTable } 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'\nimport { entityRefs } from './refs/schema.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 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 runtime translation table schema.\n * Each translatable field gets its own nullable column.\n *\n * When `parent` is provided, `entityId` gets a `.references()` FK — used\n * by `lumi migrate` so generated SQL includes `REFERENCES parent(id) ON\n * DELETE CASCADE`. Callers that only need the table shape for query\n * building (core/app.ts, content/plugin.ts) can omit `parent`.\n */\nexport function generateTranslationSchema(entity: Entity, parent?: PgTable) {\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 entityIdCol = parent\n ? uuid('entity_id')\n .notNull()\n .references(() => getTableColumns(parent)['id'], { onDelete: 'cascade' })\n : uuid('entity_id').notNull()\n const columns: Record<string, PgColumnBuilderBase> = {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: entityIdCol,\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 * 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 *\n * `parent` is optional — when provided, emits a `.references()` FK (for\n * migration generation); query-runtime callers can omit it.\n */\nexport function generateLayoutSchema(entity: Entity, parent?: PgTable) {\n if (!hasBlocksFields(entity)) return null\n\n const tableName = `${entity.name}_layout`\n const entityIdCol = parent\n ? uuid('entity_id')\n .notNull()\n .references(() => getTableColumns(parent)['id'], { onDelete: 'cascade' })\n : uuid('entity_id').notNull()\n\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: entityIdCol,\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 * 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// Runtime generators for infrastructure tables attached to an entity.\n//\n// Each one takes the parent entity's runtime `pgTable` so the\n// `.references()` FK is wired. These replace the old `generate*Code()`\n// string helpers that produced source lines to be concatenated into\n// `generated/schema.ts`.\n// ---------------------------------------------------------------------------\n\n/** Per-locale publish status table for publishable + translatable entities. */\nexport function generateLocaleStatusSchema(entity: Entity, parent: PgTable) {\n const tableName = `${entity.name}_locale_status`\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id')\n .notNull()\n .references(() => getTableColumns(parent)['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 },\n (table) => ({\n uniqueEntityLocale: unique().on(table.entityId, table.locale),\n }),\n )\n}\n\n/** Drafts overlay table for publishable entities. */\nexport function generateDraftsSchema(entity: Entity, parent: PgTable) {\n const tableName = `${entity.name}_drafts`\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id')\n .notNull()\n .references(() => getTableColumns(parent)['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 },\n (table) => ({\n uniqueEntityLocale: unique().on(table.entityId, table.locale),\n }),\n )\n}\n\n/** Versions history table for versionable entities. */\nexport function generateVersionsSchema(entity: Entity, parent: PgTable) {\n const tableName = `${entity.name}_versions`\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id')\n .notNull()\n .references(() => getTableColumns(parent)['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 },\n (table) => ({\n uniqueEntityVersionLocale: unique().on(table.entityId, table.version, table.locale),\n }),\n )\n}\n\n/**\n * Shared content locks table — one per project, independent of any entity.\n * Included exactly once in `buildRuntimeSchema()` when any entity is publishable.\n */\nexport function generateContentLocksSchema() {\n return pgTable(\n 'toolkit_content_locks',\n {\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 },\n (table) => ({\n uniqueEntityLock: unique().on(table.entityType, table.entityId, table.locale),\n }),\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 layout translation table for non-localized blocks with translatable fields.\n *\n * `layoutParent` is optional — when provided, emits a `.references()` FK\n * pointing at the corresponding `{entity}_layout` table (for migration\n * generation).\n */\nexport function generateLayoutTranslationSchema(entity: Entity, layoutParent?: PgTable) {\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 const layoutIdCol = layoutParent\n ? uuid('layout_id')\n .notNull()\n .references(() => getTableColumns(layoutParent)['id'], { onDelete: 'cascade' })\n : uuid('layout_id').notNull()\n\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n layoutId: layoutIdCol,\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\n/**\n * Build the full runtime schema map for a list of entities.\n *\n * Returns a `Record<string, PgTable>` suitable for feeding into\n * `drizzle-kit/api`'s `generateDrizzleJson()`. Includes every table\n * that would have been emitted into `generated/schema.ts` by the old\n * codegen pipeline: main entity, translations, layout, layout\n * translations, versions, drafts, locale status, and the shared\n * content locks table.\n *\n * This is the single entry point used by `lumi migrate` — plugin\n * internal tables are added separately by reading each plugin's\n * `tables` field.\n */\nexport function buildEntitySchemaMap(entities: Entity[]): Record<string, PgTable> {\n const schema: Record<string, PgTable> = {}\n let hasAnyPublishable = false\n\n for (const entity of entities) {\n const main = generateSchema(entity)\n schema[entity.name] = main\n\n const translations = generateTranslationSchema(entity, main)\n if (translations) schema[`${entity.name}_translations`] = translations\n\n if (hasBlocksFields(entity)) {\n const layout = generateLayoutSchema(entity, main)\n if (layout) {\n schema[`${entity.name}_layout`] = layout\n const layoutTrans = generateLayoutTranslationSchema(entity, layout)\n if (layoutTrans) schema[`${entity.name}_layout_translations`] = layoutTrans\n }\n }\n\n if (isVersionable(entity)) {\n schema[`${entity.name}_versions`] = generateVersionsSchema(entity, main)\n }\n if (needsLocaleStatus(entity)) {\n schema[`${entity.name}_locale_status`] = generateLocaleStatusSchema(entity, main)\n }\n if (isPublishable(entity)) {\n schema[`${entity.name}_drafts`] = generateDraftsSchema(entity, main)\n hasAnyPublishable = true\n }\n }\n\n if (hasAnyPublishable) {\n schema.toolkit_content_locks = generateContentLocksSchema()\n }\n\n // Universal reference tracking table — used by AdminClient.syncRefs()\n // for delete protection and cross-entity usage queries.\n schema.entity_refs = entityRefs\n\n return schema\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/shared/entity-data-ops.ts","../src/refs/errors.ts","../src/refs/schema.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 * String options → varchar column, number options → integer column.\n * e.g. field.select({ options: ['news', 'tutorial'] }) infers 'news' | 'tutorial'\n * e.g. field.select({ options: [0, 1, 2, 3] }) infers 0 | 1 | 2 | 3\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 * The user id is read from the BehaviorContext passed in by AdminClient.\n * AdminClient resolves it once per request from its `contextResolver` —\n * the entity package never imports @murumets-ee/core or touches ALS.\n */\n\nimport { field } from '../fields/builders.js'\nimport type { AuditableFields } from '../types/infer.js'\nimport type { Behavior } from './types.js'\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, ctx) => {\n data.createdAt = new Date()\n data.updatedAt = new Date()\n const userId = ctx?.user?.id\n if (userId) {\n data.createdBy = userId\n data.updatedBy = userId\n }\n return data\n },\n beforeUpdate: async (_id, data, ctx) => {\n data.updatedAt = new Date()\n const userId = ctx?.user?.id\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, BehaviorContext, 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, getTableColumns, gt, lt, or, type SQL } from 'drizzle-orm'\nimport type { PgTable } 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 table: PgTable,\n cursor: DecodedCursor,\n): SQL | null {\n const cols = getTableColumns(table)\n const column = cols[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 = cols.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}\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 *\n * Inferring `F` via `Behavior<infer F1>` is unreliable for inline hook-only\n * behaviors: when the literal has no `fields` property, TS falls back to the\n * `Behavior` constraint bound (`Record<string, FieldConfig>`) rather than the\n * declared default. That bound would widen every `AllFields[K]` access to\n * `FieldConfig` and break per-field type inference downstream (e.g. reference\n * cardinality, select option literals). Instead, derive the field map from\n * the behavior's `fields` property directly — if absent/undefined, contribute\n * nothing (empty object) to the intersection.\n */\ntype BehaviorFieldsOf<B> = B extends { fields?: infer F }\n ? F extends Record<string, FieldConfig>\n ? F\n : // biome-ignore lint/complexity/noBannedTypes: empty object for hook-only behaviors (fields is undefined)\n {}\n : // biome-ignore lint/complexity/noBannedTypes: empty object for behaviors without `fields`\n {}\n\ntype ExtractBehaviorFields<B extends Behavior[]> = B extends [\n infer B1,\n ...infer Rest extends Behavior[],\n]\n ? BehaviorFieldsOf<B1> & 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\n for (const behavior of definition.behaviors || []) {\n if (behavior.fields) {\n for (const [fieldName, fieldConfig] of Object.entries(\n behavior.fields as Record<string, FieldConfig>,\n )) {\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\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 } as unknown as Entity<{ id: IdField } & ExtractBehaviorFields<B> & F>\n}\n","/**\n * Shared entity data operations.\n *\n * Extracted from AdminClient and QueryClient to eliminate duplication.\n * These are standalone functions that take an EntityContext — both clients\n * satisfy this interface via `this`.\n */\n\nimport { schemaRegistry } from '@murumets-ee/db'\nimport { and, eq, inArray, isNull, or, type SQL } from 'drizzle-orm'\nimport type { PgTableWithColumns } from 'drizzle-orm/pg-core'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport type { Entity } from '../define-entity.js'\nimport type { FieldConfig } from '../fields/base.js'\n\n// ---------------------------------------------------------------------------\n// Context interface — satisfied by both AdminClient and QueryClient via `this`\n// ---------------------------------------------------------------------------\n\n/**\n * Security context resolved per-request. Provided by the consumer\n * (e.g., via React.cache() in Next.js, or runAsCli for CLI).\n * The entity package has zero knowledge of how this is resolved.\n */\nexport interface SecurityContext {\n user: { id: string; groups: string[]; name?: string; email?: string }\n checker: (role: string, resource: string, action: string) => boolean\n scope?: { type: string; id: string }\n}\n\n/**\n * Function that resolves the current request's security context.\n * Injected at construction time — the entity package never imports\n * @murumets-ee/core or any framework-specific code.\n *\n * Returns undefined only when intentionally skipping enforcement\n * (should not happen in production — throw ForbiddenError instead).\n */\nexport type ContextResolver = () => SecurityContext | undefined | Promise<SecurityContext | undefined>\n\nexport interface EntityContext {\n entity: Entity\n db: PostgresJsDatabase\n // biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any>\n table: PgTableWithColumns<any>\n /** Resolves the current request's security context. */\n resolveContext?: ContextResolver\n}\n\n// ---------------------------------------------------------------------------\n// Field accessors\n// ---------------------------------------------------------------------------\n\n/** Get the entity's full field map with proper typing. Eliminates repeated casts. */\nexport function getAllFields(ctx: EntityContext): Record<string, FieldConfig> {\n return ctx.entity.allFields as Record<string, FieldConfig>\n}\n\n/** Get all blocks field definitions for this entity. */\nexport function getBlocksFields(ctx: EntityContext): Array<{ name: string; config: FieldConfig }> {\n return Object.entries(getAllFields(ctx))\n .filter(([_, config]) => config.type === 'blocks')\n .map(([name, config]) => ({ name, config }))\n}\n\n// ---------------------------------------------------------------------------\n// Translation merging\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 */\nexport async function mergeTranslations<T extends Record<string, unknown>>(\n ctx: EntityContext,\n entities: T[],\n locale: string,\n): Promise<T[]> {\n if (!entities.length) return entities\n\n const translationTableName = `${ctx.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 ctx.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(getAllFields(ctx))\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// Block loading\n// ---------------------------------------------------------------------------\n\nexport interface LoadBlocksOptions {\n defaultLocale?: string\n /**\n * When true (admin editing), clears untranslated translatable block fields\n * to empty string — signals incomplete translation in the editing UI.\n * When false (visitor-facing), preserves base data as fallback.\n */\n strictTranslations?: boolean\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 * - Per-locale layout (localized: true): loads rows matching the provided locale only\n */\nexport async function loadBlocks(\n ctx: EntityContext,\n entityIds: string[],\n locale?: string,\n options?: LoadBlocksOptions,\n): Promise<Map<string, Record<string, unknown[]>>> {\n const blocksFields = getBlocksFields(ctx)\n if (blocksFields.length === 0 || entityIds.length === 0) {\n return new Map()\n }\n\n const layoutTable = schemaRegistry.get(`${ctx.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 ctx.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(`${ctx.entity.name}_layout_translations`)\n if (layoutTransTable) {\n const layoutIds = rows.map((r) => r.id as string)\n const translations = await ctx.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 (strict mode only)\n let blockDefMap: Map<string, Record<string, FieldConfig>> | undefined\n if (options?.strictTranslations) {\n blockDefMap = new Map()\n for (const { config } of sharedFields) {\n if (config.type !== 'blocks') continue\n for (const def of config.blocks) {\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 && blockDefMap) {\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 ctx.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 */\nexport function attachBlocks(\n ctx: EntityContext,\n entities: Record<string, unknown>[],\n blocksMap: Map<string, Record<string, unknown[]>>,\n): void {\n const blocksFields = getBlocksFields(ctx)\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// Count cache key\n// ---------------------------------------------------------------------------\n\n/**\n * Build a cache key for a count query.\n * @param prefix - Optional namespace prefix (e.g. 'query' for QueryClient)\n * @param scopeId - Optional scope ID for multi-tenant isolation\n */\nexport function buildCountCacheKey(\n entityName: string,\n where?: SQL,\n prefix?: string,\n scopeId?: string,\n): string {\n let base = prefix ? `${prefix}:${entityName}` : entityName\n if (scopeId) base = `${base}@${scopeId}`\n if (!where) return base\n // Use SQL's toString() representation as a stable key.\n // This is a display string, not executed — safe for cache keying.\n return `${base}:${String(where)}`\n}\n\n// ---------------------------------------------------------------------------\n// ForbiddenError\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when a user lacks permission for an entity operation.\n * Consumers (api-handler) catch this and return HTTP 403.\n */\nexport class ForbiddenError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ForbiddenError'\n }\n}\n\n// ---------------------------------------------------------------------------\n// Context helpers — dynamic imports to avoid entity → core circular dep\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Context resolution — framework-agnostic\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the security context from the resolver on EntityContext.\n * Throws ForbiddenError if no resolver is configured or if it returns undefined.\n */\nasync function resolveSecurityContext(resolver: ContextResolver | undefined): Promise<SecurityContext> {\n if (!resolver) {\n throw new ForbiddenError(\n 'No context resolver configured on AdminClient/QueryClient. ' +\n 'Use createAdminClient() from @murumets-ee/core/clients, or pass a contextResolver in config.',\n )\n }\n\n const ctx = await resolver()\n\n if (!ctx) {\n throw new ForbiddenError(\n 'Context resolver returned no context. ' +\n 'Ensure auth is configured in your request handler, or use runAsCli() for CLI scripts.',\n )\n }\n\n return ctx\n}\n\n// ---------------------------------------------------------------------------\n// Permission enforcement\n// ---------------------------------------------------------------------------\n\n/**\n * Assert the current user has permission for the given action on the entity.\n * Uses the PermissionChecker from the resolved SecurityContext.\n */\nexport async function assertEntityAccess(\n entity: Entity,\n resolver: ContextResolver | undefined,\n action: 'view' | 'create' | 'update' | 'delete',\n): Promise<void> {\n const ctx = await resolveSecurityContext(resolver)\n\n const role = ctx.user.groups[0]\n if (!role) {\n throw new ForbiddenError(`User '${ctx.user.id}' has no role assigned.`)\n }\n\n if (!ctx.checker(role, entity.name, action)) {\n throw new ForbiddenError(\n `Forbidden: role '${role}' cannot ${action} '${entity.name}'`,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Scope filtering\n// ---------------------------------------------------------------------------\n\n/**\n * Get the current scope ID for a scoped entity.\n * Returns undefined for global entities.\n */\nexport async function getScopeId(\n entity: Entity,\n resolver: ContextResolver | undefined,\n): Promise<string | undefined> {\n if (!entity.scope || entity.scope === 'global') return undefined\n\n const ctx = await resolveSecurityContext(resolver)\n return ctx.scope?.id\n}\n\n/**\n * Build a WHERE condition for scope filtering.\n */\nexport function buildScopeCondition(\n ctx: EntityContext,\n scopeId: string,\n): SQL {\n return eq(ctx.table._scopeId, scopeId)\n}\n\n/**\n * Require scope for a scoped entity. Throws if no scope is in context.\n * Returns undefined for global entities.\n */\nexport async function requireScopeForEntity(\n entity: Entity,\n resolver: ContextResolver | undefined,\n): Promise<string | undefined> {\n if (!entity.scope || entity.scope === 'global') return undefined\n\n const ctx = await resolveSecurityContext(resolver)\n const scopeId = ctx.scope?.id\n\n if (!scopeId) {\n throw new ForbiddenError(\n `Entity '${entity.name}' requires scope '${entity.scope}' but no scope is set in context.`,\n )\n }\n\n return scopeId\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 * 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 * Schema generator\n * Converts entity definitions to Drizzle schemas.\n * Every field gets its own Postgres column — no JSONB blobs.\n */\n\nimport { getTableColumns } from 'drizzle-orm'\nimport type { PgColumnBuilderBase, PgTable } 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'\nimport { entityRefs } from './refs/schema.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 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 runtime translation table schema.\n * Each translatable field gets its own nullable column.\n *\n * When `parent` is provided, `entityId` gets a `.references()` FK — used\n * by `lumi migrate` so generated SQL includes `REFERENCES parent(id) ON\n * DELETE CASCADE`. Callers that only need the table shape for query\n * building (core/app.ts, content/plugin.ts) can omit `parent`.\n */\nexport function generateTranslationSchema(entity: Entity, parent?: PgTable) {\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 entityIdCol = parent\n ? uuid('entity_id')\n .notNull()\n .references(() => getTableColumns(parent)['id'], { onDelete: 'cascade' })\n : uuid('entity_id').notNull()\n const columns: Record<string, PgColumnBuilderBase> = {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: entityIdCol,\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 * 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 *\n * `parent` is optional — when provided, emits a `.references()` FK (for\n * migration generation); query-runtime callers can omit it.\n */\nexport function generateLayoutSchema(entity: Entity, parent?: PgTable) {\n if (!hasBlocksFields(entity)) return null\n\n const tableName = `${entity.name}_layout`\n const entityIdCol = parent\n ? uuid('entity_id')\n .notNull()\n .references(() => getTableColumns(parent)['id'], { onDelete: 'cascade' })\n : uuid('entity_id').notNull()\n\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: entityIdCol,\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 * 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// Runtime generators for infrastructure tables attached to an entity.\n//\n// Each one takes the parent entity's runtime `pgTable` so the\n// `.references()` FK is wired. These replace the old `generate*Code()`\n// string helpers that produced source lines to be concatenated into\n// `generated/schema.ts`.\n// ---------------------------------------------------------------------------\n\n/** Per-locale publish status table for publishable + translatable entities. */\nexport function generateLocaleStatusSchema(entity: Entity, parent: PgTable) {\n const tableName = `${entity.name}_locale_status`\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id')\n .notNull()\n .references(() => getTableColumns(parent)['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 },\n (table) => ({\n uniqueEntityLocale: unique().on(table.entityId, table.locale),\n }),\n )\n}\n\n/** Drafts overlay table for publishable entities. */\nexport function generateDraftsSchema(entity: Entity, parent: PgTable) {\n const tableName = `${entity.name}_drafts`\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id')\n .notNull()\n .references(() => getTableColumns(parent)['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 },\n (table) => ({\n uniqueEntityLocale: unique().on(table.entityId, table.locale),\n }),\n )\n}\n\n/** Versions history table for versionable entities. */\nexport function generateVersionsSchema(entity: Entity, parent: PgTable) {\n const tableName = `${entity.name}_versions`\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n entityId: uuid('entity_id')\n .notNull()\n .references(() => getTableColumns(parent)['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 },\n (table) => ({\n uniqueEntityVersionLocale: unique().on(table.entityId, table.version, table.locale),\n }),\n )\n}\n\n/**\n * Shared content locks table — one per project, independent of any entity.\n * Included exactly once in `buildRuntimeSchema()` when any entity is publishable.\n */\nexport function generateContentLocksSchema() {\n return pgTable(\n 'toolkit_content_locks',\n {\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 },\n (table) => ({\n uniqueEntityLock: unique().on(table.entityType, table.entityId, table.locale),\n }),\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 layout translation table for non-localized blocks with translatable fields.\n *\n * `layoutParent` is optional — when provided, emits a `.references()` FK\n * pointing at the corresponding `{entity}_layout` table (for migration\n * generation).\n */\nexport function generateLayoutTranslationSchema(entity: Entity, layoutParent?: PgTable) {\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 const layoutIdCol = layoutParent\n ? uuid('layout_id')\n .notNull()\n .references(() => getTableColumns(layoutParent)['id'], { onDelete: 'cascade' })\n : uuid('layout_id').notNull()\n\n return pgTable(\n tableName,\n {\n id: uuid('id').primaryKey().defaultRandom(),\n layoutId: layoutIdCol,\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\n/**\n * Build the full runtime schema map for a list of entities.\n *\n * Returns a `Record<string, PgTable>` suitable for feeding into\n * `drizzle-kit/api`'s `generateDrizzleJson()`. Includes every table\n * that would have been emitted into `generated/schema.ts` by the old\n * codegen pipeline: main entity, translations, layout, layout\n * translations, versions, drafts, locale status, and the shared\n * content locks table.\n *\n * This is the single entry point used by `lumi migrate` — plugin\n * internal tables are added separately by reading each plugin's\n * `tables` field.\n */\nexport function buildEntitySchemaMap(entities: Entity[]): Record<string, PgTable> {\n const schema: Record<string, PgTable> = {}\n let hasAnyPublishable = false\n\n for (const entity of entities) {\n const main = generateSchema(entity)\n schema[entity.name] = main\n\n const translations = generateTranslationSchema(entity, main)\n if (translations) schema[`${entity.name}_translations`] = translations\n\n if (hasBlocksFields(entity)) {\n const layout = generateLayoutSchema(entity, main)\n if (layout) {\n schema[`${entity.name}_layout`] = layout\n const layoutTrans = generateLayoutTranslationSchema(entity, layout)\n if (layoutTrans) schema[`${entity.name}_layout_translations`] = layoutTrans\n }\n }\n\n if (isVersionable(entity)) {\n schema[`${entity.name}_versions`] = generateVersionsSchema(entity, main)\n }\n if (needsLocaleStatus(entity)) {\n schema[`${entity.name}_locale_status`] = generateLocaleStatusSchema(entity, main)\n }\n if (isPublishable(entity)) {\n schema[`${entity.name}_drafts`] = generateDraftsSchema(entity, main)\n hasAnyPublishable = true\n }\n }\n\n if (hasAnyPublishable) {\n schema.toolkit_content_locks = generateContentLocksSchema()\n }\n\n // Universal reference tracking table — used by AdminClient.syncRefs()\n // for delete protection and cross-entity usage queries.\n schema.entity_refs = entityRefs\n\n return schema\n}\n"],"mappings":"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"}
|
package/dist/query/index.d.mts
CHANGED
|
@@ -187,16 +187,29 @@ interface BlocksField extends BaseFieldConfig {
|
|
|
187
187
|
type FieldConfig = IdField | TextField | NumberField | BooleanField | DateField | SelectField | ReferenceField | MediaField | RichTextField | SlugField | JsonField | BlocksField;
|
|
188
188
|
//#endregion
|
|
189
189
|
//#region src/behaviors/types.d.ts
|
|
190
|
+
/**
|
|
191
|
+
* Context passed to behavior hooks. Resolved once per request by AdminClient
|
|
192
|
+
* from its `contextResolver` and forwarded into every hook so behaviors never
|
|
193
|
+
* have to reach into AsyncLocalStorage themselves — that pattern breaks under
|
|
194
|
+
* bundlers (e.g. Turbopack) that duplicate module instances across boundaries.
|
|
195
|
+
*/
|
|
196
|
+
interface BehaviorContext {
|
|
197
|
+
user?: {
|
|
198
|
+
id: string;
|
|
199
|
+
name?: string;
|
|
200
|
+
email?: string;
|
|
201
|
+
};
|
|
202
|
+
}
|
|
190
203
|
interface Behavior<F extends Record<string, FieldConfig> = {}> {
|
|
191
204
|
name: string;
|
|
192
205
|
fields?: F;
|
|
193
206
|
hooks?: {
|
|
194
|
-
beforeCreate?: (data: Record<string, unknown
|
|
195
|
-
afterCreate?: (entity: Record<string, unknown
|
|
196
|
-
beforeUpdate?: (id: string, data: Record<string, unknown
|
|
197
|
-
afterUpdate?: (entity: Record<string, unknown
|
|
198
|
-
beforeDelete?: (id: string) => Promise<void>;
|
|
199
|
-
afterDelete?: (id: string) => Promise<void>;
|
|
207
|
+
beforeCreate?: (data: Record<string, unknown>, ctx?: BehaviorContext) => Promise<Record<string, unknown>>;
|
|
208
|
+
afterCreate?: (entity: Record<string, unknown>, ctx?: BehaviorContext) => Promise<void>;
|
|
209
|
+
beforeUpdate?: (id: string, data: Record<string, unknown>, ctx?: BehaviorContext) => Promise<Record<string, unknown>>;
|
|
210
|
+
afterUpdate?: (entity: Record<string, unknown>, ctx?: BehaviorContext) => Promise<void>;
|
|
211
|
+
beforeDelete?: (id: string, ctx?: BehaviorContext) => Promise<void>;
|
|
212
|
+
afterDelete?: (id: string, ctx?: BehaviorContext) => Promise<void>;
|
|
200
213
|
};
|
|
201
214
|
}
|
|
202
215
|
//#endregion
|
|
@@ -232,6 +245,8 @@ interface SecurityContext {
|
|
|
232
245
|
user: {
|
|
233
246
|
id: string;
|
|
234
247
|
groups: string[];
|
|
248
|
+
name?: string;
|
|
249
|
+
email?: string;
|
|
235
250
|
};
|
|
236
251
|
checker: (role: string, resource: string, action: string) => boolean;
|
|
237
252
|
scope?: {
|
|
@@ -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/shared/entity-data-ops.ts","../../src/types/infer.ts","../../src/types/logger.ts","../../src/query/client.ts"],"mappings":";;;;;;;;AAiBA;;;;;;;;;;;UAAiB,cAAA;EACf,GAAA,CAAI,GAAA;EACJ,GAAA,CAAI,GAAA,UAAa,KAAA;EACjB,UAAA,CAAW,MAAA;AAAA;;;;UCKI,WAAA;;EAEf,KAAA;EAF0B;EAI1B,KAAA;EAJ0B;EAM1B,SAAA;EAFA;EAIA,EAAA;AAAA;;;;;;;UC7Be,iBAAA;EFaA;EEXf,KAAA;;EAEA,KAAA;EFUA;EERA,aAAA;EFSA;EEPA,IAAA;EFOiB;EELjB,WAAA;EFMW;EEJX,MAAA;EFIyB;EEFzB,YAAA;;EAEA,aAAA;EDKe;ECHf,cAAA,GAAiB,MAAA;IAAiB,KAAA;IAAgB,WAAA;IAAsB,WAAA;EAAA;EDSxE;ECPA,WAAA;EDSE;ECPF,oBAAA;;EAEA,QAAA;;EAEA,UAAA;EA1BgC;EA4BhC,aAAA;EAVuB;EAYvB,SAAA;EA1BA;;;;;EAgCA,MAAA;EApBA;;;;;EA0BA,YAAA;EAtBA;;;;EA2BA,iBAAA;AAAA;;;;;;;UC9Ce,eAAA;EACf,QAAA;EACA,OAAA;EACA,YAAA;EACA,OAAA;EACA,MAAA;EACA,MAAA;IACE,IAAA;IACA,IAAA;EAAA;AAAA;AAAA,UAIa,OAAA,SAAgB,eAAA;EAC/B,IAAA;AAAA;AAAA,UAGe,SAAA,SAAkB,eAAA;EACjC,IAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA,GAAU,MAAA;AAAA;AAAA,UAGK,WAAA,SAAoB,eAAA;EACnC,IAAA;EACA,GAAA;EACA,GAAA;EACA,OAAA;AAAA;AAAA,UAGe,YAAA,SAAqB,eAAA;EACpC,IAAA;AAAA;AAAA,UAGe,SAAA,SAAkB,eAAA;EACjC,IAAA;EACA,OAAA,GAAU,IAAA;EACV,OAAA,GAAU,IAAA;AAAA;AAAA,UAGK,WAAA,SAAoB,eAAA;EACnC,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,cAAA,SAAuB,eAAA;EACtC,IAAA;EACA,MAAA;EACA,WAAA;EACA,QAAA;AAAA;AAAA,UAGe,UAAA,SAAmB,eAAA;EAClC,IAAA;EACA,MAAA;EACA,OAAA;AAAA;AAAA,UAGe,aAAA,SAAsB,eAAA;EACrC,IAAA;EACA,MAAA;AAAA;AAAA,UAGe,SAAA,SAAkB,eAAA;EACjC,IAAA;EACA,IAAA;EACA,MAAA;AAAA;;;;AAlEF;UAyEiB,kBAAA;EACf,IAAA;EACA,MAAA,EAAQ,MAAA,SAAe,WAAA;AAAA;;;;;;;;;UAWR,SAAA,SAAkB,eAAA;EACjC,IAAA;AAAA;AAAA,UAGe,WAAA,SAAoB,eAAA;EACnC,IAAA;EACA,MAAA,WAAiB,kBAAA;EACjB,GAAA;EACA,GAAA;EACA,SAAA;AAAA;AAAA,KAGU,WAAA,GACR,OAAA,GACA,SAAA,GACA,WAAA,GACA,YAAA,GACA,SAAA,GACA,WAAA,GACA,cAAA,GACA,UAAA,GACA,aAAA,GACA,SAAA,GACA,SAAA,GACA,WAAA
|
|
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/shared/entity-data-ops.ts","../../src/types/infer.ts","../../src/types/logger.ts","../../src/query/client.ts"],"mappings":"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"}
|