@murumets-ee/entity 0.7.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.
@@ -189,16 +189,29 @@ interface BlocksField extends BaseFieldConfig {
189
189
  type FieldConfig = IdField | TextField | NumberField | BooleanField | DateField | SelectField | ReferenceField | MediaField | RichTextField | SlugField | JsonField | BlocksField;
190
190
  //#endregion
191
191
  //#region src/behaviors/types.d.ts
192
+ /**
193
+ * Context passed to behavior hooks. Resolved once per request by AdminClient
194
+ * from its `contextResolver` and forwarded into every hook so behaviors never
195
+ * have to reach into AsyncLocalStorage themselves — that pattern breaks under
196
+ * bundlers (e.g. Turbopack) that duplicate module instances across boundaries.
197
+ */
198
+ interface BehaviorContext {
199
+ user?: {
200
+ id: string;
201
+ name?: string;
202
+ email?: string;
203
+ };
204
+ }
192
205
  interface Behavior<F extends Record<string, FieldConfig> = {}> {
193
206
  name: string;
194
207
  fields?: F;
195
208
  hooks?: {
196
- beforeCreate?: (data: Record<string, unknown>) => Promise<Record<string, unknown>>;
197
- afterCreate?: (entity: Record<string, unknown>) => Promise<void>;
198
- beforeUpdate?: (id: string, data: Record<string, unknown>) => Promise<Record<string, unknown>>;
199
- afterUpdate?: (entity: Record<string, unknown>) => Promise<void>;
200
- beforeDelete?: (id: string) => Promise<void>;
201
- afterDelete?: (id: string) => Promise<void>;
209
+ beforeCreate?: (data: Record<string, unknown>, ctx?: BehaviorContext) => Promise<Record<string, unknown>>;
210
+ afterCreate?: (entity: Record<string, unknown>, ctx?: BehaviorContext) => Promise<void>;
211
+ beforeUpdate?: (id: string, data: Record<string, unknown>, ctx?: BehaviorContext) => Promise<Record<string, unknown>>;
212
+ afterUpdate?: (entity: Record<string, unknown>, ctx?: BehaviorContext) => Promise<void>;
213
+ beforeDelete?: (id: string, ctx?: BehaviorContext) => Promise<void>;
214
+ afterDelete?: (id: string, ctx?: BehaviorContext) => Promise<void>;
202
215
  };
203
216
  }
204
217
  //#endregion
@@ -234,6 +247,8 @@ interface SecurityContext {
234
247
  user: {
235
248
  id: string;
236
249
  groups: string[];
250
+ name?: string;
251
+ email?: string;
237
252
  };
238
253
  checker: (role: string, resource: string, action: string) => boolean;
239
254
  scope?: {
@@ -306,6 +321,14 @@ interface Logger {
306
321
  }
307
322
  //#endregion
308
323
  //#region src/admin/client.d.ts
324
+ /**
325
+ * Resolves the registry of all entities in the running app — used by cascade
326
+ * delete to look up `onDelete` strategy on referencing fields. Injected as a
327
+ * function so the entity package never has to import `@murumets-ee/core`
328
+ * (which would create a circular dependency and force runtime tricks that
329
+ * break under bundlers like Turbopack).
330
+ */
331
+ type EntityResolver = () => Map<string, Entity> | undefined;
309
332
  interface AdminClientConfig<AllFields extends Record<string, FieldConfig> = Record<string, FieldConfig>> {
310
333
  entity: Entity<AllFields>;
311
334
  db: PostgresJsDatabase;
@@ -318,6 +341,13 @@ interface AdminClientConfig<AllFields extends Record<string, FieldConfig> = Reco
318
341
  * For direct `new AdminClient()` usage, pass your own resolver or use `runAsCli()`.
319
342
  */
320
343
  contextResolver?: ContextResolver;
344
+ /**
345
+ * Resolves the running app's entity registry. Used by cascade delete to
346
+ * read each referencing field's `onDelete` strategy. When omitted, all
347
+ * incoming references are treated as `restrict` — a safe default that
348
+ * blocks deletes rather than guessing.
349
+ */
350
+ entityResolver?: EntityResolver;
321
351
  }
322
352
  interface FindManyOptions {
323
353
  where?: SQL | undefined;
@@ -370,8 +400,16 @@ declare class AdminClient<AllFields extends Record<string, FieldConfig> = Record
370
400
  private table;
371
401
  private countCache?;
372
402
  private contextResolver?;
403
+ private entityResolver?;
373
404
  /** Shared context for entity-data-ops functions. */
374
405
  private get ctx();
406
+ /**
407
+ * Resolve the BehaviorContext passed to lifecycle hooks. Reads the user
408
+ * from the configured `contextResolver`; returns `undefined` (not throw)
409
+ * when no resolver is configured so behavior tests and bare CRUD calls
410
+ * still work — security enforcement is the resolver's job, not this helper's.
411
+ */
412
+ private resolveBehaviorContext;
375
413
  constructor(config: AdminClientConfig<AllFields>);
376
414
  /**
377
415
  * Create a new entity
@@ -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/admin/client.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/count-cache.ts","../../src/cursor.ts","../../src/admin-config.ts","../../src/fields/base.ts","../../src/behaviors/types.ts","../../src/define-entity.ts","../../src/shared/entity-data-ops.ts","../../src/types/infer.ts","../../src/types/logger.ts","../../src/admin/client.ts"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{schemaRegistry as e}from"@murumets-ee/db";import{and as t,eq as n,getTableColumns as r,gt as i,inArray as a,isNull as o,lt as s,or as c,sql as l}from"drizzle-orm";import{index as u,pgTable as d,unique as f,uuid as p,varchar as m}from"drizzle-orm/pg-core";import{z as h}from"zod";function g(e,a){let o=r(e),l=o[a.field];if(!l)return null;let u=a.direction===`desc`?s:i,d=u(l,a.value);if(!a.id)return d;let f=o.id;if(!f)return d;let p=u(f,a.id);return c(d,t(n(l,a.value),p))}function _(e,t,n){if(!t)return null;let r={},i=n?.select||Object.keys(e.allFields);for(let a of i){if(!n?.includeInternal&&a.startsWith(`_`))continue;let i=e.allFields[a];i&&i.type!==`blocks`&&(r[a]=t[a])}return r}function v(e,t,n){return t.map(t=>_(e,t,n))}var y=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 b=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function x(e,t){let n=[],r=new Set;function i(e,t,i){if(!b.test(i))return;let a=`${e}|${t}|${i}`;r.has(a)||(r.add(a),n.push({sourceField:e,targetEntity:t,targetId:i}))}for(let[n,r]of Object.entries(e)){let e=t[n];if(e!=null)switch(r.type){case`media`:typeof e==`string`&&i(n,`media`,e);break;case`reference`:{let t=r;if(t.cardinality===`many`&&Array.isArray(e))for(let r of e)typeof r==`string`&&i(n,t.entity,r);else typeof e==`string`&&i(n,t.entity,e);break}case`blocks`:{let t=r;if(!Array.isArray(e))break;for(let r of e){let e=r,a=e._block;if(!a)continue;let o=t.blocks.find(e=>e.slug===a);if(o)for(let[t,r]of Object.entries(o.fields)){let a=e[t];if(a!=null&&(r.type===`media`&&typeof a==`string`&&i(n,`media`,a),r.type===`reference`)){let e=r;if(e.cardinality===`many`&&Array.isArray(a))for(let t of a)typeof t==`string`&&i(n,e.entity,t);else typeof a==`string`&&i(n,e.entity,a)}}}break}}}return n}const S=d(`entity_refs`,{sourceEntity:m(`source_entity`,{length:100}).notNull(),sourceId:p(`source_id`).notNull(),sourceField:m(`source_field`,{length:100}).notNull(),targetEntity:m(`target_entity`,{length:100}).notNull(),targetId:p(`target_id`).notNull()},e=>[f(`uq_entity_refs`).on(e.sourceEntity,e.sourceId,e.sourceField,e.targetEntity,e.targetId),u(`idx_entity_refs_target`).on(e.targetEntity,e.targetId),u(`idx_entity_refs_source`).on(e.sourceEntity,e.sourceId)]);function C(e){return e.entity.allFields}function w(e){return Object.entries(C(e)).filter(([e,t])=>t.type===`blocks`).map(([e,t])=>({name:e,config:t}))}async function T(r,i,o){if(!i.length)return i;let s=`${r.entity.name}_translations`,c=e.get(s);if(!c)return i;let l=i.map(e=>e.id),u=await r.db.select().from(c).where(t(a(c.entityId,l),n(c.locale,o))),d=Object.entries(C(r)).filter(([e,t])=>t.translatable).map(([e])=>e),f=new Map;for(let e of u){let t={};for(let n of d){let r=e[n];r!=null&&(t[n]=r)}f.set(e.entityId,t)}return i.map(e=>{let t=f.get(e.id);return t?{...e,...t}:e})}async function E(r,i,s,l){let u=w(r);if(u.length===0||i.length===0)return new Map;let d=e.get(`${r.entity.name}_layout`);if(!d)return new Map;let f=u.filter(({config:e})=>!(`localized`in e&&e.localized)),p=u.filter(({config:e})=>`localized`in e&&e.localized),m=new Map;if(f.length>0){let c=await r.db.select().from(d).where(t(a(d.entityId,i),o(d.locale))).orderBy(d.sortOrder),u;if(s&&c.length>0){let i=e.get(`${r.entity.name}_layout_translations`);if(i){let e=c.map(e=>e.id),o=await r.db.select().from(i).where(t(a(i.layoutId,e),n(i.locale,s)));u=new Map;for(let e of o)u.set(e.layoutId,e.fields??{})}}let p;if(l?.strictTranslations){p=new Map;for(let{config:e}of f)if(e.type===`blocks`)for(let t of e.blocks)p.set(t.slug,t.fields)}for(let e of c){let t=e.entityId,n=e.fieldName;m.has(t)||m.set(t,{});let r=m.get(t);r[n]||(r[n]=[]);let i=e.data??{},a=u?.get(e.id),o={_block:e.blockType,_id:e.id,...i,...a??{}};if(s&&p){let t=e.blockType,n=p.get(t);if(n)for(let[e,t]of Object.entries(n))t.translatable&&!a?.[e]&&(o[e]=``)}r[n].push(o)}}if(p.length>0){let e=!s||s===l?.defaultLocale,u=s?e?c(n(d.locale,s),o(d.locale)):n(d.locale,s):o(d.locale),f=await r.db.select().from(d).where(t(a(d.entityId,i),u)).orderBy(d.sortOrder),p=new Map;for(let e of f){let t=`${e.entityId}::${e.fieldName}`;p.has(t)||p.set(t,{localeRows:[],nullRows:[]});let n=p.get(t);e.locale?n.localeRows.push(e):n.nullRows.push(e)}for(let[,{localeRows:e,nullRows:t}]of p){let n=e.length>0?e:t;for(let e of n){let t=e.entityId,n=e.fieldName;m.has(t)||m.set(t,{});let r=m.get(t);r[n]||(r[n]=[]);let i=e.data??{};r[n].push({_block:e.blockType,_id:e.id,...i})}}}return m}function D(e,t,n){let r=w(e);if(r.length!==0)for(let e of t){let t=e.id,i=n.get(t)??{};for(let{name:t}of r)e[t]=i[t]??[]}}function O(e,t,n,r){let i=n?`${n}:${e}`:e;return r&&(i=`${i}@${r}`),t?`${i}:${String(t)}`:i}var k=class extends Error{constructor(e){super(e),this.name=`ForbiddenError`}};async function A(e){if(!e)throw new k(`No context resolver configured on AdminClient/QueryClient. Use createAdminClient() from @murumets-ee/core/clients, or pass a contextResolver in config.`);let t=await e();if(!t)throw new k(`Context resolver returned no context. Ensure auth is configured in your request handler, or use runAsCli() for CLI scripts.`);return t}async function j(e,t,n){let r=await A(t),i=r.user.groups[0];if(!i)throw new k(`User '${r.user.id}' has no role assigned.`);if(!r.checker(i,e.name,n))throw new k(`Forbidden: role '${i}' cannot ${n} '${e.name}'`)}async function M(e,t){if(!(!e.scope||e.scope===`global`))return(await A(t)).scope?.id}function N(e,t){return n(e.table._scopeId,t)}async function P(e,t){if(!e.scope||e.scope===`global`)return;let n=(await A(t)).scope?.id;if(!n)throw new k(`Entity '${e.name}' requires scope '${e.scope}' but no scope is set in context.`);return n}function F(e){let t;switch(e.type){case`id`:t=h.string().uuid();break;case`text`:{let n=h.string();e.maxLength&&(n=n.max(e.maxLength)),e.minLength&&(n=n.min(e.minLength)),e.pattern&&(n=n.regex(e.pattern)),t=n;break}case`number`:{let n=h.number();e.integer&&(n=n.int()),e.min!==void 0&&(n=n.min(e.min)),e.max!==void 0&&(n=n.max(e.max)),t=n;break}case`boolean`:t=h.boolean();break;case`date`:t=h.date().or(h.string().datetime());break;case`select`:t=h.enum(e.options);break;case`reference`:t=e.cardinality===`many`?h.array(h.string().uuid()):h.string().uuid();break;case`media`:t=h.string().uuid();break;case`richtext`:t=h.union([h.string(),h.array(h.record(h.unknown()))]);break;case`slug`:t=h.string().regex(/^[a-z0-9-]+$/);break;case`json`:t=h.union([h.record(h.unknown()),h.array(h.unknown()),h.string(),h.number(),h.boolean()]);break;case`blocks`:t=h.array(h.object({_block:h.string()}).passthrough());break;default:t=h.unknown()}return e.required||(t=t.nullable().optional()),e.default!==void 0&&(t=t.default(e.default)),t}function I(e){let t={},n=new Set([`id`,`createdAt`,`updatedAt`,`createdBy`,`_version`]);for(let[r,i]of Object.entries(e.allFields))n.has(r)||(t[r]=F(i));return h.object(t)}function L(e){let t={},n=new Set([`id`,`createdAt`,`createdBy`,`updatedAt`,`updatedBy`,`_version`]);for(let[r,i]of Object.entries(e.allFields))n.has(r)||(t[r]=F(i).optional());return h.object(t)}var R=class r{entity;db;logger;createSchema;updateSchema;table;countCache;contextResolver;get ctx(){return{entity:this.entity,db:this.db,table:this.table,resolveContext:this.contextResolver}}constructor(t){if(typeof window<`u`)throw Error(`AdminClient cannot be used in browser code. Use QueryClient for frontend data access.`);this.entity=t.entity,this.db=t.db,this.logger=t.logger,this.countCache=t.countCache,this.contextResolver=t.contextResolver,this.createSchema=I(t.entity),this.updateSchema=L(t.entity);let n=e.get(t.entity.name);if(!n)throw Error(`Schema for entity '${t.entity.name}' not found in registry. Ensure schemas are generated and registered before creating AdminClient.`);this.table=n}async create(e){this.logger?.info({entity:this.entity.name},`Creating entity`),await j(this.entity,this.contextResolver,`create`);let t=await P(this.entity,this.contextResolver),n=e;for(let e of this.entity.behaviors??[])if(e.hooks?.beforeCreate){let t=await e.hooks.beforeCreate(n);t!==void 0&&(n=t)}let r={};for(let e of[`createdAt`,`updatedAt`,`createdBy`,`updatedBy`])n[e]!==void 0&&(r[e]=n[e]);n={...this.createSchema.parse(n),...r},t&&(n._scopeId=t);let i=this.prepareDataForInsert(n),[a]=await this.db.insert(this.table).values(i).returning();await this.saveBlocks(a.id,n),await this.syncRefs(a.id,n,`create`);for(let e of this.entity.behaviors??[])e.hooks?.afterCreate&&await e.hooks.afterCreate(a);this.invalidateCountCache();let o=_(this.entity,a);if(o&&w(this.ctx).length>0){let e=await E(this.ctx,[o.id],void 0,{strictTranslations:!0});D(this.ctx,[o],e)}return o}async findById(e,r){this.logger?.info({entity:this.entity.name,id:e},`Finding entity by ID`),await j(this.entity,this.contextResolver,`view`);let i=await M(this.entity,this.contextResolver),a=[n(this.table.id,e)];i&&a.push(N(this.ctx,i));let[o]=await this.db.select().from(this.table).where(t(...a));if(!o)return null;let s=_(this.entity,o);if(!s)return null;if(w(this.ctx).length>0){let e=await E(this.ctx,[s.id],r?.locale,{defaultLocale:r?.defaultLocale,strictTranslations:!0});D(this.ctx,[s],e)}if(r?.locale){let[e]=await T(this.ctx,[s],r.locale);return e}return s}async findMany(e){this.logger?.info({entity:this.entity.name,options:e},`Finding entities`),await j(this.entity,this.contextResolver,`view`);let n=await M(this.entity,this.contextResolver),r=this.db.select().from(this.table).$dynamic(),i=[];if(n&&i.push(N(this.ctx,n)),e?.where&&i.push(e.where),e?.cursor){let t=C(this.ctx);if(!(e.cursor.field in t)&&e.cursor.field!==`id`)throw Error(`Invalid cursor field: '${e.cursor.field}' is not a field on '${this.entity.name}'`);let n=g(this.table,e.cursor);n&&i.push(n)}if(i.length>0&&(r=r.where(t(...i))),e?.limit&&(r=r.limit(e.limit)),e?.offset&&!e?.cursor&&(r=r.offset(e.offset)),e?.orderBy){let t=Array.isArray(e.orderBy)?e.orderBy:[e.orderBy];r=r.orderBy(...t)}let a=await r,o=v(this.entity,a).filter(e=>e!==null);if(w(this.ctx).length>0&&o.length>0){let t=o.map(e=>e.id),n=await E(this.ctx,t,e?.locale,{defaultLocale:e?.defaultLocale,strictTranslations:!0});D(this.ctx,o,n)}return e?.locale?await T(this.ctx,o,e.locale):o}async count(e){this.logger?.info({entity:this.entity.name,options:e},`Counting entities`),await j(this.entity,this.contextResolver,`view`);let n=await M(this.entity,this.contextResolver),r=O(this.entity.name,e?.where,void 0,n);if(this.countCache){let e=this.countCache.get(r);if(e!==void 0)return this.logger?.debug?.({entity:this.entity.name,cached:e},`Count cache hit`),e}let i=this.db.select({count:l`count(*)`}).from(this.table).$dynamic(),a=[];n&&a.push(N(this.ctx,n)),e?.where&&a.push(e.where),a.length>0&&(i=i.where(t(...a)));let[o]=await i,s=Number(o.count);return this.countCache&&this.countCache.set(r,s),s}async update(e,r,i){this.logger?.info({entity:this.entity.name,id:e},`Updating entity`),await j(this.entity,this.contextResolver,`update`);let a=await P(this.entity,this.contextResolver),o=r;for(let t of this.entity.behaviors??[])if(t.hooks?.beforeUpdate){let n=await t.hooks.beforeUpdate(e,o);n!==void 0&&(o=n)}o=this.updateSchema.parse(o);let s=this.prepareDataForUpdate(o),c=a?t(n(this.table.id,e),N(this.ctx,a)):n(this.table.id,e),l;if(Object.keys(s).length>0){let[e]=await this.db.update(this.table).set(s).where(c).returning();l=e}else{let[e]=await this.db.select().from(this.table).where(c);l=e}await this.saveBlocks(e,o,i),await this.syncRefs(e,o,`update`);for(let e of this.entity.behaviors??[])e.hooks?.afterUpdate&&await e.hooks.afterUpdate(l);let u=_(this.entity,l);if(u&&w(this.ctx).length>0){let t=await E(this.ctx,[e],void 0,{strictTranslations:!0});D(this.ctx,[u],t)}return u}async delete(e){this.logger?.info({entity:this.entity.name,id:e},`Deleting entity`),await j(this.entity,this.contextResolver,`delete`);let r=await P(this.entity,this.contextResolver);if(r){let[i]=await this.db.select({id:this.table.id}).from(this.table).where(t(n(this.table.id,e),N(this.ctx,r)));if(!i)return}await this.db.transaction(async r=>{await this.handleIncomingRefsForDelete(r,[e]);for(let t of this.entity.behaviors??[])t.hooks?.beforeDelete&&await t.hooks.beforeDelete(e);await r.delete(this.table).where(n(this.table.id,e)),await r.delete(S).where(t(n(S.sourceEntity,this.entity.name),n(S.sourceId,e)))});for(let t of this.entity.behaviors??[])t.hooks?.afterDelete&&await t.hooks.afterDelete(e);this.invalidateCountCache()}async deleteMany(e){this.logger?.info({entity:this.entity.name},`Bulk deleting entities`),await j(this.entity,this.contextResolver,`delete`);let r=await P(this.entity,this.contextResolver),i=[e];r&&i.push(N(this.ctx,r));let o=(await this.db.select({id:this.table.id}).from(this.table).where(t(...i))).map(e=>e.id);if(o.length===0)return 0;await this.db.transaction(async e=>{await this.handleIncomingRefsForDelete(e,o);for(let e of o)for(let t of this.entity.behaviors??[])t.hooks?.beforeDelete&&await t.hooks.beforeDelete(e);await e.delete(this.table).where(a(this.table.id,o)),await e.delete(S).where(t(n(S.sourceEntity,this.entity.name),a(S.sourceId,o)))});for(let e of o)for(let t of this.entity.behaviors??[])t.hooks?.afterDelete&&await t.hooks.afterDelete(e);return this.invalidateCountCache(),o.length}static MAX_CASCADE_DEPTH=10;async handleIncomingRefsForDelete(i,o,s=0){if(s>=r.MAX_CASCADE_DEPTH)throw Error(`Cascade delete exceeded maximum depth of ${r.MAX_CASCADE_DEPTH} while deleting '${this.entity.name}'. This likely indicates a circular reference chain.`);let c=await i.select({sourceEntity:S.sourceEntity,sourceId:S.sourceId,sourceField:S.sourceField}).from(S).where(t(n(S.targetEntity,this.entity.name),a(S.targetId,o)));if(c.length===0)return;let l=new Map;for(let e of c){let t=`${e.sourceEntity}:${e.sourceField}`,n=l.get(t);n||(n={sourceEntity:e.sourceEntity,sourceField:e.sourceField,sourceIds:[]},l.set(t,n)),n.sourceIds.push(e.sourceId)}let u;try{u=(await import([`@murumets-ee`,`core`].join(`/`))).getApp().entities}catch{}for(let[,c]of l){let l=u?.get(c.sourceEntity),d=l?.fields[c.sourceField]?.onDelete??`restrict`;if(d===`restrict`)throw new y(this.entity.name,o[0],c.sourceIds.map(e=>({sourceEntity:c.sourceEntity,sourceId:e,sourceField:c.sourceField})));if(d===`cascade`){let n=e.get(c.sourceEntity);if(n&&l){let e=new r({entity:l,db:i,logger:this.logger,contextResolver:this.contextResolver});await j(l,this.contextResolver,`delete`);let o=await M(l,this.contextResolver),u=a(n.id,c.sourceIds);if(o){let e={entity:l,db:i,table:n,resolveContext:this.contextResolver},r=t(u,N(e,o));r&&(u=r)}await e.deleteManyInTx(i,u,s+1)}}else if(d===`set-null`){let r=e.get(c.sourceEntity);if(r&&l&&r[c.sourceField]){await j(l,this.contextResolver,`update`);let e=await M(l,this.contextResolver),o=a(r.id,c.sourceIds);if(e){let n={entity:l,db:i,table:r,resolveContext:this.contextResolver},a=t(o,N(n,e));a&&(o=a)}await i.update(r).set({[c.sourceField]:null}).where(o),await i.delete(S).where(t(n(S.sourceEntity,c.sourceEntity),a(S.sourceId,c.sourceIds),n(S.sourceField,c.sourceField)))}}}}async deleteManyInTx(e,r,i){let o=(await e.select({id:this.table.id}).from(this.table).where(r)).map(e=>e.id);if(o.length===0)return 0;await this.handleIncomingRefsForDelete(e,o,i);for(let e of o)for(let t of this.entity.behaviors??[])t.hooks?.beforeDelete&&await t.hooks.beforeDelete(e);return await e.delete(this.table).where(a(this.table.id,o)),await e.delete(S).where(t(n(S.sourceEntity,this.entity.name),a(S.sourceId,o))),this.invalidateCountCache(),o.length}async updateMany(e,n,r){this.logger?.info({entity:this.entity.name},`Bulk updating entities`),await j(this.entity,this.contextResolver,`update`);let i=await P(this.entity,this.contextResolver),a=this.updateSchema.parse(n),o=this.prepareDataForUpdate(a);if(r?.expressions){let e=C(this.ctx);for(let[t,n]of Object.entries(r.expressions)){if(t in o)throw Error(`updateMany: field '${t}' appears in both \`data\` and \`expressions\`. Choose one — expressions silently overriding validated values is unsafe.`);if(!(t in e)&&t!==`_scopeId`)throw Error(`updateMany.expressions: unknown column '${t}' on entity '${this.entity.name}'`);o[t]=n}}let s=[e];i&&s.push(N(this.ctx,i));let c=await this.db.update(this.table).set(o).where(t(...s)).returning({id:this.table.id});return this.invalidateCountCache(),c.length}async aggregate(e){this.logger?.info({entity:this.entity.name},`Aggregate query`),await j(this.entity,this.contextResolver,`view`);let n=await M(this.entity,this.contextResolver),r=this.db.select(e.select).from(this.table).$dynamic(),i=[];if(n&&i.push(N(this.ctx,n)),e.where&&i.push(e.where),i.length>0&&(r=r.where(t(...i))),e.groupBy&&e.groupBy.length>0&&(r=r.groupBy(...e.groupBy)),e.orderBy){let t=Array.isArray(e.orderBy)?e.orderBy:[e.orderBy];r=r.orderBy(...t)}return e.limit&&(r=r.limit(e.limit)),await r}getTable(){return this.table}getDb(){return this.db}prepareDataForInsert(e){let t={};for(let[n,r]of Object.entries(e)){let e=C(this.ctx)[n];e&&n!==`id`&&e.type!==`blocks`&&(t[n]=r)}return e._scopeId!==void 0&&(t._scopeId=e._scopeId),t}prepareDataForUpdate(e){let t={};for(let[n,r]of Object.entries(e)){let e=C(this.ctx)[n];e&&n!==`id`&&e.type!==`blocks`&&(t[n]=r)}return t}async saveTranslation(t,n,r){await j(this.entity,this.contextResolver,`update`),this.logger?.info({entity:this.entity.name,entityId:t,locale:n},`Saving translation`);let i=`${this.entity.name}_translations`,a=e.get(i);if(!a)throw Error(`Translation table ${i} not found in schema registry`);let o=Object.entries(C(this.ctx)).filter(([e,t])=>t.translatable).map(([e])=>e);if(o.length===0)throw Error(`Entity ${this.entity.name} has no translatable fields`);let s={entityId:t,locale:n},c={};for(let e of o)r[e]!==void 0&&(s[e]=r[e],c[e]=r[e]);await this.db.insert(a).values(s).onConflictDoUpdate({target:[a.entityId,a.locale],set:c}),this.logger?.info({entity:this.entity.name,entityId:t,locale:n},`Translation saved`)}async deleteTranslation(r,i){await j(this.entity,this.contextResolver,`update`),this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Deleting translation`);let a=`${this.entity.name}_translations`,o=e.get(a);if(!o)throw Error(`Translation table ${a} not found in schema registry`);i?(await this.db.delete(o).where(t(n(o.entityId,r),n(o.locale,i))),this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Translation deleted`)):(await this.db.delete(o).where(n(o.entityId,r)),this.logger?.info({entity:this.entity.name,entityId:r},`All translations deleted`))}async saveBlockTranslations(r,i,a){await j(this.entity,this.contextResolver,`update`),this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Saving block translations`);let s=w(this.ctx);if(s.length===0)return;let c=e.get(`${this.entity.name}_layout_translations`);if(!c)return;let l=e.get(`${this.entity.name}_layout`);if(l){for(let{name:e,config:u}of s){let s=a[e];if(!Array.isArray(s)||u.type!==`blocks`)continue;let d=u.blocks;if(!d)continue;let f=await this.db.select({id:l.id,blockType:l.blockType}).from(l).where(t(n(l.entityId,r),n(l.fieldName,e),o(l.locale))).orderBy(l.sortOrder);for(let e=0;e<s.length;e++){let t=s[e],n=t._block;if(!n)continue;let r=f[e];if(!r||r.blockType!==n)continue;let a=d.find(e=>e.slug===n);if(!a)continue;let o={};for(let[e,n]of Object.entries(a.fields))n.translatable&&t[e]!==void 0&&(o[e]=t[e]);Object.keys(o).length!==0&&await this.db.insert(c).values({layoutId:r.id,locale:i,fields:o}).onConflictDoUpdate({target:[c.layoutId,c.locale],set:{fields:o}})}}this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Block translations saved`)}}async saveLocalizedBlocks(e,t,n){await j(this.entity,this.contextResolver,`update`),this.logger?.info({entity:this.entity.name,entityId:e,locale:t},`Saving localized blocks`),await this.saveBlocks(e,n,{locale:t})}async saveBlocks(r,i,a){let s=w(this.ctx);if(s.length===0)return;let c=e.get(`${this.entity.name}_layout`);if(c)for(let{name:e,config:l}of s){let s=i[e];if(!Array.isArray(s))continue;let u=`localized`in l&&l.localized===!0,d=u?a?.locale??null:null,f=[n(c.entityId,r),n(c.fieldName,e)];d?f.push(n(c.locale,d)):u||f.push(o(c.locale)),await this.db.delete(c).where(t(...f));for(let t=0;t<s.length;t++){let n=s[t],i=n._block;if(!i)continue;let{_block:a,_id:o,...l}=n;await this.db.insert(c).values({entityId:r,fieldName:e,blockType:i,sortOrder:t,data:l,locale:d})}}}async syncRefs(r,i,o){if(!e.has(`entity_refs`))return;let s=C(this.ctx);if(o===`update`){let e=Object.keys(i).filter(e=>{let t=s[e];return t&&(t.type===`media`||t.type===`reference`||t.type===`blocks`)});e.length>0&&await this.db.delete(S).where(t(n(S.sourceEntity,this.entity.name),n(S.sourceId,r),a(S.sourceField,e)))}let c=x(s,i);c.length>0&&await this.db.insert(S).values(c.map(e=>({sourceEntity:this.entity.name,sourceId:r,sourceField:e.sourceField,targetEntity:e.targetEntity,targetId:e.targetId}))).onConflictDoNothing()}invalidateCountCache(){this.countCache?.invalidate(this.entity.name)}};export{R as AdminClient};
1
+ import{schemaRegistry as e}from"@murumets-ee/db";import{and as t,eq as n,getTableColumns as r,gt as i,inArray as a,isNull as o,lt as s,or as c,sql as l}from"drizzle-orm";import{index as u,pgTable as d,unique as f,uuid as p,varchar as m}from"drizzle-orm/pg-core";import{z as h}from"zod";function g(e,a){let o=r(e),l=o[a.field];if(!l)return null;let u=a.direction===`desc`?s:i,d=u(l,a.value);if(!a.id)return d;let f=o.id;if(!f)return d;let p=u(f,a.id);return c(d,t(n(l,a.value),p))}function _(e,t,n){if(!t)return null;let r={},i=n?.select||Object.keys(e.allFields);for(let a of i){if(!n?.includeInternal&&a.startsWith(`_`))continue;let i=e.allFields[a];i&&i.type!==`blocks`&&(r[a]=t[a])}return r}function v(e,t,n){return t.map(t=>_(e,t,n))}var y=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 b=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function x(e,t){let n=[],r=new Set;function i(e,t,i){if(!b.test(i))return;let a=`${e}|${t}|${i}`;r.has(a)||(r.add(a),n.push({sourceField:e,targetEntity:t,targetId:i}))}for(let[n,r]of Object.entries(e)){let e=t[n];if(e!=null)switch(r.type){case`media`:typeof e==`string`&&i(n,`media`,e);break;case`reference`:{let t=r;if(t.cardinality===`many`&&Array.isArray(e))for(let r of e)typeof r==`string`&&i(n,t.entity,r);else typeof e==`string`&&i(n,t.entity,e);break}case`blocks`:{let t=r;if(!Array.isArray(e))break;for(let r of e){let e=r,a=e._block;if(!a)continue;let o=t.blocks.find(e=>e.slug===a);if(o)for(let[t,r]of Object.entries(o.fields)){let a=e[t];if(a!=null&&(r.type===`media`&&typeof a==`string`&&i(n,`media`,a),r.type===`reference`)){let e=r;if(e.cardinality===`many`&&Array.isArray(a))for(let t of a)typeof t==`string`&&i(n,e.entity,t);else typeof a==`string`&&i(n,e.entity,a)}}}break}}}return n}const S=d(`entity_refs`,{sourceEntity:m(`source_entity`,{length:100}).notNull(),sourceId:p(`source_id`).notNull(),sourceField:m(`source_field`,{length:100}).notNull(),targetEntity:m(`target_entity`,{length:100}).notNull(),targetId:p(`target_id`).notNull()},e=>[f(`uq_entity_refs`).on(e.sourceEntity,e.sourceId,e.sourceField,e.targetEntity,e.targetId),u(`idx_entity_refs_target`).on(e.targetEntity,e.targetId),u(`idx_entity_refs_source`).on(e.sourceEntity,e.sourceId)]);function C(e){return e.entity.allFields}function w(e){return Object.entries(C(e)).filter(([e,t])=>t.type===`blocks`).map(([e,t])=>({name:e,config:t}))}async function T(r,i,o){if(!i.length)return i;let s=`${r.entity.name}_translations`,c=e.get(s);if(!c)return i;let l=i.map(e=>e.id),u=await r.db.select().from(c).where(t(a(c.entityId,l),n(c.locale,o))),d=Object.entries(C(r)).filter(([e,t])=>t.translatable).map(([e])=>e),f=new Map;for(let e of u){let t={};for(let n of d){let r=e[n];r!=null&&(t[n]=r)}f.set(e.entityId,t)}return i.map(e=>{let t=f.get(e.id);return t?{...e,...t}:e})}async function E(r,i,s,l){let u=w(r);if(u.length===0||i.length===0)return new Map;let d=e.get(`${r.entity.name}_layout`);if(!d)return new Map;let f=u.filter(({config:e})=>!(`localized`in e&&e.localized)),p=u.filter(({config:e})=>`localized`in e&&e.localized),m=new Map;if(f.length>0){let c=await r.db.select().from(d).where(t(a(d.entityId,i),o(d.locale))).orderBy(d.sortOrder),u;if(s&&c.length>0){let i=e.get(`${r.entity.name}_layout_translations`);if(i){let e=c.map(e=>e.id),o=await r.db.select().from(i).where(t(a(i.layoutId,e),n(i.locale,s)));u=new Map;for(let e of o)u.set(e.layoutId,e.fields??{})}}let p;if(l?.strictTranslations){p=new Map;for(let{config:e}of f)if(e.type===`blocks`)for(let t of e.blocks)p.set(t.slug,t.fields)}for(let e of c){let t=e.entityId,n=e.fieldName;m.has(t)||m.set(t,{});let r=m.get(t);r[n]||(r[n]=[]);let i=e.data??{},a=u?.get(e.id),o={_block:e.blockType,_id:e.id,...i,...a??{}};if(s&&p){let t=e.blockType,n=p.get(t);if(n)for(let[e,t]of Object.entries(n))t.translatable&&!a?.[e]&&(o[e]=``)}r[n].push(o)}}if(p.length>0){let e=!s||s===l?.defaultLocale,u=s?e?c(n(d.locale,s),o(d.locale)):n(d.locale,s):o(d.locale),f=await r.db.select().from(d).where(t(a(d.entityId,i),u)).orderBy(d.sortOrder),p=new Map;for(let e of f){let t=`${e.entityId}::${e.fieldName}`;p.has(t)||p.set(t,{localeRows:[],nullRows:[]});let n=p.get(t);e.locale?n.localeRows.push(e):n.nullRows.push(e)}for(let[,{localeRows:e,nullRows:t}]of p){let n=e.length>0?e:t;for(let e of n){let t=e.entityId,n=e.fieldName;m.has(t)||m.set(t,{});let r=m.get(t);r[n]||(r[n]=[]);let i=e.data??{};r[n].push({_block:e.blockType,_id:e.id,...i})}}}return m}function D(e,t,n){let r=w(e);if(r.length!==0)for(let e of t){let t=e.id,i=n.get(t)??{};for(let{name:t}of r)e[t]=i[t]??[]}}function O(e,t,n,r){let i=n?`${n}:${e}`:e;return r&&(i=`${i}@${r}`),t?`${i}:${String(t)}`:i}var k=class extends Error{constructor(e){super(e),this.name=`ForbiddenError`}};async function A(e){if(!e)throw new k(`No context resolver configured on AdminClient/QueryClient. Use createAdminClient() from @murumets-ee/core/clients, or pass a contextResolver in config.`);let t=await e();if(!t)throw new k(`Context resolver returned no context. Ensure auth is configured in your request handler, or use runAsCli() for CLI scripts.`);return t}async function j(e,t,n){let r=await A(t),i=r.user.groups[0];if(!i)throw new k(`User '${r.user.id}' has no role assigned.`);if(!r.checker(i,e.name,n))throw new k(`Forbidden: role '${i}' cannot ${n} '${e.name}'`)}async function M(e,t){if(!(!e.scope||e.scope===`global`))return(await A(t)).scope?.id}function N(e,t){return n(e.table._scopeId,t)}async function P(e,t){if(!e.scope||e.scope===`global`)return;let n=(await A(t)).scope?.id;if(!n)throw new k(`Entity '${e.name}' requires scope '${e.scope}' but no scope is set in context.`);return n}function F(e){let t;switch(e.type){case`id`:t=h.string().uuid();break;case`text`:{let n=h.string();e.maxLength&&(n=n.max(e.maxLength)),e.minLength&&(n=n.min(e.minLength)),e.pattern&&(n=n.regex(e.pattern)),t=n;break}case`number`:{let n=h.number();e.integer&&(n=n.int()),e.min!==void 0&&(n=n.min(e.min)),e.max!==void 0&&(n=n.max(e.max)),t=n;break}case`boolean`:t=h.boolean();break;case`date`:t=h.date().or(h.string().datetime());break;case`select`:t=h.enum(e.options);break;case`reference`:t=e.cardinality===`many`?h.array(h.string().uuid()):h.string().uuid();break;case`media`:t=h.string().uuid();break;case`richtext`:t=h.union([h.string(),h.array(h.record(h.unknown()))]);break;case`slug`:t=h.string().regex(/^[a-z0-9-]+$/);break;case`json`:t=h.union([h.record(h.unknown()),h.array(h.unknown()),h.string(),h.number(),h.boolean()]);break;case`blocks`:t=h.array(h.object({_block:h.string()}).passthrough());break;default:t=h.unknown()}return e.required||(t=t.nullable().optional()),e.default!==void 0&&(t=t.default(e.default)),t}function I(e){let t={},n=new Set([`id`,`createdAt`,`updatedAt`,`createdBy`,`_version`]);for(let[r,i]of Object.entries(e.allFields))n.has(r)||(t[r]=F(i));return h.object(t)}function L(e){let t={},n=new Set([`id`,`createdAt`,`createdBy`,`updatedAt`,`updatedBy`,`_version`]);for(let[r,i]of Object.entries(e.allFields))n.has(r)||(t[r]=F(i).optional());return h.object(t)}var R=class r{entity;db;logger;createSchema;updateSchema;table;countCache;contextResolver;entityResolver;get ctx(){return{entity:this.entity,db:this.db,table:this.table,resolveContext:this.contextResolver}}async resolveBehaviorContext(){if(!this.contextResolver)return;let e=await this.contextResolver();if(e?.user)return{user:{id:e.user.id,name:e.user.name,email:e.user.email}}}constructor(t){if(typeof window<`u`)throw Error(`AdminClient cannot be used in browser code. Use QueryClient for frontend data access.`);this.entity=t.entity,this.db=t.db,this.logger=t.logger,this.countCache=t.countCache,this.contextResolver=t.contextResolver,this.entityResolver=t.entityResolver,this.createSchema=I(t.entity),this.updateSchema=L(t.entity);let n=e.get(t.entity.name);if(!n)throw Error(`Schema for entity '${t.entity.name}' not found in registry. Ensure schemas are generated and registered before creating AdminClient.`);this.table=n}async create(e){this.logger?.info({entity:this.entity.name},`Creating entity`),await j(this.entity,this.contextResolver,`create`);let t=await P(this.entity,this.contextResolver),n=await this.resolveBehaviorContext(),r=e;for(let e of this.entity.behaviors??[])if(e.hooks?.beforeCreate){let t=await e.hooks.beforeCreate(r,n);t!==void 0&&(r=t)}let i={};for(let e of[`createdAt`,`updatedAt`,`createdBy`,`updatedBy`])r[e]!==void 0&&(i[e]=r[e]);r={...this.createSchema.parse(r),...i},t&&(r._scopeId=t);let a=this.prepareDataForInsert(r),[o]=await this.db.insert(this.table).values(a).returning();await this.saveBlocks(o.id,r),await this.syncRefs(o.id,r,`create`);for(let e of this.entity.behaviors??[])e.hooks?.afterCreate&&await e.hooks.afterCreate(o,n);this.invalidateCountCache();let s=_(this.entity,o);if(s&&w(this.ctx).length>0){let e=await E(this.ctx,[s.id],void 0,{strictTranslations:!0});D(this.ctx,[s],e)}return s}async findById(e,r){this.logger?.info({entity:this.entity.name,id:e},`Finding entity by ID`),await j(this.entity,this.contextResolver,`view`);let i=await M(this.entity,this.contextResolver),a=[n(this.table.id,e)];i&&a.push(N(this.ctx,i));let[o]=await this.db.select().from(this.table).where(t(...a));if(!o)return null;let s=_(this.entity,o);if(!s)return null;if(w(this.ctx).length>0){let e=await E(this.ctx,[s.id],r?.locale,{defaultLocale:r?.defaultLocale,strictTranslations:!0});D(this.ctx,[s],e)}if(r?.locale){let[e]=await T(this.ctx,[s],r.locale);return e}return s}async findMany(e){this.logger?.info({entity:this.entity.name,options:e},`Finding entities`),await j(this.entity,this.contextResolver,`view`);let n=await M(this.entity,this.contextResolver),r=this.db.select().from(this.table).$dynamic(),i=[];if(n&&i.push(N(this.ctx,n)),e?.where&&i.push(e.where),e?.cursor){let t=C(this.ctx);if(!(e.cursor.field in t)&&e.cursor.field!==`id`)throw Error(`Invalid cursor field: '${e.cursor.field}' is not a field on '${this.entity.name}'`);let n=g(this.table,e.cursor);n&&i.push(n)}if(i.length>0&&(r=r.where(t(...i))),e?.limit&&(r=r.limit(e.limit)),e?.offset&&!e?.cursor&&(r=r.offset(e.offset)),e?.orderBy){let t=Array.isArray(e.orderBy)?e.orderBy:[e.orderBy];r=r.orderBy(...t)}let a=await r,o=v(this.entity,a).filter(e=>e!==null);if(w(this.ctx).length>0&&o.length>0){let t=o.map(e=>e.id),n=await E(this.ctx,t,e?.locale,{defaultLocale:e?.defaultLocale,strictTranslations:!0});D(this.ctx,o,n)}return e?.locale?await T(this.ctx,o,e.locale):o}async count(e){this.logger?.info({entity:this.entity.name,options:e},`Counting entities`),await j(this.entity,this.contextResolver,`view`);let n=await M(this.entity,this.contextResolver),r=O(this.entity.name,e?.where,void 0,n);if(this.countCache){let e=this.countCache.get(r);if(e!==void 0)return this.logger?.debug?.({entity:this.entity.name,cached:e},`Count cache hit`),e}let i=this.db.select({count:l`count(*)`}).from(this.table).$dynamic(),a=[];n&&a.push(N(this.ctx,n)),e?.where&&a.push(e.where),a.length>0&&(i=i.where(t(...a)));let[o]=await i,s=Number(o.count);return this.countCache&&this.countCache.set(r,s),s}async update(e,r,i){this.logger?.info({entity:this.entity.name,id:e},`Updating entity`),await j(this.entity,this.contextResolver,`update`);let a=await P(this.entity,this.contextResolver),o=await this.resolveBehaviorContext(),s=r;for(let t of this.entity.behaviors??[])if(t.hooks?.beforeUpdate){let n=await t.hooks.beforeUpdate(e,s,o);n!==void 0&&(s=n)}let c={};for(let e of[`updatedAt`,`updatedBy`])s[e]!==void 0&&(c[e]=s[e]);s={...this.updateSchema.parse(s),...c};let l=this.prepareDataForUpdate(s),u=a?t(n(this.table.id,e),N(this.ctx,a)):n(this.table.id,e),d;if(Object.keys(l).length>0){let[e]=await this.db.update(this.table).set(l).where(u).returning();d=e}else{let[e]=await this.db.select().from(this.table).where(u);d=e}await this.saveBlocks(e,s,i),await this.syncRefs(e,s,`update`);for(let e of this.entity.behaviors??[])e.hooks?.afterUpdate&&await e.hooks.afterUpdate(d,o);let f=_(this.entity,d);if(f&&w(this.ctx).length>0){let t=await E(this.ctx,[e],void 0,{strictTranslations:!0});D(this.ctx,[f],t)}return f}async delete(e){this.logger?.info({entity:this.entity.name,id:e},`Deleting entity`),await j(this.entity,this.contextResolver,`delete`);let r=await P(this.entity,this.contextResolver),i=await this.resolveBehaviorContext();if(r){let[i]=await this.db.select({id:this.table.id}).from(this.table).where(t(n(this.table.id,e),N(this.ctx,r)));if(!i)return}await this.db.transaction(async r=>{await this.handleIncomingRefsForDelete(r,[e]);for(let t of this.entity.behaviors??[])t.hooks?.beforeDelete&&await t.hooks.beforeDelete(e,i);await r.delete(this.table).where(n(this.table.id,e)),await r.delete(S).where(t(n(S.sourceEntity,this.entity.name),n(S.sourceId,e)))});for(let t of this.entity.behaviors??[])t.hooks?.afterDelete&&await t.hooks.afterDelete(e,i);this.invalidateCountCache()}async deleteMany(e){this.logger?.info({entity:this.entity.name},`Bulk deleting entities`),await j(this.entity,this.contextResolver,`delete`);let r=await P(this.entity,this.contextResolver),i=await this.resolveBehaviorContext(),o=[e];r&&o.push(N(this.ctx,r));let s=(await this.db.select({id:this.table.id}).from(this.table).where(t(...o))).map(e=>e.id);if(s.length===0)return 0;await this.db.transaction(async e=>{await this.handleIncomingRefsForDelete(e,s);for(let e of s)for(let t of this.entity.behaviors??[])t.hooks?.beforeDelete&&await t.hooks.beforeDelete(e,i);await e.delete(this.table).where(a(this.table.id,s)),await e.delete(S).where(t(n(S.sourceEntity,this.entity.name),a(S.sourceId,s)))});for(let e of s)for(let t of this.entity.behaviors??[])t.hooks?.afterDelete&&await t.hooks.afterDelete(e,i);return this.invalidateCountCache(),s.length}static MAX_CASCADE_DEPTH=10;async handleIncomingRefsForDelete(i,o,s=0){if(s>=r.MAX_CASCADE_DEPTH)throw Error(`Cascade delete exceeded maximum depth of ${r.MAX_CASCADE_DEPTH} while deleting '${this.entity.name}'. This likely indicates a circular reference chain.`);let c=await i.select({sourceEntity:S.sourceEntity,sourceId:S.sourceId,sourceField:S.sourceField}).from(S).where(t(n(S.targetEntity,this.entity.name),a(S.targetId,o)));if(c.length===0)return;let l=new Map;for(let e of c){let t=`${e.sourceEntity}:${e.sourceField}`,n=l.get(t);n||(n={sourceEntity:e.sourceEntity,sourceField:e.sourceField,sourceIds:[]},l.set(t,n)),n.sourceIds.push(e.sourceId)}let u=this.entityResolver?.();for(let[,c]of l){let l=u?.get(c.sourceEntity),d=l?.fields[c.sourceField]?.onDelete??`restrict`;if(d===`restrict`)throw new y(this.entity.name,o[0],c.sourceIds.map(e=>({sourceEntity:c.sourceEntity,sourceId:e,sourceField:c.sourceField})));if(d===`cascade`){let n=e.get(c.sourceEntity);if(n&&l){let e=new r({entity:l,db:i,logger:this.logger,contextResolver:this.contextResolver});await j(l,this.contextResolver,`delete`);let o=await M(l,this.contextResolver),u=a(n.id,c.sourceIds);if(o){let e={entity:l,db:i,table:n,resolveContext:this.contextResolver},r=t(u,N(e,o));r&&(u=r)}await e.deleteManyInTx(i,u,s+1)}}else if(d===`set-null`){let r=e.get(c.sourceEntity);if(r&&l&&r[c.sourceField]){await j(l,this.contextResolver,`update`);let e=await M(l,this.contextResolver),o=a(r.id,c.sourceIds);if(e){let n={entity:l,db:i,table:r,resolveContext:this.contextResolver},a=t(o,N(n,e));a&&(o=a)}await i.update(r).set({[c.sourceField]:null}).where(o),await i.delete(S).where(t(n(S.sourceEntity,c.sourceEntity),a(S.sourceId,c.sourceIds),n(S.sourceField,c.sourceField)))}}}}async deleteManyInTx(e,r,i){let o=(await e.select({id:this.table.id}).from(this.table).where(r)).map(e=>e.id);if(o.length===0)return 0;await this.handleIncomingRefsForDelete(e,o,i);let s=await this.resolveBehaviorContext();for(let e of o)for(let t of this.entity.behaviors??[])t.hooks?.beforeDelete&&await t.hooks.beforeDelete(e,s);return await e.delete(this.table).where(a(this.table.id,o)),await e.delete(S).where(t(n(S.sourceEntity,this.entity.name),a(S.sourceId,o))),this.invalidateCountCache(),o.length}async updateMany(e,n,r){this.logger?.info({entity:this.entity.name},`Bulk updating entities`),await j(this.entity,this.contextResolver,`update`);let i=await P(this.entity,this.contextResolver),a=this.updateSchema.parse(n),o=this.prepareDataForUpdate(a);if(r?.expressions){let e=C(this.ctx);for(let[t,n]of Object.entries(r.expressions)){if(t in o)throw Error(`updateMany: field '${t}' appears in both \`data\` and \`expressions\`. Choose one — expressions silently overriding validated values is unsafe.`);if(!(t in e)&&t!==`_scopeId`)throw Error(`updateMany.expressions: unknown column '${t}' on entity '${this.entity.name}'`);o[t]=n}}let s=[e];i&&s.push(N(this.ctx,i));let c=await this.db.update(this.table).set(o).where(t(...s)).returning({id:this.table.id});return this.invalidateCountCache(),c.length}async aggregate(e){this.logger?.info({entity:this.entity.name},`Aggregate query`),await j(this.entity,this.contextResolver,`view`);let n=await M(this.entity,this.contextResolver),r=this.db.select(e.select).from(this.table).$dynamic(),i=[];if(n&&i.push(N(this.ctx,n)),e.where&&i.push(e.where),i.length>0&&(r=r.where(t(...i))),e.groupBy&&e.groupBy.length>0&&(r=r.groupBy(...e.groupBy)),e.orderBy){let t=Array.isArray(e.orderBy)?e.orderBy:[e.orderBy];r=r.orderBy(...t)}return e.limit&&(r=r.limit(e.limit)),await r}getTable(){return this.table}getDb(){return this.db}prepareDataForInsert(e){let t={};for(let[n,r]of Object.entries(e)){let e=C(this.ctx)[n];e&&n!==`id`&&e.type!==`blocks`&&(t[n]=r)}return e._scopeId!==void 0&&(t._scopeId=e._scopeId),t}prepareDataForUpdate(e){let t={};for(let[n,r]of Object.entries(e)){let e=C(this.ctx)[n];e&&n!==`id`&&e.type!==`blocks`&&(t[n]=r)}return t}async saveTranslation(t,n,r){await j(this.entity,this.contextResolver,`update`),this.logger?.info({entity:this.entity.name,entityId:t,locale:n},`Saving translation`);let i=`${this.entity.name}_translations`,a=e.get(i);if(!a)throw Error(`Translation table ${i} not found in schema registry`);let o=Object.entries(C(this.ctx)).filter(([e,t])=>t.translatable).map(([e])=>e);if(o.length===0)throw Error(`Entity ${this.entity.name} has no translatable fields`);let s={entityId:t,locale:n},c={};for(let e of o)r[e]!==void 0&&(s[e]=r[e],c[e]=r[e]);await this.db.insert(a).values(s).onConflictDoUpdate({target:[a.entityId,a.locale],set:c}),this.logger?.info({entity:this.entity.name,entityId:t,locale:n},`Translation saved`)}async deleteTranslation(r,i){await j(this.entity,this.contextResolver,`update`),this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Deleting translation`);let a=`${this.entity.name}_translations`,o=e.get(a);if(!o)throw Error(`Translation table ${a} not found in schema registry`);i?(await this.db.delete(o).where(t(n(o.entityId,r),n(o.locale,i))),this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Translation deleted`)):(await this.db.delete(o).where(n(o.entityId,r)),this.logger?.info({entity:this.entity.name,entityId:r},`All translations deleted`))}async saveBlockTranslations(r,i,a){await j(this.entity,this.contextResolver,`update`),this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Saving block translations`);let s=w(this.ctx);if(s.length===0)return;let c=e.get(`${this.entity.name}_layout_translations`);if(!c)return;let l=e.get(`${this.entity.name}_layout`);if(l){for(let{name:e,config:u}of s){let s=a[e];if(!Array.isArray(s)||u.type!==`blocks`)continue;let d=u.blocks;if(!d)continue;let f=await this.db.select({id:l.id,blockType:l.blockType}).from(l).where(t(n(l.entityId,r),n(l.fieldName,e),o(l.locale))).orderBy(l.sortOrder);for(let e=0;e<s.length;e++){let t=s[e],n=t._block;if(!n)continue;let r=f[e];if(!r||r.blockType!==n)continue;let a=d.find(e=>e.slug===n);if(!a)continue;let o={};for(let[e,n]of Object.entries(a.fields))n.translatable&&t[e]!==void 0&&(o[e]=t[e]);Object.keys(o).length!==0&&await this.db.insert(c).values({layoutId:r.id,locale:i,fields:o}).onConflictDoUpdate({target:[c.layoutId,c.locale],set:{fields:o}})}}this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Block translations saved`)}}async saveLocalizedBlocks(e,t,n){await j(this.entity,this.contextResolver,`update`),this.logger?.info({entity:this.entity.name,entityId:e,locale:t},`Saving localized blocks`),await this.saveBlocks(e,n,{locale:t})}async saveBlocks(r,i,a){let s=w(this.ctx);if(s.length===0)return;let c=e.get(`${this.entity.name}_layout`);if(c)for(let{name:e,config:l}of s){let s=i[e];if(!Array.isArray(s))continue;let u=`localized`in l&&l.localized===!0,d=u?a?.locale??null:null,f=[n(c.entityId,r),n(c.fieldName,e)];d?f.push(n(c.locale,d)):u||f.push(o(c.locale)),await this.db.delete(c).where(t(...f));for(let t=0;t<s.length;t++){let n=s[t],i=n._block;if(!i)continue;let{_block:a,_id:o,...l}=n;await this.db.insert(c).values({entityId:r,fieldName:e,blockType:i,sortOrder:t,data:l,locale:d})}}}async syncRefs(r,i,o){if(!e.has(`entity_refs`))return;let s=C(this.ctx);if(o===`update`){let e=Object.keys(i).filter(e=>{let t=s[e];return t&&(t.type===`media`||t.type===`reference`||t.type===`blocks`)});e.length>0&&await this.db.delete(S).where(t(n(S.sourceEntity,this.entity.name),n(S.sourceId,r),a(S.sourceField,e)))}let c=x(s,i);c.length>0&&await this.db.insert(S).values(c.map(e=>({sourceEntity:this.entity.name,sourceId:r,sourceField:e.sourceField,targetEntity:e.targetEntity,targetId:e.targetId}))).onConflictDoNothing()}invalidateCountCache(){this.countCache?.invalidate(this.entity.name)}};export{R as AdminClient};
2
2
  //# sourceMappingURL=index.mjs.map