@easy-cms/core 1.32.0 → 1.33.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.
@@ -93,6 +93,67 @@ declare const isSignedIn: Access;
93
93
  */
94
94
  declare const isAdmin: Access;
95
95
 
96
+ /**
97
+ * Secrets that verify: the current one first, then `previousSecrets` while one is replaced. New
98
+ * values are always signed with the current one.
99
+ */
100
+ type Secrets = string | readonly string[];
101
+ /** The secrets of a config, the current one first. */
102
+ declare function secretsOf(config: {
103
+ readonly secret: string;
104
+ readonly previousSecrets?: readonly string[] | undefined;
105
+ }): readonly string[];
106
+ /** What a preview token opens: one document of a collection, or one global. */
107
+ type PreviewTarget = {
108
+ readonly collection: string;
109
+ readonly id: string;
110
+ } | {
111
+ readonly global: string;
112
+ };
113
+ /**
114
+ * Why a link was sent: a forgotten password, an invitation to a new account, or (`verify`) to
115
+ * confirm the email of an account made by signing up.
116
+ */
117
+ type PasswordPurpose = 'reset' | 'invite' | 'verify';
118
+
119
+ declare const API_KEYS = "api-keys";
120
+ /** What a key may do with a collection. `create` on `media` means uploading. */
121
+ declare const API_KEY_OPERATIONS: readonly ["read", "create", "update", "delete", "publish"];
122
+ type ApiKeyOperation = (typeof API_KEY_OPERATIONS)[number];
123
+ /** What a key may do with a global. */
124
+ declare const API_KEY_GLOBAL_OPERATIONS: readonly ["read", "update", "publish"];
125
+ type ApiKeyGlobalOperation = (typeof API_KEY_GLOBAL_OPERATIONS)[number];
126
+ /** Stored in the key's `permissions` field. Anything not listed is not allowed. */
127
+ interface ApiKeyPermissions {
128
+ readonly collections?: Readonly<Record<string, readonly ApiKeyOperation[]>>;
129
+ readonly globals?: Readonly<Record<string, readonly ApiKeyGlobalOperation[]>>;
130
+ /**
131
+ * With `upload.folders`: the media folders (ids) the key may use, with their subfolders. Empty
132
+ * or missing: every folder.
133
+ */
134
+ readonly folders?: readonly (string | number)[];
135
+ /**
136
+ * The context the key was created in (`onRequest`, e.g. its tenant), kept for good: requests
137
+ * with the key get it as `user.apiKey.permissions.context`. Set by Easy CMS, not by input.
138
+ */
139
+ readonly context?: RequestContext;
140
+ }
141
+ /** Set on `AuthUser.apiKey` when a request authenticated with an API key. */
142
+ interface ApiKeyContext {
143
+ readonly id: number | string;
144
+ readonly name: string;
145
+ readonly permissions: ApiKeyPermissions;
146
+ }
147
+ /**
148
+ * Whether a request's API key allows an operation. Always true without a key (sessions are
149
+ * governed by access rules alone); a key only narrows what its user may do.
150
+ */
151
+ declare function keyAllows(user: AuthUser | null, target: {
152
+ collection: string;
153
+ } | {
154
+ global: string;
155
+ }, operation: ApiKeyOperation): boolean;
156
+
96
157
  /** More about a log line: `error` for what failed (its stack goes to the log). */
