@murumets-ee/entity 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts DELETED
@@ -1,1027 +0,0 @@
1
- import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
2
- import * as drizzle_orm from 'drizzle-orm';
3
- import { SQL } from 'drizzle-orm';
4
- import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
5
- import { PgTableWithColumns } from 'drizzle-orm/pg-core';
6
-
7
- /**
8
- * Optional admin UI configuration for entities.
9
- * Controls how entities appear in the admin sidebar, list pages, and forms.
10
- */
11
- interface EntityAdminConfig {
12
- /** Sidebar section: 'content' | 'structure' | custom string. Default: 'content' */
13
- group?: string;
14
- /** Plural display name for sidebar + list pages. Default: title-cased pluralized entity name */
15
- label?: string;
16
- /** Singular label for "New X" button. Default: title-cased entity name */
17
- labelSingular?: string;
18
- /** Lucide icon name as string, e.g. 'file-text' */
19
- icon?: string;
20
- /** Description shown on list page */
21
- description?: string;
22
- /** Form layout. Default: 'single' */
23
- layout?: 'single' | 'two-column';
24
- /** Fields to hide in the form */
25
- hiddenFields?: string[];
26
- /** Columns to hide in the list */
27
- hiddenColumns?: string[];
28
- /** Per-field label/description/placeholder overrides */
29
- fieldOverrides?: Record<string, {
30
- label?: string;
31
- description?: string;
32
- placeholder?: string;
33
- }>;
34
- /** Default list sort field. Default: 'createdAt' */
35
- defaultSort?: string;
36
- /** Default list sort direction. Default: 'desc' */
37
- defaultSortDirection?: 'asc' | 'desc';
38
- /** List page size. Default: 20 */
39
- pageSize?: number;
40
- /** For block editor: fields to show in Puck root config */
41
- rootFields?: string[];
42
- /** Suppress "New" button in the list page */
43
- disableCreate?: boolean;
44
- /** Order within sidebar group. Default: 0 */
45
- sortOrder?: number;
46
- }
47
-
48
- /**
49
- * Field type definitions
50
- * These define the structure and configuration for all field types
51
- */
52
- interface BaseFieldConfig {
53
- required?: boolean;
54
- default?: unknown;
55
- translatable?: boolean;
56
- indexed?: boolean;
57
- unique?: boolean;
58
- access?: {
59
- view?: string;
60
- edit?: string;
61
- };
62
- }
63
- interface IdField extends BaseFieldConfig {
64
- type: 'id';
65
- }
66
- interface TextField extends BaseFieldConfig {
67
- type: 'text';
68
- maxLength?: number;
69
- minLength?: number;
70
- pattern?: RegExp;
71
- }
72
- interface NumberField extends BaseFieldConfig {
73
- type: 'number';
74
- min?: number;
75
- max?: number;
76
- integer?: boolean;
77
- }
78
- interface BooleanField extends BaseFieldConfig {
79
- type: 'boolean';
80
- }
81
- interface DateField extends BaseFieldConfig {
82
- type: 'date';
83
- minDate?: Date;
84
- maxDate?: Date;
85
- }
86
- interface SelectField extends BaseFieldConfig {
87
- type: 'select';
88
- options: readonly string[];
89
- }
90
- interface ReferenceField extends BaseFieldConfig {
91
- type: 'reference';
92
- entity: string;
93
- cardinality: 'one' | 'many';
94
- onDelete?: 'cascade' | 'set-null' | 'restrict';
95
- }
96
- interface MediaField extends BaseFieldConfig {
97
- type: 'media';
98
- accept?: string[];
99
- maxSize?: number;
100
- }
101
- interface RichTextField extends BaseFieldConfig {
102
- type: 'richtext';
103
- blocks?: string[];
104
- }
105
- interface SlugField extends BaseFieldConfig {
106
- type: 'slug';
107
- from: string;
108
- unique?: boolean;
109
- }
110
- /**
111
- * Minimal block definition reference — used by BlocksField to type-check allowed blocks.
112
- * The full BlockDefinition (with label, etc.) lives in @murumets-ee/content.
113
- */
114
- interface BlockDefinitionRef {
115
- slug: string;
116
- fields: Record<string, FieldConfig>;
117
- }
118
- /**
119
- * Blocks field — ordered array of typed content blocks.
120
- * Each block instance stores its data in {entity}_layout table.
121
- *
122
- * @property blocks - Allowed block definitions
123
- * @property min/max - Block count constraints
124
- * @property localized - true = per-locale layouts, false = shared layout with translated content (default)
125
- */
126
- interface JsonField extends BaseFieldConfig {
127
- type: 'json';
128
- }
129
- interface BlocksField extends BaseFieldConfig {
130
- type: 'blocks';
131
- blocks: readonly BlockDefinitionRef[];
132
- min?: number;
133
- max?: number;
134
- localized?: boolean;
135
- }
136
- type FieldConfig = IdField | TextField | NumberField | BooleanField | DateField | SelectField | ReferenceField | MediaField | RichTextField | SlugField | JsonField | BlocksField;
137
-
138
- /**
139
- * Type inference utilities for the entity system.
140
- * Maps field configurations to TypeScript types at compile time.
141
- *
142
- * Design principles:
143
- * - Keep type nesting shallow (no recursive types beyond bounded tuple walking)
144
- * - Use intermediate aliases to aid TS performance
145
- * - All types are pure — zero runtime footprint
146
- *
147
- * Usage:
148
- * const Article = defineEntity({ ... })
149
- * type ArticleDTO = InferEntity<typeof Article>
150
- */
151
-
152
- /**
153
- * Maps a single FieldConfig to its TypeScript output type.
154
- * Each branch is a shallow comparison — no recursion.
155
- */
156
- 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<{
157
- _block: string;
158
- _id: string;
159
- [key: string]: unknown;
160
- }> : never;
161
- /**
162
- * Extract keys of fields where `required` is literally `true`.
163
- * Fields without `required` or with `required?: false` are optional.
164
- */
165
- type RequiredFieldKeys<Fields extends Record<string, FieldConfig>> = {
166
- [K in keyof Fields]: Fields[K]['required'] extends true ? K : never;
167
- }[keyof Fields];
168
- type OptionalFieldKeys<Fields extends Record<string, FieldConfig>> = {
169
- [K in keyof Fields]: Fields[K]['required'] extends true ? never : K;
170
- }[keyof Fields];
171
- /**
172
- * Maps a full field record to its TypeScript output type.
173
- * Required fields are non-nullable; optional fields are `T | null | undefined`.
174
- *
175
- * The `id` field is always `string` and always present.
176
- * The `id` key from Fields is excluded to avoid duplication since
177
- * we hardcode `{ id: string }` at the front.
178
- */
179
- type InferEntityDTO<Fields extends Record<string, FieldConfig>> = {
180
- id: string;
181
- } & {
182
- [K in Exclude<RequiredFieldKeys<Fields>, 'id'>]: FieldToTS<Fields[K]>;
183
- } & {
184
- [K in OptionalFieldKeys<Fields>]?: FieldToTS<Fields[K]> | null;
185
- };
186
- /** Fields that are auto-generated and should not appear in create input. */
187
- type AutoGeneratedFields = 'id' | 'createdAt' | 'updatedAt' | 'createdBy' | 'updatedBy' | '_version';
188
- /**
189
- * The input type for creating an entity.
190
- * - Omits auto-generated fields (id, timestamps, version)
191
- * - Required fields stay required; optional fields stay optional
192
- */
193
- type InferCreateInput<Fields extends Record<string, FieldConfig>> = Omit<{
194
- [K in Exclude<RequiredFieldKeys<Fields>, 'id'>]: FieldToTS<Fields[K]>;
195
- } & {
196
- [K in OptionalFieldKeys<Fields>]?: FieldToTS<Fields[K]> | null;
197
- }, AutoGeneratedFields>;
198
- /** Fields that cannot be changed after creation. */
199
- type ImmutableFields = 'id' | 'createdAt' | 'createdBy';
200
- type InferUpdateInput<Fields extends Record<string, FieldConfig>> = Partial<Omit<InferEntityDTO<Fields>, ImmutableFields>>;
201
- /**
202
- * Behavior field types use `type` (not `interface`) because interfaces
203
- * lack implicit index signatures needed for Record<string, FieldConfig>.
204
- *
205
- * Types are simplified to match what the field builders actually return,
206
- * with only the specificity that FieldToTS needs (e.g., options tuple
207
- * for SelectField, cardinality for ReferenceField).
208
- */
209
- /** Fields added by publishable() */
210
- type PublishableFields = {
211
- status: SelectField & {
212
- options: readonly ['draft', 'published'];
213
- };
214
- publishedAt: DateField;
215
- };
216
- /** Fields added by auditable() */
217
- type AuditableFields = {
218
- createdBy: TextField;
219
- updatedBy: TextField;
220
- createdAt: DateField;
221
- updatedAt: DateField;
222
- };
223
- /** Fields added by sluggable() */
224
- type SluggableFields = {
225
- slug: SlugField;
226
- };
227
- /** Fields added by revisionable() */
228
- type RevisionableFields = {
229
- _version: NumberField;
230
- };
231
- /** Fields added by hierarchical() */
232
- type HierarchicalFields = {
233
- parentId: ReferenceField & {
234
- cardinality: 'one';
235
- };
236
- path: TextField;
237
- depth: NumberField;
238
- };
239
- /**
240
- * Extract the DTO type from an Entity.
241
- *
242
- * Usage:
243
- * const Article = defineEntity({ ... })
244
- * type ArticleDTO = InferEntity<typeof Article>
245
- */
246
- type InferEntity<E> = E extends {
247
- allFields: infer F extends Record<string, FieldConfig>;
248
- } ? InferEntityDTO<F> : never;
249
-
250
- /**
251
- * Behavior system
252
- * Behaviors add common functionality to entities (fields + hooks)
253
- *
254
- * @typeParam F - The field map this behavior contributes. Defaults to
255
- * Record<string, FieldConfig> for backward compatibility.
256
- */
257
-
258
- interface Behavior<F extends Record<string, FieldConfig> = Record<string, FieldConfig>> {
259
- name: string;
260
- fields?: F;
261
- hooks?: {
262
- beforeCreate?: (data: Record<string, unknown>) => Promise<Record<string, unknown>>;
263
- afterCreate?: (entity: Record<string, unknown>) => Promise<void>;
264
- beforeUpdate?: (id: string, data: Record<string, unknown>) => Promise<Record<string, unknown>>;
265
- afterUpdate?: (entity: Record<string, unknown>) => Promise<void>;
266
- beforeDelete?: (id: string) => Promise<void>;
267
- afterDelete?: (id: string) => Promise<void>;
268
- };
269
- }
270
- type BehaviorFactory = (...args: unknown[]) => Behavior;
271
-
272
- /**
273
- * Auditable behavior
274
- * Adds createdBy, updatedBy, createdAt, updatedAt fields + auto-set logic.
275
- *
276
- * When @murumets-ee/core is available (i.e. running inside createApp), the hooks
277
- * automatically populate createdBy/updatedBy from the RequestContext.
278
- */
279
-
280
- declare function auditable(): Behavior<AuditableFields>;
281
-
282
- /**
283
- * Hierarchical behavior
284
- * Adds parentId, path, depth fields for materialized path tree structures.
285
- *
286
- * Path format: /uuid1/uuid2/uuid3
287
- * - Root items: /{ownId}
288
- * - Children: /{rootId}/.../{parentId}/{ownId}
289
- *
290
- * The behavior declares fields and sets safe defaults in hooks.
291
- * Heavy lifting (path computation, circular ref validation, descendant cascading)
292
- * is done by TaxonomyClient which has DB access.
293
- */
294
-
295
- interface HierarchicalOptions {
296
- /**
297
- * What happens when deleting a node that has children.
298
- * - 'restrict': Prevent deletion (default)
299
- * - 'cascade': Delete all descendants
300
- * - 'reparent': Move children to the deleted node's parent
301
- */
302
- onDelete?: 'restrict' | 'cascade' | 'reparent';
303
- }
304
- declare function hierarchical(_options?: HierarchicalOptions): Behavior<HierarchicalFields>;
305
-
306
- /**
307
- * Publishable behavior
308
- * Adds status (draft/published) and publishedAt fields + auto-set logic
309
- */
310
-
311
- declare function publishable(): Behavior<PublishableFields>;
312
-
313
- /**
314
- * Revisionable behavior
315
- * Adds _version field + auto-increment on update
316
- */
317
-
318
- declare function revisionable(): Behavior<RevisionableFields>;
319
-
320
- /**
321
- * Sluggable behavior
322
- * Adds slug field + auto-generation from source field
323
- */
324
-
325
- interface SluggableOptions {
326
- /** Make the slug translatable — each locale gets its own slug. Default: false */
327
- translatable?: boolean;
328
- }
329
- /**
330
- * Convert text to URL-safe slug
331
- */
332
- declare function slugify(text: string): string;
333
- declare function sluggable(sourceField: string, options?: SluggableOptions): Behavior<SluggableFields>;
334
-
335
- /**
336
- * Fluent API for building field definitions
337
- * Provides type-safe field builders with sensible defaults.
338
- *
339
- * Each builder uses a `const` generic parameter on the config to preserve
340
- * literal types (e.g., `required: true` stays `true`, not `boolean`).
341
- * This enables compile-time type inference in the entity system.
342
- */
343
-
344
- declare const field: {
345
- /**
346
- * ID field (auto-added to every entity)
347
- */
348
- id: <const C extends Partial<IdField> = {}>(config?: C) => IdField & C;
349
- /**
350
- * Text field
351
- */
352
- text: <const C extends Partial<TextField> = {}>(config?: C) => TextField & C;
353
- /**
354
- * Number field
355
- */
356
- number: <const C extends Partial<NumberField> = {}>(config?: C) => NumberField & C;
357
- /**
358
- * Boolean field
359
- */
360
- boolean: <const C extends Partial<BooleanField> = {}>(config?: C) => BooleanField & C;
361
- /**
362
- * Date field
363
- */
364
- date: <const C extends Partial<DateField> = {}>(config?: C) => DateField & C;
365
- /**
366
- * Select field (enum)
367
- * Preserves literal option types for type inference.
368
- * e.g. field.select({ options: ['news', 'tutorial'] }) infers 'news' | 'tutorial'
369
- */
370
- select: <const O extends readonly string[], const C extends Omit<Partial<SelectField>, "options"> = {}>(config: {
371
- options: O;
372
- } & C) => SelectField & {
373
- options: O;
374
- } & C;
375
- /**
376
- * Reference field (foreign key to another entity)
377
- * Preserves literal cardinality to distinguish string vs string[] in inferred types.
378
- */
379
- reference: <C extends "one" | "many" = "one", const R extends Partial<Omit<ReferenceField, "entity" | "cardinality">> = {}>(config: {
380
- entity: string;
381
- cardinality?: C;
382
- } & R) => ReferenceField & {
383
- cardinality: C;
384
- } & R;
385
- /**
386
- * Media field (file upload)
387
- */
388
- media: <const C extends Partial<MediaField> = {}>(config?: C) => MediaField & C;
389
- /**
390
- * Rich text field (WYSIWYG editor)
391
- */
392
- richtext: <const C extends Partial<RichTextField> = {}>(config?: C) => RichTextField & C;
393
- /**
394
- * Slug field (URL-safe string, auto-generated from source field)
395
- */
396
- slug: <const C extends Partial<SlugField> = {}>(config: Pick<SlugField, "from"> & C) => SlugField & C;
397
- /**
398
- * JSON field (arbitrary JSON data, stored as JSONB)
399
- */
400
- json: <const C extends Partial<JsonField> = {}>(config?: C) => JsonField & C;
401
- /**
402
- * Blocks field — ordered array of typed content blocks
403
- * Preserves literal block tuple type for discriminated union inference.
404
- */
405
- blocks: <const B extends readonly BlockDefinitionRef[]>(config: {
406
- blocks: B;
407
- min?: number;
408
- max?: number;
409
- localized?: boolean;
410
- }) => BlocksField & {
411
- blocks: B;
412
- };
413
- };
414
-
415
- /**
416
- * Timestamped behavior
417
- * Adds createdAt and updatedAt fields with auto-set logic.
418
- * Lighter alternative to auditable() when you don't need createdBy/updatedBy tracking.
419
- */
420
-
421
- type TimestampedFields = {
422
- createdAt: ReturnType<typeof field.date>;
423
- updatedAt: ReturnType<typeof field.date>;
424
- };
425
- declare function timestamped(): Behavior<TimestampedFields>;
426
-
427
- /**
428
- * Behavior exports
429
- * All behaviors are exported under the `behavior` namespace
430
- */
431
-
432
- /**
433
- * Behavior namespace for fluent API
434
- */
435
- declare const behavior: {
436
- publishable: typeof publishable;
437
- auditable: typeof auditable;
438
- sluggable: typeof sluggable;
439
- revisionable: typeof revisionable;
440
- hierarchical: typeof hierarchical;
441
- timestamped: typeof timestamped;
442
- };
443
-
444
- /**
445
- * In-memory TTL cache for COUNT(*) query results.
446
- *
447
- * Reduces database load on paginated list pages where the total count
448
- * is recalculated on every pagination/sort/search interaction. The cache
449
- * is per-process (not shared across workers) with a short TTL (default 5s)
450
- * so counts are at most a few seconds stale.
451
- *
452
- * Cache keys include the entity name + serialized WHERE clause, so filtered
453
- * and unfiltered counts are cached independently.
454
- */
455
- /**
456
- * Interface for count cache implementations.
457
- * Used in client configs to avoid TypeScript private-field structural incompatibility
458
- * across separate .d.ts files.
459
- */
460
- interface CountCacheLike {
461
- get(key: string): number | undefined;
462
- set(key: string, count: number): void;
463
- invalidate(prefix: string): void;
464
- }
465
- declare class CountCache implements CountCacheLike {
466
- private cache;
467
- private ttlMs;
468
- constructor(ttlMs?: number);
469
- /**
470
- * Get a cached count. Returns undefined on miss or expired entry.
471
- */
472
- get(key: string): number | undefined;
473
- /**
474
- * Cache a count result with the configured TTL.
475
- */
476
- set(key: string, count: number): void;
477
- /**
478
- * Invalidate all cache entries whose key starts with the given prefix.
479
- * Typically called with the entity name after mutations (create/update/delete).
480
- */
481
- invalidate(prefix: string): void;
482
- /**
483
- * Remove all expired entries. Useful for periodic cleanup in long-running processes.
484
- */
485
- prune(): void;
486
- /**
487
- * Clear the entire cache.
488
- */
489
- clear(): void;
490
- /**
491
- * Number of entries currently in the cache (including expired ones not yet pruned).
492
- */
493
- get size(): number;
494
- }
495
-
496
- /**
497
- * Postgres row count estimation using pg_class statistics.
498
- *
499
- * For unfiltered counts on large tables, querying `pg_class.reltuples`
500
- * is effectively instant (no table scan) and returns a good approximation
501
- * that is updated by VACUUM and ANALYZE. This is suitable for pagination
502
- * totals where exact precision is not critical.
503
- *
504
- * SECURITY: The table name is NOT interpolated into SQL — it is passed as a
505
- * parameterized value to the `relname = $1` comparison. This prevents SQL injection.
506
- */
507
-
508
- /**
509
- * Estimate the total row count for a table using Postgres statistics.
510
- *
511
- * Returns the approximate row count from `pg_class.reltuples`, which is
512
- * updated by VACUUM/ANALYZE. Returns 0 if the table is not found or
513
- * statistics are not yet available (e.g., freshly created table).
514
- *
515
- * @param db - Drizzle Postgres database instance
516
- * @param tableName - The Postgres table name (without schema prefix)
517
- * @returns Estimated row count (non-negative integer)
518
- */
519
- declare function estimateRowCount(db: PostgresJsDatabase, tableName: string): Promise<number>;
520
-
521
- /**
522
- * Cursor-based (keyset) pagination utilities.
523
- *
524
- * Cursor pagination avoids the performance cliff of OFFSET at scale (1M+ rows).
525
- * Instead of `OFFSET N`, it uses a WHERE condition:
526
- * `WHERE (sortField < lastValue) OR (sortField = lastValue AND id < lastId)`
527
- * which Postgres can serve from an index in constant time.
528
- *
529
- * The cursor is opaque to the client — base64-encoded JSON.
530
- *
531
- * Security:
532
- * - `field` must be whitelisted against the entity's actual fields
533
- * - `id` must be a valid UUID
534
- * - `value` is parameterized (never interpolated into SQL)
535
- * - Malformed cursors return null (caller returns 400)
536
- */
537
-
538
- /** Cursor input for keyset pagination. */
539
- interface CursorInput {
540
- /** Sort field name (e.g. 'createdAt'). Must be a real column on the entity. */
541
- field: string;
542
- /** Last seen value of the sort field. */
543
- value: string | number;
544
- /** Sort direction — must match the ORDER BY direction. */
545
- direction: 'asc' | 'desc';
546
- /** Tie-breaker: last seen entity ID. Required for non-unique sort fields. */
547
- id?: string;
548
- }
549
- /** Decoded cursor (internal, after validation). */
550
- interface DecodedCursor {
551
- field: string;
552
- value: string | number;
553
- direction: 'asc' | 'desc';
554
- id?: string;
555
- }
556
- /**
557
- * Encode a cursor for API transport (base64url).
558
- * Built from the last item in a result set.
559
- */
560
- declare function encodeCursor(item: Record<string, unknown>, sortField: string, direction: 'asc' | 'desc'): string;
561
- /**
562
- * Decode and validate a cursor string from query params.
563
- * Returns null if the cursor is malformed, tampered, or invalid.
564
- *
565
- * Security: the `field` value is NOT validated here — the caller must
566
- * whitelist it against the entity's actual columns.
567
- */
568
- declare function decodeCursor(encoded: string): DecodedCursor | null;
569
- /**
570
- * Build the keyset WHERE condition from a decoded cursor.
571
- *
572
- * For DESC: `WHERE (field < value) OR (field = value AND id < cursorId)`
573
- * For ASC: `WHERE (field > value) OR (field = value AND id > cursorId)`
574
- *
575
- * The caller must verify that `cursor.field` exists on the table before calling.
576
- *
577
- * @param table - Drizzle table with columns
578
- * @param cursor - Decoded and validated cursor
579
- * @returns SQL condition, or null if the field doesn't exist on the table
580
- */
581
- declare function buildCursorCondition(table: PgTableWithColumns<any>, cursor: DecodedCursor): SQL | null;
582
-
583
- /**
584
- * Entity definition API
585
- * The core function for defining entities with full type inference.
586
- *
587
- * The generic parameters are inferred from the call site:
588
- * - F is inferred from `definition.fields`
589
- * - B is inferred from `definition.behaviors` (as a tuple)
590
- *
591
- * The returned Entity carries the complete field map:
592
- * { id: IdField } & BehaviorFields & UserFields
593
- */
594
-
595
- /**
596
- * Entity definition input (without behaviors — behaviors are typed separately
597
- * on the `defineEntity` function to enable tuple inference).
598
- *
599
- * @typeParam F - The literal field map. Inferred automatically from the call site.
600
- */
601
- interface EntityDefinition<F extends Record<string, FieldConfig> = Record<string, FieldConfig>> {
602
- name: string;
603
- kind?: 'collection' | 'singleton';
604
- fields: F;
605
- scope?: 'global' | 'team' | 'user';
606
- access?: {
607
- view?: string;
608
- create?: string;
609
- update?: string;
610
- delete?: string;
611
- };
612
- /** Admin UI configuration — controls sidebar, list, and form display */
613
- admin?: EntityAdminConfig;
614
- }
615
- /**
616
- * Full input for defineEntity (fields + behaviors + other config).
617
- * Behaviors are typed as a tuple `B` for type extraction.
618
- */
619
- type EntityInput<F extends Record<string, FieldConfig> = Record<string, FieldConfig>, B extends Behavior[] = Behavior[]> = EntityDefinition<F> & {
620
- behaviors?: B;
621
- };
622
- /**
623
- * A fully resolved entity with merged behavior fields.
624
- * @typeParam AllFields - The complete field map (id + behaviors + user fields).
625
- */
626
- interface Entity<AllFields extends Record<string, FieldConfig> = Record<string, FieldConfig>> {
627
- name: string;
628
- kind?: 'collection' | 'singleton';
629
- fields: Record<string, FieldConfig>;
630
- behaviors?: Behavior[];
631
- scope?: 'global' | 'team' | 'user';
632
- access?: {
633
- view?: string;
634
- create?: string;
635
- update?: string;
636
- delete?: string;
637
- };
638
- /** Admin UI configuration — controls sidebar, list, and form display */
639
- admin?: EntityAdminConfig;
640
- allFields: AllFields;
641
- hooks: Behavior['hooks'];
642
- }
643
- /**
644
- * Extract and intersect behavior field types from a behaviors tuple.
645
- * Walks the tuple recursively (depth bounded by number of behaviors, max ~5).
646
- */
647
- type ExtractBehaviorFields<B extends Behavior[]> = B extends [
648
- Behavior<infer F1>,
649
- ...infer Rest extends Behavior[]
650
- ] ? F1 & ExtractBehaviorFields<Rest> : {};
651
- /**
652
- * Define an entity with full type inference.
653
- *
654
- * @example
655
- * const Article = defineEntity({
656
- * name: 'article',
657
- * fields: {
658
- * title: field.text({ required: true }),
659
- * featured: field.boolean(),
660
- * },
661
- * behaviors: [publishable(), auditable()],
662
- * })
663
- *
664
- * type ArticleDTO = InferEntity<typeof Article>
665
- * // { id: string; title: string; featured?: boolean | null; status?: ...; createdAt?: ...; }
666
- */
667
- declare function defineEntity<F extends Record<string, FieldConfig>, const B extends Behavior[] = []>(definition: EntityDefinition<F> & {
668
- behaviors?: B;
669
- }): Entity<{
670
- id: IdField;
671
- } & ExtractBehaviorFields<B> & F>;
672
-
673
- /**
674
- * Generic entity usage lookup — "where is this entity referenced?"
675
- *
676
- * One indexed query on entity_refs instead of scanning every table.
677
- */
678
-
679
- interface EntityUsage {
680
- sourceEntity: string;
681
- sourceId: string;
682
- sourceField: string;
683
- }
684
-
685
- /**
686
- * Error thrown when attempting to delete an entity that is still referenced.
687
- */
688
-
689
- declare class ReferencedEntityError extends Error {
690
- readonly entityName: string;
691
- readonly entityId: string;
692
- readonly usages: EntityUsage[];
693
- constructor(entityName: string, entityId: string, usages: EntityUsage[]);
694
- }
695
-
696
- /**
697
- * Generate a runtime Drizzle schema from an entity definition.
698
- * Every field becomes its own column — no JSONB.
699
- */
700
- declare function generateSchema(entity: Entity): drizzle_orm_pg_core.PgTableWithColumns<{
701
- name: string;
702
- schema: undefined;
703
- columns: {
704
- [x: string]: drizzle_orm_pg_core.PgColumn<{
705
- name: string;
706
- tableName: string;
707
- dataType: drizzle_orm.ColumnDataType;
708
- columnType: string;
709
- data: unknown;
710
- driverParam: unknown;
711
- notNull: false;
712
- hasDefault: false;
713
- isPrimaryKey: false;
714
- isAutoincrement: false;
715
- hasRuntimeDefault: false;
716
- enumValues: string[] | undefined;
717
- baseColumn: never;
718
- identity: undefined;
719
- generated: undefined;
720
- }, {}, {}>;
721
- };
722
- dialect: "pg";
723
- }>;
724
- /**
725
- * Generate TypeScript code string for an entity schema.
726
- * Every field becomes its own column — no JSONB.
727
- */
728
- declare function generateSchemaCode(entity: Entity): string;
729
- /**
730
- * Generate runtime translation table schema.
731
- * Each translatable field gets its own nullable column.
732
- */
733
- declare function generateTranslationSchema(entity: Entity): drizzle_orm_pg_core.PgTableWithColumns<{
734
- name: string;
735
- schema: undefined;
736
- columns: {
737
- [x: string]: drizzle_orm_pg_core.PgColumn<{
738
- name: string;
739
- tableName: string;
740
- dataType: drizzle_orm.ColumnDataType;
741
- columnType: string;
742
- data: unknown;
743
- driverParam: unknown;
744
- notNull: false;
745
- hasDefault: false;
746
- isPrimaryKey: false;
747
- isAutoincrement: false;
748
- hasRuntimeDefault: false;
749
- enumValues: string[] | undefined;
750
- baseColumn: never;
751
- identity: undefined;
752
- generated: undefined;
753
- }, {}, {}>;
754
- };
755
- dialect: "pg";
756
- }> | null;
757
- /**
758
- * Generate TypeScript code string for a translation table.
759
- * Each translatable field gets its own nullable column.
760
- */
761
- declare function generateTranslationSchemaCode(entity: Entity): string | null;
762
- /**
763
- * Check if an entity has any blocks fields
764
- */
765
- declare function hasBlocksFields(entity: Entity): boolean;
766
- /**
767
- * Generate layout table schema for entities with blocks fields.
768
- * Stores block instances in a separate table with ordering.
769
- *
770
- * When blocks field has localized: false (default), locale is NULL (shared layout).
771
- * When blocks field has localized: true, locale is set per-locale.
772
- */
773
- declare function generateLayoutSchema(entity: Entity): drizzle_orm_pg_core.PgTableWithColumns<{
774
- name: string;
775
- schema: undefined;
776
- columns: {
777
- id: drizzle_orm_pg_core.PgColumn<{
778
- name: "id";
779
- tableName: string;
780
- dataType: "string";
781
- columnType: "PgUUID";
782
- data: string;
783
- driverParam: string;
784
- notNull: true;
785
- hasDefault: true;
786
- isPrimaryKey: true;
787
- isAutoincrement: false;
788
- hasRuntimeDefault: false;
789
- enumValues: undefined;
790
- baseColumn: never;
791
- identity: undefined;
792
- generated: undefined;
793
- }, {}, {}>;
794
- entityId: drizzle_orm_pg_core.PgColumn<{
795
- name: "entity_id";
796
- tableName: string;
797
- dataType: "string";
798
- columnType: "PgUUID";
799
- data: string;
800
- driverParam: string;
801
- notNull: true;
802
- hasDefault: false;
803
- isPrimaryKey: false;
804
- isAutoincrement: false;
805
- hasRuntimeDefault: false;
806
- enumValues: undefined;
807
- baseColumn: never;
808
- identity: undefined;
809
- generated: undefined;
810
- }, {}, {}>;
811
- fieldName: drizzle_orm_pg_core.PgColumn<{
812
- name: "field_name";
813
- tableName: string;
814
- dataType: "string";
815
- columnType: "PgVarchar";
816
- data: string;
817
- driverParam: string;
818
- notNull: true;
819
- hasDefault: false;
820
- isPrimaryKey: false;
821
- isAutoincrement: false;
822
- hasRuntimeDefault: false;
823
- enumValues: [string, ...string[]];
824
- baseColumn: never;
825
- identity: undefined;
826
- generated: undefined;
827
- }, {}, {
828
- length: 100;
829
- }>;
830
- blockType: drizzle_orm_pg_core.PgColumn<{
831
- name: "block_type";
832
- tableName: string;
833
- dataType: "string";
834
- columnType: "PgVarchar";
835
- data: string;
836
- driverParam: string;
837
- notNull: true;
838
- hasDefault: false;
839
- isPrimaryKey: false;
840
- isAutoincrement: false;
841
- hasRuntimeDefault: false;
842
- enumValues: [string, ...string[]];
843
- baseColumn: never;
844
- identity: undefined;
845
- generated: undefined;
846
- }, {}, {
847
- length: 100;
848
- }>;
849
- sortOrder: drizzle_orm_pg_core.PgColumn<{
850
- name: "sort_order";
851
- tableName: string;
852
- dataType: "number";
853
- columnType: "PgInteger";
854
- data: number;
855
- driverParam: string | number;
856
- notNull: true;
857
- hasDefault: true;
858
- isPrimaryKey: false;
859
- isAutoincrement: false;
860
- hasRuntimeDefault: false;
861
- enumValues: undefined;
862
- baseColumn: never;
863
- identity: undefined;
864
- generated: undefined;
865
- }, {}, {}>;
866
- data: drizzle_orm_pg_core.PgColumn<{
867
- name: "data";
868
- tableName: string;
869
- dataType: "json";
870
- columnType: "PgJsonb";
871
- data: unknown;
872
- driverParam: unknown;
873
- notNull: false;
874
- hasDefault: false;
875
- isPrimaryKey: false;
876
- isAutoincrement: false;
877
- hasRuntimeDefault: false;
878
- enumValues: undefined;
879
- baseColumn: never;
880
- identity: undefined;
881
- generated: undefined;
882
- }, {}, {}>;
883
- locale: drizzle_orm_pg_core.PgColumn<{
884
- name: "locale";
885
- tableName: string;
886
- dataType: "string";
887
- columnType: "PgVarchar";
888
- data: string;
889
- driverParam: string;
890
- notNull: false;
891
- hasDefault: false;
892
- isPrimaryKey: false;
893
- isAutoincrement: false;
894
- hasRuntimeDefault: false;
895
- enumValues: [string, ...string[]];
896
- baseColumn: never;
897
- identity: undefined;
898
- generated: undefined;
899
- }, {}, {
900
- length: 10;
901
- }>;
902
- };
903
- dialect: "pg";
904
- }> | null;
905
- /**
906
- * Check if an entity has the versionable behavior
907
- */
908
- declare function isVersionable(entity: Entity): boolean;
909
- /**
910
- * Check if an entity needs a per-locale publish status table.
911
- * Requires both publishable() behavior AND at least one translatable field.
912
- */
913
- declare function needsLocaleStatus(entity: Entity): boolean;
914
- /**
915
- * Generate TypeScript code string for a per-locale publish status table.
916
- */
917
- declare function generateLocaleStatusCode(entity: Entity): string;
918
- /**
919
- * Check if an entity is publishable (has publishable() behavior).
920
- */
921
- declare function isPublishable(entity: Entity): boolean;
922
- /**
923
- * Generate TypeScript code string for a drafts overlay table.
924
- */
925
- declare function generateDraftsCode(entity: Entity): string;
926
- /**
927
- * Generate TypeScript code string for the shared content locks table.
928
- */
929
- declare function generateContentLocksCode(): string;
930
- /**
931
- * Check if an entity has blocks fields with translatable block content (non-localized mode).
932
- */
933
- declare function hasTranslatableBlocks(entity: Entity): boolean;
934
- /**
935
- * Generate TypeScript code string for a layout table (blocks storage).
936
- */
937
- declare function generateLayoutCode(entity: Entity): string;
938
- /**
939
- * Generate TypeScript code string for a layout translation table.
940
- */
941
- declare function generateLayoutTranslationCode(entity: Entity): string;
942
- /**
943
- * Generate TypeScript code string for a versions table.
944
- */
945
- declare function generateVersionsCode(entity: Entity): string;
946
- /**
947
- * Generate layout translation table for non-localized blocks with translatable fields.
948
- */
949
- declare function generateLayoutTranslationSchema(entity: Entity): drizzle_orm_pg_core.PgTableWithColumns<{
950
- name: string;
951
- schema: undefined;
952
- columns: {
953
- id: drizzle_orm_pg_core.PgColumn<{
954
- name: "id";
955
- tableName: string;
956
- dataType: "string";
957
- columnType: "PgUUID";
958
- data: string;
959
- driverParam: string;
960
- notNull: true;
961
- hasDefault: true;
962
- isPrimaryKey: true;
963
- isAutoincrement: false;
964
- hasRuntimeDefault: false;
965
- enumValues: undefined;
966
- baseColumn: never;
967
- identity: undefined;
968
- generated: undefined;
969
- }, {}, {}>;
970
- layoutId: drizzle_orm_pg_core.PgColumn<{
971
- name: "layout_id";
972
- tableName: string;
973
- dataType: "string";
974
- columnType: "PgUUID";
975
- data: string;
976
- driverParam: string;
977
- notNull: true;
978
- hasDefault: false;
979
- isPrimaryKey: false;
980
- isAutoincrement: false;
981
- hasRuntimeDefault: false;
982
- enumValues: undefined;
983
- baseColumn: never;
984
- identity: undefined;
985
- generated: undefined;
986
- }, {}, {}>;
987
- locale: drizzle_orm_pg_core.PgColumn<{
988
- name: "locale";
989
- tableName: string;
990
- dataType: "string";
991
- columnType: "PgVarchar";
992
- data: string;
993
- driverParam: string;
994
- notNull: true;
995
- hasDefault: false;
996
- isPrimaryKey: false;
997
- isAutoincrement: false;
998
- hasRuntimeDefault: false;
999
- enumValues: [string, ...string[]];
1000
- baseColumn: never;
1001
- identity: undefined;
1002
- generated: undefined;
1003
- }, {}, {
1004
- length: 10;
1005
- }>;
1006
- fields: drizzle_orm_pg_core.PgColumn<{
1007
- name: "fields";
1008
- tableName: string;
1009
- dataType: "json";
1010
- columnType: "PgJsonb";
1011
- data: unknown;
1012
- driverParam: unknown;
1013
- notNull: true;
1014
- hasDefault: false;
1015
- isPrimaryKey: false;
1016
- isAutoincrement: false;
1017
- hasRuntimeDefault: false;
1018
- enumValues: undefined;
1019
- baseColumn: never;
1020
- identity: undefined;
1021
- generated: undefined;
1022
- }, {}, {}>;
1023
- };
1024
- dialect: "pg";
1025
- }> | null;
1026
-
1027
- export { type AuditableFields, type Behavior, type BehaviorFactory, type BlockDefinitionRef, type BlocksField, type BooleanField, CountCache, type CountCacheLike, type CursorInput, type DateField, type Entity, type EntityAdminConfig, type EntityDefinition, type EntityInput, type EntityUsage, type FieldConfig, type FieldToTS, type HierarchicalFields, type IdField, type InferCreateInput, type InferEntity, type InferEntityDTO, type InferUpdateInput, type JsonField, type MediaField, type NumberField, type PublishableFields, type ReferenceField, ReferencedEntityError, type RevisionableFields, type RichTextField, type SelectField, type SlugField, type SluggableFields, type SluggableOptions, type TextField, auditable, behavior, buildCursorCondition, decodeCursor, defineEntity, encodeCursor, estimateRowCount, field, generateContentLocksCode, generateDraftsCode, generateLayoutCode, generateLayoutSchema, generateLayoutTranslationCode, generateLayoutTranslationSchema, generateLocaleStatusCode, generateSchema, generateSchemaCode, generateTranslationSchema, generateTranslationSchemaCode, generateVersionsCode, hasBlocksFields, hasTranslatableBlocks, hierarchical, isPublishable, isVersionable, needsLocaleStatus, publishable, revisionable, sluggable, slugify, timestamped };