@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 +94 -0
- package/dist/admin/index.d.mts +88 -52
- 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 +58 -23
- 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 +50 -43
- 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
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.
|
package/dist/admin/index.d.mts
CHANGED
|
@@ -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> =
|
|
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 ?
|
|
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
|
|
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
|
|
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>> =
|
|
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
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
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
|
|
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":";;;;;;;;;;AAiBA;;;;;;;;;;;UAAiB,cAAA;EACf,GAAA,CAAI,GAAA;EACJ,GAAA,CAAI,GAAA,UAAa,KAAA;EACjB,UAAA,CAAW,MAAA;AAAA;;;;UCKI,WAAA;;EAEf,KAAA;EAF0B;EAI1B,KAAA;EAJ0B;EAM1B,SAAA;EAFA;EAIA,EAAA;AAAA;;;;;;;UC7Be,iBAAA;EFaA;EEXf,KAAA;;EAEA,KAAA;EFUA;EERA,aAAA;EFSA;EEPA,IAAA;EFOiB;EELjB,WAAA;EFMW;EEJX,MAAA;EFIyB;EEFzB,YAAA;;EAEA,aAAA;EDKe;ECHf,cAAA,GAAiB,MAAA;IAAiB,KAAA;IAAgB,WAAA;IAAsB,WAAA;EAAA;EDSxE;ECPA,WAAA;EDSE;ECPF,oBAAA;;EAEA,QAAA;;EAEA,UAAA;EA1BgC;EA4BhC,aAAA;EAVuB;EAYvB,SAAA;EA1BA;;;;;EAgCA,MAAA;EApBA;;;;;EA0BA,YAAA;EAtBA;;;;EA2BA,iBAAA;AAAA;;;;;;;UC9Ce,eAAA;EACf,QAAA;EACA,OAAA;EACA,YAAA;EACA,OAAA;EACA,MAAA;EACA,MAAA;IACE,IAAA;IACA,IAAA;EAAA;AAAA;AAAA,UAIa,OAAA,SAAgB,eAAA;EAC/B,IAAA;AAAA;AAAA,UAGe,SAAA,SAAkB,eAAA;EACjC,IAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA,GAAU,MAAA;AAAA;AAAA,UAGK,WAAA,SAAoB,eAAA;EACnC,IAAA;EACA,GAAA;EACA,GAAA;EACA,OAAA;AAAA;AAAA,UAGe,YAAA,SAAqB,eAAA;EACpC,IAAA;AAAA;AAAA,UAGe,SAAA,SAAkB,eAAA;EACjC,IAAA;EACA,OAAA,GAAU,IAAA;EACV,OAAA,GAAU,IAAA;AAAA;AAAA,UAGK,WAAA,SAAoB,eAAA;EACnC,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,cAAA,SAAuB,eAAA;EACtC,IAAA;EACA,MAAA;EACA,WAAA;EACA,QAAA;AAAA;AAAA,UAGe,UAAA,SAAmB,eAAA;EAClC,IAAA;EACA,MAAA;EACA,OAAA;AAAA;AAAA,UAGe,aAAA,SAAsB,eAAA;EACrC,IAAA;EACA,MAAA;AAAA;AAAA,UAGe,SAAA,SAAkB,eAAA;EACjC,IAAA;EACA,IAAA;EACA,MAAA;AAAA;;;;AAlEF;UAyEiB,kBAAA;EACf,IAAA;EACA,MAAA,EAAQ,MAAA,SAAe,WAAA;AAAA;;;;;;;;;UAWR,SAAA,SAAkB,eAAA;EACjC,IAAA;AAAA;AAAA,UAGe,WAAA,SAAoB,eAAA;EACnC,IAAA;EACA,MAAA,WAAiB,kBAAA;EACjB,GAAA;EACA,GAAA;EACA,SAAA;AAAA;AAAA,KAGU,WAAA,GACR,OAAA,GACA,SAAA,GACA,WAAA,GACA,YAAA,GACA,SAAA,GACA,WAAA,GACA,cAAA,GACA,UAAA,GACA,aAAA,GACA,SAAA,GACA,SAAA,GACA,WAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/count-cache.ts","../../src/cursor.ts","../../src/admin-config.ts","../../src/fields/base.ts","../../src/behaviors/types.ts","../../src/define-entity.ts","../../src/shared/entity-data-ops.ts","../../src/types/infer.ts","../../src/types/logger.ts","../../src/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,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
|