@murumets-ee/entity 0.2.2 → 0.3.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 +115 -2
- 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 +11 -17
- 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 +0 -1
- package/dist/query/index.d.mts.map +1 -1
- package/dist/query/index.mjs +2 -2
- package/dist/query/index.mjs.map +1 -1
- package/package.json +10 -10
- package/LICENSE +0 -94
package/dist/admin/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { SQL } from "drizzle-orm";
|
|
1
|
+
import { SQL, eq } from "drizzle-orm";
|
|
2
|
+
import { PgTableWithColumns } from "drizzle-orm/pg-core";
|
|
2
3
|
import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
3
4
|
|
|
4
5
|
//#region src/count-cache.d.ts
|
|
@@ -221,7 +222,6 @@ interface Entity<AllFields extends Record<string, FieldConfig> = Record<string,
|
|
|
221
222
|
/** Admin UI configuration — controls sidebar, list, and form display */
|
|
222
223
|
admin?: EntityAdminConfig;
|
|
223
224
|
allFields: AllFields;
|
|
224
|
-
hooks: Behavior['hooks'];
|
|
225
225
|
}
|
|
226
226
|
//#endregion
|
|
227
227
|
//#region src/types/infer.d.ts
|
|
@@ -377,10 +377,123 @@ declare class AdminClient<AllFields extends Record<string, FieldConfig> = Record
|
|
|
377
377
|
/**
|
|
378
378
|
* Delete entity by ID.
|
|
379
379
|
*
|
|
380
|
+
* Wraps cascade + delete in a transaction so cascaded deletes are rolled
|
|
381
|
+
* back if the final entity delete fails (no partial data loss).
|
|
382
|
+
*
|
|
380
383
|
* Checks entity_refs for incoming references first — throws
|
|
381
384
|
* ReferencedEntityError if this entity is still used somewhere.
|
|
382
385
|
*/
|
|
383
386
|
delete(id: string): Promise<void>;
|
|
387
|
+
/**
|
|
388
|
+
* Delete multiple entities matching a WHERE condition.
|
|
389
|
+
*
|
|
390
|
+
* Wraps cascade + delete in a transaction so cascaded deletes are rolled
|
|
391
|
+
* back if the final entity delete fails (no partial data loss).
|
|
392
|
+
*
|
|
393
|
+
* Respects `onDelete` config on referencing fields:
|
|
394
|
+
* - `cascade`: recursively deletes referencing entities
|
|
395
|
+
* - `set-null`: nullifies the reference column
|
|
396
|
+
* - `restrict` (default): blocks deletion if references exist
|
|
397
|
+
*
|
|
398
|
+
* Runs beforeDelete/afterDelete hooks for each entity.
|
|
399
|
+
*
|
|
400
|
+
* @returns Number of rows deleted
|
|
401
|
+
*/
|
|
402
|
+
deleteMany(where: SQL): Promise<number>;
|
|
403
|
+
/** Maximum cascade depth to prevent infinite recursion from circular references. */
|
|
404
|
+
private static readonly MAX_CASCADE_DEPTH;
|
|
405
|
+
/**
|
|
406
|
+
* Handle incoming references before deleting entities.
|
|
407
|
+
*
|
|
408
|
+
* For each referencing entity/field:
|
|
409
|
+
* - `onDelete: 'cascade'` → recursively delete referencing entities
|
|
410
|
+
* - `onDelete: 'set-null'` → nullify the FK column on referencing rows
|
|
411
|
+
* - `onDelete: 'restrict'` → throw ReferencedEntityError
|
|
412
|
+
*
|
|
413
|
+
* Looks up referencing entity definitions from the app's entity registry
|
|
414
|
+
* to determine the onDelete strategy. Falls back to 'restrict' if the
|
|
415
|
+
* entity registry is unavailable.
|
|
416
|
+
*
|
|
417
|
+
* @param tx - Transaction handle for atomicity (cascade + delete in same tx)
|
|
418
|
+
* @param ids - Entity IDs being deleted
|
|
419
|
+
* @param depth - Current recursion depth (guards against circular references)
|
|
420
|
+
*/
|
|
421
|
+
private handleIncomingRefsForDelete;
|
|
422
|
+
/**
|
|
423
|
+
* Internal: delete within an existing transaction (used by cascade).
|
|
424
|
+
* Skips wrapping in a new transaction — the caller already has one.
|
|
425
|
+
*/
|
|
426
|
+
private deleteManyInTx;
|
|
427
|
+
/**
|
|
428
|
+
* Update multiple entities matching a WHERE condition.
|
|
429
|
+
*
|
|
430
|
+
* Unlike `update(id, data)`, this does NOT run hooks (beforeUpdate/afterUpdate),
|
|
431
|
+
* does NOT validate with Zod, and does NOT update blocks/translations.
|
|
432
|
+
* It's a direct bulk column update — use for operational changes like
|
|
433
|
+
* status transitions, assignments, and cleanup jobs.
|
|
434
|
+
*
|
|
435
|
+
* @returns Number of rows updated
|
|
436
|
+
*
|
|
437
|
+
* @example Close all resolved tickets older than 30 days
|
|
438
|
+
* ```ts
|
|
439
|
+
* const closed = await ticketClient.updateMany(
|
|
440
|
+
* and(eq(table.status, 'resolved'), lte(table.updatedAt, cutoff)),
|
|
441
|
+
* { status: 'closed', updatedAt: new Date() },
|
|
442
|
+
* )
|
|
443
|
+
* ```
|
|
444
|
+
*/
|
|
445
|
+
updateMany(where: SQL, data: Partial<InferUpdateInput<AllFields>>): Promise<number>;
|
|
446
|
+
/**
|
|
447
|
+
* Run an aggregate query on this entity's table.
|
|
448
|
+
*
|
|
449
|
+
* Supports GROUP BY with standard aggregate functions (count, sum, avg,
|
|
450
|
+
* min, max). This is the entity-level equivalent of Drupal's
|
|
451
|
+
* `EntityQueryAggregate` — standardized stats without raw SQL.
|
|
452
|
+
*
|
|
453
|
+
* @example Count tickets by status
|
|
454
|
+
* ```ts
|
|
455
|
+
* const stats = await ticketClient.aggregate({
|
|
456
|
+
* select: { count: sql<number>`count(*)::int` },
|
|
457
|
+
* groupBy: [table.status],
|
|
458
|
+
* })
|
|
459
|
+
* // → [{ status: 'open', count: 12 }, { status: 'closed', count: 45 }]
|
|
460
|
+
* ```
|
|
461
|
+
*
|
|
462
|
+
* @example Dashboard stats with conditional counts
|
|
463
|
+
* ```ts
|
|
464
|
+
* const [stats] = await ticketClient.aggregate({
|
|
465
|
+
* select: {
|
|
466
|
+
* open: sql<number>`count(case when ${table.status} = ${'open'} then 1 end)::int`,
|
|
467
|
+
* pending: sql<number>`count(case when ${table.status} = ${'pending'} then 1 end)::int`,
|
|
468
|
+
* },
|
|
469
|
+
* })
|
|
470
|
+
* ```
|
|
471
|
+
*/
|
|
472
|
+
aggregate<TResult extends Record<string, unknown> = Record<string, unknown>>(options: {
|
|
473
|
+
/** Named select expressions — each key becomes an output column. Use `sql<T>` for aggregates. */select: Record<string, SQL | ReturnType<typeof eq>>; /** Columns to GROUP BY. */
|
|
474
|
+
groupBy?: SQL[]; /** WHERE filter applied before aggregation. */
|
|
475
|
+
where?: SQL; /** ORDER BY for the result. */
|
|
476
|
+
orderBy?: SQL | SQL[]; /** Limit the number of rows returned. */
|
|
477
|
+
limit?: number;
|
|
478
|
+
}): Promise<TResult[]>;
|
|
479
|
+
/**
|
|
480
|
+
* Expose the underlying Drizzle table for typed Drizzle queries.
|
|
481
|
+
*
|
|
482
|
+
* Use this when you need column references for `.where()`, `.orderBy()`,
|
|
483
|
+
* aggregate expressions, or JOINs with other entity tables. This is the
|
|
484
|
+
* standardized way to access entity columns — never use string table names
|
|
485
|
+
* or `sql.identifier()`.
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* ```ts
|
|
489
|
+
* const t = ticketClient.getTable()
|
|
490
|
+
* const stats = await ticketClient.aggregate({
|
|
491
|
+
* select: { count: sql<number>`count(*)::int` },
|
|
492
|
+
* groupBy: [t.status],
|
|
493
|
+
* })
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
getTable(): PgTableWithColumns<any>;
|
|
384
497
|
/**
|
|
385
498
|
* Prepare data for insert — every field is a real column.
|
|
386
499
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/count-cache.ts","../../src/cursor.ts","../../src/admin-config.ts","../../src/fields/base.ts","../../src/behaviors/types.ts","../../src/define-entity.ts","../../src/types/infer.ts","../../src/types/logger.ts","../../src/admin/client.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/count-cache.ts","../../src/cursor.ts","../../src/admin-config.ts","../../src/fields/base.ts","../../src/behaviors/types.ts","../../src/define-entity.ts","../../src/types/infer.ts","../../src/types/logger.ts","../../src/admin/client.ts"],"mappings":"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"}
|
package/dist/admin/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{schemaRegistry as e}from"@murumets-ee/db";import{and as t,eq as n,gt as r,inArray as i,isNull as a,lt as o,or as s,sql as c}from"drizzle-orm";import{index as l,pgTable as u,unique as d,uuid as f,varchar as p}from"drizzle-orm/pg-core";import{z as m}from"zod";function h(e,i){let a=e[i.field];if(!a)return null;let c=i.direction===`desc`?o:r,l=c(a,i.value);if(!i.id)return l;let u=e.id;if(!u)return l;let d=c(u,i.id);return s(l,t(n(a,i.value),d))}function g(e,t,n){if(!t)return null;let r={},i=n?.select||Object.keys(e.allFields);for(let a of i){if(!n?.includeInternal&&a.startsWith(`_`))continue;let i=e.allFields[a];i&&i.type!==`blocks`&&(r[a]=t[a])}return r}function _(e,t,n){return t.map(t=>g(e,t,n))}var v=class extends Error{entityName;entityId;usages;constructor(e,t,n){let r=n.length;super(`Cannot delete ${e} '${t}': referenced by ${r} other entit${r===1?`y`:`ies`}`),this.name=`ReferencedEntityError`,this.entityName=e,this.entityId=t,this.usages=n}};const y=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function b(e,t){let n=[],r=new Set;function i(e,t,i){if(!y.test(i))return;let a=`${e}|${t}|${i}`;r.has(a)||(r.add(a),n.push({sourceField:e,targetEntity:t,targetId:i}))}for(let[n,r]of Object.entries(e)){let e=t[n];if(e!=null)switch(r.type){case`media`:typeof e==`string`&&i(n,`media`,e);break;case`reference`:{let t=r;if(t.cardinality===`many`&&Array.isArray(e))for(let r of e)typeof r==`string`&&i(n,t.entity,r);else typeof e==`string`&&i(n,t.entity,e);break}case`blocks`:{let t=r;if(!Array.isArray(e))break;for(let r of e){let e=r,a=e._block;if(!a)continue;let o=t.blocks.find(e=>e.slug===a);if(o)for(let[t,r]of Object.entries(o.fields)){let a=e[t];if(a!=null&&(r.type===`media`&&typeof a==`string`&&i(n,`media`,a),r.type===`reference`)){let e=r;if(e.cardinality===`many`&&Array.isArray(a))for(let t of a)typeof t==`string`&&i(n,e.entity,t);else typeof a==`string`&&i(n,e.entity,a)}}}break}}}return n}const x=u(`entity_refs`,{sourceEntity:p(`source_entity`,{length:100}).notNull(),sourceId:f(`source_id`).notNull(),sourceField:p(`source_field`,{length:100}).notNull(),targetEntity:p(`target_entity`,{length:100}).notNull(),targetId:f(`target_id`).notNull()},e=>[d(`uq_entity_refs`).on(e.sourceEntity,e.sourceId,e.sourceField,e.targetEntity,e.targetId),l(`idx_entity_refs_target`).on(e.targetEntity,e.targetId),l(`idx_entity_refs_source`).on(e.sourceEntity,e.sourceId)]);async function S(e,r,i){return await i.select({sourceEntity:x.sourceEntity,sourceId:x.sourceId,sourceField:x.sourceField}).from(x).where(t(n(x.targetEntity,e),n(x.targetId,r)))}function C(e){let t;switch(e.type){case`id`:t=m.string().uuid();break;case`text`:t=m.string(),e.maxLength&&(t=t.max(e.maxLength)),e.minLength&&(t=t.min(e.minLength)),e.pattern&&(t=t.regex(e.pattern));break;case`number`:t=m.number(),e.integer&&(t=t.int()),e.min!==void 0&&(t=t.min(e.min)),e.max!==void 0&&(t=t.max(e.max));break;case`boolean`:t=m.boolean();break;case`date`:t=m.date().or(m.string().datetime());break;case`select`:t=m.enum(e.options);break;case`reference`:t=e.cardinality===`many`?m.array(m.string().uuid()):m.string().uuid();break;case`media`:t=m.string().uuid();break;case`richtext`:t=m.union([m.string(),m.array(m.record(m.unknown()))]);break;case`slug`:t=m.string().regex(/^[a-z0-9-]+$/);break;case`json`:t=m.record(m.unknown());break;case`blocks`:t=m.array(m.object({_block:m.string()}).passthrough());break;default:t=m.unknown()}return e.required||(t=t.nullable().optional()),e.default!==void 0&&(t=t.default(e.default)),t}function w(e){let t={},n=new Set([`id`,`createdAt`,`updatedAt`,`createdBy`,`_version`]);for(let[r,i]of Object.entries(e.allFields))n.has(r)||(t[r]=C(i));return m.object(t)}function T(e){let t={},n=new Set([`id`,`createdAt`,`createdBy`,`updatedAt`,`updatedBy`,`_version`]);for(let[r,i]of Object.entries(e.allFields))n.has(r)||(t[r]=C(i).optional());return m.object(t)}var E=class{entity;db;logger;createSchema;updateSchema;table;countCache;constructor(t){if(typeof window<`u`)throw Error(`AdminClient cannot be used in browser code. Use QueryClient for frontend data access.`);this.entity=t.entity,this.db=t.db,this.logger=t.logger,this.countCache=t.countCache,this.createSchema=w(t.entity),this.updateSchema=T(t.entity);let n=e.get(t.entity.name);if(!n)throw Error(`Schema for entity '${t.entity.name}' not found in registry. Ensure schemas are generated and registered before creating AdminClient.`);this.table=n}async create(e){this.logger?.info({entity:this.entity.name},`Creating entity`);let t=e;if(this.entity.hooks?.beforeCreate){let e=await this.entity.hooks.beforeCreate(t);t=e===void 0?t:e}let n={};for(let e of[`createdAt`,`updatedAt`,`createdBy`,`updatedBy`])t[e]!==void 0&&(n[e]=t[e]);t={...this.createSchema.parse(t),...n};let r=this.prepareDataForInsert(t),[i]=await this.db.insert(this.table).values(r).returning();await this.saveBlocks(i.id,t),await this.syncRefs(i.id,t,`create`),this.entity.hooks?.afterCreate&&await this.entity.hooks.afterCreate(i),this.invalidateCountCache();let a=g(this.entity,i);if(a&&this.getBlocksFields().length>0){let e=await this.loadBlocks([a.id]);this.attachBlocks([a],e)}return a}async findById(e,t){this.logger?.info({entity:this.entity.name,id:e},`Finding entity by ID`);let[r]=await this.db.select().from(this.table).where(n(this.table.id,e));if(!r)return null;let i=g(this.entity,r);if(!i)return null;if(this.getBlocksFields().length>0){let e=await this.loadBlocks([i.id],t?.locale,{defaultLocale:t?.defaultLocale});this.attachBlocks([i],e)}if(t?.locale){let[e]=await this.mergeTranslations([i],t.locale);return e}return i}async findMany(e){this.logger?.info({entity:this.entity.name,options:e},`Finding entities`);let n=this.db.select().from(this.table).$dynamic(),r=[];if(e?.where&&r.push(e.where),e?.cursor){let t=this.entity.allFields;if(!(e.cursor.field in t)&&e.cursor.field!==`id`)throw Error(`Invalid cursor field: '${e.cursor.field}' is not a field on '${this.entity.name}'`);let n=h(this.table,e.cursor);n&&r.push(n)}if(r.length>0&&(n=n.where(t(...r))),e?.limit&&(n=n.limit(e.limit)),e?.offset&&!e?.cursor&&(n=n.offset(e.offset)),e?.orderBy){let t=Array.isArray(e.orderBy)?e.orderBy:[e.orderBy];n=n.orderBy(...t)}let i=await n,a=_(this.entity,i).filter(e=>e!==null);if(this.getBlocksFields().length>0&&a.length>0){let t=a.map(e=>e.id),n=await this.loadBlocks(t,e?.locale,{defaultLocale:e?.defaultLocale});this.attachBlocks(a,n)}return e?.locale?await this.mergeTranslations(a,e.locale):a}async count(e){this.logger?.info({entity:this.entity.name,options:e},`Counting entities`);let t=this.buildCountCacheKey(e?.where);if(this.countCache){let e=this.countCache.get(t);if(e!==void 0)return this.logger?.debug?.({entity:this.entity.name,cached:e},`Count cache hit`),e}let n=this.db.select({count:c`count(*)`}).from(this.table).$dynamic();e?.where&&(n=n.where(e.where));let[r]=await n,i=Number(r.count);return this.countCache&&this.countCache.set(t,i),i}async update(e,t,r){this.logger?.info({entity:this.entity.name,id:e},`Updating entity`);let i=t;if(this.entity.hooks?.beforeUpdate){let t=await this.entity.hooks.beforeUpdate(e,i);i=t===void 0?i:t}i=this.updateSchema.parse(i);let a=this.prepareDataForUpdate(i),o;if(Object.keys(a).length>0){let[t]=await this.db.update(this.table).set(a).where(n(this.table.id,e)).returning();o=t}else{let[t]=await this.db.select().from(this.table).where(n(this.table.id,e));o=t}await this.saveBlocks(e,i,r),await this.syncRefs(e,i,`update`),this.entity.hooks?.afterUpdate&&await this.entity.hooks.afterUpdate(o);let s=g(this.entity,o);if(s&&this.getBlocksFields().length>0){let t=await this.loadBlocks([e]);this.attachBlocks([s],t)}return s}async delete(e){this.logger?.info({entity:this.entity.name,id:e},`Deleting entity`);let r=await S(this.entity.name,e,this.db);if(r.length>0)throw new v(this.entity.name,e,r);this.entity.hooks?.beforeDelete&&await this.entity.hooks.beforeDelete(e),await this.db.delete(this.table).where(n(this.table.id,e)),await this.db.delete(x).where(t(n(x.sourceEntity,this.entity.name),n(x.sourceId,e))),this.entity.hooks?.afterDelete&&await this.entity.hooks.afterDelete(e),this.invalidateCountCache()}prepareDataForInsert(e){let t={};for(let[n,r]of Object.entries(e)){let e=this.entity.allFields[n];e&&n!==`id`&&e.type!==`blocks`&&(t[n]=r)}return t}prepareDataForUpdate(e){let t={};for(let[n,r]of Object.entries(e)){let e=this.entity.allFields[n];e&&n!==`id`&&e.type!==`blocks`&&(t[n]=r)}return t}async mergeTranslations(r,a){if(!r.length)return r;let o=`${this.entity.name}_translations`,s=e.get(o);if(!s)return r;let c=r.map(e=>e.id),l=await this.db.select().from(s).where(t(i(s.entityId,c),n(s.locale,a))),u=Object.entries(this.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e),d=new Map;for(let e of l){let t={};for(let n of u){let r=e[n];r!=null&&(t[n]=r)}d.set(e.entityId,t)}return r.map(e=>{let t=d.get(e.id);return t?{...e,...t}:e})}async saveTranslation(t,n,r){this.logger?.info({entity:this.entity.name,entityId:t,locale:n},`Saving translation`);let i=`${this.entity.name}_translations`,a=e.get(i);if(!a)throw Error(`Translation table ${i} not found in schema registry`);let o=Object.entries(this.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e);if(o.length===0)throw Error(`Entity ${this.entity.name} has no translatable fields`);let s={entityId:t,locale:n},c={};for(let e of o)r[e]!==void 0&&(s[e]=r[e],c[e]=r[e]);await this.db.insert(a).values(s).onConflictDoUpdate({target:[a.entityId,a.locale],set:c}),this.logger?.info({entity:this.entity.name,entityId:t,locale:n},`Translation saved`)}async deleteTranslation(r,i){this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Deleting translation`);let a=`${this.entity.name}_translations`,o=e.get(a);if(!o)throw Error(`Translation table ${a} not found in schema registry`);i?(await this.db.delete(o).where(t(n(o.entityId,r),n(o.locale,i))),this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Translation deleted`)):(await this.db.delete(o).where(n(o.entityId,r)),this.logger?.info({entity:this.entity.name,entityId:r},`All translations deleted`))}async saveBlockTranslations(r,i,o){this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Saving block translations`);let s=this.getBlocksFields();if(s.length===0)return;let c=e.get(`${this.entity.name}_layout_translations`);if(!c)return;let l=e.get(`${this.entity.name}_layout`);if(l){for(let{name:e,config:u}of s){let s=o[e];if(!Array.isArray(s))continue;let d=u.blocks;if(!d)continue;let f=await this.db.select({id:l.id,blockType:l.blockType}).from(l).where(t(n(l.entityId,r),n(l.fieldName,e),a(l.locale))).orderBy(l.sortOrder);for(let e=0;e<s.length;e++){let t=s[e],n=t._block;if(!n)continue;let r=f[e];if(!r||r.blockType!==n)continue;let a=d.find(e=>e.slug===n);if(!a)continue;let o={};for(let[e,n]of Object.entries(a.fields))n.translatable&&t[e]!==void 0&&(o[e]=t[e]);Object.keys(o).length!==0&&await this.db.insert(c).values({layoutId:r.id,locale:i,fields:o}).onConflictDoUpdate({target:[c.layoutId,c.locale],set:{fields:o}})}}this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Block translations saved`)}}async saveLocalizedBlocks(e,t,n){this.logger?.info({entity:this.entity.name,entityId:e,locale:t},`Saving localized blocks`),await this.saveBlocks(e,n,{locale:t})}getBlocksFields(){return Object.entries(this.entity.allFields).filter(([e,t])=>t.type===`blocks`).map(([e,t])=>({name:e,config:t}))}async saveBlocks(r,i,o){let s=this.getBlocksFields();if(s.length===0)return;let c=e.get(`${this.entity.name}_layout`);if(c)for(let{name:e,config:l}of s){let s=i[e];if(!Array.isArray(s))continue;let u=`localized`in l&&l.localized===!0,d=u?o?.locale??null:null,f=[n(c.entityId,r),n(c.fieldName,e)];d?f.push(n(c.locale,d)):u||f.push(a(c.locale)),await this.db.delete(c).where(t(...f));for(let t=0;t<s.length;t++){let n=s[t],i=n._block;if(!i)continue;let{_block:a,_id:o,...l}=n;await this.db.insert(c).values({entityId:r,fieldName:e,blockType:i,sortOrder:t,data:l,locale:d})}}}async loadBlocks(r,o,c){let l=this.getBlocksFields();if(l.length===0||r.length===0)return new Map;let u=e.get(`${this.entity.name}_layout`);if(!u)return new Map;let d=l.filter(({config:e})=>!(`localized`in e&&e.localized)),f=l.filter(({config:e})=>`localized`in e&&e.localized),p=new Map;if(d.length>0){let s=await this.db.select().from(u).where(t(i(u.entityId,r),a(u.locale))).orderBy(u.sortOrder),c;if(o&&s.length>0){let r=e.get(`${this.entity.name}_layout_translations`);if(r){let e=s.map(e=>e.id),a=await this.db.select().from(r).where(t(i(r.layoutId,e),n(r.locale,o)));c=new Map;for(let e of a)c.set(e.layoutId,e.fields??{})}}let l=new Map;for(let{config:e}of d){let t=e.blocks;if(t)for(let e of t)l.set(e.slug,e.fields)}for(let e of s){let t=e.entityId,n=e.fieldName;p.has(t)||p.set(t,{});let r=p.get(t);r[n]||(r[n]=[]);let i=e.data??{},a=c?.get(e.id),s={_block:e.blockType,_id:e.id,...i,...a??{}};if(o){let t=e.blockType,n=l.get(t);if(n)for(let[e,t]of Object.entries(n))t.translatable&&!a?.[e]&&(s[e]=``)}r[n].push(s)}}if(f.length>0){let e=!o||o===c?.defaultLocale,l=o?e?s(n(u.locale,o),a(u.locale)):n(u.locale,o):a(u.locale),d=await this.db.select().from(u).where(t(i(u.entityId,r),l)).orderBy(u.sortOrder),f=new Map;for(let e of d){let t=`${e.entityId}::${e.fieldName}`;f.has(t)||f.set(t,{localeRows:[],nullRows:[]});let n=f.get(t);e.locale?n.localeRows.push(e):n.nullRows.push(e)}for(let[,{localeRows:e,nullRows:t}]of f){let n=e.length>0?e:t;for(let e of n){let t=e.entityId,n=e.fieldName;p.has(t)||p.set(t,{});let r=p.get(t);r[n]||(r[n]=[]);let i=e.data??{};r[n].push({_block:e.blockType,_id:e.id,...i})}}}return p}attachBlocks(e,t){let n=this.getBlocksFields();if(n.length!==0)for(let r of e){let e=r.id,i=t.get(e)??{};for(let{name:e}of n)r[e]=i[e]??[]}}async syncRefs(r,a,o){if(!e.has(`entity_refs`))return;let s=this.entity.allFields;if(o===`update`){let e=Object.keys(a).filter(e=>{let t=s[e];return t&&(t.type===`media`||t.type===`reference`||t.type===`blocks`)});e.length>0&&await this.db.delete(x).where(t(n(x.sourceEntity,this.entity.name),n(x.sourceId,r),i(x.sourceField,e)))}let c=b(s,a);c.length>0&&await this.db.insert(x).values(c.map(e=>({sourceEntity:this.entity.name,sourceId:r,sourceField:e.sourceField,targetEntity:e.targetEntity,targetId:e.targetId}))).onConflictDoNothing()}buildCountCacheKey(e){return e?`${this.entity.name}:${String(e)}`:this.entity.name}invalidateCountCache(){this.countCache?.invalidate(this.entity.name)}};export{E as AdminClient};
|
|
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){let t;switch(e.type){case`id`:t=h.string().uuid();break;case`text`:t=h.string(),e.maxLength&&(t=t.max(e.maxLength)),e.minLength&&(t=t.min(e.minLength)),e.pattern&&(t=t.regex(e.pattern));break;case`number`:t=h.number(),e.integer&&(t=t.int()),e.min!==void 0&&(t=t.min(e.min)),e.max!==void 0&&(t=t.max(e.max));break;case`boolean`:t=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.record(h.unknown());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 w(e){let t={},n=new Set([`id`,`createdAt`,`updatedAt`,`createdBy`,`_version`]);for(let[r,i]of Object.entries(e.allFields))n.has(r)||(t[r]=C(i));return h.object(t)}function T(e){let t={},n=new Set([`id`,`createdAt`,`createdBy`,`updatedAt`,`updatedBy`,`_version`]);for(let[r,i]of Object.entries(e.allFields))n.has(r)||(t[r]=C(i).optional());return h.object(t)}var E=class r{entity;db;logger;createSchema;updateSchema;table;countCache;constructor(t){if(typeof window<`u`)throw Error(`AdminClient cannot be used in browser code. Use QueryClient for frontend data access.`);this.entity=t.entity,this.db=t.db,this.logger=t.logger,this.countCache=t.countCache,this.createSchema=w(t.entity),this.updateSchema=T(t.entity);let n=e.get(t.entity.name);if(!n)throw Error(`Schema for entity '${t.entity.name}' not found in registry. Ensure schemas are generated and registered before creating AdminClient.`);this.table=n}async create(e){this.logger?.info({entity:this.entity.name},`Creating entity`);let t=e;for(let e of this.entity.behaviors??[])if(e.hooks?.beforeCreate){let n=await e.hooks.beforeCreate(t);n!==void 0&&(t=n)}let n={};for(let e of[`createdAt`,`updatedAt`,`createdBy`,`updatedBy`])t[e]!==void 0&&(n[e]=t[e]);t={...this.createSchema.parse(t),...n};let r=this.prepareDataForInsert(t),[i]=await this.db.insert(this.table).values(r).returning();await this.saveBlocks(i.id,t),await this.syncRefs(i.id,t,`create`);for(let e of this.entity.behaviors??[])e.hooks?.afterCreate&&await e.hooks.afterCreate(i);this.invalidateCountCache();let a=_(this.entity,i);if(a&&this.getBlocksFields().length>0){let e=await this.loadBlocks([a.id]);this.attachBlocks([a],e)}return a}async findById(e,t){this.logger?.info({entity:this.entity.name,id:e},`Finding entity by ID`);let[r]=await this.db.select().from(this.table).where(n(this.table.id,e));if(!r)return null;let i=_(this.entity,r);if(!i)return null;if(this.getBlocksFields().length>0){let e=await this.loadBlocks([i.id],t?.locale,{defaultLocale:t?.defaultLocale});this.attachBlocks([i],e)}if(t?.locale){let[e]=await this.mergeTranslations([i],t.locale);return e}return i}async findMany(e){this.logger?.info({entity:this.entity.name,options:e},`Finding entities`);let n=this.db.select().from(this.table).$dynamic(),r=[];if(e?.where&&r.push(e.where),e?.cursor){let t=this.entity.allFields;if(!(e.cursor.field in t)&&e.cursor.field!==`id`)throw Error(`Invalid cursor field: '${e.cursor.field}' is not a field on '${this.entity.name}'`);let n=g(this.table,e.cursor);n&&r.push(n)}if(r.length>0&&(n=n.where(t(...r))),e?.limit&&(n=n.limit(e.limit)),e?.offset&&!e?.cursor&&(n=n.offset(e.offset)),e?.orderBy){let t=Array.isArray(e.orderBy)?e.orderBy:[e.orderBy];n=n.orderBy(...t)}let i=await n,a=v(this.entity,i).filter(e=>e!==null);if(this.getBlocksFields().length>0&&a.length>0){let t=a.map(e=>e.id),n=await this.loadBlocks(t,e?.locale,{defaultLocale:e?.defaultLocale});this.attachBlocks(a,n)}return e?.locale?await this.mergeTranslations(a,e.locale):a}async count(e){this.logger?.info({entity:this.entity.name,options:e},`Counting entities`);let t=this.buildCountCacheKey(e?.where);if(this.countCache){let e=this.countCache.get(t);if(e!==void 0)return this.logger?.debug?.({entity:this.entity.name,cached:e},`Count cache hit`),e}let n=this.db.select({count:l`count(*)`}).from(this.table).$dynamic();e?.where&&(n=n.where(e.where));let[r]=await n,i=Number(r.count);return this.countCache&&this.countCache.set(t,i),i}async update(e,t,r){this.logger?.info({entity:this.entity.name,id:e},`Updating entity`);let i=t;for(let t of this.entity.behaviors??[])if(t.hooks?.beforeUpdate){let n=await t.hooks.beforeUpdate(e,i);n!==void 0&&(i=n)}i=this.updateSchema.parse(i);let a=this.prepareDataForUpdate(i),o;if(Object.keys(a).length>0){let[t]=await this.db.update(this.table).set(a).where(n(this.table.id,e)).returning();o=t}else{let[t]=await this.db.select().from(this.table).where(n(this.table.id,e));o=t}await this.saveBlocks(e,i,r),await this.syncRefs(e,i,`update`);for(let e of this.entity.behaviors??[])e.hooks?.afterUpdate&&await e.hooks.afterUpdate(o);let s=_(this.entity,o);if(s&&this.getBlocksFields().length>0){let t=await this.loadBlocks([e]);this.attachBlocks([s],t)}return s}async delete(e){this.logger?.info({entity:this.entity.name,id:e},`Deleting entity`),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`);let r=(await this.db.select({id:this.table.id}).from(this.table).where(e)).map(e=>e.id);if(r.length===0)return 0;await this.db.transaction(async e=>{await this.handleIncomingRefsForDelete(e,r);for(let e of r)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,r)),await e.delete(S).where(t(n(S.sourceEntity,this.entity.name),a(S.sourceId,r)))});for(let e of r)for(let t of this.entity.behaviors??[])t.hooks?.afterDelete&&await t.hooks.afterDelete(e);return this.invalidateCountCache(),r.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 t=e.get(c.sourceEntity);t&&l&&await new r({entity:l,db:i,logger:this.logger}).deleteManyInTx(i,a(t.id,c.sourceIds),s+1)}else if(d===`set-null`){let r=e.get(c.sourceEntity);r&&r[c.sourceField]&&(await i.update(r).set({[c.sourceField]:null}).where(a(r.id,c.sourceIds)),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,t){this.logger?.info({entity:this.entity.name},`Bulk updating entities`);let n=this.prepareDataForUpdate(t),r=await this.db.update(this.table).set(n).where(e).returning({id:this.table.id});return this.invalidateCountCache(),r.length}async aggregate(e){this.logger?.info({entity:this.entity.name},`Aggregate query`);let t=this.db.select(e.select).from(this.table).$dynamic();if(e.where&&(t=t.where(e.where)),e.groupBy&&e.groupBy.length>0&&(t=t.groupBy(...e.groupBy)),e.orderBy){let n=Array.isArray(e.orderBy)?e.orderBy:[e.orderBy];t=t.orderBy(...n)}return e.limit&&(t=t.limit(e.limit)),await t}getTable(){return this.table}prepareDataForInsert(e){let t={};for(let[n,r]of Object.entries(e)){let e=this.entity.allFields[n];e&&n!==`id`&&e.type!==`blocks`&&(t[n]=r)}return t}prepareDataForUpdate(e){let t={};for(let[n,r]of Object.entries(e)){let e=this.entity.allFields[n];e&&n!==`id`&&e.type!==`blocks`&&(t[n]=r)}return t}async mergeTranslations(r,i){if(!r.length)return r;let o=`${this.entity.name}_translations`,s=e.get(o);if(!s)return r;let c=r.map(e=>e.id),l=await this.db.select().from(s).where(t(a(s.entityId,c),n(s.locale,i))),u=Object.entries(this.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e),d=new Map;for(let e of l){let t={};for(let n of u){let r=e[n];r!=null&&(t[n]=r)}d.set(e.entityId,t)}return r.map(e=>{let t=d.get(e.id);return t?{...e,...t}:e})}async saveTranslation(t,n,r){this.logger?.info({entity:this.entity.name,entityId:t,locale:n},`Saving translation`);let i=`${this.entity.name}_translations`,a=e.get(i);if(!a)throw Error(`Translation table ${i} not found in schema registry`);let o=Object.entries(this.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e);if(o.length===0)throw Error(`Entity ${this.entity.name} has no translatable fields`);let s={entityId:t,locale:n},c={};for(let e of o)r[e]!==void 0&&(s[e]=r[e],c[e]=r[e]);await this.db.insert(a).values(s).onConflictDoUpdate({target:[a.entityId,a.locale],set:c}),this.logger?.info({entity:this.entity.name,entityId:t,locale:n},`Translation saved`)}async deleteTranslation(r,i){this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Deleting translation`);let a=`${this.entity.name}_translations`,o=e.get(a);if(!o)throw Error(`Translation table ${a} not found in schema registry`);i?(await this.db.delete(o).where(t(n(o.entityId,r),n(o.locale,i))),this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Translation deleted`)):(await this.db.delete(o).where(n(o.entityId,r)),this.logger?.info({entity:this.entity.name,entityId:r},`All translations deleted`))}async saveBlockTranslations(r,i,a){this.logger?.info({entity:this.entity.name,entityId:r,locale:i},`Saving block translations`);let s=this.getBlocksFields();if(s.length===0)return;let c=e.get(`${this.entity.name}_layout_translations`);if(!c)return;let l=e.get(`${this.entity.name}_layout`);if(l){for(let{name:e,config:u}of s){let s=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){this.logger?.info({entity:this.entity.name,entityId:e,locale:t},`Saving localized blocks`),await this.saveBlocks(e,n,{locale:t})}getBlocksFields(){return Object.entries(this.entity.allFields).filter(([e,t])=>t.type===`blocks`).map(([e,t])=>({name:e,config:t}))}async saveBlocks(r,i,a){let s=this.getBlocksFields();if(s.length===0)return;let c=e.get(`${this.entity.name}_layout`);if(c)for(let{name:e,config:l}of s){let s=i[e];if(!Array.isArray(s))continue;let u=`localized`in l&&l.localized===!0,d=u?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 loadBlocks(r,i,s){let l=this.getBlocksFields();if(l.length===0||r.length===0)return new Map;let u=e.get(`${this.entity.name}_layout`);if(!u)return new Map;let d=l.filter(({config:e})=>!(`localized`in e&&e.localized)),f=l.filter(({config:e})=>`localized`in e&&e.localized),p=new Map;if(d.length>0){let s=await this.db.select().from(u).where(t(a(u.entityId,r),o(u.locale))).orderBy(u.sortOrder),c;if(i&&s.length>0){let r=e.get(`${this.entity.name}_layout_translations`);if(r){let e=s.map(e=>e.id),o=await this.db.select().from(r).where(t(a(r.layoutId,e),n(r.locale,i)));c=new Map;for(let e of o)c.set(e.layoutId,e.fields??{})}}let l=new Map;for(let{config:e}of d)if(e.type===`blocks`)for(let t of e.blocks)l.set(t.slug,t.fields);for(let e of s){let t=e.entityId,n=e.fieldName;p.has(t)||p.set(t,{});let r=p.get(t);r[n]||(r[n]=[]);let a=e.data??{},o=c?.get(e.id),s={_block:e.blockType,_id:e.id,...a,...o??{}};if(i){let t=e.blockType,n=l.get(t);if(n)for(let[e,t]of Object.entries(n))t.translatable&&!o?.[e]&&(s[e]=``)}r[n].push(s)}}if(f.length>0){let e=!i||i===s?.defaultLocale,l=i?e?c(n(u.locale,i),o(u.locale)):n(u.locale,i):o(u.locale),d=await this.db.select().from(u).where(t(a(u.entityId,r),l)).orderBy(u.sortOrder),f=new Map;for(let e of d){let t=`${e.entityId}::${e.fieldName}`;f.has(t)||f.set(t,{localeRows:[],nullRows:[]});let n=f.get(t);e.locale?n.localeRows.push(e):n.nullRows.push(e)}for(let[,{localeRows:e,nullRows:t}]of f){let n=e.length>0?e:t;for(let e of n){let t=e.entityId,n=e.fieldName;p.has(t)||p.set(t,{});let r=p.get(t);r[n]||(r[n]=[]);let i=e.data??{};r[n].push({_block:e.blockType,_id:e.id,...i})}}}return p}attachBlocks(e,t){let n=this.getBlocksFields();if(n.length!==0)for(let r of e){let e=r.id,i=t.get(e)??{};for(let{name:e}of n)r[e]=i[e]??[]}}async syncRefs(r,i,o){if(!e.has(`entity_refs`))return;let s=this.entity.allFields;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()}buildCountCacheKey(e){return e?`${this.entity.name}:${String(e)}`:this.entity.name}invalidateCountCache(){this.countCache?.invalidate(this.entity.name)}};export{E as AdminClient};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|