@murumets-ee/entity 0.3.0 → 0.4.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/LICENSE ADDED
@@ -0,0 +1,94 @@
1
+ Elastic License 2.0 (ELv2)
2
+
3
+ URL: https://www.elastic.co/licensing/elastic-license
4
+
5
+ ## Acceptance
6
+
7
+ By using the software, you agree to all of the terms and conditions below.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
12
+ non-sublicensable, non-transferable license to use, copy, distribute, make
13
+ available, and prepare derivative works of the software, in each case subject
14
+ to the limitations and conditions below.
15
+
16
+ ## Limitations
17
+
18
+ You may not provide the software to third parties as a hosted or managed
19
+ service, where the service provides users with access to any substantial set
20
+ of the features or functionality of the software.
21
+
22
+ You may not move, change, disable, or circumvent the license key functionality
23
+ in the software, and you may not remove or obscure any functionality in the
24
+ software that is protected by the license key.
25
+
26
+ You may not alter, remove, or obscure any licensing, copyright, or other
27
+ notices of the licensor in the software. Any use of the licensor's trademarks
28
+ is subject to applicable law.
29
+
30
+ ## Patents
31
+
32
+ The licensor grants you a license, under any patent claims the licensor can
33
+ license, or becomes able to license, to make, have made, use, sell, offer for
34
+ sale, import and have imported the software, in each case subject to the
35
+ limitations and conditions in this license. This license does not cover any
36
+ patent claims that you cause to be infringed by modifications or additions to
37
+ the software. If you or your company make any written claim that the software
38
+ infringes or contributes to infringement of any patent, your patent license
39
+ for the software granted under these terms ends immediately. If your company
40
+ makes such a claim, your patent license ends immediately for work on behalf
41
+ of your company.
42
+
43
+ ## Notices
44
+
45
+ You must ensure that anyone who gets a copy of any part of the software from
46
+ you also gets a copy of these terms.
47
+
48
+ If you modify the software, you must include in any modified copies of the
49
+ software prominent notices stating that you have modified the software.
50
+
51
+ ## No Other Rights
52
+
53
+ These terms do not imply any licenses other than those expressly granted in
54
+ these terms.
55
+
56
+ ## Termination
57
+
58
+ If you use the software in violation of these terms, such use is not licensed,
59
+ and your licenses will automatically terminate. If the licensor provides you
60
+ with a notice of your violation, and you cease all violation of this license
61
+ no later than 30 days after you receive that notice, your licenses will be
62
+ reinstated retroactively. However, if you violate these terms after such
63
+ reinstatement, any additional violation of these terms will cause your
64
+ licenses to terminate automatically and permanently.
65
+
66
+ ## No Liability
67
+
68
+ As far as the law allows, the software comes as is, without any warranty or
69
+ condition, and the licensor will not be liable to you for any damages arising
70
+ out of these terms or the use or nature of the software, under any kind of
71
+ legal claim.
72
+
73
+ ## Definitions
74
+
75
+ The **licensor** is the entity offering these terms, and the **software** is
76
+ the software the licensor makes available under these terms, including any
77
+ portion of it.
78
+
79
+ **you** refers to the individual or entity agreeing to these terms.
80
+
81
+ **your company** is any legal entity, sole proprietorship, or other kind of
82
+ organization that you work for, plus all organizations that have control over,
83
+ are under the control of, or are under common control with that organization.
84
+ **control** means ownership of substantially all the assets of an entity, or
85
+ the power to direct the management and policies of an entity (for example, by
86
+ voting right, contract, or otherwise). Control can be direct or indirect.
87
+
88
+ **your licenses** are all the licenses granted to you for the software under
89
+ these terms.
90
+
91
+ **use** means anything you do with the software requiring one of your
92
+ licenses.
93
+
94
+ **trademark** means trademarks, service marks, and similar rights.
@@ -189,7 +189,7 @@ 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
- interface Behavior<F extends Record<string, FieldConfig> = Record<string, FieldConfig>> {
192
+ interface Behavior<F extends Record<string, FieldConfig> = {}> {
193
193
  name: string;
194
194
  fields?: F;
195
195
  hooks?: {
@@ -224,22 +224,50 @@ interface Entity<AllFields extends Record<string, FieldConfig> = Record<string,
224
224
  allFields: AllFields;
225
225
  }
226
226
  //#endregion
227
+ //#region src/shared/entity-data-ops.d.ts
228
+ /**
229
+ * Security context resolved per-request. Provided by the consumer
230
+ * (e.g., via React.cache() in Next.js, or runAsCli for CLI).
231
+ * The entity package has zero knowledge of how this is resolved.
232
+ */
233
+ interface SecurityContext {
234
+ user: {
235
+ id: string;
236
+ groups: string[];
237
+ };
238
+ checker: (role: string, resource: string, action: string) => boolean;
239
+ scope?: {
240
+ type: string;
241
+ id: string;
242
+ };
243
+ }
244
+ /**
245
+ * Function that resolves the current request's security context.
246
+ * Injected at construction time — the entity package never imports
247
+ * @murumets-ee/core or any framework-specific code.
248
+ *
249
+ * Returns undefined only when intentionally skipping enforcement
250
+ * (should not happen in production — throw ForbiddenError instead).
251
+ */
252
+ type ContextResolver = () => SecurityContext | undefined | Promise<SecurityContext | undefined>;
253
+ //#endregion
227
254
  //#region src/types/infer.d.ts
255
+ /**
256
+ * Recursive JSON-compatible value, for `field.json()` (jsonb column).
257
+ * Mirrors what Postgres jsonb can store: primitives, arrays, or objects.
258
+ */
259
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
260
+ [key: string]: JsonValue;
261
+ };
228
262
  /**
229
263
  * Maps a single FieldConfig to its TypeScript output type.
230
264
  * Each branch is a shallow comparison — no recursion.
231
265
  */
232
- type FieldToTS<F extends FieldConfig> = F extends IdField ? string : F extends TextField ? string : F extends NumberField ? number : F extends BooleanField ? boolean : F extends DateField ? Date | string : F extends SelectField ? F['options'][number] : F extends ReferenceField ? F['cardinality'] extends 'many' ? string[] : string : F extends MediaField ? string : F extends RichTextField ? Record<string, unknown>[] : F extends SlugField ? string : F extends JsonField ? Record<string, unknown> : F extends BlocksField ? Array<{
266
+ type FieldToTS<F extends FieldConfig> = F extends IdField ? string : F extends TextField ? string : F extends NumberField ? number : F extends BooleanField ? boolean : F extends DateField ? Date | string : F extends SelectField ? F['options'][number] : F extends ReferenceField ? F['cardinality'] extends 'many' ? string[] : string : F extends MediaField ? string : F extends RichTextField ? Record<string, unknown>[] : F extends SlugField ? string : F extends JsonField ? JsonValue : F extends BlocksField ? Array<{
233
267
  _block: string;
234
268
  _id: string;
235
269
  [key: string]: unknown;
236
270
  }> : never;
237
- /**
238
- * Extract keys of fields where `required` is literally `true`.
239
- * Fields without `required` or with `required?: false` are optional.
240
- */
241
- type RequiredFieldKeys<Fields extends Record<string, FieldConfig>> = { [K in keyof Fields]: Fields[K]['required'] extends true ? K : never }[keyof Fields];
242
- type OptionalFieldKeys<Fields extends Record<string, FieldConfig>> = { [K in keyof Fields]: Fields[K]['required'] extends true ? never : K }[keyof Fields];
243
271
  /**
244
272
  * Maps a full field record to its TypeScript output type.
245
273
  * Required fields are non-nullable; optional fields are `T | null | undefined`.
@@ -250,7 +278,7 @@ type OptionalFieldKeys<Fields extends Record<string, FieldConfig>> = { [K in key
250
278
  */
251
279
  type InferEntityDTO<Fields extends Record<string, FieldConfig>> = {
252
280
  id: string;
253
- } & { [K in Exclude<RequiredFieldKeys<Fields>, 'id'>]: FieldToTS<Fields[K]> } & { [K in OptionalFieldKeys<Fields>]?: FieldToTS<Fields[K]> | null };
281
+ } & { [K in keyof Fields as K extends 'id' ? never : Fields[K]['required'] extends true ? K : never]: FieldToTS<Fields[K]> } & { [K in keyof Fields as Fields[K]['required'] extends true ? never : K]?: FieldToTS<Fields[K]> | null };
254
282
  /** Fields that are auto-generated and should not appear in create input. */
255
283
  type AutoGeneratedFields = 'id' | 'createdAt' | 'updatedAt' | 'createdBy' | 'updatedBy' | '_version';
256
284
  /**
@@ -258,10 +286,10 @@ type AutoGeneratedFields = 'id' | 'createdAt' | 'updatedAt' | 'createdBy' | 'upd
258
286
  * - Omits auto-generated fields (id, timestamps, version)
259
287
  * - Required fields stay required; optional fields stay optional
260
288
  */
261
- type InferCreateInput<Fields extends Record<string, FieldConfig>> = Omit<{ [K in Exclude<RequiredFieldKeys<Fields>, 'id'>]: FieldToTS<Fields[K]> } & { [K in OptionalFieldKeys<Fields>]?: FieldToTS<Fields[K]> | null }, AutoGeneratedFields>;
289
+ type InferCreateInput<Fields extends Record<string, FieldConfig>> = Omit<{ [K in keyof Fields as K extends 'id' ? never : Fields[K]['required'] extends true ? K : never]: FieldToTS<Fields[K]> } & { [K in keyof Fields as Fields[K]['required'] extends true ? never : K]?: FieldToTS<Fields[K]> | null }, AutoGeneratedFields>;
262
290
  /** Fields that cannot be changed after creation. */
263
291
  type ImmutableFields = 'id' | 'createdAt' | 'createdBy';
264
- type InferUpdateInput<Fields extends Record<string, FieldConfig>> = Partial<Omit<InferEntityDTO<Fields>, ImmutableFields>>;
292
+ type InferUpdateInput<Fields extends Record<string, FieldConfig>> = { [K in keyof Fields as K extends ImmutableFields ? never : K]?: FieldToTS<Fields[K]> | null };
265
293
  //#endregion
266
294
  //#region src/types/logger.d.ts
267
295
  /**
@@ -284,6 +312,12 @@ interface AdminClientConfig<AllFields extends Record<string, FieldConfig> = Reco
284
312
  logger?: Logger;
285
313
  /** Optional count cache for COUNT(*) query optimization. */
286
314
  countCache?: CountCacheLike;
315
+ /**
316
+ * Resolves the current request's security context (user, role checker, scope).
317
+ * Provided automatically by `createAdminClient()` from @murumets-ee/core/clients.
318
+ * For direct `new AdminClient()` usage, pass your own resolver or use `runAsCli()`.
319
+ */
320
+ contextResolver?: ContextResolver;
287
321
  }
