@dyrected/core 2.16.0 → 2.17.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.
@@ -446,1356 +446,1458 @@ declare const AI_ACTIONS_COLLECTION = "_dyrected_ai_actions";
446
446
  declare const AI_AUDIT_COLLECTION = "_dyrected_ai_audit";
447
447
  declare function isAICollection(slug: string): boolean;
448
448
 
449
- /** A valid icon component name from the Lucide icon set bundled with Dyrected Admin. */
450
- type AdminIconName = keyof typeof icons;
451
- /** Ordered custom component keys rendered around the built-in Admin dashboard. */
452
- interface AdminDashboardComponentSlots {
453
- beforeDashboard?: string[];
454
- afterDashboard?: string[];
455
- }
456
- /** Ordered custom component keys rendered around an operational view's content. */
457
- interface CollectionViewComponentSlots {
458
- beforeViewHeader?: string[];
459
- afterViewHeader?: string[];
460
- beforeViewContent?: string[];
461
- afterViewContent?: string[];
462
- }
463
- /** Ordered custom component keys rendered around a collection's list content. */
464
- interface CollectionListComponentSlots {
465
- beforeList?: string[];
466
- beforeListTable?: string[];
467
- afterListTable?: string[];
468
- afterList?: string[];
469
- beforeViewHeader?: string[];
470
- afterViewHeader?: string[];
471
- beforeViewContent?: string[];
472
- afterViewContent?: string[];
473
- collectionView?: CollectionViewComponentSlots;
474
- }
475
449
  /**
476
- * Branding and metadata options for the Dyrected Admin UI.
450
+ * The set of type-cast operations supported during aggregation.
477
451
  *
478
- * @example
479
- * admin: {
480
- * branding: {
481
- * logo: '/logo.svg',
482
- * primaryColor: '#6366f1',
483
- * },
484
- * meta: {
485
- * titleSuffix: '- My App',
486
- * },
487
- * }
488
- */
489
- interface AdminConfig {
490
- /** Custom component slots around the built-in dashboard. */
491
- components?: AdminDashboardComponentSlots;
492
- branding?: {
493
- /** Full logo image shown in the expanded sidebar. URL or imported image asset. */
494
- logo?: string;
495
- /** Compact logo mark used in the collapsed sidebar state. */
496
- logoMark?: string;
497
- /** Text alternative or addition to the logo image. */
498
- logoText?: string;
499
- /**
500
- * Brand colour used for committed, filled actions — Save, Create, Upload —
501
- * and active/selected states. Accepts a hex string, a named colour
502
- * (`amber`, `lime`, `violet`, `green`, `blue`, `red`, `purple`, `orange`),
503
- * or a raw HSL triplet (`"217 91% 60%"`). Applied in both light and dark mode.
504
- *
505
- * This is one half of the two-colour brand model. Use {@link accentColor}
506
- * for links and navigation accents. If you only set `primaryColor`, it is
507
- * reused for accents too, matching the single-brand-colour look.
508
- * @example '#6366f1'
509
- * @example 'blue'
510
- * @example 'hsl(240 50% 60%)'
511
- */
512
- primaryColor?: string;
513
- /**
514
- * Brand colour used for links, active navigation details, hover text, and
515
- * focus rings — the "accent" half of the two-colour brand model, mapped to
516
- * the admin's `--intelligence` token. Accepts the same formats as
517
- * {@link primaryColor} and applies in both light and dark mode.
518
- *
519
- * Set this when your brand has a distinct link/accent colour separate from
520
- * your primary action colour. When omitted, accents fall back to
521
- * `primaryColor` (if set) or the built-in default.
522
- * @example '#8b5cf6'
523
- * @example 'violet'
524
- */
525
- accentColor?: string;
526
- /** Browser tab favicon URL. */
527
- favicon?: string;
528
- /** Font family for body and UI text. Must be loaded separately. */
529
- fontSans?: string;
530
- /** Font family for headings. Must be loaded separately. */
531
- fontSerif?: string;
532
- };
533
- meta?: {
534
- /**
535
- * String appended to every Admin page's `<title>`.
536
- * @default '- Dyrected'
537
- */
538
- titleSuffix?: string;
539
- };
540
- /**
541
- * The canonical/base URL of the frontend website for links and iframe live previews.
542
- */
543
- siteUrl?: string;
544
- }
545
-
546
- type DeclarativeHookExpression = string;
547
- /**
548
- * @deprecated Use {@link FieldBeforeChangeHook} or `FieldAfterReadHook` instead.
549
- * This alias remains for backwards compatibility.
452
+ * Each adapter maps these to its own native conversion primitive
453
+ * (e.g. `$toDouble` in MongoDB, `CAST(x AS REAL)` in SQLite).
454
+ * The public API is always database-independent.
550
455
  */
551
- type FieldHook<TDoc extends object = Record<string, unknown>, TValue = unknown> = FieldBeforeChangeHook<TValue, TDoc>;
456
+ type AggregateCastType = "number" | "integer" | "float" | "string" | "boolean" | "date";
552
457
  /**
553
- * Runs before Dyrected queries the database for a list or single-document fetch.
458
+ * A `count` aggregate operation.
554
459
  *
555
- * Return a new `where` query object to override or extend the current filter.
556
- * Return `undefined` (or nothing) to leave the query unchanged.
460
+ * - `count: "*"` — count every document matched by `where`.
461
+ * - `cast` is intentionally absent: counting is document-level,
462
+ * not field-value-level, so type conversion has no meaning here.
557
463
  */
558
- type CollectionBeforeReadHook = (args: {
559
- req: HookRequestContext;
560
- query?: Record<string, unknown>;
561
- user?: AuthenticatedUser;
562
- db: ReadonlyDatabaseAdapter;
563
- }) => Record<string, unknown> | void | Promise<Record<string, unknown> | void>;
564
- type CollectionBeforeReadHookEntry = CollectionBeforeReadHook | DeclarativeHookExpression;
464
+ interface CountOperation {
465
+ count: "*";
466
+ where?: Record<string, unknown>;
467
+ }
565
468
  /**
566
- * Runs after a document (or list of documents) is fetched from the database,
567
- * before the response is sent to the client.
469
+ * A `countDistinct` aggregate operation that counts the number of unique
470
+ * non-null values for a specific field.
568
471
  */
569
- type CollectionAfterReadHook<TDoc extends object = Record<string, unknown>> = (args: {
570
- doc: TDoc;
571
- req: HookRequestContext;
572
- user?: AuthenticatedUser;
573
- db: ReadonlyDatabaseAdapter;
574
- }) => TDoc | Promise<TDoc>;
575
- type CollectionAfterReadHookEntry<TDoc extends object = Record<string, unknown>> = CollectionAfterReadHook<TDoc> | DeclarativeHookExpression;
472
+ interface DistinctCountOperation {
473
+ countDistinct: string;
474
+ where?: Record<string, unknown>;
475
+ }
576
476
  /**
577
- * Runs **before** a document is created or updated in the database.
477
+ * A `distinct` aggregate operation that retrieves the unique values
478
+ * (or unique values with item counts) for a specific field.
578
479
  */
579
- type CollectionBeforeChangeHook<TDoc extends object = Record<string, unknown>> = (args: {
580
- data: Partial<TDoc>;
581
- doc?: TDoc;
582
- req: HookRequestContext;
583
- user?: AuthenticatedUser;
584
- operation: "create" | "update";
585
- db: ReadonlyDatabaseAdapter;
586
- }) => Partial<TDoc> | void | Promise<Partial<TDoc> | void>;
587
- type CollectionBeforeChangeHookEntry<TDoc extends object = Record<string, unknown>> = CollectionBeforeChangeHook<TDoc> | DeclarativeHookExpression;
480
+ interface DistinctValuesOperation {
481
+ distinct: string;
482
+ where?: Record<string, unknown>;
483
+ }
588
484
  /**
589
- * Runs **after** a document is created or updated in the database.
485
+ * A `sum`, `avg`, `min`, or `max` aggregate operation on a named field.
486
+ *
487
+ * - `cast` converts stored values before the aggregation runs.
488
+ * Invalid values (e.g. `"unknown"` cast to `number`) become `null`
489
+ * and are ignored by `sum`, `avg`, `min`, and `max`.
590
490
  */
591
- type CollectionAfterChangeHook<TDoc extends object = Record<string, unknown>> = (args: {
592
- doc: TDoc;
593
- previousDoc?: TDoc;
594
- req: HookRequestContext;
595
- user?: AuthenticatedUser;
596
- operation: "create" | "update";
597
- db: DatabaseAdapter;
598
- }) => void | Promise<void>;
491
+ interface NumericOperation {
492
+ sum?: string;
493
+ avg?: string;
494
+ min?: string;
495
+ max?: string;
496
+ cast?: AggregateCastType;
497
+ where?: Record<string, unknown>;
498
+ }
599
499
  /**
600
- * Runs **before** a document is deleted from the database.
500
+ * A single named aggregate request — either a count, distinct count, distinct values, or numeric operation.
601
501
  */
602
- type CollectionBeforeDeleteHook<TDoc extends object = Record<string, unknown>> = (args: {
603
- id: string;
604
- doc: TDoc;
605
- req: HookRequestContext;
606
- user?: AuthenticatedUser;
607
- db: ReadonlyDatabaseAdapter;
608
- }) => void | Promise<void>;
502
+ type AggregateOperation = CountOperation | DistinctCountOperation | DistinctValuesOperation | NumericOperation;
609
503
  /**
610
- * Runs **after** a document has been deleted from the database.
504
+ * The map of named aggregate operations sent in a single aggregate request.
505
+ *
506
+ * @example
507
+ * ```ts
508
+ * {
509
+ * totalSubmitted: { count: "*" },
510
+ * uniqueGuests: { countDistinct: "email" },
511
+ * availableSizes: { distinct: "asoebiSize" },
512
+ * totalAsoebiYards: { sum: "asoebiYards", cast: "number", where: { wantsAsoebi: { equals: true } } },
513
+ * }
514
+ * ```
611
515
  */
612
- type CollectionAfterDeleteHook<TDoc extends object = Record<string, unknown>> = (args: {
613
- id: string;
614
- doc: TDoc;
615
- req: HookRequestContext;
616
- user?: AuthenticatedUser;
617
- db: DatabaseAdapter;
618
- }) => void | Promise<void>;
619
- /** @see {@link CollectionBeforeReadHook} */
620
- type GlobalBeforeReadHook = CollectionBeforeReadHook;
621
- type GlobalBeforeReadHookEntry = CollectionBeforeReadHookEntry;
516
+ type AggregateInput = Record<string, AggregateOperation>;
622
517
  /**
623
- * Runs after the global document is fetched, before the response is sent.
518
+ * The arguments passed to `DatabaseAdapter.aggregate`.
624
519
  */
625
- type GlobalAfterReadHook<TDoc extends object = Record<string, unknown>> = (args: {
626
- doc: TDoc;
627
- req: HookRequestContext;
628
- user?: AuthenticatedUser;
629
- db: ReadonlyDatabaseAdapter;
630
- }) => TDoc | Promise<TDoc>;
631
- type GlobalAfterReadHookEntry<TDoc extends object = Record<string, unknown>> = GlobalAfterReadHook<TDoc> | DeclarativeHookExpression;
520
+ interface AggregateArgs {
521
+ /** Collection slug. */
522
+ collection: string;
523
+ /** Named aggregate operations to compute. */
524
+ aggregates: AggregateInput;
525
+ /** Optional field to group the aggregates by. */
526
+ groupBy?: string;
527
+ }
632
528
  /**
633
- * Runs before the global document is updated.
634
- * Operation is always `'update'` (globals cannot be created or deleted).
529
+ * The result returned by `DatabaseAdapter.aggregate`.
530
+ *
531
+ * For scalar aggregates, every named key maps to a `number | null` or `any[]` (for distinct).
532
+ * When `groupBy` is used, returns a breakdown per group.
635
533
  */
636
- type GlobalBeforeChangeHook<TDoc extends object = Record<string, unknown>> = (args: {
637
- data: Partial<TDoc>;
638
- doc?: TDoc;
639
- req: HookRequestContext;
640
- user?: AuthenticatedUser;
641
- operation: "update";
642
- db: ReadonlyDatabaseAdapter;
643
- }) => Partial<TDoc> | void | Promise<Partial<TDoc> | void>;
644
- type GlobalBeforeChangeHookEntry<TDoc extends object = Record<string, unknown>> = GlobalBeforeChangeHook<TDoc> | DeclarativeHookExpression;
534
+ type AggregateResult = Record<string, any>;
645
535
  /**
646
- * Runs after the global document is updated. Side-effects only.
536
+ * Derives the typed result shape from an `AggregateInput`.
647
537
  */
648
- type GlobalAfterChangeHook<TDoc extends object = Record<string, unknown>> = (args: {
649
- doc: TDoc;
650
- previousDoc?: TDoc;
651
- req: HookRequestContext;
652
- user?: AuthenticatedUser;
653
- operation: "update";
654
- db: DatabaseAdapter;
655
- }) => void | Promise<void>;
538
+ type InferAggregateResult<T extends AggregateInput> = {
539
+ [K in keyof T]: any;
540
+ };
541
+
656
542
  /**
657
- * @deprecated Use the specific hook types instead:
658
- * `CollectionBeforeChangeHook`, `CollectionAfterReadHook`, etc.
543
+ * Dyrected Where Clause DSL — shared query language across all database adapters.
659
544
  *
660
- * This broad type remains for backwards compatibility with the internal hook runner.
545
+ * Adapters translate this DSL into their native format:
546
+ * - SQL adapters (SQLite, Postgres, MySQL): use parseSqlWhere()
547
+ * - MongoDB adapter: use parseMongoWhere()
548
+ * - Future adapters: implement their own translator against WhereClause
549
+ *
550
+ * Exhaustive operator handling is enforced at compile time via `assertNever`,
551
+ * so adding a new operator without handling it will produce a TypeScript error
552
+ * in every adapter translator.
661
553
  */
662
- type HookFunction<TDoc extends object = Record<string, unknown>> = (args: {
663
- data?: Partial<TDoc>;
664
- doc?: TDoc;
665
- user?: AuthenticatedUser;
666
- req?: HookRequestContext;
667
- operation?: "create" | "update" | "delete";
668
- db?: DatabaseAdapter;
669
- [key: string]: unknown;
670
- }) => unknown | Promise<unknown>;
554
+ type WhereOperatorName = 'equals' | 'not_equals' | 'in' | 'not_in' | 'gt' | 'greater_than' | 'gte' | 'greater_than_equal' | 'greater_than_or_equal' | 'lt' | 'less_than' | 'lte' | 'less_than_equal' | 'less_than_or_equal' | 'contains' | 'like' | 'starts_with' | 'exists';
555
+ type WhereOperator = {
556
+ equals: any;
557
+ } | {
558
+ not_equals: any;
559
+ } | {
560
+ in: any[];
561
+ } | {
562
+ not_in: any[];
563
+ } | {
564
+ gt: any;
565
+ } | {
566
+ greater_than: any;
567
+ } | {
568
+ gte: any;
569
+ } | {
570
+ greater_than_equal: any;
571
+ } | {
572
+ greater_than_or_equal: any;
573
+ } | {
574
+ lt: any;
575
+ } | {
576
+ less_than: any;
577
+ } | {
578
+ lte: any;
579
+ } | {
580
+ less_than_equal: any;
581
+ } | {
582
+ less_than_or_equal: any;
583
+ } | {
584
+ contains: string;
585
+ } | {
586
+ like: string;
587
+ } | {
588
+ starts_with: string;
589
+ } | {
590
+ exists: boolean;
591
+ };
592
+ /** Top-level where clause. Fields map to operator objects or shorthand scalar values. */
593
+ type WhereClause = {
594
+ [field: string]: WhereOperator | any;
595
+ OR?: WhereClause[] | readonly WhereClause[];
596
+ or?: WhereClause[] | readonly WhereClause[];
597
+ AND?: WhereClause[] | readonly WhereClause[];
598
+ and?: WhereClause[] | readonly WhereClause[];
599
+ };
600
+ interface SqlWhereResult {
601
+ sql: string;
602
+ params: any[];
603
+ }
604
+ /**
605
+ * Translates a WhereClause into a parameterized SQL WHERE fragment.
606
+ *
607
+ * @param where The Dyrected where DSL object
608
+ * @param getJsonField Returns the SQL expression for a JSON-stored field (dialect-specific):
609
+ * SQLite: (f) => `json_extract(data, '$.${f}')`
610
+ * Postgres: (f) => `data->>'${f}'`
611
+ * MySQL: (f) => `JSON_UNQUOTE(JSON_EXTRACT(data, '$.${f}'))`
612
+ * @param placeholder '?' for SQLite/MySQL, 'pg' for auto-incrementing Postgres $1/$2/…
613
+ */
614
+ declare function parseSqlWhere(where: WhereClause, getJsonField: (field: string) => string, placeholder?: '?' | 'pg',
615
+ /**
616
+ * SQL keyword used for `contains`/`starts_with`. Postgres `LIKE` is
617
+ * case-sensitive, so the Postgres adapter defaults to `'ILIKE'` to keep substring
618
+ * matching case-insensitive like SQLite and MySQL.
619
+ */
620
+ likeOperator?: 'LIKE' | 'ILIKE'): SqlWhereResult;
621
+ /**
622
+ * Translates a WhereClause into a MongoDB filter object.
623
+ * Handles nested OR/AND via $or/$and, and all operators via $eq/$in/$gt etc.
624
+ */
625
+ declare function parseMongoWhere(where: WhereClause): Record<string, any>;
671
626
 