97
158
  interface LogDetails {
98
159
  readonly error?: unknown;
@@ -361,66 +422,155 @@ interface BundleHints {
361
422
  readonly traceInclude: readonly string[];
362
423
  }
363
424
 
364
- /**
365
- * Secrets that verify: the current one first, then `previousSecrets` while one is replaced. New
366
- * values are always signed with the current one.
367
- */
368
- type Secrets = string | readonly string[];
369
- /** The secrets of a config, the current one first. */
370
- declare function secretsOf(config: {
371
- readonly secret: string;
372
- readonly previousSecrets?: readonly string[] | undefined;
373
- }): readonly string[];
374
- /** What a preview token opens: one document of a collection, or one global. */
375
- type PreviewTarget = {
376
- readonly collection: string;
377
- readonly id: string;
378
- } | {
379
- readonly global: string;
380
- };
381
- /**
382
- * Why a link was sent: a forgotten password, an invitation to a new account, or (`verify`) to
383
- * confirm the email of an account made by signing up.
384
- */
385
- type PasswordPurpose = 'reset' | 'invite' | 'verify';
386
-
387
- declare const API_KEYS = "api-keys";
388
- /** What a key may do with a collection. `create` on `media` means uploading. */
389
- declare const API_KEY_OPERATIONS: readonly ["read", "create", "update", "delete", "publish"];
390
- type ApiKeyOperation = (typeof API_KEY_OPERATIONS)[number];
391
- /** What a key may do with a global. */
392
- declare const API_KEY_GLOBAL_OPERATIONS: readonly ["read", "update", "publish"];
393
- type ApiKeyGlobalOperation = (typeof API_KEY_GLOBAL_OPERATIONS)[number];
394
- /** Stored in the key's `permissions` field. Anything not listed is not allowed. */
395
- interface ApiKeyPermissions {
396
- readonly collections?: Readonly<Record<string, readonly ApiKeyOperation[]>>;
397
- readonly globals?: Readonly<Record<string, readonly ApiKeyGlobalOperation[]>>;
425
+ /** Audit log settings (`audit`). */
426
+ interface AuditConfig {
427
+ /** Days to keep entries; older ones are deleted. `0`: keep them all. Default 365. */
428
+ readonly keepDays?: number;
429
+ /** Keep the values before and after a change, not only which fields changed. Default `true`. */
430
+ readonly values?: boolean;
431
+ /** Failed logins within an hour that the dashboard warns about. Default 20. */
432
+ readonly failedLogins?: number;
398
433
  /**
399
- * With `upload.folders`: the media folders (ids) the key may use, with their subfolders. Empty
400
- * or missing: every folder.
434
+ * The part of the site an entry belongs to, from the request's context, e.g. its tenant
435
+ * (usually set by a plugin). Users whose role holds in a part only (`scoped`) see that part's
436
+ * entries; others see the chosen part's, or all.
401
437
  */
402
- readonly folders?: readonly (string | number)[];
438
+ readonly scope?: ((args: {
439
+ readonly context: RequestContext;
440
+ readonly user: AuthUser | null;
441
+ }) => string | null | undefined) | false;
442
+ }
443
+ /** A field that changed: values before and after, unless they are not kept. */
444
+ interface AuditChange {
445
+ field: string;
446
+ before?: unknown;
447
+ after?: unknown;
448
+ }
449
+ /** One entry (`GET <api>/admin/audit`). */
450
+ interface AuditEntry {
451
+ id: ID;
452
+ /** When, as an ISO date. */
453
+ at: string;
454
+ /** E.g. `create`, `update`, `publish`, `delete`, `login`, `login.failed`, `role.update`. */
455
+ action: string;
456
+ /** A collection slug, `global:<slug>`, or an area such as `auth`, `roles`, `backups`. */
457
+ target: string | null;
458
+ /** The document's id. */
459
+ doc: string | null;
460
+ /** The document's title then (kept after it is deleted). */
461
+ title: string | null;
462
+ actor: {
463
+ id: string | null;
464
+ email: string | null;
465
+ /** `user` (admin or REST), `api-key`, `system` (code), `scheduler`. */
466
+ via: string;
467
+ ip: string | null;
468
+ userAgent: string | null;
469
+ };
470
+ changes: AuditChange[] | null;
471
+ detail: Record<string, unknown> | null;
472
+ }
473
+ /** `GET <api>/admin/audit`: a page of entries, newest first. */
474
+ /** A page of the audit log: the list shape of the rest of the API. */
475
+ type AuditPage = PaginatedDocs<AuditEntry>;
476
+ interface AuditFilter {
477
+ action?: string | null;
478
+ target?: string | null;
479
+ doc?: string | null;
480
+ /** An email, or part of one. */
481
+ actor?: string | null;
482
+ /** ISO dates. */
483
+ from?: string | null;
484
+ to?: string | null;
485
+ /** Only entries of this part of the site (`scope`). */
486
+ scope?: string | null;
487
+ }
488
+ /** `POST <api>/admin/audit/verify`. */
489
+ interface AuditVerification {
490
+ checked: number;
491
+ /** Entries whose signature does not match: changed after they were written. */
492
+ invalid: ID[];
403
493
  /**
404
- * The context the key was created in (`onRequest`, e.g. its tenant), kept for good: requests
405
- * with the key get it as `user.apiKey.permissions.context`. Set by Easy CMS, not by input.
494
+ * Missing ids between entries: deleted entries, or (rarely) inserts that failed. Ids are never
495
+ * reused, so the newest entries deleted show up here once anything is written after them (a
496
+ * check writes an entry itself). The server's log has each check's last id, to compare with.
406
497
  */
407
- readonly context?: RequestContext;
408
- }
409
- /** Set on `AuthUser.apiKey` when a request authenticated with an API key. */
410
- interface ApiKeyContext {
411
- readonly id: number | string;
412
- readonly name: string;
413
- readonly permissions: ApiKeyPermissions;
498
+ gaps: number;
499
+ at: string;
414
500
  }
415
501
  /**
416
- * Whether a request's API key allows an operation. Always true without a key (sessions are
417
- * governed by access rules alone); a key only narrows what its user may do.
502
+ * The audit log (`audit` in the config): who did what, when, from where. Entries can't be
503
+ * changed through Easy CMS; each is signed with the secret, so changes in the database show.
504
+ * Available as `cms.audit`.
418
505
  */
419
- declare function keyAllows(user: AuthUser | null, target: {
420
- collection: string;
421
- } | {
422
- global: string;
423
- }, operation: ApiKeyOperation): boolean;
506
+ declare class AuditLog {
507
+ private readonly cms;
508
+ /** Entries that could not be written since the server started. */
509
+ failures: number;
510
+ constructor(cms: EasyCMS);
511
+ get enabled(): boolean;
512
+ private get settings();
513
+ /**
514
+ * Writes an entry. The user defaults to the request's. Never throws: a failure is logged and
515
+ * counted, and the dashboard says so.
516
+ */
517
+ record(entry: {
518
+ action: string;
519
+ target?: string | null;
520
+ doc?: ID | null;
521
+ title?: string | null;
522
+ user?: AuthUser | null;
523
+ /** Who it was when there is no user, e.g. the email of a failed login. */
524
+ email?: string | null;
525
+ changes?: AuditChange[] | null;
526
+ detail?: Record<string, unknown> | null;
527
+ /** The call's context, when it is not the request's. */
528
+ context?: RequestContext;
529
+ }): Promise<void>;
530
+ /** Entries, newest first: `page` (from 1) of `limit` (default 50, at most 100). */
531
+ list(options?: AuditFilter & {
532
+ page?: number;
533
+ limit?: number;
534
+ }): Promise<AuditPage>;
535
+ /** The filtered entries as CSV (the newest 10,000). */
536
+ csv(filter: AuditFilter): Promise<string>;
537
+ /** Checks every entry's signature, and that no ids are missing. */
538
+ verify(): Promise<AuditVerification>;
539
+ /** The secret an entry was signed with: the current one, a previous one, or none (changed). */
540
+ private signer;
541
+ /**
542
+ * After `secret` changed: signs again, with the current secret, the entries signed with one of
543
+ * `previousSecrets`, so they still verify once it is removed. Entries no secret verifies were
544
+ * changed: they are left as they are, and reported. Records `audit.resign`.
545
+ */
546
+ resign(): Promise<{
547
+ checked: number;
548
+ resigned: number;
549
+ invalid: ID[];
550
+ }>;
551
+ /**
552
+ * What the dashboard warns about: many failed logins, sign-ins of break-glass accounts in the
553
+ * last day, entries not written, a failed check.
554
+ */
555
+ attention(now?: Date): Promise<({
556
+ id: 'failed-logins';
557
+ count: number;
558
+ } | {
559
+ id: 'break-glass';
560
+ count: number;
561
+ emails: string[];
562
+ } | {
563
+ id: 'audit-failures';
564
+ count: number;
565
+ } | {
566
+ id: 'audit-tampered';
567
+ invalid: number;
568
+ at: string;
569
+ })[]>;
570
+ private sign;
571
+ private lastVerification;
572
+ private where;
573
+ }
424
574
 
425
575
  /** The cookie that carries a sign-in through the provider and back. */
426
576
  declare const SSO_COOKIE = "ecms-sso";
@@ -717,338 +867,40 @@ interface EmailAdapter {
717
867
  * Leave it out to skip the check.
718
868
  */
719
869
  readonly apiVersion?: number;
720
- /** The sender when a message has none, e.g. `Easy CMS <no-reply@example.com>`. */
721
- readonly from?: string | undefined;
722
- send(message: EmailMessage & {
723
- readonly from: string;
724
- }): Promise<void>;
725
- /**
726
- * The settings in use, shown to admins on Settings → Email. Never return secrets: say whether
727
- * a password is set, not what it is.
728
- */
729
- describe?(): readonly EmailSetting[];
730
- /** Checks the connection without sending (Settings → Email); throws with the reason. */
731
- verify?(): Promise<void>;
732
- }
733
- /** One setting an email adapter shows to admins (`describe()`). */
734
- interface EmailSetting {
735
- /**
736
- * `host`, `port`, `secure`, `user`, `password`, `from` and `delivery` (`log`: printed, not sent)
737
- * are translated in the admin; others are shown as they are.
738
- */
739
- readonly key: string;
740
- /** `null`: not set. */
741
- readonly value: string | null;
742
- /** Where it comes from, e.g. `SMTP_HOST` or `smtp({ host })`. */
743
- readonly source?: string;
744
- }
745
- /** Prints emails to the log instead of sending them: for development and tests. */
746
- declare function consoleEmail(options?: {
747
- from?: string;
748
- log?: (text: string) => void;
749
- }): EmailAdapter & {
750
- readonly sent: (EmailMessage & {
751
- from: string;
752
- })[];
753
- };
754
-
755
- /** A label shown in the admin UI. Either one string or one string per admin locale. */
756
- type Label = string | {
757
- readonly [locale: string]: string;
758
- };
759
- interface FieldValidateContext {
760
- /** The whole document being validated, including sibling fields. */
761
- readonly data: Readonly<Record<string, unknown>>;
762
- readonly operation: 'create' | 'update';
763
- }
764
- /** Return `true` when valid, or an error message to show on the field. */
765
- type FieldValidate<TValue> = (value: TValue | null | undefined, ctx: FieldValidateContext) => true | string | Promise<true | string>;
766
- /**
767
- * A Web Component from an admin module (`admin.modules`), by its tag name, which must start
768
- * with `ecms-`. `props` (plain JSON) reach the element as its `options` property.
769
- */
770
- type AdminComponent = string | {
771
- readonly tag: string;
772
- readonly props?: Readonly<Record<string, unknown>>;
773
- };
774
- /** How the admin shows a field, with components from admin modules. */
775
- interface FieldAdmin {
776
- /** Help below the field's label. */
777
- readonly description?: Label;
778
- /** Its share of a row (`admin.layout`'s `row`). Default: an equal share. */
779
- readonly width?: '1/4' | '1/3' | '1/2' | '2/3' | '3/4' | 'full';
780
- /**
781
- * Shown only when its sibling fields match, e.g. `{ field: 'linkType', equals: 'external' }`.
782
- * A hidden field isn't required. See `FieldCondition`.
783
- */
784
- readonly condition?: FieldCondition;
785
- /** Shown instead of the field's input; the admin keeps the label and error messages. */
786
- readonly component?: AdminComponent;
787
- /** Shown below the field, e.g. a length meter or a preview. */
788
- readonly after?: readonly AdminComponent[];
789
- /** Shows the value in the admin's lists (e.g. a color swatch). Default: the plain value. */
790
- readonly cell?: AdminComponent;
791
- /**
792
- * A column of the list at first (each user can change it): `true`, or per request, e.g.
793
- * only when the admin shows every tenant.
794
- */
795
- readonly column?: boolean | ((args: AdminFieldArgs) => boolean | Promise<boolean>);
796
- /** Relationships: offer to create the related document in place. Default true. */
797
- readonly allowCreate?: boolean;
798
- /**
799
- * The value a new document's form starts with, worked out for each user, e.g. from the
800
- * request's context. The server's own default (`defaultValue`, hooks) still applies.
801
- */
802
- readonly initialValue?: (args: AdminFieldArgs) => unknown;
803
- /** `sidebar`: shown in the edit page's side panel instead of the main form (top-level fields). */
804
- readonly position?: 'sidebar';
805
- /** `false`: the list's filters don't offer it. Default: every field the user may read. */
806
- readonly filter?: boolean;
807
- }
808
- /** What per-request admin options of a field receive. */
809
- interface AdminFieldArgs {
810
- readonly user: AuthUser;
811
- readonly context: RequestContext;
812
- }
813
- /**
814
- * Field types that packages add (`fieldTypes` in the config), for the types inferred from the
815
- * config. A package declares its own by augmenting this interface:
816
- *
817
- * ```ts
818
- * declare module '@easy-cms/core' {
819
- * interface CustomFieldTypes {
820
- * color: { value: string; options: { readonly presets?: readonly string[] } }
821
- * }
822
- * }
823
- * ```
824
- */
825
- interface CustomFieldTypes {
826
- }
827
- /** A field of a type from `CustomFieldTypes`: the common options, plus the type's own. */
828
- type CustomField = {
829
- [K in keyof CustomFieldTypes]: BaseField<K & string, CustomFieldTypes[K] extends {
830
- value: infer V;
831
- } ? V : unknown> & (CustomFieldTypes[K] extends {
832
- options: infer O;
833
- } ? O : unknown);
834
- }[keyof CustomFieldTypes];
835
- interface BaseField<TType extends string, TValue> {
836
- readonly type: TType;
837
- readonly name: string;
838
- readonly label?: Label;
839
- readonly required?: boolean;
840
- readonly unique?: boolean;
841
- /**
842
- * With `unique` (or on a slug field): a sibling field (e.g. `parent` or `tenant`), or several,
843
- * whose documents only need to differ among themselves, so `/about/team` and `/careers/team`
844
- * can both be `team`. Single, unlocalized relationship, select, text or number fields.
845
- */
846
- readonly uniqueWithin?: string | readonly string[];
847
- /**
848
- * `true` for a database index (filters and sorts on this field). `'trigram'`, on text, textarea
849
- * and email fields: an index for `like`, which finds any part of the text, in Thai as in other
850
- * languages. Postgres only (pg_trgm); nothing on SQLite. There, `like` needs at least three
851
- * characters, and the admin's searches wait for them.
852
- */
853
- readonly index?: boolean | 'trigram';
854
- readonly defaultValue?: TValue;
855
- readonly validate?: FieldValidate<TValue>;
856
- readonly access?: FieldAccess;
857
- /** Stored but never returned by the API nor accepted as input (e.g. a password hash). */
858
- readonly hidden?: boolean;
859
- /**
860
- * One value per locale (needs `localization` in the config). Not for groups, arrays or
861
- * hasMany fields: localize the fields inside a group or array instead.
862
- */
863
- readonly localized?: boolean;
864
- /** How the field looks in the admin: components, position, width, conditions. */
865
- readonly admin?: FieldAdmin;
866
- /** Set by Easy CMS on fields of an added type (`fieldTypes`): the type's name, e.g. `color`. */
867
- readonly customType?: string;
868
- }
869
- interface TextField extends BaseField<'text', string> {
870
- readonly minLength?: number;
871
- readonly maxLength?: number;
872
- }
873
- interface TextareaField extends BaseField<'textarea', string> {
874
- readonly minLength?: number;
875
- readonly maxLength?: number;
876
- }
877
- interface NumberField extends BaseField<'number', number> {
878
- readonly min?: number;
879
- readonly max?: number;
880
- }
881
- interface BooleanField extends BaseField<'boolean', boolean> {
882
- }
883
- /** Stored and returned as an ISO 8601 string. */
884
- interface DateField extends BaseField<'date', string> {
885
- }
886
- interface EmailField extends BaseField<'email', string> {
887
- }
888
- interface JsonField extends BaseField<'json', unknown> {
889
- }
890
- type SelectOption = string | {
891
- readonly label: Label;
892
- readonly value: string;
893
- };
894
- interface SelectField extends BaseField<'select', string | readonly string[]> {
895
- readonly options: readonly SelectOption[];
896
- readonly hasMany?: boolean;
897
- }
898
- interface SlugField extends BaseField<'slug', string> {
899
- /** Name of a sibling `text` field to generate the slug from. */
900
- readonly from?: string;
901
- }
902
- /** Tiptap / ProseMirror JSON document. */
903
- interface RichTextDocument {
904
- readonly type: 'doc';
905
- readonly content?: readonly unknown[];
906
- }
907
- interface RichTextField extends BaseField<'richText', RichTextDocument> {
908
- /**
909
- * Blocks editors place between paragraphs (#328): a card, a callout, a video. Each is a node
910
- * `{ type: 'block', attrs: { blockType, id, fields } }` in the document.
911
- */
912
- readonly blocks?: readonly RichTextBlock[];
913
- }
914
- /** Arguments of a rich text block's `admin.summary` function. */
915
- interface BlockSummaryArgs {
916
- readonly fields: Record<string, unknown>;
917
- readonly user: AuthUser | null;
918
- readonly cms: EasyCMS;
919
- }
920
- /** A block in rich text (`richText.blocks`): the fields of `Block`, and how its card reads. */
921
- interface RichTextBlock extends Block {
922
- readonly admin?: {
923
- /**
924
- * The text of the block's card in the editor: a function, run on the server, or the tag of a
925
- * custom element from an admin module, which gets `fields`. Default: the block's label and
926
- * its first value.
927
- */
928
- readonly summary?: string | ((args: BlockSummaryArgs) => string | Promise<string>);
929
- };
930
- }
931
- /** References a document in the built-in `media` collection: a file or an image. */
932
- interface UploadField extends BaseField<'upload', never> {
933
- /** Several files, e.g. a gallery: the value is a list, in the order editors arrange. */
934
- readonly hasMany?: boolean;
935
- /** With `hasMany`: the fewest files. */
936
- readonly minRows?: number;
937
- /** With `hasMany`: the most files. */
938
- readonly maxRows?: number;
939
- /**
940
- * The file types allowed, e.g. `['image/*']` or `['image/*', 'application/pdf']`. The admin
941
- * offers only these, and saving refuses others.
942
- */
943
- readonly mimeTypes?: readonly string[];
944
- /**
945
- * With `upload.folders`: the `key` of the media folder this field's files go in, e.g.
946
- * `'banners'`. Its picker opens there and its uploads land there; the folder is made when it
947
- * is first needed.
948
- */
949
- readonly folder?: string;
950
- /** With `folder`: only files in that folder (and its subfolders) may be chosen. */
951
- readonly folderOnly?: boolean;
952
- /** Which media documents may be chosen, as for relationships: offered and checked on save. */
953
- readonly filterOptions?: FilterOptions;
954
- }
955
- interface FilterOptionsArgs {
956
- /** The document being edited; `undefined` while it is being created. */
957
- readonly id: ID | undefined;
958
- /** The collection the choices come from: one of the field's `to`. */
959
- readonly relationTo: string;
960
- readonly user: AuthUser | null;
961
- readonly cms: EasyCMS;
962
- /** The request's context (`onRequest`), `{}` when there is none. */
963
- readonly context: RequestContext;
964
- }
965
- /**
966
- * Which documents a relationship may point to: a `where` on the target collection, or `true`
967
- * for any. Runs on the server: the admin's picker offers only these, and saving checks them.
968
- */
969
- type FilterOptions = (args: FilterOptionsArgs) => Where | true | Promise<Where | true>;
970
- interface RelationshipField extends BaseField<'relationship', never> {
971
- /**
972
- * Slug of the target collection, or several: then each value is `{ relationTo, value }`, the
973
- * collection and the document's id (#327).
974
- */
975
- readonly to: string | readonly string[];
976
- readonly hasMany?: boolean;
977
- /** With `hasMany`: the fewest documents. */
978
- readonly minRows?: number;
979
- /** With `hasMany`: the most documents. */
980
- readonly maxRows?: number;
981
- readonly filterOptions?: FilterOptions;
982
- }
983
- interface ArrayField extends BaseField<'array', never> {
984
- readonly fields: readonly Field[];
985
- readonly minRows?: number;
986
- readonly maxRows?: number;
987
- }
988
- interface GroupField extends BaseField<'group', never> {
989
- readonly fields: readonly Field[];
990
- }
991
- /** One kind of block in a `blocks` field. */
992
- interface Block {
993
- /** Stored in each row as `blockType`. */
994
- readonly slug: string;
995
- readonly labels?: {
996
- readonly singular?: Label;
997
- readonly plural?: Label;
998
- };
999
- readonly fields: readonly Field[];
1000
- }
1001
- /**
1002
- * A list of rows, each one of several block kinds (e.g. hero, text, gallery), for pages that
1003
- * editors lay out themselves. Rows are `{ id, blockType, ...fields }`, stored as JSON.
1004
- */
1005
- interface BlocksField extends BaseField<'blocks', never> {
1006
- readonly blocks: readonly Block[];
1007
- readonly minRows?: number;
1008
- readonly maxRows?: number;
1009
- }
1010
- /** The fields `uniqueWithin` names, as a list. */
1011
- declare const uniqueWithinOf: (field: Field) => readonly string[];
1012
- /** Fields with `hasMany`: their value is a list of options, documents or files. */
1013
- declare const isHasMany: (field: Field) => boolean;
1014
- /**
1015
- * Whether a MIME type is one of `patterns` (`image/*` matches every image but SVG, which can
1016
- * carry scripts and is allowed only when listed by name: `image/svg+xml`).
1017
- */
1018
- declare function mimeAllowedBy(mimeType: string, patterns: readonly string[]): boolean;
1019
- /** A relationship to several collections: its values are `{ relationTo, value }` (#327). */
1020
- declare const isPolymorphic: (field: Field) => field is RelationshipField & {
1021
- readonly to: readonly string[];
1022
- };
1023
- /** The collections a relationship or upload field points to. */
1024
- declare function relationTargets(field: Field): readonly string[];
1025
- /** A `{ relationTo, value }` of a relationship to several collections, or `undefined`. */
1026
- declare function polymorphicValue(value: unknown): {
1027
- relationTo: string;
1028
- value: unknown;
1029
- } | undefined;
1030
- /** Fields whose value is a list of rows: `array` and `blocks`. */
1031
- declare const hasRows: (field: Field) => field is ArrayField | BlocksField;
1032
- /** The fields of one row of an `array` or `blocks` field (`undefined` for an unknown block). */
1033
- declare function rowFields(field: ArrayField | BlocksField, row: unknown): readonly Field[] | undefined;
1034
- /**
1035
- * Stores nothing: a component in the edit form (`admin.component`), e.g. a calculator, a preview
1036
- * or a button. Placed like any field (layout, groups, conditions); not in the database, the API or
1037
- * the generated types.
1038
- */
1039
- interface UiField extends BaseField<'ui', never> {
1040
- readonly admin: FieldAdmin & {
1041
- readonly component: AdminComponent;
1042
- };
870
+ /** The sender when a message has none, e.g. `Easy CMS <no-reply@example.com>`. */
871
+ readonly from?: string | undefined;
872
+ send(message: EmailMessage & {
873
+ readonly from: string;
874
+ }): Promise<void>;
875
+ /**
876
+ * The settings in use, shown to admins on Settings → Email. Never return secrets: say whether
877
+ * a password is set, not what it is.
878
+ */
879
+ describe?(): readonly EmailSetting[];
880
+ /** Checks the connection without sending (Settings → Email); throws with the reason. */
881
+ verify?(): Promise<void>;
1043
882
  }
1044
- type Field = CustomField | TextField | TextareaField | NumberField | BooleanField | DateField | EmailField | JsonField | SelectField | SlugField | RichTextField | UploadField | RelationshipField | ArrayField | GroupField | BlocksField | UiField;
1045
- type FieldType = Field['type'];
1046
- /**
1047
- * The shortest `like` on a field with `index: 'trigram'`: trigrams are three characters, so a
1048
- * shorter search can't use the index and would read every row.
1049
- */
1050
- declare const TRIGRAM_LIKE_MIN = 3;
1051
- declare const FIELD_TYPES: readonly ["text", "textarea", "number", "boolean", "date", "email", "json", "select", "slug", "richText", "upload", "relationship", "array", "group", "blocks", "ui"];
883
+ /** One setting an email adapter shows to admins (`describe()`). */
884
+ interface EmailSetting {
885
+ /**
886
+ * `host`, `port`, `secure`, `user`, `password`, `from` and `delivery` (`log`: printed, not sent)
887
+ * are translated in the admin; others are shown as they are.
888
+ */
889
+ readonly key: string;
890
+ /** `null`: not set. */
891
+ readonly value: string | null;
892
+ /** Where it comes from, e.g. `SMTP_HOST` or `smtp({ host })`. */
893
+ readonly source?: string;
894
+ }
895
+ /** Prints emails to the log instead of sending them: for development and tests. */
896
+ declare function consoleEmail(options?: {
897
+ from?: string;
898
+ log?: (text: string) => void;
899
+ }): EmailAdapter & {
900
+ readonly sent: (EmailMessage & {
901
+ from: string;
902
+ })[];
903
+ };
1052
904
 
1053
905
  type Simplify<T> = {
1054
906
  [K in keyof T]: T[K];
@@ -1473,6 +1325,20 @@ type RichTextBlocks<C extends Config, S extends CollectionSlug<C>, F extends str
1473
1325
  } : never : never;
1474
1326
 
1475
1327
  type IntegrationCategory = 'social' | 'ai' | 'notifications' | 'automation';
1328
+ /**
1329
+ * A provider's logo: SVG paths, e.g. from simple-icons. Without `fill`, a path takes `color` (the
1330
+ * brand's), or the text color where that wouldn't show on the admin's background.
1331
+ */
1332
+ interface IntegrationLogo {
1333
+ readonly paths: readonly {
1334
+ readonly d: string;
1335
+ readonly fill?: string;
1336
+ }[];
1337
+ /** Default `0 0 24 24`. */
1338
+ readonly viewBox?: string;
1339
+ /** `#rrggbb`. */
1340
+ readonly color?: string;
1341
+ }
1476
1342
  /** A value to fill when connecting (`kind: 'fields'`). */
1477
1343
  interface IntegrationField {
1478
1344
  readonly name: string;
@@ -1527,7 +1393,8 @@ interface IntegrationConfig {
1527
1393
  readonly label: Label;
1528
1394
  readonly category: IntegrationCategory;
1529
1395
  readonly description?: Label;
1530
- readonly icon?: string;
1396
+ /** Its logo (#363), or the name of an admin icon (`ADMIN_ICONS`). Without one: its first letter. */
1397
+ readonly icon?: string | IntegrationLogo;
1531
1398
  /** A link to its guide. */
1532
1399
  readonly docs?: string;
1533
1400
  /** The plugin it comes from, e.g. `@easy-cms/plugin-notify`. */
@@ -1551,10 +1418,19 @@ interface IntegrationConfig {
1551
1418
  redirectUri: string;
1552
1419
  }) => Promise<IntegrationOAuthResult> | IntegrationOAuthResult;
1553
1420
  };
1554
- /** Checks the values without posting anything. */
1421
+ /** Checks the values without posting anything; `name` is the connection's. */
1555
1422
  readonly test?: (values: Readonly<Record<string, string>>, args: {
1556
1423
  cms: EasyCMS;
1424
+ name?: string;
1557
1425
  }) => Promise<IntegrationTestResult> | IntegrationTestResult;
1426
+ /**
1427
+ * Asked before the first connection (#357), e.g. that text sent to an AI provider leaves the
1428
+ * site: an admin accepts it, and who did is audit-logged.
1429
+ */
1430
+ readonly consent?: {
1431
+ readonly text: Label;
1432
+ readonly policy?: string;
1433
+ };
1558
1434
  }
1559
1435
  /** A connection as plugins read it: its values, secrets decrypted. */
1560
1436
  interface IntegrationConnection {
@@ -1573,6 +1449,12 @@ interface AdminConnection {
1573
1449
  lastError: string | null;
1574
1450
  /** Plain values; secrets as `••••1234`. */
1575
1451
  values: Record<string, string>;
1452
+ /** AI providers (#357): requests and tokens this month. */
1453
+ usage?: {
1454
+ requests: number;
1455
+ input: number;
1456
+ output: number;
1457
+ };
1576
1458
  }
1577
1459
  /** A field as the admin gets it: without its functions. */
1578
1460
  type AdminIntegrationField = Omit<IntegrationField, 'lookup'> & {
@@ -1583,7 +1465,7 @@ interface AdminIntegration {
1583
1465
  label: Label;
1584
1466
  category: IntegrationCategory;
1585
1467
  description?: Label;
1586
- icon?: string;
1468
+ icon?: string | IntegrationLogo;
1587
1469
  docs?: string;
1588
1470
  plugin?: string;
1589
1471
  kind: 'fields' | 'oauth';
@@ -1593,6 +1475,15 @@ interface AdminIntegration {
1593
1475
  envFields: string[];
1594
1476
  testable: boolean;
1595
1477
  connections: AdminConnection[];
1478
+ /** Asked before the first connection; `consented` says who accepted it. */
1479
+ consent?: {
1480
+ text: Label;
1481
+ policy?: string;
1482
+ };
1483
+ consented?: {
1484
+ at: string;
1485
+ by: string;
1486
+ } | null;
1596
1487
  }
1597
1488
  /** What the code configures: shown, not changed, on the Integrations page. */
1598
1489
  interface ConfiguredService {
@@ -1673,6 +1564,18 @@ declare class Integrations {
1673
1564
  setEnabled(id: string, enabled: boolean, user: AuthUser): Promise<{
1674
1565
  enabled: boolean;
1675
1566
  }>;
1567
+ /** Who accepted the integration's `consent`, and when; `null` before anyone did. */
1568
+ private consented;
1569
+ private checkConsent;
1570
+ /** An admin accepts the integration's `consent`; audit-logged. */
1571
+ consent(id: string, user: AuthUser): Promise<{
1572
+ consented: {
1573
+ at: string;
1574
+ by: string;
1575
+ };
1576
+ }>;
1577
+ /** The integration's own row (on or off, consent), made when first needed. */
1578
+ private saveIntegration;
1676
1579
  /** Checks a connection's values with the provider; the card shows the result. */
1677
1580
  test(id: string, cid: ID | 'env'): Promise<IntegrationTestResult>;
1678
1581
  /**
@@ -1689,9 +1592,9 @@ declare class Integrations {
1689
1592
  })[];
1690
1593
  }>;
1691
1594
  /** The provider's sign-in page to send the admin to. */
1692
- authorize(id: string, user: AuthUser, redirectUri: string, name: string): {
1595
+ authorize(id: string, user: AuthUser, redirectUri: string, name: string): Promise<{
1693
1596
  url: string;
1694
- };
1597
+ }>;
1695
1598
  /**
1696
1599
  * Back from the provider: one account is connected at once; several wait (encrypted, 10
1697
1600
  * minutes) for the admin to choose. Returns where the admin goes next.
@@ -1750,6 +1653,8 @@ interface RolePermissions {
1750
1653
  readonly collections?: FieldRules;
1751
1654
  readonly globals?: FieldRules;
1752
1655
  };
1656
+ /** AI requests a user of the role makes a day (with `ai`); 0: none. Unset: everyone's limit. */
1657
+ readonly aiPerDay?: number;
1753
1658
  }
1754
1659
  /** One role as Settings → Roles shows it (`GET <api>/admin/roles`). */
1755
1660
  interface AdminRole {
@@ -1799,6 +1704,8 @@ interface AdminRoles {
1799
1704
  })[];
1800
1705
  globals: AdminRoleTarget[];
1801
1706
  views: AdminRoleView[];
1707
+ /** Roles can be given a daily AI limit (`ai` in the config). */
1708
+ ai: boolean;
1802
1709
  }
1803
1710
  /** One change to a role (`GET <api>/admin/roles/:id/history`). */
1804
1711
  interface AdminRoleChange {
@@ -1898,6 +1805,8 @@ declare class Roles {
1898
1805
  /** Sets one field of every matching document, keeping the rest as stored. */
1899
1806
  private setWhere;
1900
1807
  private cleanName;
1808
+ /** The role's daily AI limit set in Settings → Roles, if any (`auth.rbac`). */
1809
+ aiLimit(key: string): Promise<number | undefined>;
1901
1810
  private permissionsOf;
1902
1811
  private load;
1903
1812
  private rows;
@@ -2220,6 +2129,8 @@ declare class EasyCMS<C extends Config = Config> {
2220
2129
  readonly folders: MediaFolders;
2221
2130
  /** Accounts connected to plugins' integrations (`integrations`, Settings → Integrations). */
2222
2131
  readonly integrations: Integrations;
2132
+ /** AI for the admin's features (#357): `null` without an AI plugin (`ai` in the config). */
2133
+ readonly ai: Ai | null;
2223
2134
  private readonly versions;
2224
2135
  private readonly webhooks;
2225
2136
  private readonly mailer;
@@ -2770,155 +2681,472 @@ interface LivePreview<T> {
2770
2681
  /** From the collection's `preview` function; `null` without one. */
2771
2682
  readonly url: string | null;
2772
2683
  }
2773
-
2774
- /** Audit log settings (`audit`). */
2775
- interface AuditConfig {
2776
- /** Days to keep entries; older ones are deleted. `0`: keep them all. Default 365. */
2777
- readonly keepDays?: number;
2778
- /** Keep the values before and after a change, not only which fields changed. Default `true`. */
2779
- readonly values?: boolean;
2780
- /** Failed logins within an hour that the dashboard warns about. Default 20. */
2781
- readonly failedLogins?: number;
2684
+
2685
+ /** A label shown in the admin UI. Either one string or one string per admin locale. */
2686
+ type Label = string | {
2687
+ readonly [locale: string]: string;
2688
+ };
2689
+ interface FieldValidateContext {
2690
+ /** The whole document being validated, including sibling fields. */
2691
+ readonly data: Readonly<Record<string, unknown>>;
2692
+ readonly operation: 'create' | 'update';
2693
+ }
2694
+ /** Return `true` when valid, or an error message to show on the field. */
2695
+ type FieldValidate<TValue> = (value: TValue | null | undefined, ctx: FieldValidateContext) => true | string | Promise<true | string>;
2696
+ /**
2697
+ * A Web Component from an admin module (`admin.modules`), by its tag name, which must start
2698
+ * with `ecms-`. `props` (plain JSON) reach the element as its `options` property.
2699
+ */
2700
+ type AdminComponent = string | {
2701
+ readonly tag: string;
2702
+ readonly props?: Readonly<Record<string, unknown>>;
2703
+ };
2704
+ /** How the admin shows a field, with components from admin modules. */
2705
+ interface FieldAdmin {
2706
+ /** Help below the field's label. */
2707
+ readonly description?: Label;
2708
+ /** Its share of a row (`admin.layout`'s `row`). Default: an equal share. */
2709
+ readonly width?: '1/4' | '1/3' | '1/2' | '2/3' | '3/4' | 'full';
2710
+ /**
2711
+ * Shown only when its sibling fields match, e.g. `{ field: 'linkType', equals: 'external' }`.
2712
+ * A hidden field isn't required. See `FieldCondition`.
2713
+ */
2714
+ readonly condition?: FieldCondition;
2715
+ /** Shown instead of the field's input; the admin keeps the label and error messages. */
2716
+ readonly component?: AdminComponent;
2717
+ /** Shown below the field, e.g. a length meter or a preview. */
2718
+ readonly after?: readonly AdminComponent[];
2719
+ /** Shows the value in the admin's lists (e.g. a color swatch). Default: the plain value. */
2720
+ readonly cell?: AdminComponent;
2721
+ /**
2722
+ * A column of the list at first (each user can change it): `true`, or per request, e.g.
2723
+ * only when the admin shows every tenant.
2724
+ */
2725
+ readonly column?: boolean | ((args: AdminFieldArgs) => boolean | Promise<boolean>);
2726
+ /** Relationships: offer to create the related document in place. Default true. */
2727
+ readonly allowCreate?: boolean;
2728
+ /**
2729
+ * The value a new document's form starts with, worked out for each user, e.g. from the
2730
+ * request's context. The server's own default (`defaultValue`, hooks) still applies.
2731
+ */
2732
+ readonly initialValue?: (args: AdminFieldArgs) => unknown;
2733
+ /** `sidebar`: shown in the edit page's side panel instead of the main form (top-level fields). */
2734
+ readonly position?: 'sidebar';
2735
+ /** `false`: the list's filters don't offer it. Default: every field the user may read. */
2736
+ readonly filter?: boolean;
2737
+ }
2738
+ /** What per-request admin options of a field receive. */
2739
+ interface AdminFieldArgs {
2740
+ readonly user: AuthUser;
2741
+ readonly context: RequestContext;
2742
+ }
2743
+ /**
2744
+ * Field types that packages add (`fieldTypes` in the config), for the types inferred from the
2745
+ * config. A package declares its own by augmenting this interface:
2746
+ *
2747
+ * ```ts
2748
+ * declare module '@easy-cms/core' {
2749
+ * interface CustomFieldTypes {
2750
+ * color: { value: string; options: { readonly presets?: readonly string[] } }
2751
+ * }
2752
+ * }
2753
+ * ```
2754
+ */
2755
+ interface CustomFieldTypes {
2756
+ }
2757
+ /** A field of a type from `CustomFieldTypes`: the common options, plus the type's own. */
2758
+ type CustomField = {
2759
+ [K in keyof CustomFieldTypes]: BaseField<K & string, CustomFieldTypes[K] extends {
2760
+ value: infer V;
2761
+ } ? V : unknown> & (CustomFieldTypes[K] extends {
2762
+ options: infer O;
2763
+ } ? O : unknown);
2764
+ }[keyof CustomFieldTypes];
2765
+ interface BaseField<TType extends string, TValue> {
2766
+ readonly type: TType;
2767
+ readonly name: string;
2768
+ readonly label?: Label;
2769
+ readonly required?: boolean;
2770
+ readonly unique?: boolean;
2771
+ /**
2772
+ * With `unique` (or on a slug field): a sibling field (e.g. `parent` or `tenant`), or several,
2773
+ * whose documents only need to differ among themselves, so `/about/team` and `/careers/team`
2774
+ * can both be `team`. Single, unlocalized relationship, select, text or number fields.
2775
+ */
2776
+ readonly uniqueWithin?: string | readonly string[];
2777
+ /**
2778
+ * `true` for a database index (filters and sorts on this field). `'trigram'`, on text, textarea
2779
+ * and email fields: an index for `like`, which finds any part of the text, in Thai as in other
2780
+ * languages. Postgres only (pg_trgm); nothing on SQLite. There, `like` needs at least three
2781
+ * characters, and the admin's searches wait for them.
2782
+ */
2783
+ readonly index?: boolean | 'trigram';
2784
+ readonly defaultValue?: TValue;
2785
+ readonly validate?: FieldValidate<TValue>;
2786
+ readonly access?: FieldAccess;
2787
+ /** Stored but never returned by the API nor accepted as input (e.g. a password hash). */
2788
+ readonly hidden?: boolean;
2789
+ /**
2790
+ * One value per locale (needs `localization` in the config). Not for groups, arrays or
2791
+ * hasMany fields: localize the fields inside a group or array instead.
2792
+ */
2793
+ readonly localized?: boolean;
2794
+ /** How the field looks in the admin: components, position, width, conditions. */
2795
+ readonly admin?: FieldAdmin;
2796
+ /** Set by Easy CMS on fields of an added type (`fieldTypes`): the type's name, e.g. `color`. */
2797
+ readonly customType?: string;
2798
+ }
2799
+ interface TextField extends BaseField<'text', string> {
2800
+ readonly minLength?: number;
2801
+ readonly maxLength?: number;
2802
+ }
2803
+ interface TextareaField extends BaseField<'textarea', string> {
2804
+ readonly minLength?: number;
2805
+ readonly maxLength?: number;
2806
+ }
2807
+ interface NumberField extends BaseField<'number', number> {
2808
+ readonly min?: number;
2809
+ readonly max?: number;
2810
+ }
2811
+ interface BooleanField extends BaseField<'boolean', boolean> {
2812
+ }
2813
+ /** Stored and returned as an ISO 8601 string. */
2814
+ interface DateField extends BaseField<'date', string> {
2815
+ }
2816
+ interface EmailField extends BaseField<'email', string> {
2817
+ }
2818
+ interface JsonField extends BaseField<'json', unknown> {
2819
+ }
2820
+ type SelectOption = string | {
2821
+ readonly label: Label;
2822
+ readonly value: string;
2823
+ };
2824
+ interface SelectField extends BaseField<'select', string | readonly string[]> {
2825
+ readonly options: readonly SelectOption[];
2826
+ readonly hasMany?: boolean;
2827
+ }
2828
+ interface SlugField extends BaseField<'slug', string> {
2829
+ /** Name of a sibling `text` field to generate the slug from. */
2830
+ readonly from?: string;
2831
+ }
2832
+ /** Tiptap / ProseMirror JSON document. */
2833
+ interface RichTextDocument {
2834
+ readonly type: 'doc';
2835
+ readonly content?: readonly unknown[];
2836
+ }
2837
+ interface RichTextField extends BaseField<'richText', RichTextDocument> {
2838
+ /**
2839
+ * Blocks editors place between paragraphs (#328): a card, a callout, a video. Each is a node
2840
+ * `{ type: 'block', attrs: { blockType, id, fields } }` in the document.
2841
+ */
2842
+ readonly blocks?: readonly RichTextBlock[];
2843
+ }
2844
+ /** Arguments of a rich text block's `admin.summary` function. */
2845
+ interface BlockSummaryArgs {
2846
+ readonly fields: Record<string, unknown>;
2847
+ readonly user: AuthUser | null;
2848
+ readonly cms: EasyCMS;
2849
+ }
2850
+ /** A block in rich text (`richText.blocks`): the fields of `Block`, and how its card reads. */
2851
+ interface RichTextBlock extends Block {
2852
+ readonly admin?: {
2853
+ /**
2854
+ * The text of the block's card in the editor: a function, run on the server, or the tag of a
2855
+ * custom element from an admin module, which gets `fields`. Default: the block's label and
2856
+ * its first value.
2857
+ */
2858
+ readonly summary?: string | ((args: BlockSummaryArgs) => string | Promise<string>);
2859
+ };
2860
+ }
2861
+ /** References a document in the built-in `media` collection: a file or an image. */
2862
+ interface UploadField extends BaseField<'upload', never> {
2863
+ /** Several files, e.g. a gallery: the value is a list, in the order editors arrange. */
2864
+ readonly hasMany?: boolean;
2865
+ /** With `hasMany`: the fewest files. */
2866
+ readonly minRows?: number;
2867
+ /** With `hasMany`: the most files. */
2868
+ readonly maxRows?: number;
2869
+ /**
2870
+ * The file types allowed, e.g. `['image/*']` or `['image/*', 'application/pdf']`. The admin
2871
+ * offers only these, and saving refuses others.
2872
+ */
2873
+ readonly mimeTypes?: readonly string[];
2874
+ /**
2875
+ * With `upload.folders`: the `key` of the media folder this field's files go in, e.g.
2876
+ * `'banners'`. Its picker opens there and its uploads land there; the folder is made when it
2877
+ * is first needed.
2878
+ */
2879
+ readonly folder?: string;
2880
+ /** With `folder`: only files in that folder (and its subfolders) may be chosen. */
2881
+ readonly folderOnly?: boolean;
2882
+ /** Which media documents may be chosen, as for relationships: offered and checked on save. */
2883
+ readonly filterOptions?: FilterOptions;
2884
+ }
2885
+ interface FilterOptionsArgs {
2886
+ /** The document being edited; `undefined` while it is being created. */
2887
+ readonly id: ID | undefined;
2888
+ /** The collection the choices come from: one of the field's `to`. */
2889
+ readonly relationTo: string;
2890
+ readonly user: AuthUser | null;
2891
+ readonly cms: EasyCMS;
2892
+ /** The request's context (`onRequest`), `{}` when there is none. */
2893
+ readonly context: RequestContext;
2894
+ }
2895
+ /**
2896
+ * Which documents a relationship may point to: a `where` on the target collection, or `true`
2897
+ * for any. Runs on the server: the admin's picker offers only these, and saving checks them.
2898
+ */
2899
+ type FilterOptions = (args: FilterOptionsArgs) => Where | true | Promise<Where | true>;
2900
+ interface RelationshipField extends BaseField<'relationship', never> {
2782
2901
  /**
2783
- * The part of the site an entry belongs to, from the request's context, e.g. its tenant
2784
- * (usually set by a plugin). Users whose role holds in a part only (`scoped`) see that part's
2785
- * entries; others see the chosen part's, or all.
2902
+ * Slug of the target collection, or several: then each value is `{ relationTo, value }`, the
2903
+ * collection and the document's id (#327).
2786
2904
  */
2787
- readonly scope?: ((args: {
2788
- readonly context: RequestContext;
2789
- readonly user: AuthUser | null;
2790
- }) => string | null | undefined) | false;
2905
+ readonly to: string | readonly string[];
2906
+ readonly hasMany?: boolean;
2907
+ /** With `hasMany`: the fewest documents. */
2908
+ readonly minRows?: number;
2909
+ /** With `hasMany`: the most documents. */
2910
+ readonly maxRows?: number;
2911
+ readonly filterOptions?: FilterOptions;
2791
2912
  }
2792
- /** A field that changed: values before and after, unless they are not kept. */
2793
- interface AuditChange {
2794
- field: string;
2795
- before?: unknown;
2796
- after?: unknown;
2913
+ interface ArrayField extends BaseField<'array', never> {
2914
+ readonly fields: readonly Field[];
2915
+ readonly minRows?: number;
2916
+ readonly maxRows?: number;
2797
2917
  }
2798
- /** One entry (`GET <api>/admin/audit`). */
2799
- interface AuditEntry {
2800
- id: ID;
2801
- /** When, as an ISO date. */
2802
- at: string;
2803
- /** E.g. `create`, `update`, `publish`, `delete`, `login`, `login.failed`, `role.update`. */
2804
- action: string;
2805
- /** A collection slug, `global:<slug>`, or an area such as `auth`, `roles`, `backups`. */
2806
- target: string | null;
2807
- /** The document's id. */
2808
- doc: string | null;
2809
- /** The document's title then (kept after it is deleted). */
2810
- title: string | null;
2811
- actor: {
2812
- id: string | null;
2813
- email: string | null;
2814
- /** `user` (admin or REST), `api-key`, `system` (code), `scheduler`. */
2815
- via: string;
2816
- ip: string | null;
2817
- userAgent: string | null;
2918
+ interface GroupField extends BaseField<'group', never> {
2919
+ readonly fields: readonly Field[];
2920
+ }
2921
+ /** One kind of block in a `blocks` field. */
2922
+ interface Block {
2923
+ /** Stored in each row as `blockType`. */
2924
+ readonly slug: string;
2925
+ readonly labels?: {
2926
+ readonly singular?: Label;
2927
+ readonly plural?: Label;
2818
2928
  };
2819
- changes: AuditChange[] | null;
2820
- detail: Record<string, unknown> | null;
2929
+ readonly fields: readonly Field[];
2821
2930
  }
2822
- /** `GET <api>/admin/audit`: a page of entries, newest first. */
2823
- /** A page of the audit log: the list shape of the rest of the API. */
2824
- type AuditPage = PaginatedDocs<AuditEntry>;
2825
- interface AuditFilter {
2826
- action?: string | null;
2827
- target?: string | null;
2828
- doc?: string | null;
2829
- /** An email, or part of one. */
2830
- actor?: string | null;
2831
- /** ISO dates. */
2832
- from?: string | null;
2833
- to?: string | null;
2834
- /** Only entries of this part of the site (`scope`). */
2835
- scope?: string | null;
2931
+ /**
2932
+ * A list of rows, each one of several block kinds (e.g. hero, text, gallery), for pages that
2933
+ * editors lay out themselves. Rows are `{ id, blockType, ...fields }`, stored as JSON.
2934
+ */
2935
+ interface BlocksField extends BaseField<'blocks', never> {
2936
+ readonly blocks: readonly Block[];
2937
+ readonly minRows?: number;
2938
+ readonly maxRows?: number;
2836
2939
  }
2837
- /** `POST <api>/admin/audit/verify`. */
2838
- interface AuditVerification {
2839
- checked: number;
2840
- /** Entries whose signature does not match: changed after they were written. */
2841
- invalid: ID[];
2842
- /**
2843
- * Missing ids between entries: deleted entries, or (rarely) inserts that failed. Ids are never
2844
- * reused, so the newest entries deleted show up here once anything is written after them (a
2845
- * check writes an entry itself). The server's log has each check's last id, to compare with.
2846
- */
2847
- gaps: number;
2848
- at: string;
2940
+ /** The fields `uniqueWithin` names, as a list. */
2941
+ declare const uniqueWithinOf: (field: Field) => readonly string[];
2942
+ /** Fields with `hasMany`: their value is a list of options, documents or files. */
2943
+ declare const isHasMany: (field: Field) => boolean;
2944
+ /**
2945
+ * Whether a MIME type is one of `patterns` (`image/*` matches every image but SVG, which can
2946
+ * carry scripts and is allowed only when listed by name: `image/svg+xml`).
2947
+ */
2948
+ declare function mimeAllowedBy(mimeType: string, patterns: readonly string[]): boolean;
2949
+ /** A relationship to several collections: its values are `{ relationTo, value }` (#327). */
2950
+ declare const isPolymorphic: (field: Field) => field is RelationshipField & {
2951
+ readonly to: readonly string[];
2952
+ };
2953
+ /** The collections a relationship or upload field points to. */
2954
+ declare function relationTargets(field: Field): readonly string[];
2955
+ /** A `{ relationTo, value }` of a relationship to several collections, or `undefined`. */
2956
+ declare function polymorphicValue(value: unknown): {
2957
+ relationTo: string;
2958
+ value: unknown;
2959
+ } | undefined;
2960
+ /** Fields whose value is a list of rows: `array` and `blocks`. */
2961
+ declare const hasRows: (field: Field) => field is ArrayField | BlocksField;
2962
+ /** The fields of one row of an `array` or `blocks` field (`undefined` for an unknown block). */
2963
+ declare function rowFields(field: ArrayField | BlocksField, row: unknown): readonly Field[] | undefined;
2964
+ /**
2965
+ * Stores nothing: a component in the edit form (`admin.component`), e.g. a calculator, a preview
2966
+ * or a button. Placed like any field (layout, groups, conditions); not in the database, the API or
2967
+ * the generated types.
2968
+ */
2969
+ interface UiField extends BaseField<'ui', never> {
2970
+ readonly admin: FieldAdmin & {
2971
+ readonly component: AdminComponent;
2972
+ };
2849
2973
  }
2974
+ type Field = CustomField | TextField | TextareaField | NumberField | BooleanField | DateField | EmailField | JsonField | SelectField | SlugField | RichTextField | UploadField | RelationshipField | ArrayField | GroupField | BlocksField | UiField;
2975
+ type FieldType = Field['type'];
2850
2976
  /**
2851
- * The audit log (`audit` in the config): who did what, when, from where. Entries can't be
2852
- * changed through Easy CMS; each is signed with the secret, so changes in the database show.
2853
- * Available as `cms.audit`.
2977
+ * The shortest `like` on a field with `index: 'trigram'`: trigrams are three characters, so a
2978
+ * shorter search can't use the index and would read every row.
2854
2979
  */
2855
- declare class AuditLog {
2980
+ declare const TRIGRAM_LIKE_MIN = 3;
2981
+ declare const FIELD_TYPES: readonly ["text", "textarea", "number", "boolean", "date", "email", "json", "select", "slug", "richText", "upload", "relationship", "array", "group", "blocks", "ui"];
2982
+
2983
+ /** What a provider is asked: the prompt, images as base64, and a JSON schema for the answer. */
2984
+ interface AiProviderRequest {
2985
+ readonly system?: string;
2986
+ readonly prompt: string;
2987
+ readonly images: readonly {
2988
+ readonly data: string;
2989
+ readonly mediaType: string;
2990
+ }[];
2991
+ /** A JSON schema: the answer is JSON of this shape. */
2992
+ readonly json?: Readonly<Record<string, unknown>>;
2993
+ readonly maxTokens: number;
2994
+ readonly signal: AbortSignal;
2995
+ }
2996
+ interface AiProviderResult {
2997
+ readonly text: string;
2998
+ readonly usage: {
2999
+ readonly input: number;
3000
+ readonly output: number;
3001
+ };
3002
+ }
3003
+ /** A provider's refusal: `status` says whether to retry (429, 5xx) or the key is wrong (401, 403). */
3004
+ declare class AiProviderError extends Error {
3005
+ readonly status: number | undefined;
3006
+ constructor(message: string, status?: number);
3007
+ }
3008
+ /** A provider a plugin adds: its integration (fields, Test) and how it answers. */
3009
+ interface AiProvider {
3010
+ /** The id of the integration whose connections it answers with, e.g. `anthropic`. */
3011
+ readonly integration: string;
3012
+ readonly generate: (values: Readonly<Record<string, string>>, request: AiProviderRequest) => Promise<AiProviderResult>;
3013
+ }
3014
+ /** `ai` in the config, set by the AI plugin. */
3015
+ interface AiConfig {
3016
+ readonly providers: readonly AiProvider[];
3017
+ readonly limits?: {
3018
+ /** Requests a user makes a day (in `timeZone`). Admins can change it. Default 100. */
3019
+ readonly perUserPerDay?: number;
3020
+ /** Per role, e.g. `{ editor: 50 }`; 0 keeps a role from using AI. Settings → Roles wins. */
3021
+ readonly roles?: Readonly<Record<string, number>>;
3022
+ };
3023
+ }
3024
+ /** A feature that uses AI (`aiFeatures` in the config): admins turn it off or pick its connection. */
3025
+ interface AiFeatureConfig {
3026
+ /** Lowercase letters, digits and `-`, e.g. `alt-text`. */
3027
+ readonly id: string;
3028
+ readonly label: Label;
3029
+ readonly description?: Label;
3030
+ /** On until an admin turns it off. Default true. */
3031
+ readonly default?: boolean;
3032
+ }
3033
+ interface AiImage {
3034
+ /** A media document: its smallest size at least 1024 pixels wide, or the file. */
3035
+ readonly media?: ID;
3036
+ /** Or the image itself, as base64. */
3037
+ readonly data?: string;
3038
+ readonly mediaType?: string;
3039
+ }
3040
+ interface AiRequest {
3041
+ /** The feature asking (`aiFeatures`): it must be on. */
3042
+ readonly feature: string;
3043
+ readonly system?: string;
3044
+ readonly prompt: string;
3045
+ readonly images?: readonly AiImage[];
3046
+ /** A JSON schema: the answer is parsed into `json`. */
3047
+ readonly json?: Readonly<Record<string, unknown>>;
3048
+ /** Default 1024. */
3049
+ readonly maxTokens?: number;
3050
+ }
3051
+ interface AiResult {
3052
+ readonly text: string;
3053
+ readonly json?: unknown;
3054
+ readonly usage: {
3055
+ readonly input: number;
3056
+ readonly output: number;
3057
+ };
3058
+ /** The connection that answered, e.g. `anthropic:Main`. */
3059
+ readonly connection: string;
3060
+ }
3061
+ interface AdminAi {
3062
+ main: string | null;
3063
+ perUserPerDay: number;
3064
+ /** The config's, for "back to the default". */
3065
+ defaultPerUserPerDay: number;
3066
+ connections: {
3067
+ id: string;
3068
+ integration: string;
3069
+ name: string;
3070
+ label: Label;
3071
+ }[];
3072
+ features: {
3073
+ id: string;
3074
+ label: Label;
3075
+ description?: Label;
3076
+ enabled: boolean;
3077
+ connection: string | null;
3078
+ }[];
3079
+ /** This month, in `timeZone`. */
3080
+ usage: {
3081
+ byUser: {
3082
+ user: string;
3083
+ requests: number;
3084
+ input: number;
3085
+ output: number;
3086
+ }[];
3087
+ byFeature: {
3088
+ feature: string;
3089
+ requests: number;
3090
+ input: number;
3091
+ output: number;
3092
+ }[];
3093
+ };
3094
+ }
3095
+ declare class Ai {
2856
3096
  private readonly cms;
2857
- /** Entries that could not be written since the server started. */
2858
- failures: number;
2859
- constructor(cms: EasyCMS);
2860
- get enabled(): boolean;
2861
- private get settings();
2862
- /**
2863
- * Writes an entry. The user defaults to the request's. Never throws: a failure is logged and
2864
- * counted, and the dashboard says so.
2865
- */
2866
- record(entry: {
2867
- action: string;
2868
- target?: string | null;
2869
- doc?: ID | null;
2870
- title?: string | null;
2871
- user?: AuthUser | null;
2872
- /** Who it was when there is no user, e.g. the email of a failed login. */
2873
- email?: string | null;
2874
- changes?: AuditChange[] | null;
2875
- detail?: Record<string, unknown> | null;
2876
- /** The call's context, when it is not the request's. */
2877
- context?: RequestContext;
2878
- }): Promise<void>;
2879
- /** Entries, newest first: `page` (from 1) of `limit` (default 50, at most 100). */
2880
- list(options?: AuditFilter & {
2881
- page?: number;
2882
- limit?: number;
2883
- }): Promise<AuditPage>;
2884
- /** The filtered entries as CSV (the newest 10,000). */
2885
- csv(filter: AuditFilter): Promise<string>;
2886
- /** Checks every entry's signature, and that no ids are missing. */
2887
- verify(): Promise<AuditVerification>;
2888
- /** The secret an entry was signed with: the current one, a previous one, or none (changed). */
2889
- private signer;
2890
- /**
2891
- * After `secret` changed: signs again, with the current secret, the entries signed with one of
2892
- * `previousSecrets`, so they still verify once it is removed. Entries no secret verifies were
2893
- * changed: they are left as they are, and reported. Records `audit.resign`.
2894
- */
2895
- resign(): Promise<{
2896
- checked: number;
2897
- resigned: number;
2898
- invalid: ID[];
3097
+ private readonly ai;
3098
+ constructor(cms: EasyCMS, ai: AiConfig);
3099
+ private get config();
3100
+ private get defaultLimit();
3101
+ /** The features of the config. */
3102
+ get features(): readonly AiFeatureConfig[];
3103
+ private settingsRow;
3104
+ private settings;
3105
+ /** Every connection of every provider, in the providers' order. */
3106
+ private connections;
3107
+ /** Whether a feature is on: admins' choice, else its `default`. */
3108
+ enabled(feature: string): Promise<boolean>;
3109
+ /** Requests a user may make today: their role's, else everyone's. */
3110
+ limitFor(user: AuthUser): Promise<number>;
3111
+ /** Requests the user made today (in `timeZone`) and how many are left. */
3112
+ remaining(user: AuthUser, now?: Date): Promise<{
3113
+ used: number;
3114
+ limit: number;
2899
3115
  }>;
2900
3116
  /**
2901
- * What the dashboard warns about: many failed logins, sign-ins of break-glass accounts in the
2902
- * last day, entries not written, a failed check.
3117
+ * Asks the feature's connection (or the main one). With `user`, the request counts toward their
3118
+ * daily limit; it is refused once they reach it. Usage is recorded, never the prompt.
2903
3119
  */
2904
- attention(now?: Date): Promise<({
2905
- id: 'failed-logins';
2906
- count: number;
2907
- } | {
2908
- id: 'break-glass';
2909
- count: number;
2910
- emails: string[];
2911
- } | {
2912
- id: 'audit-failures';
2913
- count: number;
2914
- } | {
2915
- id: 'audit-tampered';
2916
- invalid: number;
2917
- at: string;
2918
- })[]>;
2919
- private sign;
2920
- private lastVerification;
2921
- private where;
3120
+ generate(request: AiRequest, options?: {
3121
+ user?: AuthUser | null;
3122
+ about?: {
3123
+ collection: string;
3124
+ id: ID;
3125
+ };
3126
+ }): Promise<AiResult>;
3127
+ /**
3128
+ * The Test button of a provider's connection: a short prompt; its tokens count on the card,
3129
+ * not toward anyone's limit.
3130
+ */
3131
+ test(integration: string, values: Readonly<Record<string, string>>, name?: string): Promise<IntegrationTestResult>;
3132
+ private connectionFor;
3133
+ private ask;
3134
+ /** One try, and another after a second when the provider is busy or failing (429, 5xx). */
3135
+ private call;
3136
+ /** An image as base64: given, or read from a media document. */
3137
+ private image;
3138
+ /** Settings → Integrations, AI: the main connection, the features, the limit and this month. */
3139
+ overview(now?: Date): Promise<AdminAi>;
3140
+ /** Tokens and requests of each connection this month, for the cards. */
3141
+ usageByConnection(now?: Date): Promise<Map<string, {
3142
+ requests: number;
3143
+ input: number;
3144
+ output: number;
3145
+ }>>;
3146
+ /** Saves admins' choices; audit-logged. */
3147
+ update(body: unknown, user: AuthUser): Promise<AdminAi>;
3148
+ /** Forgets usage older than 90 days (with the scheduled jobs). */
3149
+ upkeep(now?: Date): Promise<void>;
2922
3150
  }
2923
3151
 
2924
3152
  /** What the email functions in `auth.emails` receive. */
@@ -4189,9 +4417,19 @@ interface Config {
4189
4417
  * by plugins: a Telegram bot, an AI provider, a Facebook Page. See `IntegrationConfig`.
4190
4418
  */
4191
4419
  readonly integrations?: readonly IntegrationConfig[];
4420
+ /**
4421
+ * AI providers and limits (#357), set by `@easy-cms/plugin-ai`: `cms.ai` answers the admin's AI
4422
+ * features with them. See `AiConfig`.
4423
+ */
4424
+ readonly ai?: AiConfig;
4425
+ /**
4426
+ * Features that use AI, added by plugins and by Easy CMS itself: admins turn each off or pick
4427
+ * the connection it uses on the Integrations page. See `AiFeatureConfig`.
4428
+ */
4429
+ readonly aiFeatures?: readonly AiFeatureConfig[];
4192
4430
  }
4193
4431
  /** The config after plugins ran, validation passed and defaults were applied. */
4194
- interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes' | 'admin' | 'upload' | 'auth' | 'collections' | 'globals' | 'endpoints' | 'plugins' | 'audit' | 'integrations'> {
4432
+ interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes' | 'admin' | 'upload' | 'auth' | 'collections' | 'globals' | 'endpoints' | 'plugins' | 'audit' | 'integrations' | 'aiFeatures'> {
4195
4433
  readonly cors: readonly string[] | '*';
4196
4434
  /** `null` without localization. */
4197
4435
  readonly localization: Required<LocalizationConfig> | null;
@@ -4205,6 +4443,7 @@ interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes'
4205
4443
  readonly globals: readonly GlobalConfig[];
4206
4444
  readonly endpoints: readonly Endpoint[];
4207
4445
  readonly integrations: readonly IntegrationConfig[];
4446
+ readonly aiFeatures: readonly AiFeatureConfig[];
4208
4447
  readonly cliCommands: readonly CliCommand[];
4209
4448
  readonly events: readonly string[];
4210
4449
  readonly jobs: readonly JobConfig[];
@@ -4220,5 +4459,5 @@ interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes'
4220
4459
  */
4221
4460
  declare function defineConfig<const TConfig extends Config>(config: TConfig): TConfig;
4222
4461
 
4223
- export { ADAPTER_API_VERSION as A, EasyCMS as E, AuditLog as _, Auth as a1, EasyCMSError as aK, FIELD_TYPES as aT, ConfigError as at, NotFoundError as bI, PLUGIN_API_VERSION as bO, PayloadTooLargeError as bT, QueryError as bZ, ROLE_OPERATIONS as b_, ForbiddenError as bb, definePlugin as c$, TooManyRequestsError as cD, UnauthorizedError as cG, ValidationError as cN, WEBHOOK_EVENTS as cR, anyone as cV, consoleEmail as cW, consoleLogger as cX, createEasyCMS as cY, defineConfig as cZ, defineFieldType as c_, Roles as ca, SchemaError as cd, SingleSignOn as cs, TRIGRAM_LIKE_MIN as cz, diskStorage as d0, hasRows as d1, isAdmin as d2, isHasMany as d3, isPolymorphic as d4, isSignedIn as d5, isStaff as d6, isSystemAdmin as d7, jsonLogger as d8, memorySearch as d9, mimeAllowedBy as da, polymorphicValue as db, relationTargets as dc, rowFields as dd, silentLogger as de, uniqueWithinOf as df, API_KEYS as dg, API_KEY_GLOBAL_OPERATIONS as dh, API_KEY_OPERATIONS as di, DEFAULT_MAX_VERSIONS as dj, DEFAULT_PASSWORD_EMAILS as dk, FIELD_TYPE_BASES as dl, FOLDER_LEVELS as dm, MediaFolders as dn, SSO_COOKIE as dp, codeOfStatus as dr, isJsonLogger as ds, keyAllows as dt, logFormat as du, secretsOf as dv, ADMIN_ICONS as j };
4224
- export type { AuditPage as $, AdminRoles as B, Config as C, AdminSso as D, Field as F, GlobalSlug as G, AdminSwitcher as H, ID as I, AdminViewAccess as J, AfterChangeHook as K, LocalizationConfig as L, AfterDeleteHook as M, AfterReadHook as N, ApiKeyContext as O, PaginatedDocs as P, ApiKeyGlobalOperation as Q, ResolvedConfig as R, ApiKeyOperation as S, ApiKeyPermissions as T, UncountedDocs as U, ArrayField as V, Where as W, AuditChange as X, AuditConfig as Y, AuditEntry as Z, CollectionSlug as a, FieldTypeValidateContext as a$, AuditVerification as a0, AuthConfig as a2, AuthProvider as a3, AuthProviderProfile as a4, AuthProviderRequest as a5, AuthUser as a6, BackupsConfig as a7, BeforeChangeHook as a8, BeforeDeleteHook as a9, DashboardWidget as aA, Database as aB, DatabaseAdapter as aC, DatabaseInitArgs as aD, DatabaseTransfer as aE, DateField as aF, DepthOptions as aG, DirectUpload as aH, DiskStorageOptions as aI, DocumentOf as aJ, EmailAdapter as aL, EmailField as aM, EmailMessage as aN, Endpoint as aO, EndpointRequest as aP, ErrorCode as aQ, EventListener as aR, EventMessage as aS, FieldAccess as aU, FieldAccessFn as aV, FieldAdmin as aW, FieldRule as aX, FieldType as aY, FieldTypeBase as aZ, FieldTypeDefinition as a_, BeforeValidateHook as aa, Block as ab, BlockSummaryArgs as ac, BlocksField as ad, BooleanField as ae, BuiltinApiKeysCollection as af, BuiltinMediaCollection as ag, BuiltinMediaFoldersCollection as ah, BuiltinUsersCollection as ai, BundleHints as aj, CliCommand as ak, CollectionAccess as al, CollectionAdmin as am, CollectionCalendarAdmin as an, CollectionConfig as ao, CollectionHooks as ap, CollectionListAdmin as aq, CollectionSearch as ar, CollectionView as as, ConfigIssue as au, ContainerAdmin as av, CreateInput as aw, CreateMigrationResult as ax, CustomField as ay, CustomFieldTypes as az, CollectionDocument as b, RawDocument as b$, FieldValidate as b0, FieldValidateContext as b1, FieldValue as b2, FieldsInput as b3, FieldsValue as b4, FilterOptionsArgs as b5, FindAfterArgs as b6, FindArgs as b7, FindOptions as b8, FolderLevel as b9, LoginArgs as bA, LogsConfig as bB, MediaDocument as bC, MediaSize as bD, MembersConfig as bE, MembersSignup as bF, MigrationInfo as bG, NavGroup as bH, NumberField as bJ, OnRequest as bK, OnRequestArgs as bL, Operation as bM, OutboxRow as bN, PasswordEmail as bP, PasswordEmailArgs as bQ, PasswordEmailFn as bR, PasswordLinkOptions as bS, Plugin as bU, PluginInfo as bV, PluginTypes as bW, PreviewTarget as bX, PreviewURL as bY, FolderPermissions as ba, GlobalAccess as bc, GlobalConfig as bd, GlobalHooks as be, GlobalInput as bf, GroupField as bg, ImageSize as bh, IncrementOptions as bi, InferCollection as bj, InferGlobal as bk, IntegrationCategory as bl, IntegrationConfig as bm, IntegrationConnection as bn, IntegrationField as bo, IntegrationOAuthResult as bp, IntegrationTestResult as bq, JobConfig as br, JsonField as bs, KeysetPage as bt, Label as bu, LayoutNode as bv, LivePreview as bw, LocaleOf as bx, LogDetails as by, Logger as bz, CursorDocs as c, ReadOptions as c0, RelationshipField as c1, RequestContext as c2, RichTextBlock as c3, RichTextBlocks as c4, RichTextDocument as c5, RichTextField as c6, Role as c7, RoleOperation as c8, RolePermissions as c9, TelemetryConfig as cA, TextField as cB, TextareaField as cC, TypedPlugin as cE, UiField as cF, UpdateInput as cH, UpdateOptions as cI, UploadConfig as cJ, UploadField as cK, UploadFromURLConfig as cL, UserIdentity as cM, Version as cO, VersionSummary as cP, VersionsConfig as cQ, WebhookConfig as cS, WebhookEvent as cT, WhereOperators as cU, RoutesConfig as cb, ScheduledPublish as cc, SchemaMode as ce, SearchAdapter as cf, SearchEntry as cg, SearchHits as ch, SearchOptions as ci, SearchQuery as cj, SearchResults as ck, SearchSetting as cl, SelectField as cm, SelectOption as cn, Session as co, SidebarPanel as cp, SignupArgs as cq, SignupResult as cr, SlugField as ct, SlugOf as cu, SsoOutcome as cv, SsoProviderRef as cw, StorageAdapter as cx, StoredFile as cy, GlobalDocument as d, Secrets as dq, FieldError as e, FilterOptions as f, CreateEasyCMSOptions as g, EmailSetting as h, WebhookPayload as i, Access as k, AccessArgs as l, AccessOptions as m, AdminBrand as n, AdminCommand as o, AdminComponent as p, AdminConfig as q, AdminFieldArgs as r, AdminIcon as s, AdminLocale as t, AdminPage as u, AdminRole as v, AdminRoleChange as w, AdminRoleField as x, AdminRoleTarget as y, AdminRoleView as z };
4462
+ export { ADAPTER_API_VERSION as A, EasyCMS as E, Ai as Q, AiProviderError as Y, AuditLog as a9, ConfigError as aE, EasyCMSError as aV, Auth as ac, FIELD_TYPES as b2, NotFoundError as bU, PLUGIN_API_VERSION as b_, ForbiddenError as bm, PayloadTooLargeError as c3, QueryError as c9, SingleSignOn as cE, TRIGRAM_LIKE_MIN as cL, TooManyRequestsError as cP, UnauthorizedError as cS, ValidationError as cZ, ROLE_OPERATIONS as ca, Roles as cm, SchemaError as cp, WEBHOOK_EVENTS as d1, anyone as d5, consoleEmail as d6, consoleLogger as d7, createEasyCMS as d8, defineConfig as d9, MediaFolders as dA, SSO_COOKIE as dB, codeOfStatus as dD, isJsonLogger as dE, keyAllows as dF, logFormat as dG, secretsOf as dH, defineFieldType as da, definePlugin as db, diskStorage as dc, hasRows as dd, isAdmin as de, isHasMany as df, isPolymorphic as dg, isSignedIn as dh, isStaff as di, isSystemAdmin as dj, jsonLogger as dk, memorySearch as dl, mimeAllowedBy as dm, polymorphicValue as dn, relationTargets as dp, rowFields as dq, silentLogger as dr, uniqueWithinOf as ds, API_KEYS as dt, API_KEY_GLOBAL_OPERATIONS as du, API_KEY_OPERATIONS as dv, DEFAULT_MAX_VERSIONS as dw, DEFAULT_PASSWORD_EMAILS as dx, FIELD_TYPE_BASES as dy, FOLDER_LEVELS as dz, ADMIN_ICONS as j };
4463
+ export type { AiRequest as $, AdminRoleView as B, Config as C, AdminRoles as D, Field as F, GlobalSlug as G, AdminSso as H, ID as I, AdminSwitcher as J, AdminViewAccess as K, LocalizationConfig as L, AfterChangeHook as M, AfterDeleteHook as N, AfterReadHook as O, PaginatedDocs as P, ResolvedConfig as R, AiConfig as S, AiFeatureConfig as T, UncountedDocs as U, AiImage as V, Where as W, AiProvider as X, AiProviderRequest as Z, AiProviderResult as _, CollectionSlug as a, ErrorCode as a$, AiResult as a0, ApiKeyContext as a1, ApiKeyGlobalOperation as a2, ApiKeyOperation as a3, ApiKeyPermissions as a4, ArrayField as a5, AuditChange as a6, AuditConfig as a7, AuditEntry as a8, CollectionHooks as aA, CollectionListAdmin as aB, CollectionSearch as aC, CollectionView as aD, ConfigIssue as aF, ContainerAdmin as aG, CreateInput as aH, CreateMigrationResult as aI, CustomField as aJ, CustomFieldTypes as aK, DashboardWidget as aL, Database as aM, DatabaseAdapter as aN, DatabaseInitArgs as aO, DatabaseTransfer as aP, DateField as aQ, DepthOptions as aR, DirectUpload as aS, DiskStorageOptions as aT, DocumentOf as aU, EmailAdapter as aW, EmailField as aX, EmailMessage as aY, Endpoint as aZ, EndpointRequest as a_, AuditPage as aa, AuditVerification as ab, AuthConfig as ad, AuthProvider as ae, AuthProviderProfile as af, AuthProviderRequest as ag, AuthUser as ah, BackupsConfig as ai, BeforeChangeHook as aj, BeforeDeleteHook as ak, BeforeValidateHook as al, Block as am, BlockSummaryArgs as an, BlocksField as ao, BooleanField as ap, BuiltinApiKeysCollection as aq, BuiltinMediaCollection as ar, BuiltinMediaFoldersCollection as as, BuiltinUsersCollection as at, BundleHints as au, CliCommand as av, CollectionAccess as aw, CollectionAdmin as ax, CollectionCalendarAdmin as ay, CollectionConfig as az, CollectionDocument as b, PasswordEmail as b$, EventListener as b0, EventMessage as b1, FieldAccess as b3, FieldAccessFn as b4, FieldAdmin as b5, FieldRule as b6, FieldType as b7, FieldTypeBase as b8, FieldTypeDefinition as b9, IntegrationLogo as bA, IntegrationOAuthResult as bB, IntegrationTestResult as bC, JobConfig as bD, JsonField as bE, KeysetPage as bF, Label as bG, LayoutNode as bH, LivePreview as bI, LocaleOf as bJ, LogDetails as bK, Logger as bL, LoginArgs as bM, LogsConfig as bN, MediaDocument as bO, MediaSize as bP, MembersConfig as bQ, MembersSignup as bR, MigrationInfo as bS, NavGroup as bT, NumberField as bV, OnRequest as bW, OnRequestArgs as bX, Operation as bY, OutboxRow as bZ, FieldTypeValidateContext as ba, FieldValidate as bb, FieldValidateContext as bc, FieldValue as bd, FieldsInput as be, FieldsValue as bf, FilterOptionsArgs as bg, FindAfterArgs as bh, FindArgs as bi, FindOptions as bj, FolderLevel as bk, FolderPermissions as bl, GlobalAccess as bn, GlobalConfig as bo, GlobalHooks as bp, GlobalInput as bq, GroupField as br, ImageSize as bs, IncrementOptions as bt, InferCollection as bu, InferGlobal as bv, IntegrationCategory as bw, IntegrationConfig as bx, IntegrationConnection as by, IntegrationField as bz, CursorDocs as c, VersionSummary as c$, PasswordEmailArgs as c0, PasswordEmailFn as c1, PasswordLinkOptions as c2, Plugin as c4, PluginInfo as c5, PluginTypes as c6, PreviewTarget as c7, PreviewURL as c8, Session as cA, SidebarPanel as cB, SignupArgs as cC, SignupResult as cD, SlugField as cF, SlugOf as cG, SsoOutcome as cH, SsoProviderRef as cI, StorageAdapter as cJ, StoredFile as cK, TelemetryConfig as cM, TextField as cN, TextareaField as cO, TypedPlugin as cQ, UiField as cR, UpdateInput as cT, UpdateOptions as cU, UploadConfig as cV, UploadField as cW, UploadFromURLConfig as cX, UserIdentity as cY, Version as c_, RawDocument as cb, ReadOptions as cc, RelationshipField as cd, RequestContext as ce, RichTextBlock as cf, RichTextBlocks as cg, RichTextDocument as ch, RichTextField as ci, Role as cj, RoleOperation as ck, RolePermissions as cl, RoutesConfig as cn, ScheduledPublish as co, SchemaMode as cq, SearchAdapter as cr, SearchEntry as cs, SearchHits as ct, SearchOptions as cu, SearchQuery as cv, SearchResults as cw, SearchSetting as cx, SelectField as cy, SelectOption as cz, GlobalDocument as d, VersionsConfig as d0, WebhookConfig as d2, WebhookEvent as d3, WhereOperators as d4, Secrets as dC, FieldError as e, FilterOptions as f, CreateEasyCMSOptions as g, EmailSetting as h, WebhookPayload as i, Access as k, AccessArgs as l, AccessOptions as m, AdminAi as n, AdminBrand as o, AdminCommand as p, AdminComponent as q, AdminConfig as r, AdminFieldArgs as s, AdminIcon as t, AdminLocale as u, AdminPage as v, AdminRole as w, AdminRoleChange as x, AdminRoleField as y, AdminRoleTarget as z };