288
322
  interface FindManyOptions {
289
323
  where?: SQL | undefined;
@@ -307,20 +341,23 @@ interface CountOptions {
307
341
  where?: SQL | undefined;
308
342
  }
309
343
  /**
310
- * AdminClient - Full CRUD operations with security enforcement
344
+ * AdminClient - Full CRUD operations with data integrity + security enforcement
311
345
  *
312
- * Security layers:
313
- * 1. Runtime check: typeof window !== 'undefined' throw (allows CLI scripts)
314
- * 2. Uses read-write DB connection
315
- * 3. Validation with Zod before every write
316
- * 4. Hook execution (beforeCreate, afterCreate, etc.)
346
+ * **Requires RequestContext.** All operations check permissions and scope via
347
+ * the context established by `runWithContextAsync()`. CLI scripts use `runAsCli()`.
348
+ *
349
+ * Security & integrity layers:
350
+ * 1. Runtime check: `typeof window !== 'undefined'` → throw (prevents browser usage)
351
+ * 2. Permission enforcement: `assertEntityAccess()` checks `checker(role, entity, action)` from context
352
+ * 3. Scope filtering: scoped entities auto-filter by `_scopeId` from context
353
+ * 4. Zod validation: every write (create, update, updateMany) validated before DB
354
+ * 5. Column whitelist: `prepareDataForInsert/Update` drops unknown fields
355
+ * 6. Hook execution: `beforeCreate`, `afterCreate`, etc. from entity behaviors
356
+ * 7. Reference tracking: `entity_refs` table for delete protection
317
357
  *
318
358
  * Note: We don't use 'server-only' import because it blocks CLI scripts.
319
359
  * Next.js bundler protection comes from subpath exports (@murumets-ee/core/clients).
320
360
  *
321
- * Phase 1: Core CRUD + hooks + validation
322
- * Phase 2 (TODO): Access control, request context, scoping
323
- *
324
361
  * @typeParam AllFields - The entity's complete field map. Inferred automatically
325
362
  * when constructing via `createAdminClient(entity)`.
326
363
  */
@@ -332,6 +369,9 @@ declare class AdminClient<AllFields extends Record<string, FieldConfig> = Record
332
369
  private updateSchema;
333
370
  private table;
334
371
  private countCache?;
372
+ private contextResolver?;
373
+ /** Shared context for entity-data-ops functions. */
374
+ private get ctx();
335
375
  constructor(config: AdminClientConfig<AllFields>);
336
376
  /**
337
377
  * Create a new entity
@@ -441,8 +481,26 @@ declare class AdminClient<AllFields extends Record<string, FieldConfig> = Record
441
481
  * { status: 'closed', updatedAt: new Date() },
442
482
  * )
443
483
  * ```
484
+ *
485
+ * @example Cascade materialized path updates with SQL expressions
486
+ * ```ts
487
+ * const t = taxonomyClient.getTable()
488
+ * await taxonomyClient.updateMany(
489
+ * like(t.path, `${oldPath}/%`),
490
+ * {},
491
+ * {
492
+ * expressions: {
493
+ * path: sql`replace(${t.path}, ${oldPath}, ${newPath})`,
494
+ * depth: sql`length(replace(${t.path}, ${oldPath}, ${newPath}))
495
+ * - length(replace(replace(${t.path}, ${oldPath}, ${newPath}), '/', ''))`,
496
+ * },
497
+ * },
498
+ * )
499
+ * ```
444
500
  */
445
- updateMany(where: SQL, data: Partial<InferUpdateInput<AllFields>>): Promise<number>;
501
+ updateMany(where: SQL, data: Partial<InferUpdateInput<AllFields>>, options?: {
502
+ expressions?: Record<string, SQL>;
503
+ }): Promise<number>;
446
504
  /**
447
505
  * Run an aggregate query on this entity's table.
448
506
  *
@@ -494,6 +552,15 @@ declare class AdminClient<AllFields extends Record<string, FieldConfig> = Record
494
552
  * ```
495
553
  */
496
554
  getTable(): PgTableWithColumns<any>;
555
+ /**
556
+ * Expose the database handle for sibling infrastructure.
557
+ *
558
+ * Wrapper clients (MediaClient, ContentClient, TaxonomyClient) use this
559
+ * to create `TableClient` instances for infrastructure tables (versions,
560
+ * drafts, locks) that sit alongside entity tables. Not intended for
561
+ * end-user code — wrapper packages are trusted consumers.
562
+ */
563
+ getDb(): PostgresJsDatabase;
497
564
  /**
498
565
  * Prepare data for insert — every field is a real column.
499
566
  */
@@ -503,11 +570,6 @@ declare class AdminClient<AllFields extends Record<string, FieldConfig> = Record
503
570
  * Standard column SET, no JSONB merge needed.
504
571
  */
505
572
  private prepareDataForUpdate;
506
- /**
507
- * Merge translations into entities for the specified locale.
508
- * Reads translatable field values from real columns on the translation row.
509
- */
510
- private mergeTranslations;
511
573
  /**
512
574
  * Save translation for an entity.
513
575
  * Each translatable field is a real column on the translation table.
@@ -532,32 +594,11 @@ declare class AdminClient<AllFields extends Record<string, FieldConfig> = Record
532
594
  * its own independent block layout rows in the layout table.
533
595
  */
534
596
  saveLocalizedBlocks(entityId: string, locale: string, data: Record<string, unknown>): Promise<void>;
535
- /**
536
- * Get all blocks field names for this entity.
537
- */
538
- private getBlocksFields;
539
597
  /**
540
598
  * Save blocks for an entity after create/update.
541
599
  * For each blocks field, writes rows to {entity}_layout table.
542
600
  */
543
601
  private saveBlocks;
544
- /**
545
- * Load blocks for one or more entities from the layout table.
546
- *
547
- * Handles both block translation modes:
548
- * - Shared layout (localized: false): loads locale=NULL rows, merges translations,
549
- * clears untranslated translatable fields to '' (strict mode for admin editing)
550
- * - Per-locale layout (localized: true): loads rows matching the provided locale only
551
- *
552
- * @param entityIds - Entity IDs to load blocks for
553
- * @param locale - Content locale (omit for default locale / base data)
554
- * @param options.defaultLocale - Default locale; NULL rows fall back only for this locale
555
- */
556
- private loadBlocks;
557
- /**
558
- * Attach loaded blocks to shaped DTOs.
559
- */
560
- private attachBlocks;
561
602
  /**
562
603
  * Sync outgoing references in entity_refs after create/update.
563
604
  *
@@ -567,11 +608,6 @@ declare class AdminClient<AllFields extends Record<string, FieldConfig> = Record
567
608
  * Gracefully skips if entity_refs table is not registered (e.g. before migration).
568
609
  */
569
610
  private syncRefs;
570
- /**
571
- * Build a cache key for a count query.
572
- * Key format: `entityName` for unfiltered, `entityName:where_sql` for filtered.
573
- */
574
- private buildCountCacheKey;
575
611
  /**
576
612
  * Invalidate all count cache entries for this entity.
577
613
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/count-cache.ts","../../src/cursor.ts","../../src/admin-config.ts","../../src/fields/base.ts","../../src/behaviors/types.ts","../../src/define-entity.ts","../../src/types/infer.ts","../../src/types/logger.ts","../../src/admin/client.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/count-cache.ts","../../src/cursor.ts","../../src/admin-config.ts","../../src/fields/base.ts","../../src/behaviors/types.ts","../../src/define-entity.ts","../../src/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){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};
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};
2
2
  //# sourceMappingURL=index.mjs.map