672
- declare const LIFECYCLE_EVENT_NAMES: readonly ["revision.created", "workflow.transitioned", "entry.published", "entry.unpublished"];
673
- type LifecycleEventName = (typeof LIFECYCLE_EVENT_NAMES)[number];
674
- interface WorkflowState {
675
- /** Stable machine-readable state key. */
676
- name: string;
677
- /** Label rendered in the Admin UI. */
627
+ /**
628
+ * Custom component slots rendered around an operational view's content.
629
+ */
630
+ type ViewComponentSlots = CollectionViewComponentSlots;
631
+ /**
632
+ * Supported layout engines for operational views in the Dyrected Admin UI:
633
+ * - `'table'`: The workhorse — column sorting, search, faceted filters, and bulk actions.
634
+ * - `'spreadsheet'`: Inline-editable matrix with batch saving (`Save changes` / `Discard`).
635
+ * - `'kanban'`: Interactive card board grouped by status or category with drag-and-drop.
636
+ * - `'calendar'`: Event schedule grid plotting records across month/week/day views by date.
637
+ * - `'gantt'`: Timeline bars plotted across start and end dates.
638
+ * - `'cards'`: Visual image/thumbnail gallery grid.
639
+ */
640
+ type ViewLayout = 'table' | 'spreadsheet' | 'kanban' | 'calendar' | 'gantt' | 'cards';
641
+ /**
642
+ * Scope and placement for an operational view action:
643
+ * - `'row'`: Renders as an inline action button on each document/row.
644
+ * - `'bulk'`: Appears in the floating bulk action bar when one or more rows are selected.
645
+ * - `'header'`: Renders as a primary action button in the view header.
646
+ */
647
+ type ActionType = 'row' | 'bulk' | 'header';
648
+ /**
649
+ * Toggles for the built-in document operations an operational view surfaces.
650
+ * All default to enabled (`true`). Set any to `false` to hide that operation.
651
+ *
652
+ * @example
653
+ * ```ts
654
+ * features: {
655
+ * delete: false, // Prevent deleting records from this view
656
+ * exportSelected: true, // Enable CSV export for checked rows
657
+ * }
658
+ * ```
659
+ */
660
+ interface ViewActionFeatures {
661
+ /** Enables the "View" preview / read-only drawer button. Defaults to `true`. */
662
+ view?: boolean;
663
+ /** Enables the "Edit" document form link. Defaults to `true`. */
664
+ edit?: boolean;
665
+ /** Enables the "Duplicate" record action. Defaults to `true`. */
666
+ duplicate?: boolean;
667
+ /** Enables the "Delete" record action. Defaults to `true`. */
668
+ delete?: boolean;
669
+ /** Enables the "Export selected" CSV action in the bulk action bar. Defaults to `true`. */
670
+ exportSelected?: boolean;
671
+ }
672
+ /**
673
+ * Color palette options for metric cards:
674
+ * `'purple' | 'emerald' | 'amber' | 'rose' | 'blue' | 'indigo' | 'cyan' | 'orange'`
675
+ */
676
+ type MetricColor = 'purple' | 'emerald' | 'amber' | 'rose' | 'blue' | 'indigo' | 'cyan' | 'orange' | string;
677
+ /**
678
+ * A sub-metric breakdown item displayed in the compact footer of a metric card.
679
+ *
680
+ * @example
681
+ * ```ts
682
+ * {
683
+ * label: "Pending",
684
+ * aggregates: {
685
+ * checkedIn: { count: "*", where: { checkedIn: { equals: true } } },
686
+ * attending: { count: "*", where: { attending: { equals: true } } },
687
+ * },
688
+ * expression: "aggregates.attending - aggregates.checkedIn",
689
+ * }
690
+ * ```
691
+ */
692
+ interface ViewSubMetric {
693
+ /** Short label for the sub-metric (e.g. "Pending", "Collected"). */
678
694
  label: string;
679
- /** Marks the state whose revision is visible to public readers. */
680
- published?: boolean;
681
- /** Optional visual tone used by the Admin UI. */
682
- color?: "neutral" | "warning" | "success" | "danger" | "info";
695
+ /** Single database aggregate operation. */
696
+ aggregate?: AggregateOperation;
697
+ /** Map of named aggregate operations referenced in `expression` as `aggregates.key`. */
698
+ aggregates?: Record<string, AggregateOperation>;
699
+ /** JEXL math formula evaluated over `value` for a single aggregate (e.g. `"value * 100"`). */
700
+ transform?: string;
701
+ /** JEXL math formula evaluated over `aggregates` (e.g. `"aggregates.a - aggregates.b"`). */
702
+ expression?: string;
703
+ /** Display formatter: `'currency' | 'number' | 'percent'`. */
704
+ format?: 'currency' | 'number' | 'percent' | string;
705
+ /** 3-letter currency code (e.g. `"USD"`, `"NGN"`, `"EUR"`) used when `format: "currency"`. */
706
+ currency?: string;
683
707
  }
684
- interface WorkflowTransition {
685
- /** Stable transition key used by the REST and SDK APIs. */
708
+ /**
709
+ * Configuration for a KPI summary card rendered at the top of an operational view.
710
+ *
711
+ * All aggregations run directly on the database engine for maximum performance.
712
+ *
713
+ * @example
714
+ * ```ts
715
+ * {
716
+ * label: "Total Revenue",
717
+ * color: "emerald",
718
+ * aggregate: { sum: "amount", cast: "number", where: { status: "paid" } },
719
+ * format: "currency",
720
+ * currency: "USD",
721
+ * }
722
+ * ```
723
+ */
724
+ interface ViewMetric {
725
+ /** Title displayed on the metric card header. */
726
+ label: string;
727
+ /** Accent color for the card's visual indicator. */
728
+ color?: MetricColor;
729
+ /** Unit badge shown alongside the numeric value (e.g. `"Guests"`, `"Orders"`, `"Tables"`). */
730
+ unit?: string;
731
+ /** Single database aggregate operation (`count`, `distinctCount`, `sum`, `avg`, `min`, `max`). */
732
+ aggregate?: AggregateOperation;
733
+ /** Map of named aggregate operations referenced in `expression` as `aggregates.key`. */
734
+ aggregates?: Record<string, AggregateOperation>;
735
+ /** JEXL math formula evaluated over `value` for a single aggregate (e.g. `"value * 25000"`). */
736
+ transform?: string;
737
+ /** JEXL math formula evaluated over `aggregates` (e.g. `"(aggregates.paid / aggregates.total) * 100"`). */
738
+ expression?: string;
739
+ /** Display formatter: `'currency' | 'number' | 'percent'`. */
740
+ format?: 'currency' | 'number' | 'percent' | string;
741
+ /** 3-letter currency code (e.g. `"USD"`, `"NGN"`, `"EUR"`) used when `format: "currency"`. */
742
+ currency?: string;
743
+ /** Optional secondary breakdown items shown in the card footer. */
744
+ subMetrics?: ViewSubMetric[];
745
+ }
746
+ /**
747
+ * Base configuration shared by all action types.
748
+ */
749
+ interface ActionConfigBase<TDoc extends Record<string, unknown> = Record<string, unknown>> {
750
+ /** Unique action identifier (e.g. `"checkIn"`, `"markPaid"`). */
686
751
  name: string;
752
+ /** Button label displayed in the UI. */
687
753
  label: string;
688
- /**
689
- * Source state(s) this transition originates from.
690
- * Must match names in the workflow's `states` array.
691
- */
692
- from: string | string[];
693
- /**
694
- * Target state this transition leads to.
695
- * Must match a name in the workflow's `states` array.
696
- */
697
- to: string;
698
- /** Every listed capability is required. */
699
- requiredCapabilities?: string[];
700
- /** Require a non-empty comment when performing the transition. */
701
- requireComment?: boolean;
702
- /** Remove the public snapshot after this transition commits. */
703
- unpublish?: boolean;
754
+ /** Optional custom label for the modal submit button (defaults to "Run" or "Confirm"). */
755
+ submitLabel?: string;
756
+ /** Lucide icon name for the button (e.g. `"UserCheck"`, `"CheckCircle"`). */
757
+ icon?: string;
758
+ /** Action placement: `'row'` (per-row), `'bulk'` (multi-row selection bar), or `'header'` (view header). Defaults to `'row'`. */
759
+ type?: ActionType;
760
+ /** Optional confirmation prompt shown before executing (e.g. `"Confirm guest check-in at the door?"`). */
761
+ confirm?: string;
762
+ /** Optional interactive modal form fields prompting the user for input before executing. */
763
+ fields?: Field[];
764
+ /** Role-based access rules controlling who can trigger this action. */
765
+ access?: AccessConfig;
704
766
  }
705
- interface WorkflowRole {
706
- /** Existing user role value, for example `editor` or `publisher`. */
707
- role: string;
708
- capabilities: string[];
767
+ /**
768
+ * An operational workflow action with declarative data mutation.
769
+ */
770
+ interface ActionConfigWithMutation<TDoc extends Record<string, unknown> = Record<string, unknown>> extends ActionConfigBase<TDoc> {
771
+ /** Declarative database mutation applied to targeted documents (e.g. `{ checkedIn: true, checkedInAt: "now()" }`). */
772
+ mutation: Partial<TDoc> | Record<string, unknown>;
773
+ /** Optional server handler for additional logic. */
774
+ handler?: (context: ActionContext<TDoc>) => Promise<unknown> | unknown;
709
775
  }
710
776
  /**
711
- * Configuration for a collection's editorial workflow: the states a document
712
- * can be in, the transitions between them, and the roles allowed to perform
713
- * each. Attach it to a collection's `workflow`, or build one with
714
- * `definePublishingWorkflow`.
777
+ * An operational workflow action with server handler.
715
778
  */
716
- interface WorkflowConfig<TDoc extends object = Record<string, unknown>> {
717
- /** The state every new document starts in, e.g. `"draft"`. */
718
- initialState: string;
719
- /** State used for a new working revision created from published content. */
720
- draftState?: string;
721
- /** All states a document can occupy. Exactly one should be marked `published`. */
722
- states: WorkflowState[];
723
- /** The allowed moves between states, each with its own capability and comment rules. */
724
- transitions: WorkflowTransition[];
725
- /** Maps values in `user.roles` to workflow capabilities. */
726
- roles?: WorkflowRole[];
727
- /** Server-side hooks that run around every transition. */
728
- hooks?: {
729
- /** Runs before a transition commits — throw to validate or block the move. */
730
- beforeTransition?: CollectionBeforeTransitionHook<TDoc>[];
731
- /** Runs after a transition commits — trigger notifications or downstream work. */
732
- afterTransition?: CollectionAfterTransitionHook<TDoc>[];
779
+ interface ActionConfigWithHandler<TDoc extends Record<string, unknown> = Record<string, unknown>> extends ActionConfigBase<TDoc> {
780
+ /** Self-hosted server handler function executed when the action is triggered (required when mutation is not provided). */
781
+ handler: (context: ActionContext<TDoc>) => Promise<unknown> | unknown;
782
+ /** Optional declarative mutation. */
783
+ mutation?: Partial<TDoc> | Record<string, unknown>;
784
+ }
785
+ /**
786
+ * An operational workflow action that can mutate data or trigger server logic.
787
+ * Requires either `mutation` or `handler` (or both).
788
+ */
789
+ type ActionConfig<TDoc extends Record<string, unknown> = Record<string, unknown>> = ActionConfigWithMutation<TDoc> | ActionConfigWithHandler<TDoc>;
790
+ /**
791
+ * Context passed to an action's server handler function.
792
+ */
793
+ interface ActionContext<TDoc extends Record<string, unknown> = Record<string, unknown>> {
794
+ /** The target document (for row actions), or null for header actions. */
795
+ doc: TDoc | null;
796
+ /** All targeted documents (for bulk actions). Empty array for header actions. */
797
+ docs: TDoc[];
798
+ /** The authenticated acting user, if available. */
799
+ user: Record<string, unknown> | null;
800
+ /** User inputs submitted via the action's modal form fields. */
801
+ input: Record<string, unknown>;
802
+ /** The collection configuration object. */
803
+ collection: Record<string, unknown>;
804
+ }
805
+ /**
806
+ * Role-based access control rules for a view or action.
807
+ */
808
+ interface AccessConfig {
809
+ read?: AccessRule<any>;
810
+ create?: AccessRule<any>;
811
+ update?: AccessRule<any>;
812
+ delete?: AccessRule<any>;
813
+ }
814
+ /**
815
+ * Base options shared by all operational view configurations.
816
+ */
817
+ interface ViewConfigBase {
818
+ /** Stable URL slug for the view (`/collections/:slug/views/:viewSlug` or `/:workspaceSlug/:viewSlug`). */
819
+ slug: string;
820
+ /** Human-readable title displayed in the sidebar navigation and view header. */
821
+ label: string;
822
+ /** Lucide icon name (e.g. `"UserCheck"`, `"Calendar"`, `"Shirt"`, `"TableProperties"`). */
823
+ icon?: string;
824
+ /** Optional target collection slug for views defined inside standalone operational workspaces. */
825
+ collection?: string;
826
+ /** Base query filter applied before user toolbar filters (e.g. `{ attending: { equals: true } }` or JEXL string). */
827
+ filter?: WhereClause | Record<string, any> | string;
828
+ /** Field names to show in this view, in order. If omitted, Dyrected infers default display fields. */
829
+ columns?: string[];
830
+ /** Default sorting rule when entering the view. */
831
+ sort?: {
832
+ field: string;
833
+ direction: 'asc' | 'desc';
733
834
  };
835
+ /** Custom workflow actions available in this view. */
836
+ actions?: ActionConfig[];
837
+ /** Toggles built-in operations (view/edit/duplicate/delete/export). */
838
+ features?: ViewActionFeatures;
839
+ /**
840
+ * Explicit display order for action buttons — mixing custom action names and built-in names
841
+ * (`"view"`, `"edit"`, `"duplicate"`, `"delete"`). Unlisted actions append in default order.
842
+ */
843
+ actionOrder?: string[];
844
+ /** KPI summary cards rendered in the hero row above the view. */
845
+ metrics?: ViewMetric[];
846
+ /** Optional real-time counter badge or status indicator displayed on this view in navigation and tabs. */
847
+ badge?: NavBadgeConfig | string;
848
+ /** Custom component slots rendered around this operational view. */
849
+ components?: ViewComponentSlots;
850
+ /** Role-based access rules controlling who can see or use this view. */
851
+ access?: AccessConfig;
734
852
  }
735
- interface WorkflowMetadata {
736
- /** The document's current workflow state, e.g. `"draft"` or `"published"`. */
737
- state: string;
738
- /** Revision counter, incremented on every committed transition. */
739
- revision: number;
740
- /** Revision number currently exposed as the public snapshot, once published. */
741
- publishedRevision?: number;
742
- /** ISO timestamp of the most recent publish. */
743
- publishedAt?: string;
744
- /** Id of the user who last published this document. */
745
- publishedBy?: string;
746
- /** Transitions currently allowed for the requesting user. Response-only. */
747
- availableTransitions?: string[];
853
+ /** Table view — requires no special fields. */
854
+ interface TableViewConfig extends ViewConfigBase {
855
+ layout?: 'table' | 'spreadsheet';
856
+ /** Field name used to organize records into kanban columns or collapsible grouped sections in table, cards, and spreadsheet. */
857
+ groupBy?: string;
748
858
  }
749
- interface WorkflowTransitionContext<TDoc extends object = Record<string, unknown>> {
750
- /** The transition being performed. */
751
- transition: WorkflowTransition;
752
- /** State the document is moving out of. */
753
- from: string;
754
- /** State the document is moving into. */
755
- to: string;
756
- /** The document as it stands before the transition commits. */
757
- doc: TDoc;
758
- /** The user performing the transition, when the request is authenticated. */
759
- user?: AuthenticatedUser;
760
- /** Comment supplied with the transition; required when `requireComment` is set. */
761
- comment?: string;
762
- /** Request context for the transition. */
763
- req: HookRequestContext;
764
- /** Transaction-scoped database adapter for reads and writes inside the hook. */
765
- db: DatabaseAdapter;
859
+ /** Kanban view — requires `groupBy` to organize into columns. */
860
+ interface KanbanViewConfig extends ViewConfigBase {
861
+ layout: 'kanban';
862
+ /** Field name to organize records into kanban columns. */
863
+ groupBy: string;
766
864
  }
767
- type CollectionBeforeTransitionHook<TDoc extends object = Record<string, unknown>> = (args: WorkflowTransitionContext<TDoc>) => void | Promise<void>;
768
- type CollectionAfterTransitionHook<TDoc extends object = Record<string, unknown>> = (args: WorkflowTransitionContext<TDoc> & {
769
- event: LifecycleEvent;
770
- }) => void | Promise<void>;
771
- interface LifecycleEvent<TPayload = Record<string, unknown>> {
772
- /** Unique id for this event. */
773
- id: string;
774
- /** The lifecycle event name, e.g. `"workflow.transitioned"` or `"entry.published"`. */
775
- name: LifecycleEventName;
776
- /** Slug of the collection the event is about. */
777
- collection: string;
778
- /** Id of the document the event is about. */
779
- documentId: string;
780
- /** ISO timestamp of when the event occurred. */
781
- occurredAt: string;
782
- /** Id of the user who triggered the event, when known. */
783
- actorId?: string;
784
- /** Event-specific data, such as the transition name and affected revision. */
785
- payload: TPayload;
786
- /** Number of delivery attempts made so far. */
787
- attempts: number;
788
- /** Current delivery status as the dispatcher works through the queue. */
789
- status: "pending" | "processing" | "delivered" | "failed";
790
- /** ISO timestamp of the next delivery retry, while pending. */
791
- nextAttemptAt?: string;
792
- /** ISO timestamp of successful delivery. */
793
- deliveredAt?: string;
794
- /** Message from the most recent failed delivery attempt. */
795
- lastError?: string;
865
+ /** Calendar view — requires `dateField` to place records on the calendar. */
866
+ interface CalendarViewConfig extends ViewConfigBase {
867
+ layout: 'calendar';
868
+ /** Field name containing the ISO date string for calendar placement. */
869
+ dateField: string;
796
870
  }
797
- type LifecycleEventHandler = (event: LifecycleEvent) => void | Promise<void>;
871
+ /** Gantt view — requires both `startDateField` and `endDateField`. */
872
+ interface GanttViewConfig extends ViewConfigBase {
873
+ layout: 'gantt';
874
+ /** Field name for the start date in the gantt timeline. */
875
+ startDateField: string;
876
+ /** Field name for the end date in the gantt timeline. */
877
+ endDateField: string;
878
+ }
879
+ /** Cards view — shows records as image/thumbnail gallery. */
880
+ interface CardsViewConfig extends ViewConfigBase {
881
+ layout: 'cards';
882
+ /** Field name used to organize records into groups. */
883
+ groupBy?: string;
884
+ }
885
+ /**
886
+ * Complete configuration for a tailored operational view.
887
+ */
888
+ type ViewConfig = TableViewConfig | KanbanViewConfig | CalendarViewConfig | GanttViewConfig | CardsViewConfig;
889
+ /**
890
+ * Backwards-compatible aliases for builder types.
891
+ */
892
+ type DefineViewBaseOptions = ViewConfigBase;
893
+ type DefineTableViewOptions = TableViewConfig;
894
+ type DefineKanbanViewOptions = KanbanViewConfig;
895
+ type DefineCalendarViewOptions = CalendarViewConfig;
896
+ type DefineGanttViewOptions = GanttViewConfig;
897
+ type DefineCardsViewOptions = CardsViewConfig;
898
+ type DefineViewOptions = ViewConfig;
899
+ /**
900
+ * Options for defining a custom workflow action with `defineAction`.
901
+ *
902
+ * @example
903
+ * ```ts
904
+ * export const checkInAction = defineAction({
905
+ * name: "checkIn",
906
+ * label: "Check In",
907
+ * icon: "UserCheck",
908
+ * type: "row",
909
+ * confirm: "Confirm guest check-in at the door?",
910
+ * mutation: { checkedIn: true, checkedInAt: "now()" },
911
+ * });
912
+ * ```
913
+ */
914
+ interface DefineActionOptions<TDoc extends Record<string, unknown> = Record<string, unknown>> {
915
+ /** Unique action identifier (e.g. `"checkIn"`, `"markPaid"`). */
916
+ name: string;
917
+ /** Button label displayed in the UI. */
918
+ label: string;
919
+ /** Lucide icon name for the button (e.g. `"UserCheck"`, `"CheckCircle"`). */
920
+ icon?: string;
921
+ /** Action placement: `'row'` (per-row), `'bulk'` (multi-row selection bar), or `'header'` (view header). Defaults to `'row'`. */
922
+ type?: ActionType;
923
+ /** Optional confirmation prompt shown before executing (e.g. `"Confirm guest check-in at the door?"`). */
924
+ confirm?: string;
925
+ /** Optional interactive modal form fields prompting the user for input before executing. */
926
+ fields?: Field[];
927
+ /** Declarative database mutation applied to targeted documents (e.g. `{ checkedIn: true, checkedInAt: "now()" }`). */
928
+ mutation?: Partial<TDoc> | Record<string, unknown>;
929
+ /** Self-hosted server handler function executed when the action is triggered. */
930
+ handler?: (context: ActionContext<TDoc>) => Promise<unknown> | unknown;
931
+ /** Role-based access rules controlling who can trigger this action. */
932
+ access?: AccessConfig;
933
+ }
934
+ /**
935
+ * Defines a typed operational view for a collection.
936
+ * Layout-specific overloads enforce required fields for each view type.
937
+ *
938
+ * @example
939
+ * ```ts
940
+ * // Table view (no special requirements)
941
+ * export const allGuests = defineView({
942
+ * slug: "all-guests",
943
+ * label: "All Guests",
944
+ * layout: "table", // or omit for default
945
+ * });
946
+ *
947
+ * // Kanban view (requires groupBy)
948
+ * export const guestsByTable = defineView({
949
+ * slug: "by-table",
950
+ * label: "Guests by Table",
951
+ * layout: "kanban",
952
+ * groupBy: "tableNumber", // required!
953
+ * });
954
+ *
955
+ * // Calendar view (requires dateField)
956
+ * export const eventCalendar = defineView({
957
+ * slug: "event-calendar",
958
+ * label: "Event Calendar",
959
+ * layout: "calendar",
960
+ * dateField: "eventDate", // required!
961
+ * });
962
+ *
963
+ * // Gantt view (requires startDateField and endDateField)
964
+ * export const projectTimeline = defineView({
965
+ * slug: "timeline",
966
+ * label: "Project Timeline",
967
+ * layout: "gantt",
968
+ * startDateField: "startDate", // required!
969
+ * endDateField: "endDate", // required!
970
+ * });
971
+ * ```
972
+ */
973
+ declare function defineView(config: DefineTableViewOptions): DefineTableViewOptions;
974
+ declare function defineView(config: DefineKanbanViewOptions): DefineKanbanViewOptions;
975
+ declare function defineView(config: DefineCalendarViewOptions): DefineCalendarViewOptions;
976
+ declare function defineView(config: DefineGanttViewOptions): DefineGanttViewOptions;
977
+ declare function defineView(config: DefineCardsViewOptions): DefineCardsViewOptions;
978
+ declare function defineView<const T extends DefineViewOptions>(config: T): T;
979
+ /** Convenience helper for table views. Enforces no view-specific required fields. */
980
+ declare function defineTableView(config: DefineTableViewOptions): DefineTableViewOptions;
981
+ /** Convenience helper for kanban views. Enforces `groupBy` is provided. */
982
+ declare function defineKanbanView(config: DefineKanbanViewOptions): DefineKanbanViewOptions;
983
+ /** Convenience helper for calendar views. Enforces `dateField` is provided. */
984
+ declare function defineCalendarView(config: DefineCalendarViewOptions): DefineCalendarViewOptions;
985
+ /** Convenience helper for gantt views. Enforces `startDateField` and `endDateField` are provided. */
986
+ declare function defineGanttView(config: DefineGanttViewOptions): DefineGanttViewOptions;
987
+ /** Convenience helper for cards views. Optional `groupBy`. */
988
+ declare function defineCardsView(config: DefineCardsViewOptions): DefineCardsViewOptions;
989
+ /**
990
+ * Defines a custom workflow action for an operational view.
991
+ *
992
+ * @example
993
+ * ```ts
994
+ * export const markPaidAction = defineAction({
995
+ * name: "markPaid",
996
+ * label: "Mark Paid",
997
+ * icon: "DollarSign",
998
+ * type: "bulk",
999
+ * confirm: "Mark selected orders as paid?",
1000
+ * mutation: { status: "paid" },
1001
+ * });
1002
+ * ```
1003
+ */
1004
+ declare function defineAction<const T extends DefineActionOptions>(config: T): T;
798
1005
 
799
- /** Column span in the 12-column Detail View layout grid (1 to 12). */
800
- type DetailSpan = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
801
1006
  /**
802
- * Visual display variants supported by DetailFieldRenderer.
1007
+ * Definition of a navigation group in the Dyrected admin sidebar.
803
1008
  */
804
- type DisplayVariant = "text" | "badge" | "code-badge" | "code" | "copyable" | "link" | "url" | "email" | "phone" | "currency" | "percent" | "progress" | "star" | "star-rating" | "boolean" | "date" | "datetime" | "time" | "relative" | "image" | "avatar" | "color" | "color-swatches" | "icon" | "key-value" | "table" | "tags" | "badges" | "json";
1009
+ interface NavGroup {
1010
+ /** Display title for the group in the sidebar. */
1011
+ name: string;
1012
+ /** Optional stable identifier/slug for the group. */
1013
+ slug?: string;
1014
+ /** Lucide icon name for the group accordion header. */
1015
+ icon?: AdminIconName | string;
1016
+ /** Whether the group is expanded by default in the sidebar. */
1017
+ defaultExpanded?: boolean;
1018
+ /** Sort order weight for the group section in the sidebar (lower numbers appear first). */
1019
+ order?: number;
1020
+ }
1021
+ /**
1022
+ * Configuration for real-time count badges on navigation items.
1023
+ */
1024
+ interface NavBadgeConfig {
1025
+ /** If true, automatically calculates a count based on the primary view's filter. */
1026
+ count?: boolean;
1027
+ /** Static text override for the badge (e.g. "Live", "New", "Urgent"). */
1028
+ text?: string;
1029
+ /** Custom aggregate filter evaluated on the database. */
1030
+ aggregate?: {
1031
+ collection: string;
1032
+ where?: Record<string, unknown>;
1033
+ };
1034
+ /** Visual badge color variant. */
1035
+ variant?: "default" | "warning" | "destructive" | "info" | "success";
1036
+ }
805
1037
  /**
806
- * Configuration options for rendering a field in a Detail View.
1038
+ * Options for defining an operational workspace, or customizing placement of collections/globals, with `defineWorkspace`.
807
1039
  */
808
- interface DisplayFieldOptions {
809
- /** Custom label overriding the field's schema label. */
1040
+ interface DefineWorkspaceOptions {
1041
+ /** If provided, injects views into an existing collection submenu. If omitted, item stands alone. */
1042
+ addToCollection?: string;
1043
+ /** References an existing collection to position it in navigation. */
1044
+ collection?: string;
1045
+ /** References an existing global to position it in navigation. */
1046
+ global?: string;
1047
+ /** Designates this item as the dashboard link. */
1048
+ dashboard?: boolean;
1049
+ /** Stable URL slug for standalone operational workspaces (`/:slug`). */
1050
+ slug?: string;
1051
+ /** Display label in the sidebar (defaults to collection/global label when applicable). */
810
1052
  label?: string;
811
- /** When true, hides the field label entirely and renders only the value. */
812
- hideLabel?: boolean;
813
- /** Informational tooltip shown next to the label. */
814
- tooltip?: string;
815
- /** Grid column span across the 12-column grid. */
816
- span?: DetailSpan;
817
- /** Specific visual representation variant (e.g. 'badge', 'currency', 'star', 'copyable'). */
818
- display?: DisplayVariant;
819
- /** Preset format for numbers, dates, and currencies ('currency', 'date', 'datetime', 'relative', 'number', 'percent'). */
820
- format?: "currency" | "date" | "datetime" | "relative" | "number" | "percent" | string;
821
- /** ISO currency code (e.g. 'USD', 'EUR') when display or format is 'currency'. */
822
- currency?: string;
823
- /** Color mapping for badge and tag values (named palette, hex color, Tailwind class, or wildcard). */
824
- badgeColors?: Record<string, string>;
825
- /** Column header for object keys when display is 'key-value'. */
826
- keyLabel?: string;
827
- /** Column header for object values when display is 'key-value'. */
828
- valueLabel?: string;
829
- /** Custom placeholder text when the field value is null, undefined, or empty. */
830
- emptyText?: string;
831
- /** When true, hides this entire field item if the value is null or empty. */
832
- hideIfEmpty?: boolean;
833
- /**
834
- * When true, allows inline editing of this field in the Detail View
835
- * via an interactive toggle.
836
- */
837
- editable?: boolean;
838
- /**
839
- * Visibility condition for this field item.
840
- * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
841
- * When false or evaluating to falsy, this item is hidden in the Detail View.
842
- *
843
- * @example `visible: "doc.status == 'published'"`
844
- * @example `visible: false`
845
- */
846
- visible?: string | boolean;
847
- /** Specific aspect ratio constraint for media (e.g., "16/9", "4/3", "1/1", "auto"). */
848
- aspectRatio?: string;
849
- /** CSS object-fit behavior for media ('cover', 'contain', 'fill', 'scale-down'). */
850
- objectFit?: "cover" | "contain" | "fill" | "scale-down";
851
- /** Media alignment ('left', 'center', 'right'). */
852
- align?: "left" | "center" | "right";
853
- /** Fixed pixel width or CSS dimension for media preview. */
854
- width?: string | number;
855
- /** Fixed pixel height or CSS dimension for media preview. */
856
- height?: string | number;
1053
+ /** Lucide icon name (e.g. "ShieldAlert", "Users", "Briefcase"). */
1054
+ icon?: AdminIconName | string;
1055
+ /** Section group name (string) or group object with icon and default expanded state. */
1056
+ group?: string | NavGroup;
1057
+ /** Relative position: insert immediately after this collection or nav item slug. */
1058
+ after?: string;
1059
+ /** Relative position: insert immediately before this collection or nav item slug. */
1060
+ before?: string;
1061
+ /** Numeric sort weight within its group (lower numbers first, default 100). */
1062
+ order?: number;
1063
+ /** Fast shortcut to pin item to start or end of its group: 'first' | 'last'. */
1064
+ position?: "first" | "last";
1065
+ /** Real-time counter badge (auto-computed from primary view or custom aggregate). */
1066
+ badge?: NavBadgeConfig | string;
1067
+ /** Role-based access rules controlling who can see this navigation item. */
1068
+ access?: string[] | AccessRule;
1069
+ /** Custom internal route or external URL. */
1070
+ href?: string;
1071
+ /** Views belonging to this item. The first view is automatically the default. */
1072
+ views?: ViewConfig[];
857
1073
  }
858
1074
  /**
859
- * Declared field item in a Detail View.
1075
+ * Helper to define an operational workspace or customize placement of collections/globals.
1076
+ *
1077
+ * @example
1078
+ * ```ts
1079
+ * export const kycQueue = defineWorkspace({
1080
+ * slug: "kyc-review",
1081
+ * label: "KYC Review",
1082
+ * icon: "ShieldAlert",
1083
+ * group: "Compliance",
1084
+ * views: [
1085
+ * defineView({ collection: "investors", slug: "pending", label: "Pending" }),
1086
+ * ],
1087
+ * });
1088
+ * ```
860
1089
  */
861
- interface DetailField {
862
- /** Identifies this item as a field. */
863
- type: "field";
864
- /** Field name or nested path in the document. */
865
- field: string;
866
- /** Display options for this field. */
867
- options?: DisplayFieldOptions;
868
- }
1090
+ declare function defineWorkspace(options: DefineWorkspaceOptions): DefineWorkspaceOptions;
869
1091
  /**
870
- * Configuration options for a Detail View section container.
1092
+ * Runtime compiled representation of a single navigation item.
871
1093
  */
872
- interface DetailSectionOptions {
873
- /** Lucide icon name displayed in the section header. */
1094
+ interface CompiledNavItem {
1095
+ id: string;
1096
+ type: "workspace" | "collection" | "global" | "dashboard" | "link";
1097
+ slug: string;
1098
+ label: string;
874
1099
  icon?: string;
875
- /** Status or counter badge displayed beside the section title. */
876
- badge?: string;
877
- /** Color palette name or hex code for the section badge. */
878
- badgeColor?: string;
879
- /** Subtitle description displayed beneath the section title. */
880
- description?: string;
881
- /** Grid width of the section on the main 12-column layout. */
882
- span?: DetailSpan;
883
- /** Internal grid column count for child items. */
884
- columns?: number;
885
- /** When true, allows editors to collapse and expand the section. */
886
- collapsible?: boolean;
887
- /** Starts the section in collapsed state when collapsible is enabled. */
888
- collapsedByDefault?: boolean;
889
- /**
890
- * Visibility condition for this section.
891
- * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
892
- *
893
- * @example `visible: "user.roles != null and 'admin' in user.roles"`
894
- */
895
- visible?: string | boolean;
1100
+ group?: string;
1101
+ href?: string;
1102
+ badge?: NavBadgeConfig | string;
1103
+ access?: string[] | AccessRule;
1104
+ order: number;
1105
+ collection?: string;
1106
+ global?: string;
1107
+ views: ViewConfig[];
896
1108
  }
897
1109
  /**
898
- * Section container grouping fields and components in a Detail View.
1110
+ * Runtime compiled representation of a navigation group containing items.
899
1111
  */
900
- interface DetailSection {
901
- /** Identifies this item as a section. */
902
- type: "section";
903
- /** Section heading title. */
904
- title: string;
905
- /** Child items rendered inside this section. */
906
- items: DetailItem[];
907
- /** Section container options. */
908
- options?: DetailSectionOptions;
1112
+ interface CompiledNavGroup {
1113
+ id: string;
1114
+ name: string;
1115
+ slug: string;
1116
+ icon?: string;
1117
+ defaultExpanded?: boolean;
1118
+ order: number;
1119
+ items: CompiledNavItem[];
909
1120
  }
910
1121
  /**
911
- * Configuration options for an individual tab in `displayTabs()`.
1122
+ * Complete runtime compiled navigation tree.
912
1123
  */
913
- interface DetailTabOptions {
914
- /** Lucide icon name displayed inside the tab trigger. */
1124
+ interface CompiledNavTree {
1125
+ groups: CompiledNavGroup[];
1126
+ ungrouped: CompiledNavItem[];
1127
+ }
1128
+ interface PinnedNavItemRef {
1129
+ type: "workspace" | "collection" | "global" | "link" | "dashboard";
1130
+ slug: string;
1131
+ view?: string;
1132
+ label?: string;
915
1133
  icon?: string;
916
- /** Counter or status badge rendered next to the tab label. */
917
- badge?: string;
918
- /** Color palette name or hex code for the tab badge. */
919
- badgeColor?: string;
920
- /**
921
- * Visibility condition for this individual tab.
922
- * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
923
- */
924
- visible?: string | boolean;
925
1134
  }
926
- /**
927
- * Single tab entry inside a tabbed container.
928
- */
929
- interface DetailTab {
930
- /** Identifies this item as a tab. */
931
- type: "tab";
932
- /** Tab label shown in the tab bar. */
933
- label: string;
934
- /** Child items rendered when this tab is active. */
935
- items: DetailItem[];
936
- /** Tab options. */
937
- options?: DetailTabOptions;
1135
+ interface UserNavigationPreferences {
1136
+ _version: number;
1137
+ pinned?: PinnedNavItemRef[];
1138
+ hidden?: string[];
1139
+ groupOrder?: string[];
1140
+ itemOrder?: Record<string, string[]>;
1141
+ groups?: NavGroup[];
1142
+ items?: DefineWorkspaceOptions[];
1143
+ }
1144
+ declare const CURRENT_NAV_PREFERENCES_VERSION = 1;
1145
+ declare const DEFAULT_USER_NAV_PREFERENCES: UserNavigationPreferences;
1146
+
1147
+ /** A valid icon component name from the Lucide icon set bundled with Dyrected Admin. */
1148
+ type AdminIconName = keyof typeof icons;
1149
+ /** Ordered custom component keys rendered around the built-in Admin dashboard. */
1150
+ interface AdminDashboardComponentSlots {
1151
+ beforeDashboard?: string[];
1152
+ afterDashboard?: string[];
938
1153
  }
939
- /**
940
- * Configuration options for a tabbed container.
941
- */
942
- interface DetailTabsOptions {
943
- /** Grid width of the tabs container on the main layout. */
944
- span?: DetailSpan;
945
- /** Label of the tab that should be active on initial render. */
946
- defaultTab?: string;
947
- /**
948
- * Visibility condition for this tab container.
949
- * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
950
- */
951
- visible?: string | boolean;
1154
+ /** Ordered custom component keys rendered around an operational view's content. */
1155
+ interface CollectionViewComponentSlots {
1156
+ beforeViewHeader?: string[];
1157
+ afterViewHeader?: string[];
1158
+ beforeViewContent?: string[];
1159
+ afterViewContent?: string[];
952
1160
  }
953
- /**
954
- * Tabbed navigation container in a Detail View layout.
955
- */
956
- interface DetailTabs {
957
- /** Identifies this item as a tab container. */
958
- type: "tabs";
959
- /** List of tabs in this container. */
960
- tabs: DetailTab[];
961
- /** Tab container options. */
962
- options?: DetailTabsOptions;
1161
+ /** Ordered custom component keys rendered around a collection's list content. */
1162
+ interface CollectionListComponentSlots {
1163
+ beforeList?: string[];
1164
+ beforeListTable?: string[];
1165
+ afterListTable?: string[];
1166
+ afterList?: string[];
1167
+ beforeViewHeader?: string[];
1168
+ afterViewHeader?: string[];
1169
+ beforeViewContent?: string[];
1170
+ afterViewContent?: string[];
1171
+ collectionView?: CollectionViewComponentSlots;
963
1172
  }
964
1173
  /**
965
- * Configuration options for a sub-grid container.
1174
+ * Branding and metadata options for the Dyrected Admin UI.
1175
+ *
1176
+ * @example
1177
+ * admin: {
1178
+ * branding: {
1179
+ * logo: '/logo.svg',
1180
+ * primaryColor: '#6366f1',
1181
+ * },
1182
+ * meta: {
1183
+ * titleSuffix: '- My App',
1184
+ * },
1185
+ * }
966
1186
  */
967
- interface DetailGridOptions {
968
- /** Grid width of the sub-grid container in the surrounding layout. */
969
- span?: DetailSpan;
1187
+ interface AdminConfig {
1188
+ /**
1189
+ * Custom navigation layout for the admin sidebar.
1190
+ * If omitted, all collections and globals appear in default sections.
1191
+ */
1192
+ navigation?: DefineWorkspaceOptions[];
1193
+ /** Custom component slots around the built-in dashboard. */
1194
+ components?: AdminDashboardComponentSlots;
1195
+ branding?: {
1196
+ /** Full logo image shown in the expanded sidebar. URL or imported image asset. */
1197
+ logo?: string;
1198
+ /** Compact logo mark used in the collapsed sidebar state. */
1199
+ logoMark?: string;
1200
+ /** Text alternative or addition to the logo image. */
1201
+ logoText?: string;
1202
+ /**
1203
+ * Brand colour used for committed, filled actions — Save, Create, Upload —
1204
+ * and active/selected states. Accepts a hex string, a named colour
1205
+ * (`amber`, `lime`, `violet`, `green`, `blue`, `red`, `purple`, `orange`),
1206
+ * or a raw HSL triplet (`"217 91% 60%"`). Applied in both light and dark mode.
1207
+ *
1208
+ * This is one half of the two-colour brand model. Use {@link accentColor}
1209
+ * for links and navigation accents. If you only set `primaryColor`, it is
1210
+ * reused for accents too, matching the single-brand-colour look.
1211
+ * @example '#6366f1'
1212
+ * @example 'blue'
1213
+ * @example 'hsl(240 50% 60%)'
1214
+ */
1215
+ primaryColor?: string;
1216
+ /**
1217
+ * Brand colour used for links, active navigation details, hover text, and
1218
+ * focus rings — the "accent" half of the two-colour brand model, mapped to
1219
+ * the admin's `--intelligence` token. Accepts the same formats as
1220
+ * {@link primaryColor} and applies in both light and dark mode.
1221
+ *
1222
+ * Set this when your brand has a distinct link/accent colour separate from
1223
+ * your primary action colour. When omitted, accents fall back to
1224
+ * `primaryColor` (if set) or the built-in default.
1225
+ * @example '#8b5cf6'
1226
+ * @example 'violet'
1227
+ */
1228
+ accentColor?: string;
1229
+ /** Browser tab favicon URL. */
1230
+ favicon?: string;
1231
+ /** Font family for body and UI text. Must be loaded separately. */
1232
+ fontSans?: string;
1233
+ /** Font family for headings. Must be loaded separately. */
1234
+ fontSerif?: string;
1235
+ };
1236
+ meta?: {
1237
+ /**
1238
+ * String appended to every Admin page's `<title>`.
1239
+ * @default '- Dyrected'
1240
+ */
1241
+ titleSuffix?: string;
1242
+ };
970
1243
  /**
971
- * Visibility condition for this grid container.
972
- * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1244
+ * The canonical/base URL of the frontend website for links and iframe live previews.
973
1245
  */
974
- visible?: string | boolean;
975
- }
976
- /**
977
- * Multi-column sub-grid layout inside a section or tab.
978
- */
979
- interface DetailGrid {
980
- /** Identifies this item as a grid. */
981
- type: "grid";
982
- /** Number of balanced columns in this grid. */
983
- columns: number;
984
- /** Child items rendered across the grid columns. */
985
- items: DetailItem[];
986
- /** Grid container options. */
987
- options?: DetailGridOptions;
1246
+ siteUrl?: string;
988
1247
  }
1248
+
1249
+ type DeclarativeHookExpression = string;
989
1250
  /**
990
- * Configuration options for a repeated array field.
1251
+ * @deprecated Use {@link FieldBeforeChangeHook} or `FieldAfterReadHook` instead.
1252
+ * This alias remains for backwards compatibility.
991
1253
  */
992
- interface DetailRepeatOptions {
993
- /** Layout style for repeated items: 'table', 'cards', or 'list'. */
994
- layout?: "table" | "cards" | "list";
995
- /** Message shown when the repeated array is empty. */
996
- emptyText?: string;
997
- /** Grid width of the repeated container. */
998
- span?: DetailSpan;
999
- /**
1000
- * Visibility condition for this repeat container.
1001
- * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1002
- */
1003
- visible?: string | boolean;
1004
- /** Field name in each row to use as the card header title (e.g. 'title', 'key', 'name'). */
1005
- useAsTitle?: string;
1006
- /** Field name in each row to use as the card header title (alias for useAsTitle). */
1007
- titleField?: string;
1008
- /** Static card title prefix or template (e.g. 'Category' or 'Card #{index}'). */
1009
- title?: string;
1010
- /** Optional icon name for card header. */
1011
- icon?: string;
1012
- /** Number of grid columns for cards layout (1, 2, 3, or 4; defaults to 3). */
1013
- columns?: 1 | 2 | 3 | 4;
1014
- }
1254
+ type FieldHook<TDoc extends object = Record<string, unknown>, TValue = unknown> = FieldBeforeChangeHook<TValue, TDoc>;
1015
1255
  /**
1016
- * Delegated repeated array field item in a Detail View.
1256
+ * Runs before Dyrected queries the database for a list or single-document fetch.
1257
+ *
1258
+ * Return a new `where` query object to override or extend the current filter.
1259
+ * Return `undefined` (or nothing) to leave the query unchanged.
1017
1260
  */
1018
- interface DetailRepeat {
1019
- /** Identifies this item as a repeat container. */
1020
- type: "repeat";
1021
- /** Array field name in the document. */
1022
- field: string;
1023
- /** Item template schemas rendered for each element in the array. */
1024
- items: DetailItem[];
1025
- /** Repeat options. */
1026
- options?: DetailRepeatOptions;
1027
- }
1261
+ type CollectionBeforeReadHook = (args: {
1262
+ req: HookRequestContext;
1263
+ query?: Record<string, unknown>;
1264
+ user?: AuthenticatedUser;
1265
+ db: ReadonlyDatabaseAdapter;
1266
+ }) => Record<string, unknown> | void | Promise<Record<string, unknown> | void>;
1267
+ type CollectionBeforeReadHookEntry = CollectionBeforeReadHook | DeclarativeHookExpression;
1028
1268
  /**
1029
- * Async or synchronous handler function calculating a computed metric.
1269
+ * Runs after a document (or list of documents) is fetched from the database,
1270
+ * before the response is sent to the client.
1030
1271
  */
1031
- type ComputedHandler<TDoc = any> = (context: {
1272
+ type CollectionAfterReadHook<TDoc extends object = Record<string, unknown>> = (args: {
1032
1273
  doc: TDoc;
1033
- user?: any;
1034
- db?: any;
1035
- }) => any | Promise<any>;
1274
+ req: HookRequestContext;
1275
+ user?: AuthenticatedUser;
1276
+ db: ReadonlyDatabaseAdapter;
1277
+ }) => TDoc | Promise<TDoc>;
1278
+ type CollectionAfterReadHookEntry<TDoc extends object = Record<string, unknown>> = CollectionAfterReadHook<TDoc> | DeclarativeHookExpression;
1036
1279
  /**
1037
- * Configuration options for a computed value card.
1280
+ * Runs **before** a document is created or updated in the database.
1038
1281
  */
1039
- interface DetailComputedOptions<TDoc = any> {
1040
- /** Unique identifier for the computed metric. */
1041
- id?: string;
1042
- /** JEXL expression string evaluated against `{ doc, user }`. */
1043
- expression?: string;
1044
- /** Async or sync function receiving `{ doc, user, db }` returning the computed value. */
1045
- handler?: ComputedHandler<TDoc>;
1046
- /** Grid width for the computed card. */
1047
- span?: DetailSpan;
1048
- /** Formatting preset for the computed value ('currency', 'percent', 'number', 'date'). */
1049
- format?: string;
1050
- /** Currency code when format is 'currency'. */
1051
- currency?: string;
1052
- /**
1053
- * Visibility condition for this computed card.
1054
- * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1055
- */
1056
- visible?: string | boolean;
1057
- }
1282
+ type CollectionBeforeChangeHook<TDoc extends object = Record<string, unknown>> = (args: {
1283
+ data: Partial<TDoc>;
1284
+ doc?: TDoc;
1285
+ req: HookRequestContext;
1286
+ user?: AuthenticatedUser;
1287
+ operation: "create" | "update";
1288
+ db: ReadonlyDatabaseAdapter;
1289
+ }) => Partial<TDoc> | void | Promise<Partial<TDoc> | void>;
1290
+ type CollectionBeforeChangeHookEntry<TDoc extends object = Record<string, unknown>> = CollectionBeforeChangeHook<TDoc> | DeclarativeHookExpression;
1058
1291
  /**
1059
- * Computed KPI or metric card in a Detail View.
1292
+ * Runs **after** a document is created or updated in the database.
1060
1293
  */
1061
- interface DetailComputed<TDoc = any> {
1062
- /** Identifies this item as a computed value. */
1063
- type: "computed";
1064
- /** Unique computed identifier. */
1065
- id?: string;
1066
- /** Human-readable card label. */
1067
- label: string;
1068
- /** JEXL evaluation string. */
1069
- expression?: string;
1070
- /** Computed function handler. */
1071
- handler?: ComputedHandler<TDoc>;
1072
- /** Computed options. */
1073
- options?: DetailComputedOptions<TDoc>;
1074
- }
1294
+ type CollectionAfterChangeHook<TDoc extends object = Record<string, unknown>> = (args: {
1295
+ doc: TDoc;
1296
+ previousDoc?: TDoc;
1297
+ req: HookRequestContext;
1298
+ user?: AuthenticatedUser;
1299
+ operation: "create" | "update";
1300
+ db: DatabaseAdapter;
1301
+ }) => void | Promise<void>;
1075
1302
  /**
1076
- * Configuration options for a horizontal divider line.
1303
+ * Runs **before** a document is deleted from the database.
1077
1304
  */
1078
- interface DetailDividerOptions {
1079
- /** Grid span in the surrounding layout. */
1080
- span?: DetailSpan;
1081
- /**
1082
- * Visibility condition for this divider.
1083
- * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1084
- */
1085
- visible?: string | boolean;
1086
- /** Vertical margin spacing around the divider line. */
1087
- spacing?: "sm" | "md" | "lg" | "none";
1088
- }
1305
+ type CollectionBeforeDeleteHook<TDoc extends object = Record<string, unknown>> = (args: {
1306
+ id: string;
1307
+ doc: TDoc;
1308
+ req: HookRequestContext;
1309
+ user?: AuthenticatedUser;
1310
+ db: ReadonlyDatabaseAdapter;
1311
+ }) => void | Promise<void>;
1089
1312
  /**
1090
- * Horizontal divider line in a Detail View layout.
1313
+ * Runs **after** a document has been deleted from the database.
1091
1314
  */
1092
- interface DetailDivider {
1093
- /** Identifies this item as a divider. */
1094
- type: "divider";
1095
- /** Divider options. */
1096
- options?: DetailDividerOptions;
1097
- }
1315
+ type CollectionAfterDeleteHook<TDoc extends object = Record<string, unknown>> = (args: {
1316
+ id: string;
1317
+ doc: TDoc;
1318
+ req: HookRequestContext;
1319
+ user?: AuthenticatedUser;
1320
+ db: DatabaseAdapter;
1321
+ }) => void | Promise<void>;
1322
+ /** @see {@link CollectionBeforeReadHook} */
1323
+ type GlobalBeforeReadHook = CollectionBeforeReadHook;
1324
+ type GlobalBeforeReadHookEntry = CollectionBeforeReadHookEntry;
1098
1325
  /**
1099
- * Configuration options for a static text or callout block.
1326
+ * Runs after the global document is fetched, before the response is sent.
1100
1327
  */
1101
- interface DetailTextOptions {
1102
- /** Grid width in the 12-column layout. */
1103
- span?: DetailSpan;
1104
- /**
1105
- * Visibility condition for this text block.
1106
- * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1107
- */
1108
- visible?: string | boolean;
1109
- /** Typography and alert styling variant ('body', 'heading', 'subheading', 'muted', 'caption', 'callout', 'info', 'warning'). */
1110
- variant?: "body" | "heading" | "subheading" | "muted" | "caption" | "callout" | "info" | "warning";
1111
- /** Custom CSS or Tailwind classes applied to the text wrapper. */
1112
- className?: string;
1113
- }
1328
+ type GlobalAfterReadHook<TDoc extends object = Record<string, unknown>> = (args: {
1329
+ doc: TDoc;
1330
+ req: HookRequestContext;
1331
+ user?: AuthenticatedUser;
1332
+ db: ReadonlyDatabaseAdapter;
1333
+ }) => TDoc | Promise<TDoc>;
1334
+ type GlobalAfterReadHookEntry<TDoc extends object = Record<string, unknown>> = GlobalAfterReadHook<TDoc> | DeclarativeHookExpression;
1114
1335
  /**
1115
- * Static text, heading, callout, or notice block in a Detail View.
1336
+ * Runs before the global document is updated.
1337
+ * Operation is always `'update'` (globals cannot be created or deleted).
1116
1338
  */
1117
- interface DetailText {
1118
- /** Identifies this item as a text block. */
1119
- type: "text";
1120
- /** Text or markdown content. */
1121
- content: string;
1122
- /** Text options. */
1123
- options?: DetailTextOptions;
1124
- }
1339
+ type GlobalBeforeChangeHook<TDoc extends object = Record<string, unknown>> = (args: {
1340
+ data: Partial<TDoc>;
1341
+ doc?: TDoc;
1342
+ req: HookRequestContext;
1343
+ user?: AuthenticatedUser;
1344
+ operation: "update";
1345
+ db: ReadonlyDatabaseAdapter;
1346
+ }) => Partial<TDoc> | void | Promise<Partial<TDoc> | void>;
1347
+ type GlobalBeforeChangeHookEntry<TDoc extends object = Record<string, unknown>> = GlobalBeforeChangeHook<TDoc> | DeclarativeHookExpression;
1125
1348
  /**
1126
- * Configuration options for a custom React component slot.
1349
+ * Runs after the global document is updated. Side-effects only.
1127
1350
  */
1128
- interface DetailCustomOptions<TDoc = any> {
1129
- /** Grid span in the 12-column layout. */
1130
- span?: DetailSpan;
1131
- /**
1132
- * Visibility condition for this custom component.
1133
- * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1134
- */
1135
- visible?: string | boolean;
1136
- /** Static or initial props passed into the custom component. */
1137
- props?: Record<string, any>;
1138
- /** Inline render function receiving `{ doc, user }` (Self-hosted React runtimes). */
1139
- render?: (context: {
1140
- doc: TDoc;
1141
- user?: any;
1142
- [key: string]: any;
1143
- }) => any;
1144
- }
1351
+ type GlobalAfterChangeHook<TDoc extends object = Record<string, unknown>> = (args: {
1352
+ doc: TDoc;
1353
+ previousDoc?: TDoc;
1354
+ req: HookRequestContext;
1355
+ user?: AuthenticatedUser;
1356
+ operation: "update";
1357
+ db: DatabaseAdapter;
1358
+ }) => void | Promise<void>;
1145
1359
  /**
1146
- * Custom React component slot mounted in a Detail View.
1360
+ * @deprecated Use the specific hook types instead:
1361
+ * `CollectionBeforeChangeHook`, `CollectionAfterReadHook`, etc.
1362
+ *
1363
+ * This broad type remains for backwards compatibility with the internal hook runner.
1147
1364
  */
1148
- interface DetailCustom<TDoc = any> {
1149
- /** Identifies this item as a custom component. */
1150
- type: "custom";
1151
- /** Registered custom component name. */
1365
+ type HookFunction<TDoc extends object = Record<string, unknown>> = (args: {
1366
+ data?: Partial<TDoc>;
1367
+ doc?: TDoc;
1368
+ user?: AuthenticatedUser;
1369
+ req?: HookRequestContext;
1370
+ operation?: "create" | "update" | "delete";
1371
+ db?: DatabaseAdapter;
1372
+ [key: string]: unknown;
1373
+ }) => unknown | Promise<unknown>;
1374
+
1375
+ declare const LIFECYCLE_EVENT_NAMES: readonly ["revision.created", "workflow.transitioned", "entry.published", "entry.unpublished"];
1376
+ type LifecycleEventName = (typeof LIFECYCLE_EVENT_NAMES)[number];
1377
+ interface WorkflowState {
1378
+ /** Stable machine-readable state key. */
1152
1379
  name: string;
1153
- /** Custom component options. */
1154
- options?: DetailCustomOptions<TDoc>;
1380
+ /** Label rendered in the Admin UI. */
1381
+ label: string;
1382
+ /** Marks the state whose revision is visible to public readers. */
1383
+ published?: boolean;
1384
+ /** Optional visual tone used by the Admin UI. */
1385
+ color?: "neutral" | "warning" | "success" | "danger" | "info";
1155
1386
  }
1156
- /**
1157
- * Configuration options for rendering an action button in a Detail View.
1158
- */
1159
- interface DetailActionOptions {
1160
- /** Grid column span across the 12-column grid. */
1161
- span?: DetailSpan;
1162
- /** Button visual style. Defaults to `'outline'`. */
1163
- variant?: "default" | "outline" | "secondary" | "ghost" | "destructive";
1164
- /** Button size. Defaults to `'sm'`. */
1165
- size?: "sm" | "default";
1166
- /** Overrides the action's own `label` for this placement. */
1167
- label?: string;
1387
+ interface WorkflowTransition {
1388
+ /** Stable transition key used by the REST and SDK APIs. */
1389
+ name: string;
1390
+ label: string;
1168
1391
  /**
1169
- * Visibility condition for this action item.
1170
- * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1392
+ * Source state(s) this transition originates from.
1393
+ * Must match names in the workflow's `states` array.
1171
1394
  */
1172
- visible?: string | boolean;
1395
+ from: string | string[];
1396
+ /**
1397
+ * Target state this transition leads to.
1398
+ * Must match a name in the workflow's `states` array.
1399
+ */
1400
+ to: string;
1401
+ /** Every listed capability is required. */
1402
+ requiredCapabilities?: string[];
1403
+ /** Require a non-empty comment when performing the transition. */
1404
+ requireComment?: boolean;
1405
+ /** Remove the public snapshot after this transition commits. */
1406
+ unpublish?: boolean;
1173
1407
  }
1174
- /**
1175
- * Renders a button for a named action from `collection.actions` at this
1176
- * position in a Detail View. The same action (and its confirm dialog, modal
1177
- * fields, and execution) can also render automatically in the Detail View's
1178
- * header toolbar when the action's `type` is `'header'` — placing it here
1179
- * too does not duplicate that config, it just adds another entry point to
1180
- * the same action.
1181
- */
1182
- interface DetailAction {
1183
- /** Identifies this item as an action button. */
1184
- type: "action";
1185
- /** Name of the action, matching an entry in `collection.actions`. */
1186
- name: string;
1187
- /** Display and visibility options for this placement. */
1188
- options?: DetailActionOptions;
1408
+ interface WorkflowRole {
1409
+ /** Existing user role value, for example `editor` or `publisher`. */
1410
+ role: string;
1411
+ capabilities: string[];
1189
1412
  }
1190
1413
  /**
1191
- * Union of all valid item types in a Detail View schema.
1192
- */
1193
- type DetailItem<TDoc = any> = DetailSection | DetailTabs | DetailGrid | DetailField | DetailRepeat | DetailComputed<TDoc> | DetailDivider | DetailText | DetailCustom<TDoc> | DetailAction | string;
1194
- /**
1195
- * Detail View layout schema definition for a collection or global.
1414
+ * Configuration for a collection's editorial workflow: the states a document
1415
+ * can be in, the transitions between them, and the roles allowed to perform
1416
+ * each. Attach it to a collection's `workflow`, or build one with
1417
+ * `definePublishingWorkflow`.
1196
1418
  */
1197
- type DetailSchema<TDoc = any> = DetailItem<TDoc>[];
1419
+ interface WorkflowConfig<TDoc extends object = Record<string, unknown>> {
1420
+ /** The state every new document starts in, e.g. `"draft"`. */
1421
+ initialState: string;
1422
+ /** State used for a new working revision created from published content. */
1423
+ draftState?: string;
1424
+ /** All states a document can occupy. Exactly one should be marked `published`. */
1425
+ states: WorkflowState[];
1426
+ /** The allowed moves between states, each with its own capability and comment rules. */
1427
+ transitions: WorkflowTransition[];
1428
+ /** Maps values in `user.roles` to workflow capabilities. */
1429
+ roles?: WorkflowRole[];
1430
+ /** Server-side hooks that run around every transition. */
1431
+ hooks?: {
1432
+ /** Runs before a transition commits — throw to validate or block the move. */
1433
+ beforeTransition?: CollectionBeforeTransitionHook<TDoc>[];
1434
+ /** Runs after a transition commits — trigger notifications or downstream work. */
1435
+ afterTransition?: CollectionAfterTransitionHook<TDoc>[];
1436
+ };
1437
+ }
1438
+ interface WorkflowMetadata {
1439
+ /** The document's current workflow state, e.g. `"draft"` or `"published"`. */
1440
+ state: string;
1441
+ /** Revision counter, incremented on every committed transition. */
1442
+ revision: number;
1443
+ /** Revision number currently exposed as the public snapshot, once published. */
1444
+ publishedRevision?: number;
1445
+ /** ISO timestamp of the most recent publish. */
1446
+ publishedAt?: string;
1447
+ /** Id of the user who last published this document. */
1448
+ publishedBy?: string;
1449
+ /** Transitions currently allowed for the requesting user. Response-only. */
1450
+ availableTransitions?: string[];
1451
+ }
1452
+ interface WorkflowTransitionContext<TDoc extends object = Record<string, unknown>> {
1453
+ /** The transition being performed. */
1454
+ transition: WorkflowTransition;
1455
+ /** State the document is moving out of. */
1456
+ from: string;
1457
+ /** State the document is moving into. */
1458
+ to: string;
1459
+ /** The document as it stands before the transition commits. */
1460
+ doc: TDoc;
1461
+ /** The user performing the transition, when the request is authenticated. */
1462
+ user?: AuthenticatedUser;
1463
+ /** Comment supplied with the transition; required when `requireComment` is set. */
1464
+ comment?: string;
1465
+ /** Request context for the transition. */
1466
+ req: HookRequestContext;
1467
+ /** Transaction-scoped database adapter for reads and writes inside the hook. */
1468
+ db: DatabaseAdapter;
1469
+ }
1470
+ type CollectionBeforeTransitionHook<TDoc extends object = Record<string, unknown>> = (args: WorkflowTransitionContext<TDoc>) => void | Promise<void>;
1471
+ type CollectionAfterTransitionHook<TDoc extends object = Record<string, unknown>> = (args: WorkflowTransitionContext<TDoc> & {
1472
+ event: LifecycleEvent;
1473
+ }) => void | Promise<void>;
1474
+ interface LifecycleEvent<TPayload = Record<string, unknown>> {
1475
+ /** Unique id for this event. */
1476
+ id: string;
1477
+ /** The lifecycle event name, e.g. `"workflow.transitioned"` or `"entry.published"`. */
1478
+ name: LifecycleEventName;
1479
+ /** Slug of the collection the event is about. */
1480
+ collection: string;
1481
+ /** Id of the document the event is about. */
1482
+ documentId: string;
1483
+ /** ISO timestamp of when the event occurred. */
1484
+ occurredAt: string;
1485
+ /** Id of the user who triggered the event, when known. */
1486
+ actorId?: string;
1487
+ /** Event-specific data, such as the transition name and affected revision. */
1488
+ payload: TPayload;
1489
+ /** Number of delivery attempts made so far. */
1490
+ attempts: number;
1491
+ /** Current delivery status as the dispatcher works through the queue. */
1492
+ status: "pending" | "processing" | "delivered" | "failed";
1493
+ /** ISO timestamp of the next delivery retry, while pending. */
1494
+ nextAttemptAt?: string;
1495
+ /** ISO timestamp of successful delivery. */
1496
+ deliveredAt?: string;
1497
+ /** Message from the most recent failed delivery attempt. */
1498
+ lastError?: string;
1499
+ }
1500
+ type LifecycleEventHandler = (event: LifecycleEvent) => void | Promise<void>;
1198
1501
 
1502
+ /** Column span in the 12-column Detail View layout grid (1 to 12). */
1503
+ type DetailSpan = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
1199
1504
  /**
1200
- * The set of type-cast operations supported during aggregation.
1201
- *
1202
- * Each adapter maps these to its own native conversion primitive
1203
- * (e.g. `$toDouble` in MongoDB, `CAST(x AS REAL)` in SQLite).
1204
- * The public API is always database-independent.
1505
+ * Visual display variants supported by DetailFieldRenderer.
1205
1506
  */
1206
- type AggregateCastType = "number" | "integer" | "float" | "string" | "boolean" | "date";
1507
+ type DisplayVariant = "text" | "badge" | "code-badge" | "code" | "copyable" | "link" | "url" | "email" | "phone" | "currency" | "percent" | "progress" | "star" | "star-rating" | "boolean" | "date" | "datetime" | "time" | "relative" | "image" | "avatar" | "color" | "color-swatches" | "icon" | "key-value" | "table" | "tags" | "badges" | "json";
1207
1508
  /**
1208
- * A `count` aggregate operation.
1209
- *
1210
- * - `count: "*"` — count every document matched by `where`.
1211
- * - `cast` is intentionally absent: counting is document-level,
1212
- * not field-value-level, so type conversion has no meaning here.
1509
+ * Configuration options for rendering a field in a Detail View.
1213
1510
  */
1214
- interface CountOperation {
1215
- count: "*";
1216
- where?: Record<string, unknown>;
1511
+ interface DisplayFieldOptions {
1512
+ /** Custom label overriding the field's schema label. */
1513
+ label?: string;
1514
+ /** When true, hides the field label entirely and renders only the value. */
1515
+ hideLabel?: boolean;
1516
+ /** Informational tooltip shown next to the label. */
1517
+ tooltip?: string;
1518
+ /** Grid column span across the 12-column grid. */
1519
+ span?: DetailSpan;
1520
+ /** Specific visual representation variant (e.g. 'badge', 'currency', 'star', 'copyable'). */
1521
+ display?: DisplayVariant;
1522
+ /** Preset format for numbers, dates, and currencies ('currency', 'date', 'datetime', 'relative', 'number', 'percent'). */
1523
+ format?: "currency" | "date" | "datetime" | "relative" | "number" | "percent" | string;
1524
+ /** ISO currency code (e.g. 'USD', 'EUR') when display or format is 'currency'. */
1525
+ currency?: string;
1526
+ /** Color mapping for badge and tag values (named palette, hex color, Tailwind class, or wildcard). */
1527
+ badgeColors?: Record<string, string>;
1528
+ /** Column header for object keys when display is 'key-value'. */
1529
+ keyLabel?: string;
1530
+ /** Column header for object values when display is 'key-value'. */
1531
+ valueLabel?: string;
1532
+ /** Custom placeholder text when the field value is null, undefined, or empty. */
1533
+ emptyText?: string;
1534
+ /** When true, hides this entire field item if the value is null or empty. */
1535
+ hideIfEmpty?: boolean;
1536
+ /**
1537
+ * When true, allows inline editing of this field in the Detail View
1538
+ * via an interactive toggle.
1539
+ */
1540
+ editable?: boolean;
1541
+ /**
1542
+ * Visibility condition for this field item.
1543
+ * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1544
+ * When false or evaluating to falsy, this item is hidden in the Detail View.
1545
+ *
1546
+ * @example `visible: "doc.status == 'published'"`
1547
+ * @example `visible: false`
1548
+ */
1549
+ visible?: string | boolean;
1550
+ /** Specific aspect ratio constraint for media (e.g., "16/9", "4/3", "1/1", "auto"). */
1551
+ aspectRatio?: string;
1552
+ /** CSS object-fit behavior for media ('cover', 'contain', 'fill', 'scale-down'). */
1553
+ objectFit?: "cover" | "contain" | "fill" | "scale-down";
1554
+ /** Media alignment ('left', 'center', 'right'). */
1555
+ align?: "left" | "center" | "right";
1556
+ /** Fixed pixel width or CSS dimension for media preview. */
1557
+ width?: string | number;
1558
+ /** Fixed pixel height or CSS dimension for media preview. */
1559
+ height?: string | number;
1217
1560
  }
1218
1561
  /**
1219
- * A `countDistinct` aggregate operation that counts the number of unique
1220
- * non-null values for a specific field.
1562
+ * Declared field item in a Detail View.
1221
1563
  */
1222
- interface DistinctCountOperation {
1223
- countDistinct: string;
1224
- where?: Record<string, unknown>;
1564
+ interface DetailField {
1565
+ /** Identifies this item as a field. */
1566
+ type: "field";
1567
+ /** Field name or nested path in the document. */
1568
+ field: string;
1569
+ /** Display options for this field. */
1570
+ options?: DisplayFieldOptions;
1225
1571
  }
1226
1572
  /**
1227
- * A `distinct` aggregate operation that retrieves the unique values
1228
- * (or unique values with item counts) for a specific field.
1573
+ * Configuration options for a Detail View section container.
1229
1574
  */
1230
- interface DistinctValuesOperation {
1231
- distinct: string;
1232
- where?: Record<string, unknown>;
1575
+ interface DetailSectionOptions {
1576
+ /** Lucide icon name displayed in the section header. */
1577
+ icon?: string;
1578
+ /** Status or counter badge displayed beside the section title. */
1579
+ badge?: string;
1580
+ /** Color palette name or hex code for the section badge. */
1581
+ badgeColor?: string;
1582
+ /** Subtitle description displayed beneath the section title. */
1583
+ description?: string;
1584
+ /** Grid width of the section on the main 12-column layout. */
1585
+ span?: DetailSpan;
1586
+ /** Internal grid column count for child items. */
1587
+ columns?: number;
1588
+ /** When true, allows editors to collapse and expand the section. */
1589
+ collapsible?: boolean;
1590
+ /** Starts the section in collapsed state when collapsible is enabled. */
1591
+ collapsedByDefault?: boolean;
1592
+ /**
1593
+ * Visibility condition for this section.
1594
+ * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1595
+ *
1596
+ * @example `visible: "user.roles != null and 'admin' in user.roles"`
1597
+ */
1598
+ visible?: string | boolean;
1233
1599
  }
1234
1600
  /**
1235
- * A `sum`, `avg`, `min`, or `max` aggregate operation on a named field.
1236
- *
1237
- * - `cast` converts stored values before the aggregation runs.
1238
- * Invalid values (e.g. `"unknown"` cast to `number`) become `null`
1239
- * and are ignored by `sum`, `avg`, `min`, and `max`.
1601
+ * Section container grouping fields and components in a Detail View.
1240
1602
  */
1241
- interface NumericOperation {
1242
- sum?: string;
1243
- avg?: string;
1244
- min?: string;
1245
- max?: string;
1246
- cast?: AggregateCastType;
1247
- where?: Record<string, unknown>;
1603
+ interface DetailSection {
1604
+ /** Identifies this item as a section. */
1605
+ type: "section";
1606
+ /** Section heading title. */
1607
+ title: string;
1608
+ /** Child items rendered inside this section. */
1609
+ items: DetailItem[];
1610
+ /** Section container options. */
1611
+ options?: DetailSectionOptions;
1248
1612
  }
1249
1613
  /**
1250
- * A single named aggregate request — either a count, distinct count, distinct values, or numeric operation.
1251
- */
1252
- type AggregateOperation = CountOperation | DistinctCountOperation | DistinctValuesOperation | NumericOperation;
1253
- /**
1254
- * The map of named aggregate operations sent in a single aggregate request.
1255
- *
1256
- * @example
1257
- * ```ts
1258
- * {
1259
- * totalSubmitted: { count: "*" },
1260
- * uniqueGuests: { countDistinct: "email" },
1261
- * availableSizes: { distinct: "asoebiSize" },
1262
- * totalAsoebiYards: { sum: "asoebiYards", cast: "number", where: { wantsAsoebi: { equals: true } } },
1263
- * }
1264
- * ```
1265
- */
1266
- type AggregateInput = Record<string, AggregateOperation>;
1267
- /**
1268
- * The arguments passed to `DatabaseAdapter.aggregate`.
1614
+ * Configuration options for an individual tab in `displayTabs()`.
1269
1615
  */
1270
- interface AggregateArgs {
1271
- /** Collection slug. */
1272
- collection: string;
1273
- /** Named aggregate operations to compute. */
1274
- aggregates: AggregateInput;
1275
- /** Optional field to group the aggregates by. */
1276
- groupBy?: string;
1616
+ interface DetailTabOptions {
1617
+ /** Lucide icon name displayed inside the tab trigger. */
1618
+ icon?: string;
1619
+ /** Counter or status badge rendered next to the tab label. */
1620
+ badge?: string;
1621
+ /** Color palette name or hex code for the tab badge. */
1622
+ badgeColor?: string;
1623
+ /**
1624
+ * Visibility condition for this individual tab.
1625
+ * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1626
+ */
1627
+ visible?: string | boolean;
1277
1628
  }
1278
1629
  /**
1279
- * The result returned by `DatabaseAdapter.aggregate`.
1280
- *
1281
- * For scalar aggregates, every named key maps to a `number | null` or `any[]` (for distinct).
1282
- * When `groupBy` is used, returns a breakdown per group.
1283
- */
1284
- type AggregateResult = Record<string, any>;
1285
- /**
1286
- * Derives the typed result shape from an `AggregateInput`.
1287
- */
1288
- type InferAggregateResult<T extends AggregateInput> = {
1289
- [K in keyof T]: any;
1290
- };
1291
-
1292
- /**
1293
- * Dyrected Where Clause DSL — shared query language across all database adapters.
1294
- *
1295
- * Adapters translate this DSL into their native format:
1296
- * - SQL adapters (SQLite, Postgres, MySQL): use parseSqlWhere()
1297
- * - MongoDB adapter: use parseMongoWhere()
1298
- * - Future adapters: implement their own translator against WhereClause
1299
- *
1300
- * Exhaustive operator handling is enforced at compile time via `assertNever`,
1301
- * so adding a new operator without handling it will produce a TypeScript error
1302
- * in every adapter translator.
1630
+ * Single tab entry inside a tabbed container.
1303
1631
  */
1304
- type WhereOperatorName = 'equals' | 'not_equals' | 'in' | 'not_in' | 'gt' | 'greater_than' | 'gte' | 'greater_than_equal' | 'greater_than_or_equal' | 'lt' | 'less_than' | 'lte' | 'less_than_equal' | 'less_than_or_equal' | 'contains' | 'like' | 'starts_with' | 'exists';
1305
- type WhereOperator = {
1306
- equals: any;
1307
- } | {
1308
- not_equals: any;
1309
- } | {
1310
- in: any[];
1311
- } | {
1312
- not_in: any[];
1313
- } | {
1314
- gt: any;
1315
- } | {
1316
- greater_than: any;
1317
- } | {
1318
- gte: any;
1319
- } | {
1320
- greater_than_equal: any;
1321
- } | {
1322
- greater_than_or_equal: any;
1323
- } | {
1324
- lt: any;
1325
- } | {
1326
- less_than: any;
1327
- } | {
1328
- lte: any;
1329
- } | {
1330
- less_than_equal: any;
1331
- } | {
1332
- less_than_or_equal: any;
1333
- } | {
1334
- contains: string;
1335
- } | {
1336
- like: string;
1337
- } | {
1338
- starts_with: string;
1339
- } | {
1340
- exists: boolean;
1341
- };
1342
- /** Top-level where clause. Fields map to operator objects or shorthand scalar values. */
1343
- type WhereClause = {
1344
- [field: string]: WhereOperator | any;
1345
- OR?: WhereClause[] | readonly WhereClause[];
1346
- or?: WhereClause[] | readonly WhereClause[];
1347
- AND?: WhereClause[] | readonly WhereClause[];
1348
- and?: WhereClause[] | readonly WhereClause[];
1349
- };
1350
- interface SqlWhereResult {
1351
- sql: string;
1352
- params: any[];
1632
+ interface DetailTab {
1633
+ /** Identifies this item as a tab. */
1634
+ type: "tab";
1635
+ /** Tab label shown in the tab bar. */
1636
+ label: string;
1637
+ /** Child items rendered when this tab is active. */
1638
+ items: DetailItem[];
1639
+ /** Tab options. */
1640
+ options?: DetailTabOptions;
1353
1641
  }
1354
1642
  /**
1355
- * Translates a WhereClause into a parameterized SQL WHERE fragment.
1356
- *
1357
- * @param where The Dyrected where DSL object
1358
- * @param getJsonField Returns the SQL expression for a JSON-stored field (dialect-specific):
1359
- * SQLite: (f) => `json_extract(data, '$.${f}')`
1360
- * Postgres: (f) => `data->>'${f}'`
1361
- * MySQL: (f) => `JSON_UNQUOTE(JSON_EXTRACT(data, '$.${f}'))`
1362
- * @param placeholder '?' for SQLite/MySQL, 'pg' for auto-incrementing Postgres $1/$2/…
1363
- */
1364
- declare function parseSqlWhere(where: WhereClause, getJsonField: (field: string) => string, placeholder?: '?' | 'pg',
1365
- /**
1366
- * SQL keyword used for `contains`/`starts_with`. Postgres `LIKE` is
1367
- * case-sensitive, so the Postgres adapter defaults to `'ILIKE'` to keep substring
1368
- * matching case-insensitive like SQLite and MySQL.
1643
+ * Configuration options for a tabbed container.
1369
1644
  */
1370
- likeOperator?: 'LIKE' | 'ILIKE'): SqlWhereResult;
1645
+ interface DetailTabsOptions {
1646
+ /** Grid width of the tabs container on the main layout. */
1647
+ span?: DetailSpan;
1648
+ /** Label of the tab that should be active on initial render. */
1649
+ defaultTab?: string;
1650
+ /**
1651
+ * Visibility condition for this tab container.
1652
+ * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1653
+ */
1654
+ visible?: string | boolean;
1655
+ }
1371
1656
  /**
1372
- * Translates a WhereClause into a MongoDB filter object.
1373
- * Handles nested OR/AND via $or/$and, and all operators via $eq/$in/$gt etc.
1657
+ * Tabbed navigation container in a Detail View layout.
1374
1658
  */
1375
- declare function parseMongoWhere(where: WhereClause): Record<string, any>;
1376
-
1659
+ interface DetailTabs {
1660
+ /** Identifies this item as a tab container. */
1661
+ type: "tabs";
1662
+ /** List of tabs in this container. */
1663
+ tabs: DetailTab[];
1664
+ /** Tab container options. */
1665
+ options?: DetailTabsOptions;
1666
+ }
1377
1667
  /**
1378
- * Custom component slots rendered around an operational view's content.
1668
+ * Configuration options for a sub-grid container.
1379
1669
  */
1380
- type ViewComponentSlots = CollectionViewComponentSlots;
1670
+ interface DetailGridOptions {
1671
+ /** Grid width of the sub-grid container in the surrounding layout. */
1672
+ span?: DetailSpan;
1673
+ /**
1674
+ * Visibility condition for this grid container.
1675
+ * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1676
+ */
1677
+ visible?: string | boolean;
1678
+ }
1381
1679
  /**
1382
- * Supported layout engines for operational views in the Dyrected Admin UI:
1383
- * - `'table'`: The workhorse — column sorting, search, faceted filters, and bulk actions.
1384
- * - `'spreadsheet'`: Inline-editable matrix with batch saving (`Save changes` / `Discard`).
1385
- * - `'kanban'`: Interactive card board grouped by status or category with drag-and-drop.
1386
- * - `'calendar'`: Event schedule grid plotting records across month/week/day views by date.
1387
- * - `'gantt'`: Timeline bars plotted across start and end dates.
1388
- * - `'cards'`: Visual image/thumbnail gallery grid.
1680
+ * Multi-column sub-grid layout inside a section or tab.
1389
1681
  */
1390
- type ViewLayout = 'table' | 'spreadsheet' | 'kanban' | 'calendar' | 'gantt' | 'cards';
1682
+ interface DetailGrid {
1683
+ /** Identifies this item as a grid. */
1684
+ type: "grid";
1685
+ /** Number of balanced columns in this grid. */
1686
+ columns: number;
1687
+ /** Child items rendered across the grid columns. */
1688
+ items: DetailItem[];
1689
+ /** Grid container options. */
1690
+ options?: DetailGridOptions;
1691
+ }
1391
1692
  /**
1392
- * Scope and placement for an operational view action:
1393
- * - `'row'`: Renders as an inline action button on each document/row.
1394
- * - `'bulk'`: Appears in the floating bulk action bar when one or more rows are selected.
1395
- * - `'header'`: Renders as a primary action button in the view header.
1693
+ * Configuration options for a repeated array field.
1396
1694
  */
1397
- type ActionType = 'row' | 'bulk' | 'header';
1695
+ interface DetailRepeatOptions {
1696
+ /** Layout style for repeated items: 'table', 'cards', or 'list'. */
1697
+ layout?: "table" | "cards" | "list";
1698
+ /** Message shown when the repeated array is empty. */
1699
+ emptyText?: string;
1700
+ /** Grid width of the repeated container. */
1701
+ span?: DetailSpan;
1702
+ /**
1703
+ * Visibility condition for this repeat container.
1704
+ * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1705
+ */
1706
+ visible?: string | boolean;
1707
+ /** Field name in each row to use as the card header title (e.g. 'title', 'key', 'name'). */
1708
+ useAsTitle?: string;
1709
+ /** Field name in each row to use as the card header title (alias for useAsTitle). */
1710
+ titleField?: string;
1711
+ /** Static card title prefix or template (e.g. 'Category' or 'Card #{index}'). */
1712
+ title?: string;
1713
+ /** Optional icon name for card header. */
1714
+ icon?: string;
1715
+ /** Number of grid columns for cards layout (1, 2, 3, or 4; defaults to 3). */
1716
+ columns?: 1 | 2 | 3 | 4;
1717
+ }
1398
1718
  /**
1399
- * Toggles for the built-in document operations an operational view surfaces.
1400
- * All default to enabled (`true`). Set any to `false` to hide that operation.
1401
- *
1402
- * @example
1403
- * ```ts
1404
- * features: {
1405
- * delete: false, // Prevent deleting records from this view
1406
- * exportSelected: true, // Enable CSV export for checked rows
1407
- * }
1408
- * ```
1719
+ * Delegated repeated array field item in a Detail View.
1409
1720
  */
1410
- interface ViewActionFeatures {
1411
- /** Enables the "View" preview / read-only drawer button. Defaults to `true`. */
1412
- view?: boolean;
1413
- /** Enables the "Edit" document form link. Defaults to `true`. */
1414
- edit?: boolean;
1415
- /** Enables the "Duplicate" record action. Defaults to `true`. */
1416
- duplicate?: boolean;
1417
- /** Enables the "Delete" record action. Defaults to `true`. */
1418
- delete?: boolean;
1419
- /** Enables the "Export selected" CSV action in the bulk action bar. Defaults to `true`. */
1420
- exportSelected?: boolean;
1721
+ interface DetailRepeat {
1722
+ /** Identifies this item as a repeat container. */
1723
+ type: "repeat";
1724
+ /** Array field name in the document. */
1725
+ field: string;
1726
+ /** Item template schemas rendered for each element in the array. */
1727
+ items: DetailItem[];
1728
+ /** Repeat options. */
1729
+ options?: DetailRepeatOptions;
1421
1730
  }
1422
1731
  /**
1423
- * Color palette options for metric cards:
1424
- * `'purple' | 'emerald' | 'amber' | 'rose' | 'blue' | 'indigo' | 'cyan' | 'orange'`
1732
+ * Async or synchronous handler function calculating a computed metric.
1425
1733
  */
1426
- type MetricColor = 'purple' | 'emerald' | 'amber' | 'rose' | 'blue' | 'indigo' | 'cyan' | 'orange' | string;
1734
+ type ComputedHandler<TDoc = any> = (context: {
1735
+ doc: TDoc;
1736
+ user?: any;
1737
+ db?: any;
1738
+ }) => any | Promise<any>;
1427
1739
  /**
1428
- * A sub-metric breakdown item displayed in the compact footer of a metric card.
1429
- *
1430
- * @example
1431
- * ```ts
1432
- * {
1433
- * label: "Pending",
1434
- * aggregates: {
1435
- * checkedIn: { count: "*", where: { checkedIn: { equals: true } } },
1436
- * attending: { count: "*", where: { attending: { equals: true } } },
1437
- * },
1438
- * expression: "aggregates.attending - aggregates.checkedIn",
1439
- * }
1440
- * ```
1740
+ * Configuration options for a computed value card.
1441
1741
  */
1442
- interface ViewSubMetric {
1443
- /** Short label for the sub-metric (e.g. "Pending", "Collected"). */
1444
- label: string;
1445
- /** Single database aggregate operation. */
1446
- aggregate?: AggregateOperation;
1447
- /** Map of named aggregate operations referenced in `expression` as `aggregates.key`. */
1448
- aggregates?: Record<string, AggregateOperation>;
1449
- /** JEXL math formula evaluated over `value` for a single aggregate (e.g. `"value * 100"`). */
1450
- transform?: string;
1451
- /** JEXL math formula evaluated over `aggregates` (e.g. `"aggregates.a - aggregates.b"`). */
1742
+ interface DetailComputedOptions<TDoc = any> {
1743
+ /** Unique identifier for the computed metric. */
1744
+ id?: string;
1745
+ /** JEXL expression string evaluated against `{ doc, user }`. */
1452
1746
  expression?: string;
1453
- /** Display formatter: `'currency' | 'number' | 'percent'`. */
1454
- format?: 'currency' | 'number' | 'percent' | string;
1455
- /** 3-letter currency code (e.g. `"USD"`, `"NGN"`, `"EUR"`) used when `format: "currency"`. */
1747
+ /** Async or sync function receiving `{ doc, user, db }` returning the computed value. */
1748
+ handler?: ComputedHandler<TDoc>;
1749
+ /** Grid width for the computed card. */
1750
+ span?: DetailSpan;
1751
+ /** Formatting preset for the computed value ('currency', 'percent', 'number', 'date'). */
1752
+ format?: string;
1753
+ /** Currency code when format is 'currency'. */
1456
1754
  currency?: string;
1755
+ /**
1756
+ * Visibility condition for this computed card.
1757
+ * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1758
+ */
1759
+ visible?: string | boolean;
1457
1760
  }
1458
1761
  /**
1459
- * Configuration for a KPI summary card rendered at the top of an operational view.
1460
- *
1461
- * All aggregations run directly on the database engine for maximum performance.
1462
- *
1463
- * @example
1464
- * ```ts
1465
- * {
1466
- * label: "Total Revenue",
1467
- * color: "emerald",
1468
- * aggregate: { sum: "amount", cast: "number", where: { status: "paid" } },
1469
- * format: "currency",
1470
- * currency: "USD",
1471
- * }
1472
- * ```
1762
+ * Computed KPI or metric card in a Detail View.
1473
1763
  */
1474
- interface ViewMetric {
1475
- /** Title displayed on the metric card header. */
1764
+ interface DetailComputed<TDoc = any> {
1765
+ /** Identifies this item as a computed value. */
1766
+ type: "computed";
1767
+ /** Unique computed identifier. */
1768
+ id?: string;
1769
+ /** Human-readable card label. */
1476
1770
  label: string;
1477
- /** Accent color for the card's visual indicator. */
1478
- color?: MetricColor;
1479
- /** Unit badge shown alongside the numeric value (e.g. `"Guests"`, `"Orders"`, `"Tables"`). */
1480
- unit?: string;
1481
- /** Single database aggregate operation (`count`, `distinctCount`, `sum`, `avg`, `min`, `max`). */
1482
- aggregate?: AggregateOperation;
1483
- /** Map of named aggregate operations referenced in `expression` as `aggregates.key`. */
1484
- aggregates?: Record<string, AggregateOperation>;
1485
- /** JEXL math formula evaluated over `value` for a single aggregate (e.g. `"value * 25000"`). */
1486
- transform?: string;
1487
- /** JEXL math formula evaluated over `aggregates` (e.g. `"(aggregates.paid / aggregates.total) * 100"`). */
1771
+ /** JEXL evaluation string. */
1488
1772
  expression?: string;
1489
- /** Display formatter: `'currency' | 'number' | 'percent'`. */
1490
- format?: 'currency' | 'number' | 'percent' | string;
1491
- /** 3-letter currency code (e.g. `"USD"`, `"NGN"`, `"EUR"`) used when `format: "currency"`. */
1492
- currency?: string;
1493
- /** Optional secondary breakdown items shown in the card footer. */
1494
- subMetrics?: ViewSubMetric[];
1773
+ /** Computed function handler. */
1774
+ handler?: ComputedHandler<TDoc>;
1775
+ /** Computed options. */
1776
+ options?: DetailComputedOptions<TDoc>;
1495
1777
  }
1496
1778
  /**
1497
- * Complete configuration for a tailored operational view.
1779
+ * Configuration options for a horizontal divider line.
1498
1780
  */
1499
- interface ViewConfig {
1500
- /** Stable URL slug for the view (`/collections/:slug/views/:viewSlug`). */
1501
- slug: string;
1502
- /** Human-readable title displayed in the sidebar navigation and view header. */
1503
- label: string;
1504
- /** Lucide icon name (e.g. `"UserCheck"`, `"Calendar"`, `"Shirt"`, `"TableProperties"`). */
1505
- icon?: string;
1506
- /** Layout engine to render: `'table' | 'spreadsheet' | 'kanban' | 'calendar' | 'gantt' | 'cards'`. Defaults to `'table'`. */
1507
- layout?: ViewLayout;
1508
- /** Base query filter applied before user toolbar filters (e.g. `{ attending: { equals: true } }` or JEXL string). */
1509
- filter?: WhereClause | string;
1510
- /** Field name used to organize records into kanban columns or collapsible grouped sections in table, cards, and spreadsheet. */
1511
- groupBy?: string;
1512
- /** Field name containing the ISO date string for calendar placement. Required when `layout: 'calendar'`. */
1513
- dateField?: string;
1514
- /** Field name for the start date in timeline/gantt views. Required when `layout: 'gantt'`. */
1515
- startDateField?: string;
1516
- /** Field name for the end date in timeline/gantt views. Required when `layout: 'gantt'`. */
1517
- endDateField?: string;
1518
- /** Field names to show in this view, in order. If omitted, Dyrected infers default display fields. */
1519
- columns?: string[];
1520
- /** Default sorting rule when entering the view. */
1521
- sort?: {
1522
- field: string;
1523
- direction: 'asc' | 'desc';
1524
- };
1525
- /** Custom workflow actions available in this view. */
1526
- actions?: ActionConfig[];
1527
- /** Toggles built-in operations (view/edit/duplicate/delete/export). */
1528
- features?: ViewActionFeatures;
1781
+ interface DetailDividerOptions {
1782
+ /** Grid span in the surrounding layout. */
1783
+ span?: DetailSpan;
1529
1784
  /**
1530
- * Explicit display order for action buttons — mixing custom action names and built-in names
1531
- * (`"view"`, `"edit"`, `"duplicate"`, `"delete"`). Unlisted actions append in default order.
1785
+ * Visibility condition for this divider.
1786
+ * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1532
1787
  */
1533
- actionOrder?: string[];
1534
- /** KPI summary cards rendered in the hero row above the view. */
1535
- metrics?: ViewMetric[];
1536
- /** Role-based access rules controlling who can see or use this view. */
1537
- access?: AccessConfig;
1788
+ visible?: string | boolean;
1789
+ /** Vertical margin spacing around the divider line. */
1790
+ spacing?: "sm" | "md" | "lg" | "none";
1538
1791
  }
1539
1792
  /**
1540
- * Base configuration shared by all action types.
1793
+ * Horizontal divider line in a Detail View layout.
1541
1794
  */
1542
- interface ActionConfigBase<TDoc extends Record<string, unknown> = Record<string, unknown>> {
1543
- /** Unique action identifier (e.g. `"checkIn"`, `"markPaid"`). */
1544
- name: string;
1545
- /** Button label displayed in the UI. */
1546
- label: string;
1547
- /** Optional custom label for the modal submit button (defaults to "Run" or "Confirm"). */
1548
- submitLabel?: string;
1549
- /** Lucide icon name for the button (e.g. `"UserCheck"`, `"CheckCircle"`). */
1550
- icon?: string;
1551
- /** Action placement: `'row'` (per-row), `'bulk'` (multi-row selection bar), or `'header'` (view header). Defaults to `'row'`. */
1552
- type?: ActionType;
1553
- /** Optional confirmation prompt shown before executing (e.g. `"Confirm guest check-in at the door?"`). */
1554
- confirm?: string;
1555
- /** Optional interactive modal form fields prompting the user for input before executing. */
1556
- fields?: Field[];
1557
- /** Role-based access rules controlling who can trigger this action. */
1558
- access?: AccessConfig;
1795
+ interface DetailDivider {
1796
+ /** Identifies this item as a divider. */
1797
+ type: "divider";
1798
+ /** Divider options. */
1799
+ options?: DetailDividerOptions;
1559
1800
  }
1560
1801
  /**
1561
- * An operational workflow action with declarative data mutation.
1802
+ * Configuration options for a static text or callout block.
1562
1803
  */
1563
- interface ActionConfigWithMutation<TDoc extends Record<string, unknown> = Record<string, unknown>> extends ActionConfigBase<TDoc> {
1564
- /** Declarative database mutation applied to targeted documents (e.g. `{ checkedIn: true, checkedInAt: "now()" }`). */
1565
- mutation: Partial<TDoc> | Record<string, unknown>;
1566
- /** Optional server handler for additional logic. */
1567
- handler?: (context: ActionContext<TDoc>) => Promise<unknown> | unknown;
1804
+ interface DetailTextOptions {
1805
+ /** Grid width in the 12-column layout. */
1806
+ span?: DetailSpan;
1807
+ /**
1808
+ * Visibility condition for this text block.
1809
+ * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1810
+ */
1811
+ visible?: string | boolean;
1812
+ /** Typography and alert styling variant ('body', 'heading', 'subheading', 'muted', 'caption', 'callout', 'info', 'warning'). */
1813
+ variant?: "body" | "heading" | "subheading" | "muted" | "caption" | "callout" | "info" | "warning";
1814
+ /** Custom CSS or Tailwind classes applied to the text wrapper. */
1815
+ className?: string;
1568
1816
  }
1569
1817
  /**
1570
- * An operational workflow action with server handler.
1818
+ * Static text, heading, callout, or notice block in a Detail View.
1571
1819
  */
1572
- interface ActionConfigWithHandler<TDoc extends Record<string, unknown> = Record<string, unknown>> extends ActionConfigBase<TDoc> {
1573
- /** Self-hosted server handler function executed when the action is triggered (required when mutation is not provided). */
1574
- handler: (context: ActionContext<TDoc>) => Promise<unknown> | unknown;
1575
- /** Optional declarative mutation. */
1576
- mutation?: Partial<TDoc> | Record<string, unknown>;
1820
+ interface DetailText {
1821
+ /** Identifies this item as a text block. */
1822
+ type: "text";
1823
+ /** Text or markdown content. */
1824
+ content: string;
1825
+ /** Text options. */
1826
+ options?: DetailTextOptions;
1577
1827
  }
1578
1828
  /**
1579
- * An operational workflow action that can mutate data or trigger server logic.
1580
- * Requires either `mutation` or `handler` (or both).
1581
- */
1582
- type ActionConfig<TDoc extends Record<string, unknown> = Record<string, unknown>> = ActionConfigWithMutation<TDoc> | ActionConfigWithHandler<TDoc>;
1583
- /**
1584
- * Context passed to an action's server handler function.
1829
+ * Configuration options for a custom React component slot.
1585
1830
  */
1586
- interface ActionContext<TDoc extends Record<string, unknown> = Record<string, unknown>> {
1587
- /** The target document (for row actions), or null for header actions. */
1588
- doc: TDoc | null;
1589
- /** All targeted documents (for bulk actions). Empty array for header actions. */
1590
- docs: TDoc[];
1591
- /** The authenticated acting user, if available. */
1592
- user: Record<string, unknown> | null;
1593
- /** User inputs submitted via the action's modal form fields. */
1594
- input: Record<string, unknown>;
1595
- /** The collection configuration object. */
1596
- collection: Record<string, unknown>;
1831
+ interface DetailCustomOptions<TDoc = any> {
1832
+ /** Grid span in the 12-column layout. */
1833
+ span?: DetailSpan;
1834
+ /**
1835
+ * Visibility condition for this custom component.
1836
+ * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1837
+ */
1838
+ visible?: string | boolean;
1839
+ /** Static or initial props passed into the custom component. */
1840
+ props?: Record<string, any>;
1841
+ /** Inline render function receiving `{ doc, user }` (Self-hosted React runtimes). */
1842
+ render?: (context: {
1843
+ doc: TDoc;
1844
+ user?: any;
1845
+ [key: string]: any;
1846
+ }) => any;
1597
1847
  }
1598
1848
  /**
1599
- * Role-based access control rules for a view or action.
1849
+ * Custom React component slot mounted in a Detail View.
1600
1850
  */
1601
- interface AccessConfig {
1602
- read?: AccessRule<any>;
1603
- create?: AccessRule<any>;
1604
- update?: AccessRule<any>;
1605
- delete?: AccessRule<any>;
1851
+ interface DetailCustom<TDoc = any> {
1852
+ /** Identifies this item as a custom component. */
1853
+ type: "custom";
1854
+ /** Registered custom component name. */
1855
+ name: string;
1856
+ /** Custom component options. */
1857
+ options?: DetailCustomOptions<TDoc>;
1606
1858
  }
1607
1859
  /**
1608
- * Base options shared by all operational view types.
1860
+ * Configuration options for rendering an action button in a Detail View.
1609
1861
  */
1610
- interface DefineViewBaseOptions {
1611
- /** Stable URL slug for the view (`/collections/:slug/views/:viewSlug`). */
1612
- slug: string;
1613
- /** Human-readable title displayed in the sidebar navigation and view header. */
1614
- label: string;
1615
- /** Lucide icon name (e.g. `"UserCheck"`, `"Calendar"`, `"Shirt"`, `"TableProperties"`). */
1616
- icon?: string;
1617
- /** Base query filter applied before user toolbar filters (e.g. `{ attending: { equals: true } }` or JEXL string). */
1618
- filter?: Record<string, any> | string;
1619
- /** Field names to show in this view, in order. If omitted, Dyrected infers default display fields. */
1620
- columns?: string[];
1621
- /** Default sorting rule when entering the view. */
1622
- sort?: {
1623
- field: string;
1624
- direction: 'asc' | 'desc';
1625
- };
1626
- /** Custom workflow actions available in this view. */
1627
- actions?: ActionConfig[];
1628
- /** Toggles built-in operations (view/edit/duplicate/delete/export). */
1629
- features?: ViewActionFeatures;
1862
+ interface DetailActionOptions {
1863
+ /** Grid column span across the 12-column grid. */
1864
+ span?: DetailSpan;
1865
+ /** Button visual style. Defaults to `'outline'`. */
1866
+ variant?: "default" | "outline" | "secondary" | "ghost" | "destructive";
1867
+ /** Button size. Defaults to `'sm'`. */
1868
+ size?: "sm" | "default";
1869
+ /** Overrides the action's own `label` for this placement. */
1870
+ label?: string;
1630
1871
  /**
1631
- * Explicit display order for action buttons — mixing custom action names and built-in names
1632
- * (`"view"`, `"edit"`, `"duplicate"`, `"delete"`). Unlisted actions append in default order.
1872
+ * Visibility condition for this action item.
1873
+ * Can be a boolean or a JEXL expression evaluated against `{ doc, user }`.
1633
1874
  */
1634
- actionOrder?: string[];
1635
- /** KPI summary cards rendered in the hero row above the view. */
1636
- metrics?: ViewMetric[];
1637
- /** Custom component slots rendered around this operational view. */
1638
- components?: ViewComponentSlots;
1639
- /** Role-based access rules controlling who can see or use this view. */
1640
- access?: AccessConfig;
1641
- }
1642
- /** Table view — requires no special fields. */
1643
- interface DefineTableViewOptions extends DefineViewBaseOptions {
1644
- layout?: 'table' | 'spreadsheet';
1645
- /** Field name used to organize records into kanban columns or collapsible grouped sections in table, cards, and spreadsheet. */
1646
- groupBy?: string;
1647
- }
1648
- /** Kanban view — requires `groupBy` to organize into columns. */
1649
- interface DefineKanbanViewOptions extends DefineViewBaseOptions {
1650
- layout: 'kanban';
1651
- /** Field name to organize records into kanban columns. */
1652
- groupBy: string;
1653
- }
1654
- /** Calendar view — requires `dateField` to place records on the calendar. */
1655
- interface DefineCalendarViewOptions extends DefineViewBaseOptions {
1656
- layout: 'calendar';
1657
- /** Field name containing the ISO date string for calendar placement. */
1658
- dateField: string;
1659
- }
1660
- /** Gantt view — requires both `startDateField` and `endDateField`. */
1661
- interface DefineGanttViewOptions extends DefineViewBaseOptions {
1662
- layout: 'gantt';
1663
- /** Field name for the start date in the gantt timeline. */
1664
- startDateField: string;
1665
- /** Field name for the end date in the gantt timeline. */
1666
- endDateField: string;
1667
- }
1668
- /** Cards view — shows records as image/thumbnail gallery. */
1669
- interface DefineCardsViewOptions extends DefineViewBaseOptions {
1670
- layout: 'cards';
1671
- /** Field name used to organize records into groups. */
1672
- groupBy?: string;
1875
+ visible?: string | boolean;
1673
1876
  }
1674
1877
  /**
1675
- * Options for defining an operational view with `defineView`.
1676
- * Use layout-specific overloads to ensure required fields are provided.
1677
- *
1678
- * @example
1679
- * ```ts
1680
- * export const attendingGuests = defineView({
1681
- * slug: "attending-guests",
1682
- * label: "Attending Guests",
1683
- * icon: "UserCheck",
1684
- * layout: "table",
1685
- * groupBy: "tableNumber",
1686
- * filter: { attending: { equals: true } },
1687
- * columns: ["name", "email", "guestCount", "checkedIn"],
1688
- * sort: { field: "name", direction: "asc" },
1689
- * });
1690
- * ```
1691
- */
1692
- type DefineViewOptions = DefineTableViewOptions | DefineKanbanViewOptions | DefineCalendarViewOptions | DefineGanttViewOptions | DefineCardsViewOptions;
1693
- /**
1694
- * Options for defining a custom workflow action with `defineAction`.
1695
- *
1696
- * @example
1697
- * ```ts
1698
- * export const checkInAction = defineAction({
1699
- * name: "checkIn",
1700
- * label: "Check In",
1701
- * icon: "UserCheck",
1702
- * type: "row",
1703
- * confirm: "Confirm guest check-in at the door?",
1704
- * mutation: { checkedIn: true, checkedInAt: "now()" },
1705
- * });
1706
- * ```
1878
+ * Renders a button for a named action from `collection.actions` at this
1879
+ * position in a Detail View. The same action (and its confirm dialog, modal
1880
+ * fields, and execution) can also render automatically in the Detail View's
1881
+ * header toolbar when the action's `type` is `'header'` — placing it here
1882
+ * too does not duplicate that config, it just adds another entry point to
1883
+ * the same action.
1707
1884
  */
1708
- interface DefineActionOptions<TDoc extends Record<string, unknown> = Record<string, unknown>> {
1709
- /** Unique action identifier (e.g. `"checkIn"`, `"markPaid"`). */
1885
+ interface DetailAction {
1886
+ /** Identifies this item as an action button. */
1887
+ type: "action";
1888
+ /** Name of the action, matching an entry in `collection.actions`. */
1710
1889
  name: string;
1711
- /** Button label displayed in the UI. */
1712
- label: string;
1713
- /** Lucide icon name for the button (e.g. `"UserCheck"`, `"CheckCircle"`). */
1714
- icon?: string;
1715
- /** Action placement: `'row'` (per-row), `'bulk'` (multi-row selection bar), or `'header'` (view header). Defaults to `'row'`. */
1716
- type?: ActionType;
1717
- /** Optional confirmation prompt shown before executing (e.g. `"Confirm guest check-in at the door?"`). */
1718
- confirm?: string;
1719
- /** Optional interactive modal form fields prompting the user for input before executing. */
1720
- fields?: Field[];
1721
- /** Declarative database mutation applied to targeted documents (e.g. `{ checkedIn: true, checkedInAt: "now()" }`). */
1722
- mutation?: Partial<TDoc> | Record<string, unknown>;
1723
- /** Self-hosted server handler function executed when the action is triggered. */
1724
- handler?: (context: ActionContext<TDoc>) => Promise<unknown> | unknown;
1725
- /** Role-based access rules controlling who can trigger this action. */
1726
- access?: AccessConfig;
1890
+ /** Display and visibility options for this placement. */
1891
+ options?: DetailActionOptions;
1727
1892
  }
1728
1893
  /**
1729
- * Defines a typed operational view for a collection.
1730
- * Layout-specific overloads enforce required fields for each view type.
1731
- *
1732
- * @example
1733
- * ```ts
1734
- * // Table view (no special requirements)
1735
- * export const allGuests = defineView({
1736
- * slug: "all-guests",
1737
- * label: "All Guests",
1738
- * layout: "table", // or omit for default
1739
- * });
1740
- *
1741
- * // Kanban view (requires groupBy)
1742
- * export const guestsByTable = defineView({
1743
- * slug: "by-table",
1744
- * label: "Guests by Table",
1745
- * layout: "kanban",
1746
- * groupBy: "tableNumber", // required!
1747
- * });
1748
- *
1749
- * // Calendar view (requires dateField)
1750
- * export const eventCalendar = defineView({
1751
- * slug: "event-calendar",
1752
- * label: "Event Calendar",
1753
- * layout: "calendar",
1754
- * dateField: "eventDate", // required!
1755
- * });
1756
- *
1757
- * // Gantt view (requires startDateField and endDateField)
1758
- * export const projectTimeline = defineView({
1759
- * slug: "timeline",
1760
- * label: "Project Timeline",
1761
- * layout: "gantt",
1762
- * startDateField: "startDate", // required!
1763
- * endDateField: "endDate", // required!
1764
- * });
1765
- * ```
1894
+ * Union of all valid item types in a Detail View schema.
1766
1895
  */
1767
- declare function defineView(config: DefineTableViewOptions): DefineTableViewOptions;
1768
- declare function defineView(config: DefineKanbanViewOptions): DefineKanbanViewOptions;
1769
- declare function defineView(config: DefineCalendarViewOptions): DefineCalendarViewOptions;
1770
- declare function defineView(config: DefineGanttViewOptions): DefineGanttViewOptions;
1771
- declare function defineView(config: DefineCardsViewOptions): DefineCardsViewOptions;
1772
- declare function defineView<const T extends DefineViewOptions>(config: T): T;
1773
- /** Convenience helper for table views. Enforces no view-specific required fields. */
1774
- declare function defineTableView(config: DefineTableViewOptions): DefineTableViewOptions;
1775
- /** Convenience helper for kanban views. Enforces `groupBy` is provided. */
1776
- declare function defineKanbanView(config: DefineKanbanViewOptions): DefineKanbanViewOptions;
1777
- /** Convenience helper for calendar views. Enforces `dateField` is provided. */
1778
- declare function defineCalendarView(config: DefineCalendarViewOptions): DefineCalendarViewOptions;
1779
- /** Convenience helper for gantt views. Enforces `startDateField` and `endDateField` are provided. */
1780
- declare function defineGanttView(config: DefineGanttViewOptions): DefineGanttViewOptions;
1781
- /** Convenience helper for cards views. Optional `groupBy`. */
1782
- declare function defineCardsView(config: DefineCardsViewOptions): DefineCardsViewOptions;
1896
+ type DetailItem<TDoc = any> = DetailSection | DetailTabs | DetailGrid | DetailField | DetailRepeat | DetailComputed<TDoc> | DetailDivider | DetailText | DetailCustom<TDoc> | DetailAction | string;
1783
1897
  /**
1784
- * Defines a custom workflow action for an operational view.
1785
- *
1786
- * @example
1787
- * ```ts
1788
- * export const markPaidAction = defineAction({
1789
- * name: "markPaid",
1790
- * label: "Mark Paid",
1791
- * icon: "DollarSign",
1792
- * type: "bulk",
1793
- * confirm: "Mark selected orders as paid?",
1794
- * mutation: { status: "paid" },
1795
- * });
1796
- * ```
1898
+ * Detail View layout schema definition for a collection or global.
1797
1899
  */
1798
- declare function defineAction<const T extends DefineActionOptions>(config: T): T;
1900
+ type DetailSchema<TDoc = any> = DetailItem<TDoc>[];
1799
1901
 
1800
1902
  /**
1801
1903
  * Configures account lockout behavior for an auth-enabled collection.
@@ -2162,6 +2264,10 @@ interface CollectionConfig<TDoc extends object = Record<string, unknown>> {
2162
2264
  * Collections with the same `group` are visually grouped together.
2163
2265
  */
2164
2266
  group?: string;
2267
+ /**
2268
+ * Numeric sort order within navigation or sidebar group (lower numbers appear first).
2269
+ */
2270
+ order?: number;
2165
2271
  /**
2166
2272
  * Slug of the view to render by default when navigating to `/collections/:slug`.
2167
2273
  * If not specified, defaults to the view with `default: true` or the all-records table view.
@@ -2425,6 +2531,10 @@ interface GlobalConfig<TDoc extends object = Record<string, unknown>> {
2425
2531
  icon?: AdminIconName;
2426
2532
  /** Groups this global under a named section in the Admin sidebar. */
2427
2533
  group?: string;
2534
+ /**
2535
+ * Numeric sort order within navigation or sidebar group (lower numbers appear first).
2536
+ */
2537
+ order?: number;
2428
2538
  /** If `true`, this global is not shown in the Admin UI sidebar. */
2429
2539
  hidden?: boolean;
2430
2540
  };
@@ -3954,4 +4064,4 @@ interface DyrectedContext {
3954
4064
  */
3955
4065
  declare function createDyrectedApp(rawConfig: DyrectedConfig | any): Promise<Hono<DyrectedContext, hono_types.BlankSchema, "/">>;
3956
4066
 
3957
- export { type ImageField as $, type AdminAuthConfig as A, type Block as B, type CollectionConfig as C, type DyrectedConfig as D, type DetailTabs as E, type Field as F, type GlobalConfig as G, type HookRequestContext as H, type DetailTextOptions as I, type DetailText as J, type DetailSchema as K, type LifecycleEventName as L, type DyrectedContext as M, type AIConfig as N, type AIPIIConfig as O, type PublicAdminAuthConfig as P, type IdStrategy as Q, type ArrayField as R, type FieldType as S, type BooleanField as T, type AuthConfig as U, type AccessPolicyResolver as V, type WorkflowConfig as W, type DateField as X, type DateTimeField as Y, type EmailField as Z, type IconField as _, type DeclarativeHookExpression as a, type BlocksField as a$, type JoinField as a0, type JsonField as a1, type MultiSelectField as a2, type NumberField as a3, type ObjectField as a4, type RadioField as a5, type RelationshipField as a6, type RichTextField as a7, type RowField as a8, type SelectField as a9, type AccessRule as aA, type ActionConfig as aB, type ActionConfigBase as aC, type ActionConfigWithHandler as aD, type ActionConfigWithMutation as aE, type ActionContext as aF, type ActionType as aG, type AdminAuthAccessResolution as aH, type AdminAuthClaimMapping as aI, type AdminAuthMember as aJ, type AdminAuthProvider as aK, type AdminAuthProviderMembersConfig as aL, type AdminAuthProvisioningMode as aM, type AdminAuthResolveAccessArgs as aN, type AdminAuthResolvedIdentity as aO, type AdminConfig as aP, type AdminDashboardComponentSlots as aQ, type AdminIconName as aR, type AggregateArgs as aS, type AggregateCastType as aT, type AggregateCollectionResult as aU, type AggregateInput as aV, type AggregateOperation as aW, type AggregateResult as aX, type BaseAdminAuthProvider as aY, type BaseFieldAdmin as aZ, type BlockVariant as a_, type TextField as aa, type TextareaField as ab, type TimeField as ac, type UrlField as ad, type AIAction as ae, type AIActionStatus as af, type AIActionType as ag, type AIAuditActionType as ah, type AIAuditRecord as ai, type AIChunk as aj, type AIMessage as ak, type AIPIIPattern as al, type AIRateLimitResult as am, type AIRateLimitStore as an, type AIThread as ao, type AIToolContext as ap, type AIToolDefinition as aq, AI_ACTIONS_COLLECTION as ar, AI_AUDIT_COLLECTION as as, AI_CHUNKS_COLLECTION as at, AI_MESSAGES_COLLECTION as au, AI_THREADS_COLLECTION as av, type AccessConfig as aw, type AccessFunction as ax, type AccessFunctionArgs as ay, type AccessResult as az, type AuthenticatedUser as b, type GlobalAfterChangeHook as b$, type BooleanFieldAdmin as b0, type BooleanFormat as b1, type CharacterLimitFieldAdmin as b2, type CharacterLimitFieldConfig as b3, type CollectionAfterChangeHook as b4, type CollectionAfterDeleteHook as b5, type CollectionAfterReadHook as b6, type CollectionAfterReadHookEntry as b7, type CollectionAfterTransitionHook as b8, type CollectionBeforeChangeHook as b9, type DisplayTone as bA, type DisplayVariant as bB, type DistinctCountOperation as bC, type DistinctValuesOperation as bD, type DynamicOptionItem as bE, type DynamicOptionsConfig as bF, type DynamicOptionsResolver as bG, type DynamicOptionsResolverArgs as bH, type DyrectedAIContext as bI, type DyrectedLoggerConfig as bJ, type DyrectedObservabilityConfig as bK, type EmailFieldAdmin as bL, type FieldAdminHooks as bM, type FieldAdminOnChangeHook as bN, type FieldAdminOnChangeHookArgs as bO, type FieldAdminOptionsHook as bP, type FieldAdminOptionsHookArgs as bQ, type FieldAdminOptionsHookResult as bR, type FieldAfterReadHook as bS, type FieldAfterReadHookArgs as bT, type FieldBase as bU, type FieldBeforeChangeHook as bV, type FieldBeforeChangeHookArgs as bW, type FieldHook as bX, type FieldHooks as bY, type FileData as bZ, type GetDocumentResult as b_, type CollectionBeforeChangeHookEntry as ba, type CollectionBeforeDeleteHook as bb, type CollectionBeforeReadHook as bc, type CollectionBeforeReadHookEntry as bd, type CollectionBeforeTransitionHook as be, type CollectionIndex as bf, type CollectionListComponentSlots as bg, type CollectionRAGConfig as bh, type CollectionSchemaResult as bi, type CollectionSummaryResult as bj, type CollectionViewComponentSlots as bk, type CountOperation as bl, type CustomAdminAuthProvider as bm, type DatabaseAdapter as bn, type DateFieldAdmin as bo, type DateFormat as bp, type DefaultValueContext as bq, type DefineActionOptions as br, type DefineCalendarViewOptions as bs, type DefineCardsViewOptions as bt, type DefineGanttViewOptions as bu, type DefineKanbanViewOptions as bv, type DefineTableViewOptions as bw, type DefineViewBaseOptions as bx, type DefineViewOptions as by, type DetailSpan as bz, type WorkflowTransition as c, type WorkflowMetadata as c$, type GlobalAfterReadHook as c0, type GlobalAfterReadHookEntry as c1, type GlobalBeforeChangeHook as c2, type GlobalBeforeChangeHookEntry as c3, type GlobalBeforeReadHook as c4, type GlobalBeforeReadHookEntry as c5, type GlobalRAGConfig as c6, type GlobalSchemaResult as c7, type GlobalSummaryResult as c8, type HeadingLevel as c9, type RadioFieldAdmin as cA, type RateLimitConfig as cB, type ReadonlyDatabaseAdapter as cC, type RichTextFeature as cD, type RichTextFieldConfig as cE, type SelectFieldAdmin as cF, type SqlWhereResult as cG, type StorageAdapter as cH, type TextFieldAdmin as cI, type TextFormat as cJ, type TextareaFieldAdmin as cK, type TrustProxyConfig as cL, type TypedField as cM, type UploadConfig as cN, type UrlFieldAdmin as cO, type UrlLinkValue as cP, type ViewActionFeatures as cQ, type ViewComponentSlots as cR, type ViewConfig as cS, type ViewLayout as cT, type ViewMetric as cU, type ViewSubMetric as cV, type WhereClause as cW, type WhereOperator as cX, type WhereOperatorName as cY, type WordLimitFieldAdmin as cZ, type WordLimitFieldConfig as c_, type HookFunction as ca, type IconFieldAdmin as cb, type ImageService as cc, type ImageTransformOptions as cd, type InferAggregateResult as ce, type JoinFieldAdmin as cf, type JsonFieldAdmin as cg, type JsonFormat as ch, LIFECYCLE_EVENT_NAMES as ci, type LifecycleEventHandler as cj, type LinkFormat as ck, type MetricColor as cl, type MultiSelectFieldAdmin as cm, type NamedAccessPolicy as cn, type NumberFieldAdmin as co, type NumberFormat as cp, type NumberLimitFieldAdmin as cq, type NumberLimitFieldConfig as cr, type NumericOperation as cs, type NumericUpdateOperator as ct, type OIDCAdminAuthProvider as cu, type OptionFormat as cv, type PaginatedResult as cw, type PublicAdminAuthProvider as cx, type QueryCollectionResult as cy, type RAGSearchResult as cz, type LifecycleEvent as d, type WorkflowRole as d0, type WorkflowState as d1, type WorkflowTransitionContext as d2, defineAction as d3, defineCalendarView as d4, defineCardsView as d5, defineGanttView as d6, defineKanbanView as d7, defineTableView as d8, defineView as d9, resolveSessionTokenExpiry as dA, shouldSampleRequest as dB, signCollectionToken as dC, verifyCollectionToken as dD, isAICollection as da, parseMongoWhere as db, parseSqlWhere as dc, type CollectionTokenPayload as dd, DEFAULT_SESSION_EXPIRY as de, type DyrectedObservabilityRuntime as df, type RequestBodyCapture as dg, type RequestTraceContext as dh, type ResolvedObservabilityConfig as di, attachRequestMetrics as dj, bindObservabilityRuntime as dk, buildRequestLogger as dl, captureRequestBody as dm, createDyrectedApp as dn, createObservabilityRuntime as dp, createRequestSpan as dq, decodeCollectionToken as dr, endRequestSpan as ds, getConfigLogger as dt, getObservabilityRuntime as du, getRequestLogger as dv, redactBody as dw, redactHeaders as dx, renderPrometheusMetrics as dy, resolveObservabilityConfig as dz, type BaseDocument as e, type DetailActionOptions as f, type DetailAction as g, type DetailComputedOptions as h, type ComputedHandler as i, type DetailComputed as j, type DetailCustomOptions as k, type DetailCustom as l, type DetailDividerOptions as m, type DetailDivider as n, type DisplayFieldOptions as o, type DetailField as p, type DetailItem as q, type DetailGridOptions as r, type DetailGrid as s, type DetailRepeatOptions as t, type DetailRepeat as u, type DetailSectionOptions as v, type DetailSection as w, type DetailTabOptions as x, type DetailTab as y, type DetailTabsOptions as z };
4067
+ export { type DateField as $, type AdminAuthConfig as A, type Block as B, type CollectionConfig as C, type DyrectedConfig as D, type DetailTabs as E, type Field as F, type GlobalConfig as G, type HookRequestContext as H, type DetailTextOptions as I, type DetailText as J, type DetailSchema as K, type LifecycleEventName as L, type DyrectedContext as M, type AIConfig as N, type AIPIIConfig as O, type PublicAdminAuthConfig as P, type IdStrategy as Q, type CompiledNavTree as R, type CompiledNavItem as S, type DefineWorkspaceOptions as T, type UserNavigationPreferences as U, type ArrayField as V, type WorkflowConfig as W, type FieldType as X, type BooleanField as Y, type AuthConfig as Z, type AccessPolicyResolver as _, type DeclarativeHookExpression as a, type AggregateResult as a$, type DateTimeField as a0, type EmailField as a1, type IconField as a2, type ImageField as a3, type JoinField as a4, type JsonField as a5, type MultiSelectField as a6, type NumberField as a7, type ObjectField as a8, type RadioField as a9, type AccessConfig as aA, type AccessFunction as aB, type AccessFunctionArgs as aC, type AccessResult as aD, type AccessRule as aE, type ActionConfig as aF, type ActionConfigBase as aG, type ActionConfigWithHandler as aH, type ActionConfigWithMutation as aI, type ActionContext as aJ, type ActionType as aK, type AdminAuthAccessResolution as aL, type AdminAuthClaimMapping as aM, type AdminAuthMember as aN, type AdminAuthProvider as aO, type AdminAuthProviderMembersConfig as aP, type AdminAuthProvisioningMode as aQ, type AdminAuthResolveAccessArgs as aR, type AdminAuthResolvedIdentity as aS, type AdminConfig as aT, type AdminDashboardComponentSlots as aU, type AdminIconName as aV, type AggregateArgs as aW, type AggregateCastType as aX, type AggregateCollectionResult as aY, type AggregateInput as aZ, type AggregateOperation as a_, type RelationshipField as aa, type RichTextField as ab, type RowField as ac, type SelectField as ad, type TextField as ae, type TextareaField as af, type TimeField as ag, type UrlField as ah, type AIAction as ai, type AIActionStatus as aj, type AIActionType as ak, type AIAuditActionType as al, type AIAuditRecord as am, type AIChunk as an, type AIMessage as ao, type AIPIIPattern as ap, type AIRateLimitResult as aq, type AIRateLimitStore as ar, type AIThread as as, type AIToolContext as at, type AIToolDefinition as au, AI_ACTIONS_COLLECTION as av, AI_AUDIT_COLLECTION as aw, AI_CHUNKS_COLLECTION as ax, AI_MESSAGES_COLLECTION as ay, AI_THREADS_COLLECTION as az, type AuthenticatedUser as b, type FieldAfterReadHook as b$, type BaseAdminAuthProvider as b0, type BaseFieldAdmin as b1, type BlockVariant as b2, type BlocksField as b3, type BooleanFieldAdmin as b4, type BooleanFormat as b5, CURRENT_NAV_PREFERENCES_VERSION as b6, type CalendarViewConfig as b7, type CardsViewConfig as b8, type CharacterLimitFieldAdmin as b9, type DefineActionOptions as bA, type DefineCalendarViewOptions as bB, type DefineCardsViewOptions as bC, type DefineGanttViewOptions as bD, type DefineKanbanViewOptions as bE, type DefineTableViewOptions as bF, type DefineViewBaseOptions as bG, type DefineViewOptions as bH, type DetailSpan as bI, type DisplayTone as bJ, type DisplayVariant as bK, type DistinctCountOperation as bL, type DistinctValuesOperation as bM, type DynamicOptionItem as bN, type DynamicOptionsConfig as bO, type DynamicOptionsResolver as bP, type DynamicOptionsResolverArgs as bQ, type DyrectedAIContext as bR, type DyrectedLoggerConfig as bS, type DyrectedObservabilityConfig as bT, type EmailFieldAdmin as bU, type FieldAdminHooks as bV, type FieldAdminOnChangeHook as bW, type FieldAdminOnChangeHookArgs as bX, type FieldAdminOptionsHook as bY, type FieldAdminOptionsHookArgs as bZ, type FieldAdminOptionsHookResult as b_, type CharacterLimitFieldConfig as ba, type CollectionAfterChangeHook as bb, type CollectionAfterDeleteHook as bc, type CollectionAfterReadHook as bd, type CollectionAfterReadHookEntry as be, type CollectionAfterTransitionHook as bf, type CollectionBeforeChangeHook as bg, type CollectionBeforeChangeHookEntry as bh, type CollectionBeforeDeleteHook as bi, type CollectionBeforeReadHook as bj, type CollectionBeforeReadHookEntry as bk, type CollectionBeforeTransitionHook as bl, type CollectionIndex as bm, type CollectionListComponentSlots as bn, type CollectionRAGConfig as bo, type CollectionSchemaResult as bp, type CollectionSummaryResult as bq, type CollectionViewComponentSlots as br, type CompiledNavGroup as bs, type CountOperation as bt, type CustomAdminAuthProvider as bu, DEFAULT_USER_NAV_PREFERENCES as bv, type DatabaseAdapter as bw, type DateFieldAdmin as bx, type DateFormat as by, type DefaultValueContext as bz, type WorkflowTransition as c, type TypedField as c$, type FieldAfterReadHookArgs as c0, type FieldBase as c1, type FieldBeforeChangeHook as c2, type FieldBeforeChangeHookArgs as c3, type FieldHook as c4, type FieldHooks as c5, type FileData as c6, type GanttViewConfig as c7, type GetDocumentResult as c8, type GlobalAfterChangeHook as c9, type NavGroup as cA, type NumberFieldAdmin as cB, type NumberFormat as cC, type NumberLimitFieldAdmin as cD, type NumberLimitFieldConfig as cE, type NumericOperation as cF, type NumericUpdateOperator as cG, type OIDCAdminAuthProvider as cH, type OptionFormat as cI, type PaginatedResult as cJ, type PinnedNavItemRef as cK, type PublicAdminAuthProvider as cL, type QueryCollectionResult as cM, type RAGSearchResult as cN, type RadioFieldAdmin as cO, type RateLimitConfig as cP, type ReadonlyDatabaseAdapter as cQ, type RichTextFeature as cR, type RichTextFieldConfig as cS, type SelectFieldAdmin as cT, type SqlWhereResult as cU, type StorageAdapter as cV, type TableViewConfig as cW, type TextFieldAdmin as cX, type TextFormat as cY, type TextareaFieldAdmin as cZ, type TrustProxyConfig as c_, type GlobalAfterReadHook as ca, type GlobalAfterReadHookEntry as cb, type GlobalBeforeChangeHook as cc, type GlobalBeforeChangeHookEntry as cd, type GlobalBeforeReadHook as ce, type GlobalBeforeReadHookEntry as cf, type GlobalRAGConfig as cg, type GlobalSchemaResult as ch, type GlobalSummaryResult as ci, type HeadingLevel as cj, type HookFunction as ck, type IconFieldAdmin as cl, type ImageService as cm, type ImageTransformOptions as cn, type InferAggregateResult as co, type JoinFieldAdmin as cp, type JsonFieldAdmin as cq, type JsonFormat as cr, type KanbanViewConfig as cs, LIFECYCLE_EVENT_NAMES as ct, type LifecycleEventHandler as cu, type LinkFormat as cv, type MetricColor as cw, type MultiSelectFieldAdmin as cx, type NamedAccessPolicy as cy, type NavBadgeConfig as cz, type LifecycleEvent as d, type UploadConfig as d0, type UrlFieldAdmin as d1, type UrlLinkValue as d2, type ViewActionFeatures as d3, type ViewComponentSlots as d4, type ViewConfig as d5, type ViewConfigBase as d6, type ViewLayout as d7, type ViewMetric as d8, type ViewSubMetric as d9, type ResolvedObservabilityConfig as dA, attachRequestMetrics as dB, bindObservabilityRuntime as dC, buildRequestLogger as dD, captureRequestBody as dE, createDyrectedApp as dF, createObservabilityRuntime as dG, createRequestSpan as dH, decodeCollectionToken as dI, endRequestSpan as dJ, getConfigLogger as dK, getObservabilityRuntime as dL, getRequestLogger as dM, redactBody as dN, redactHeaders as dO, renderPrometheusMetrics as dP, resolveObservabilityConfig as dQ, resolveSessionTokenExpiry as dR, shouldSampleRequest as dS, signCollectionToken as dT, verifyCollectionToken as dU, type WhereClause as da, type WhereOperator as db, type WhereOperatorName as dc, type WordLimitFieldAdmin as dd, type WordLimitFieldConfig as de, type WorkflowMetadata as df, type WorkflowRole as dg, type WorkflowState as dh, type WorkflowTransitionContext as di, defineAction as dj, defineCalendarView as dk, defineCardsView as dl, defineGanttView as dm, defineKanbanView as dn, defineTableView as dp, defineView as dq, defineWorkspace as dr, isAICollection as ds, parseMongoWhere as dt, parseSqlWhere as du, type CollectionTokenPayload as dv, DEFAULT_SESSION_EXPIRY as dw, type DyrectedObservabilityRuntime as dx, type RequestBodyCapture as dy, type RequestTraceContext as dz, type BaseDocument as e, type DetailActionOptions as f, type DetailAction as g, type DetailComputedOptions as h, type ComputedHandler as i, type DetailComputed as j, type DetailCustomOptions as k, type DetailCustom as l, type DetailDividerOptions as m, type DetailDivider as n, type DisplayFieldOptions as o, type DetailField as p, type DetailItem as q, type DetailGridOptions as r, type DetailGrid as s, type DetailRepeatOptions as t, type DetailRepeat as u, type DetailSectionOptions as v, type DetailSection as w, type DetailTabOptions as x, type DetailTab as y, type DetailTabsOptions as z };