@easy-cms/core 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +214 -72
- package/dist/index.js +152 -6
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -254,6 +254,58 @@ declare function consoleEmail(options?: {
|
|
|
254
254
|
})[];
|
|
255
255
|
};
|
|
256
256
|
|
|
257
|
+
interface ConfigIssue {
|
|
258
|
+
/** Where the problem is, e.g. `collections.posts.fields.slug.from`. */
|
|
259
|
+
readonly path: string;
|
|
260
|
+
readonly message: string;
|
|
261
|
+
/** How to fix it. */
|
|
262
|
+
readonly hint?: string;
|
|
263
|
+
}
|
|
264
|
+
declare class ConfigError extends Error {
|
|
265
|
+
readonly issues: readonly ConfigIssue[];
|
|
266
|
+
constructor(issues: readonly ConfigIssue[]);
|
|
267
|
+
static format(issues: readonly ConfigIssue[]): string;
|
|
268
|
+
}
|
|
269
|
+
/** Base class for errors that map to an HTTP status in the REST API. */
|
|
270
|
+
declare class EasyCMSError extends Error {
|
|
271
|
+
readonly status: number;
|
|
272
|
+
constructor(message: string, status: number);
|
|
273
|
+
}
|
|
274
|
+
interface FieldError {
|
|
275
|
+
/** Dotted path, e.g. `links.0.url`. */
|
|
276
|
+
readonly field: string;
|
|
277
|
+
readonly message: string;
|
|
278
|
+
}
|
|
279
|
+
declare class ValidationError extends EasyCMSError {
|
|
280
|
+
readonly errors: readonly FieldError[];
|
|
281
|
+
constructor(collection: string, errors: readonly FieldError[]);
|
|
282
|
+
}
|
|
283
|
+
declare class NotFoundError extends EasyCMSError {
|
|
284
|
+
constructor(collection: string, id: unknown);
|
|
285
|
+
}
|
|
286
|
+
/** A bad `where`, `sort` or unknown collection/global. */
|
|
287
|
+
declare class QueryError extends EasyCMSError {
|
|
288
|
+
constructor(message: string);
|
|
289
|
+
}
|
|
290
|
+
/** The database schema does not match the config (pending or missing migrations). */
|
|
291
|
+
declare class SchemaError extends EasyCMSError {
|
|
292
|
+
constructor(message: string);
|
|
293
|
+
}
|
|
294
|
+
/** Not logged in, or the session is invalid. */
|
|
295
|
+
declare class UnauthorizedError extends EasyCMSError {
|
|
296
|
+
constructor(message?: string);
|
|
297
|
+
}
|
|
298
|
+
/** Logged in, but not allowed to do this. */
|
|
299
|
+
declare class ForbiddenError extends EasyCMSError {
|
|
300
|
+
constructor(message?: string);
|
|
301
|
+
}
|
|
302
|
+
declare class TooManyRequestsError extends EasyCMSError {
|
|
303
|
+
constructor(message: string);
|
|
304
|
+
}
|
|
305
|
+
declare class PayloadTooLargeError extends EasyCMSError {
|
|
306
|
+
constructor(message: string);
|
|
307
|
+
}
|
|
308
|
+
|
|
257
309
|
/** What a preview token opens: one document of a collection, or one global. */
|
|
258
310
|
type PreviewTarget = {
|
|
259
311
|
readonly collection: string;
|
|
@@ -439,16 +491,41 @@ interface BuiltinApiKeysCollection {
|
|
|
439
491
|
type ApiKeysOf<C extends Config> = C extends {
|
|
440
492
|
readonly apiKeys: true;
|
|
441
493
|
} ? BuiltinApiKeysCollection : never;
|
|
442
|
-
/**
|
|
443
|
-
type
|
|
494
|
+
/** The `PluginTypes` of each plugin in the config (a union). */
|
|
495
|
+
type PluginTypesOf<C extends Config> = C extends {
|
|
496
|
+
readonly plugins: readonly (infer P)[];
|
|
497
|
+
} ? P extends TypedPlugin<infer T extends PluginTypes> ? T : never : never;
|
|
498
|
+
/** Fields that plugins add to the collection (or global) `S`, as a union. */
|
|
499
|
+
type AddedFields<T, S> = T extends {
|
|
500
|
+
readonly [K in 'fields' | 'globalFields']?: infer M;
|
|
501
|
+
} ? M extends {
|
|
502
|
+
readonly [slug: string]: readonly Field[];
|
|
503
|
+
} ? string extends keyof M ? never : S extends keyof M ? M[S][number] : never : never : never;
|
|
504
|
+
type PluginFields<C extends Config, S, Key extends 'fields' | 'globalFields'> = AddedFields<PluginTypesOf<C> extends infer T ? T extends PluginTypes ? Pick<T, Key & keyof T> : never : never, S>;
|
|
505
|
+
/** Collections that plugins add. */
|
|
506
|
+
type PluginCollections<C extends Config> = PluginTypesOf<C> extends infer T ? T extends {
|
|
507
|
+
readonly collections: readonly (infer K extends CollectionConfig)[];
|
|
508
|
+
} ? K : never : never;
|
|
509
|
+
/** A collection or global with the fields plugins add to it. */
|
|
510
|
+
type WithPluginFields<C extends Config, T extends {
|
|
511
|
+
readonly slug: string;
|
|
512
|
+
readonly fields: readonly Field[];
|
|
513
|
+
}, Key extends 'fields' | 'globalFields'> = [PluginFields<C, T['slug'], Key>] extends [never] ? T : Omit<T, 'fields'> & {
|
|
514
|
+
readonly fields: readonly (T['fields'][number] | PluginFields<C, T['slug'], Key>)[];
|
|
515
|
+
};
|
|
516
|
+
/** Every collection of a config, including the built-in ones and those plugins add. */
|
|
517
|
+
type AllCollections<C extends Config> = NonNullable<C['collections']>[number] | BuiltinUsersCollection | BuiltinMediaCollection | ApiKeysOf<C> | PluginCollections<C>;
|
|
444
518
|
/** The built-in media collection; its documents are typed as `MediaDocument`. */
|
|
445
519
|
interface BuiltinMediaCollection {
|
|
446
520
|
readonly slug: 'media';
|
|
447
521
|
readonly fields: readonly [];
|
|
448
522
|
}
|
|
449
|
-
type CollectionBySlug<C extends Config, S> = Extract<AllCollections<C>, {
|
|
523
|
+
type CollectionBySlug<C extends Config, S> = WithPluginFields<C, Extract<AllCollections<C>, {
|
|
450
524
|
readonly slug: S;
|
|
451
|
-
}>;
|
|
525
|
+
}>, 'fields'>;
|
|
526
|
+
type GlobalBySlug<C extends Config, S> = WithPluginFields<C, Extract<NonNullable<C['globals']>[number], {
|
|
527
|
+
readonly slug: S;
|
|
528
|
+
}>, 'globalFields'>;
|
|
452
529
|
/** Relationships are ids at depth 0 and documents when populated. */
|
|
453
530
|
type RelationValue<C extends Config, S> = [CollectionBySlug<C, S>] extends [never] ? ID | Record<string, unknown> : ID | CollectionDocument<C, S & string>;
|
|
454
531
|
interface MediaSize {
|
|
@@ -508,7 +585,13 @@ type FieldValue<F extends Field, C extends Config = Config> = F extends {
|
|
|
508
585
|
} ? FieldsValue<Sub, C> : F extends {
|
|
509
586
|
readonly type: 'blocks';
|
|
510
587
|
readonly blocks: infer B extends readonly Block[];
|
|
511
|
-
} ? BlockValue<B[number], C>[] :
|
|
588
|
+
} ? BlockValue<B[number], C>[] : CustomValue<F>;
|
|
589
|
+
/** The value of a field of an added type (`CustomFieldTypes`). */
|
|
590
|
+
type CustomValue<F> = F extends {
|
|
591
|
+
readonly type: infer T extends keyof CustomFieldTypes;
|
|
592
|
+
} ? CustomFieldTypes[T] extends {
|
|
593
|
+
value: infer V;
|
|
594
|
+
} ? V : unknown : never;
|
|
512
595
|
/** One row of a `blocks` field: the block's fields plus `id` and `blockType` (distributes over kinds). */
|
|
513
596
|
type BlockValue<B, C extends Config> = B extends {
|
|
514
597
|
readonly slug: infer S;
|
|
@@ -544,7 +627,7 @@ type FieldsValue<Fs extends readonly Field[], C extends Config = Config> = Simpl
|
|
|
544
627
|
-readonly [F in OptionalFields<Fs> as F['name']]?: FieldValue<F, C> | null;
|
|
545
628
|
}>;
|
|
546
629
|
type SystemFields<T extends {
|
|
547
|
-
readonly drafts?:
|
|
630
|
+
readonly drafts?: unknown;
|
|
548
631
|
}> = {
|
|
549
632
|
id: ID;
|
|
550
633
|
createdAt: string;
|
|
@@ -554,8 +637,13 @@ type SystemFields<T extends {
|
|
|
554
637
|
} ? {
|
|
555
638
|
status: 'draft' | 'published';
|
|
556
639
|
} : unknown);
|
|
557
|
-
|
|
558
|
-
type
|
|
640
|
+
/** What document types are inferred from: a collection's or global's fields and drafts. */
|
|
641
|
+
type FieldsOwner = {
|
|
642
|
+
readonly fields: readonly Field[];
|
|
643
|
+
readonly drafts?: unknown;
|
|
644
|
+
};
|
|
645
|
+
type InferCollection<T extends FieldsOwner, C extends Config = Config> = Simplify<SystemFields<T> & FieldsValue<T['fields'], C>>;
|
|
646
|
+
type InferGlobal<T extends FieldsOwner, C extends Config = Config> = Simplify<
|
|
559
647
|
/** `null` until the global is saved for the first time. */
|
|
560
648
|
{
|
|
561
649
|
updatedAt: string | null;
|
|
@@ -564,14 +652,12 @@ type InferGlobal<T extends GlobalConfig, C extends Config = Config> = Simplify<
|
|
|
564
652
|
} ? {
|
|
565
653
|
status: 'draft' | 'published';
|
|
566
654
|
} : unknown) & FieldsValue<T['fields'], C>>;
|
|
567
|
-
type CollectionSlug<C extends Config> = NonNullable<C['collections']>[number]['slug'] | 'users' | 'media' | ApiKeysOf<C>['slug'];
|
|
655
|
+
type CollectionSlug<C extends Config> = NonNullable<C['collections']>[number]['slug'] | 'users' | 'media' | ApiKeysOf<C>['slug'] | PluginCollections<C>['slug'];
|
|
568
656
|
type GlobalSlug<C extends Config> = NonNullable<C['globals']>[number]['slug'];
|
|
569
657
|
/** Document type of a collection, e.g. `CollectionDocument<typeof config, 'posts'>`. */
|
|
570
658
|
type CollectionDocument<C extends Config, S extends CollectionSlug<C>> = S extends 'media' ? MediaDocument : InferCollection<CollectionBySlug<C, S>, C>;
|
|
571
659
|
/** Data type of a global, e.g. `GlobalDocument<typeof config, 'site'>`. */
|
|
572
|
-
type GlobalDocument<C extends Config, S extends GlobalSlug<C>> = InferGlobal<
|
|
573
|
-
readonly slug: S;
|
|
574
|
-
}>, C>;
|
|
660
|
+
type GlobalDocument<C extends Config, S extends GlobalSlug<C>> = InferGlobal<GlobalBySlug<C, S>, C>;
|
|
575
661
|
type InputValue<F extends Field> = F extends {
|
|
576
662
|
readonly type: 'relationship';
|
|
577
663
|
} ? F extends {
|
|
@@ -630,11 +716,7 @@ type CreateInput<C extends Config, S extends CollectionSlug<C>> = Simplify<Field
|
|
|
630
716
|
/** Data accepted by `update`: any subset of `CreateInput`. */
|
|
631
717
|
type UpdateInput<C extends Config, S extends CollectionSlug<C>> = Partial<CreateInput<C, S>>;
|
|
632
718
|
/** Data accepted by `updateGlobal`. */
|
|
633
|
-
type GlobalInput<C extends Config, S extends GlobalSlug<C>> = Partial<Simplify<FieldsInput<
|
|
634
|
-
readonly slug: S;
|
|
635
|
-
}>['fields']> & StatusInput<Extract<NonNullable<C['globals']>[number], {
|
|
636
|
-
readonly slug: S;
|
|
637
|
-
}>>>>;
|
|
719
|
+
type GlobalInput<C extends Config, S extends GlobalSlug<C>> = Partial<Simplify<FieldsInput<GlobalBySlug<C, S>['fields']> & StatusInput<GlobalBySlug<C, S>>>>;
|
|
638
720
|
|
|
639
721
|
interface StoredFile {
|
|
640
722
|
readonly body: Uint8Array;
|
|
@@ -690,6 +772,13 @@ type Create<C extends Config, S> = string extends CollectionSlug<C> ? Data$2 : C
|
|
|
690
772
|
type Update<C extends Config, S> = string extends CollectionSlug<C> ? Data$2 : UpdateInput<C, S & CollectionSlug<C>>;
|
|
691
773
|
type GDoc<C extends Config, S> = string extends GlobalSlug<C> ? Data$2 : GlobalDocument<C, S & GlobalSlug<C>>;
|
|
692
774
|
type GInput<C extends Config, S> = string extends GlobalSlug<C> ? Data$2 : GlobalInput<C, S & GlobalSlug<C>>;
|
|
775
|
+
/** A collection slug of the config `C` (any string when the config isn't a literal). */
|
|
776
|
+
type SlugOf<C extends Config> = Slug<C>;
|
|
777
|
+
/**
|
|
778
|
+
* A document of collection `S` as the Local API returns it, e.g. for helpers that take an
|
|
779
|
+
* `EasyCMS<C>`: inferred from a literal config, loose otherwise.
|
|
780
|
+
*/
|
|
781
|
+
type DocumentOf<C extends Config, S> = Doc<C, S>;
|
|
693
782
|
interface AccessOptions {
|
|
694
783
|
/**
|
|
695
784
|
* The Local API trusts its caller and skips access rules by default.
|
|
@@ -1051,7 +1140,31 @@ interface FieldAdmin {
|
|
|
1051
1140
|
readonly component?: AdminComponent;
|
|
1052
1141
|
/** Shown below the field, e.g. a length meter or a preview. */
|
|
1053
1142
|
readonly after?: readonly AdminComponent[];
|
|
1143
|
+
/** Shows the value in the admin's lists (e.g. a color swatch). Default: the plain value. */
|
|
1144
|
+
readonly cell?: AdminComponent;
|
|
1054
1145
|
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Field types that packages add (`fieldTypes` in the config), for the types inferred from the
|
|
1148
|
+
* config. A package declares its own by augmenting this interface:
|
|
1149
|
+
*
|
|
1150
|
+
* ```ts
|
|
1151
|
+
* declare module '@easy-cms/core' {
|
|
1152
|
+
* interface CustomFieldTypes {
|
|
1153
|
+
* color: { value: string; options: { readonly presets?: readonly string[] } }
|
|
1154
|
+
* }
|
|
1155
|
+
* }
|
|
1156
|
+
* ```
|
|
1157
|
+
*/
|
|
1158
|
+
interface CustomFieldTypes {
|
|
1159
|
+
}
|
|
1160
|
+
/** A field of a type from `CustomFieldTypes`: the common options, plus the type's own. */
|
|
1161
|
+
type CustomField = {
|
|
1162
|
+
[K in keyof CustomFieldTypes]: BaseField<K & string, CustomFieldTypes[K] extends {
|
|
1163
|
+
value: infer V;
|
|
1164
|
+
} ? V : unknown> & (CustomFieldTypes[K] extends {
|
|
1165
|
+
options: infer O;
|
|
1166
|
+
} ? O : unknown);
|
|
1167
|
+
}[keyof CustomFieldTypes];
|
|
1055
1168
|
interface BaseField<TType extends string, TValue> {
|
|
1056
1169
|
readonly type: TType;
|
|
1057
1170
|
readonly name: string;
|
|
@@ -1073,6 +1186,8 @@ interface BaseField<TType extends string, TValue> {
|
|
|
1073
1186
|
readonly position?: 'sidebar';
|
|
1074
1187
|
/** Custom admin components for this field. */
|
|
1075
1188
|
readonly admin?: FieldAdmin;
|
|
1189
|
+
/** Set by Easy CMS on fields of an added type (`fieldTypes`): the type's name, e.g. `color`. */
|
|
1190
|
+
readonly customType?: string;
|
|
1076
1191
|
}
|
|
1077
1192
|
interface TextField extends BaseField<'text', string> {
|
|
1078
1193
|
readonly minLength?: number;
|
|
@@ -1189,10 +1304,48 @@ declare function mimeAllowedBy(mimeType: string, patterns: readonly string[]): b
|
|
|
1189
1304
|
declare const hasRows: (field: Field) => field is ArrayField | BlocksField;
|
|
1190
1305
|
/** The fields of one row of an `array` or `blocks` field (`undefined` for an unknown block). */
|
|
1191
1306
|
declare function rowFields(field: ArrayField | BlocksField, row: unknown): readonly Field[] | undefined;
|
|
1192
|
-
type Field = TextField | TextareaField | NumberField | BooleanField | DateField | EmailField | JsonField | SelectField | SlugField | RichTextField | UploadField | RelationshipField | ArrayField | GroupField | BlocksField;
|
|
1307
|
+
type Field = CustomField | TextField | TextareaField | NumberField | BooleanField | DateField | EmailField | JsonField | SelectField | SlugField | RichTextField | UploadField | RelationshipField | ArrayField | GroupField | BlocksField;
|
|
1193
1308
|
type FieldType = Field['type'];
|
|
1194
1309
|
declare const FIELD_TYPES: readonly ["text", "textarea", "number", "boolean", "date", "email", "json", "select", "slug", "richText", "upload", "relationship", "array", "group", "blocks"];
|
|
1195
1310
|
|
|
1311
|
+
/** Built-in types a field type can be stored as. */
|
|
1312
|
+
declare const FIELD_TYPE_BASES: readonly ["text", "textarea", "email", "number", "boolean", "date", "json"];
|
|
1313
|
+
type FieldTypeBase = (typeof FIELD_TYPE_BASES)[number];
|
|
1314
|
+
/** What a field type's `validate` gets besides the value: the field, with its own options. */
|
|
1315
|
+
interface FieldTypeValidateContext extends FieldValidateContext {
|
|
1316
|
+
readonly field: Readonly<Record<string, unknown>> & {
|
|
1317
|
+
readonly name: string;
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* A field type a package adds, e.g. `color`: stored, queried and translated like its `base`
|
|
1322
|
+
* type, with its own checks and admin components. List it in the config's `fieldTypes`.
|
|
1323
|
+
*/
|
|
1324
|
+
interface FieldTypeDefinition {
|
|
1325
|
+
/** The `type` fields use, e.g. `color`. Lowercase; not one of the built-in types. */
|
|
1326
|
+
readonly name: string;
|
|
1327
|
+
/** How values are stored and queried: one of the built-in scalar types. */
|
|
1328
|
+
readonly base: FieldTypeBase;
|
|
1329
|
+
/** Checks a value (not empty ones; `required` covers those). Return `true` or a message. */
|
|
1330
|
+
readonly validate?: (value: unknown, ctx: FieldTypeValidateContext) => true | string | Promise<true | string>;
|
|
1331
|
+
/** Checks a field's own options when the config loads; return a message for a mistake. */
|
|
1332
|
+
readonly checkOptions?: (field: Readonly<Record<string, unknown>>) => string | undefined;
|
|
1333
|
+
readonly admin?: {
|
|
1334
|
+
/** The input: a Web Component from `module`. */
|
|
1335
|
+
readonly component?: string;
|
|
1336
|
+
/** Shown in the admin's lists. */
|
|
1337
|
+
readonly cell?: string;
|
|
1338
|
+
/** The admin module (package export or file) that defines the components. */
|
|
1339
|
+
readonly module?: string;
|
|
1340
|
+
/** The field options its components get (as their `options`), e.g. `['presets']`. */
|
|
1341
|
+
readonly props?: readonly string[];
|
|
1342
|
+
};
|
|
1343
|
+
/** The value's TypeScript type for `generate:types`. Default: the base type's. */
|
|
1344
|
+
readonly typescript?: string;
|
|
1345
|
+
}
|
|
1346
|
+
/** Declares a field type; returns it unchanged. */
|
|
1347
|
+
declare function defineFieldType(definition: FieldTypeDefinition): FieldTypeDefinition;
|
|
1348
|
+
|
|
1196
1349
|
declare const WEBHOOK_EVENTS: readonly ["create", "update", "delete", "publish", "unpublish", "draft"];
|
|
1197
1350
|
/**
|
|
1198
1351
|
* `create`, `update`, `delete`: the stored document changed. `publish` / `unpublish`: its status
|
|
@@ -1519,6 +1672,38 @@ interface Endpoint {
|
|
|
1519
1672
|
}
|
|
1520
1673
|
/** Receives the config and returns a modified copy. Runs before validation. */
|
|
1521
1674
|
type Plugin = (config: Config) => MaybePromise<Config>;
|
|
1675
|
+
/** Holds a plugin's `PluginTypes` at the type level only: nothing is stored at runtime. */
|
|
1676
|
+
declare const PLUGIN_TYPES: unique symbol;
|
|
1677
|
+
/**
|
|
1678
|
+
* What a plugin adds, for the document types inferred from the config: fields on collections
|
|
1679
|
+
* and globals (by slug), and whole collections. Field types are written like config fields,
|
|
1680
|
+
* e.g. `{ fields: { posts: readonly [{ readonly name: 'meta'; readonly type: 'group'; … }] } }`.
|
|
1681
|
+
*/
|
|
1682
|
+
interface PluginTypes {
|
|
1683
|
+
readonly fields?: {
|
|
1684
|
+
readonly [collection: string]: readonly Field[];
|
|
1685
|
+
};
|
|
1686
|
+
readonly globalFields?: {
|
|
1687
|
+
readonly [global: string]: readonly Field[];
|
|
1688
|
+
};
|
|
1689
|
+
readonly collections?: readonly CollectionConfig[];
|
|
1690
|
+
}
|
|
1691
|
+
/** A plugin that tells the type system what it adds (see `definePlugin`). */
|
|
1692
|
+
type TypedPlugin<T extends PluginTypes> = Plugin & {
|
|
1693
|
+
readonly [PLUGIN_TYPES]?: T;
|
|
1694
|
+
};
|
|
1695
|
+
/**
|
|
1696
|
+
* Returns the plugin unchanged, with what it adds as types, so `cms.find()` and
|
|
1697
|
+
* `CollectionDocument` know the plugin's fields:
|
|
1698
|
+
*
|
|
1699
|
+
* ```ts
|
|
1700
|
+
* export const colorPlugin = (options: { collections: readonly string[] }) =>
|
|
1701
|
+
* definePlugin<{ fields: { posts: readonly [{ readonly name: 'color'; readonly type: 'text' }] } }>(
|
|
1702
|
+
* (config) => ({ ...config, collections: … }),
|
|
1703
|
+
* )
|
|
1704
|
+
* ```
|
|
1705
|
+
*/
|
|
1706
|
+
declare function definePlugin<const T extends PluginTypes = Record<never, never>>(plugin: Plugin): TypedPlugin<T>;
|
|
1522
1707
|
interface Config {
|
|
1523
1708
|
/** Signs sessions. At least 32 characters; read it from `process.env.EASY_CMS_SECRET`. */
|
|
1524
1709
|
readonly secret: string;
|
|
@@ -1558,6 +1743,11 @@ interface Config {
|
|
|
1558
1743
|
readonly endpoints?: readonly Endpoint[];
|
|
1559
1744
|
/** Extra `easy-cms <name>` CLI commands, e.g. from plugins. */
|
|
1560
1745
|
readonly commands?: readonly CliCommand[];
|
|
1746
|
+
/**
|
|
1747
|
+
* Field types from packages, e.g. `color` from `@easy-cms/fields`: fields then use
|
|
1748
|
+
* `type: 'color'`. See `defineFieldType`.
|
|
1749
|
+
*/
|
|
1750
|
+
readonly fieldTypes?: readonly FieldTypeDefinition[];
|
|
1561
1751
|
/**
|
|
1562
1752
|
* API keys for scripts and other apps (`Authorization: Bearer ecms_…`), managed in the admin
|
|
1563
1753
|
* under Settings. A key acts as its owner, limited to the collections and operations it lists.
|
|
@@ -1584,6 +1774,7 @@ interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes'
|
|
|
1584
1774
|
readonly globals: readonly GlobalConfig[];
|
|
1585
1775
|
readonly endpoints: readonly Endpoint[];
|
|
1586
1776
|
readonly commands: readonly CliCommand[];
|
|
1777
|
+
readonly fieldTypes: readonly FieldTypeDefinition[];
|
|
1587
1778
|
}
|
|
1588
1779
|
/**
|
|
1589
1780
|
* Declares the Easy CMS config. Returns it unchanged, keeping literal types
|
|
@@ -1657,58 +1848,6 @@ declare function copyDatabase(source: Database, target: Database, options?: {
|
|
|
1657
1848
|
onTable?: (progress: CopyProgress) => void;
|
|
1658
1849
|
}): Promise<CopyResult>;
|
|
1659
1850
|
|
|
1660
|
-
interface ConfigIssue {
|
|
1661
|
-
/** Where the problem is, e.g. `collections.posts.fields.slug.from`. */
|
|
1662
|
-
readonly path: string;
|
|
1663
|
-
readonly message: string;
|
|
1664
|
-
/** How to fix it. */
|
|
1665
|
-
readonly hint?: string;
|
|
1666
|
-
}
|
|
1667
|
-
declare class ConfigError extends Error {
|
|
1668
|
-
readonly issues: readonly ConfigIssue[];
|
|
1669
|
-
constructor(issues: readonly ConfigIssue[]);
|
|
1670
|
-
static format(issues: readonly ConfigIssue[]): string;
|
|
1671
|
-
}
|
|
1672
|
-
/** Base class for errors that map to an HTTP status in the REST API. */
|
|
1673
|
-
declare class EasyCMSError extends Error {
|
|
1674
|
-
readonly status: number;
|
|
1675
|
-
constructor(message: string, status: number);
|
|
1676
|
-
}
|
|
1677
|
-
interface FieldError {
|
|
1678
|
-
/** Dotted path, e.g. `links.0.url`. */
|
|
1679
|
-
readonly field: string;
|
|
1680
|
-
readonly message: string;
|
|
1681
|
-
}
|
|
1682
|
-
declare class ValidationError extends EasyCMSError {
|
|
1683
|
-
readonly errors: readonly FieldError[];
|
|
1684
|
-
constructor(collection: string, errors: readonly FieldError[]);
|
|
1685
|
-
}
|
|
1686
|
-
declare class NotFoundError extends EasyCMSError {
|
|
1687
|
-
constructor(collection: string, id: unknown);
|
|
1688
|
-
}
|
|
1689
|
-
/** A bad `where`, `sort` or unknown collection/global. */
|
|
1690
|
-
declare class QueryError extends EasyCMSError {
|
|
1691
|
-
constructor(message: string);
|
|
1692
|
-
}
|
|
1693
|
-
/** The database schema does not match the config (pending or missing migrations). */
|
|
1694
|
-
declare class SchemaError extends EasyCMSError {
|
|
1695
|
-
constructor(message: string);
|
|
1696
|
-
}
|
|
1697
|
-
/** Not logged in, or the session is invalid. */
|
|
1698
|
-
declare class UnauthorizedError extends EasyCMSError {
|
|
1699
|
-
constructor(message?: string);
|
|
1700
|
-
}
|
|
1701
|
-
/** Logged in, but not allowed to do this. */
|
|
1702
|
-
declare class ForbiddenError extends EasyCMSError {
|
|
1703
|
-
constructor(message?: string);
|
|
1704
|
-
}
|
|
1705
|
-
declare class TooManyRequestsError extends EasyCMSError {
|
|
1706
|
-
constructor(message: string);
|
|
1707
|
-
}
|
|
1708
|
-
declare class PayloadTooLargeError extends EasyCMSError {
|
|
1709
|
-
constructor(message: string);
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
1851
|
type Localization = Required<LocalizationConfig>;
|
|
1713
1852
|
|
|
1714
1853
|
type Data = Record<string, unknown>;
|
|
@@ -1871,11 +2010,14 @@ interface AdminField {
|
|
|
1871
2010
|
localized?: boolean;
|
|
1872
2011
|
/** Shown in the edit page's side panel. */
|
|
1873
2012
|
position?: 'sidebar';
|
|
1874
|
-
/** Components from admin modules: instead of the input,
|
|
2013
|
+
/** Components from admin modules: instead of the input, below the field, and in lists. */
|
|
1875
2014
|
admin?: {
|
|
1876
2015
|
component?: AdminComponentRef;
|
|
1877
2016
|
after?: AdminComponentRef[];
|
|
2017
|
+
cell?: AdminComponentRef;
|
|
1878
2018
|
};
|
|
2019
|
+
/** A field of an added type (`fieldTypes`), e.g. `color`; `type` is the base it's stored as. */
|
|
2020
|
+
customType?: string;
|
|
1879
2021
|
}
|
|
1880
2022
|
interface AdminCollection {
|
|
1881
2023
|
slug: string;
|
|
@@ -1979,4 +2121,4 @@ declare const BUILTIN_COLLECTIONS: readonly ["users", "media"];
|
|
|
1979
2121
|
/** Checks a config and returns every problem found. An empty array means it is valid. */
|
|
1980
2122
|
declare function validateConfig(config: Config): ConfigIssue[];
|
|
1981
2123
|
|
|
1982
|
-
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminSchema, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ApiKeyContext, type ApiKeyGlobalOperation, type ApiKeyOperation, type ApiKeyPermissions, type ArrayField, Auth, type AuthConfig, type AuthUser, BUILTIN_COLLECTIONS, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinApiKeysCollection, type BuiltinMediaCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CliCommand, type CollectionAccess, type CollectionAdmin, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionListAdmin, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_PASSWORD_EMAILS, DEFAULT_TOKEN_EXPIRATION, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DepthOptions, EXTENSIONS, EasyCMS, EasyCMSError, type EmailAdapter, type EmailField, type EmailMessage, type Endpoint, type EndpointRequest, FIELD_TYPES, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldType, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, type FilterOptions, type FilterOptionsArgs, type FindArgs, type FindOptions, ForbiddenError, type GlobalAccess, type GlobalConfig, type GlobalDocument, type GlobalHooks, type GlobalInput, type GlobalSlug, type GroupField, type ID, INTERNAL_COLLECTIONS, type ImageSize, type InferCollection, type InferGlobal, type JsonField, type Label, type LivePreview, type LoadConfigOptions, type LocalStorageOptions, type LocalizationConfig, type Logger, type LoginArgs, MAX_DEPTH, MEDIA, MIN_PASSWORD_LENGTH, MIN_SECRET_LENGTH, type MediaDocument, type MediaSize, type MigrationInfo, NotFoundError, type NumberField, type Operation, type PaginatedDocs, type PasswordEmail, type PasswordEmailArgs, type PasswordEmailFn, type PasswordLinkOptions, PayloadTooLargeError, type Plugin, type PreviewTarget, type PreviewURL, QueryError, type RawDocument, type ReadOptions, type RelationshipField, type ResolvedConfig, type RestHandler, type RestHandlerOptions, type RichTextDocument, type RichTextField, type Role, type RoutesConfig, SESSION_COOKIE, type ScheduledJob, SchemaError, type SchemaMode, type SelectField, type SelectOption, type Session, type SlugField, type StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, UnauthorizedError, type UpdateInput, type UpdateOptions, type UploadConfig, type UploadField, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, configSignature, consoleEmail, consoleLogger, copyDatabase, createEasyCMS, createRestHandler, createRootEndpointHandler, defineConfig, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isHasMany, isLoggedIn, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, mimeAllowedBy, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|
|
2124
|
+
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminSchema, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ApiKeyContext, type ApiKeyGlobalOperation, type ApiKeyOperation, type ApiKeyPermissions, type ArrayField, Auth, type AuthConfig, type AuthUser, BUILTIN_COLLECTIONS, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinApiKeysCollection, type BuiltinMediaCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CliCommand, type CollectionAccess, type CollectionAdmin, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionListAdmin, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, type CustomField, type CustomFieldTypes, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_PASSWORD_EMAILS, DEFAULT_TOKEN_EXPIRATION, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DepthOptions, type DocumentOf, EXTENSIONS, EasyCMS, EasyCMSError, type EmailAdapter, type EmailField, type EmailMessage, type Endpoint, type EndpointRequest, FIELD_TYPES, FIELD_TYPE_BASES, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldType, type FieldTypeBase, type FieldTypeDefinition, type FieldTypeValidateContext, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, type FilterOptions, type FilterOptionsArgs, type FindArgs, type FindOptions, ForbiddenError, type GlobalAccess, type GlobalConfig, type GlobalDocument, type GlobalHooks, type GlobalInput, type GlobalSlug, type GroupField, type ID, INTERNAL_COLLECTIONS, type ImageSize, type InferCollection, type InferGlobal, type JsonField, type Label, type LivePreview, type LoadConfigOptions, type LocalStorageOptions, type LocalizationConfig, type Logger, type LoginArgs, MAX_DEPTH, MEDIA, MIN_PASSWORD_LENGTH, MIN_SECRET_LENGTH, type MediaDocument, type MediaSize, type MigrationInfo, NotFoundError, type NumberField, type Operation, type PaginatedDocs, type PasswordEmail, type PasswordEmailArgs, type PasswordEmailFn, type PasswordLinkOptions, PayloadTooLargeError, type Plugin, type PluginTypes, type PreviewTarget, type PreviewURL, QueryError, type RawDocument, type ReadOptions, type RelationshipField, type ResolvedConfig, type RestHandler, type RestHandlerOptions, type RichTextDocument, type RichTextField, type Role, type RoutesConfig, SESSION_COOKIE, type ScheduledJob, SchemaError, type SchemaMode, type SelectField, type SelectOption, type Session, type SlugField, type SlugOf, type StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, type TypedPlugin, UnauthorizedError, type UpdateInput, type UpdateOptions, type UploadConfig, type UploadField, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, configSignature, consoleEmail, consoleLogger, copyDatabase, createEasyCMS, createRestHandler, createRootEndpointHandler, defineConfig, defineFieldType, definePlugin, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isHasMany, isLoggedIn, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, mimeAllowedBy, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|
package/dist/index.js
CHANGED
|
@@ -1140,6 +1140,9 @@ var ADMIN_ICONS = [
|
|
|
1140
1140
|
"camera",
|
|
1141
1141
|
"key"
|
|
1142
1142
|
];
|
|
1143
|
+
function definePlugin(plugin) {
|
|
1144
|
+
return plugin;
|
|
1145
|
+
}
|
|
1143
1146
|
function defineConfig(config) {
|
|
1144
1147
|
return config;
|
|
1145
1148
|
}
|
|
@@ -1666,6 +1669,126 @@ var Mailer = class {
|
|
|
1666
1669
|
}
|
|
1667
1670
|
};
|
|
1668
1671
|
|
|
1672
|
+
// src/field-types.ts
|
|
1673
|
+
var FIELD_TYPE_BASES = [
|
|
1674
|
+
"text",
|
|
1675
|
+
"textarea",
|
|
1676
|
+
"email",
|
|
1677
|
+
"number",
|
|
1678
|
+
"boolean",
|
|
1679
|
+
"date",
|
|
1680
|
+
"json"
|
|
1681
|
+
];
|
|
1682
|
+
function defineFieldType(definition) {
|
|
1683
|
+
return definition;
|
|
1684
|
+
}
|
|
1685
|
+
var NAME = /^[a-z][a-zA-Z0-9]*$/;
|
|
1686
|
+
function fieldTypeModules(config) {
|
|
1687
|
+
const list = config.fieldTypes;
|
|
1688
|
+
if (!Array.isArray(list)) return [];
|
|
1689
|
+
return list.map((t) => t?.admin?.module).filter((m) => typeof m === "string");
|
|
1690
|
+
}
|
|
1691
|
+
function applyFieldTypes(config) {
|
|
1692
|
+
const issues = [];
|
|
1693
|
+
const list = config.fieldTypes;
|
|
1694
|
+
if (list === void 0) return { config, issues };
|
|
1695
|
+
if (!Array.isArray(list)) {
|
|
1696
|
+
issues.push({ path: "fieldTypes", message: "must be an array of field types" });
|
|
1697
|
+
return { config, issues };
|
|
1698
|
+
}
|
|
1699
|
+
const types = /* @__PURE__ */ new Map();
|
|
1700
|
+
for (const [i, type] of list.entries()) {
|
|
1701
|
+
const path = `fieldTypes[${i}]`;
|
|
1702
|
+
if (!type || typeof type.name !== "string" || !NAME.test(type.name)) {
|
|
1703
|
+
issues.push({ path: `${path}.name`, message: 'must be a lowercase name like "color"' });
|
|
1704
|
+
} else if (FIELD_TYPES.includes(type.name)) {
|
|
1705
|
+
issues.push({ path: `${path}.name`, message: `"${type.name}" is a built-in field type` });
|
|
1706
|
+
} else if (types.has(type.name)) {
|
|
1707
|
+
issues.push({ path: `${path}.name`, message: `"${type.name}" is listed twice` });
|
|
1708
|
+
} else if (!FIELD_TYPE_BASES.includes(type.base)) {
|
|
1709
|
+
issues.push({
|
|
1710
|
+
path: `${path}.base`,
|
|
1711
|
+
message: `must be one of ${FIELD_TYPE_BASES.join(", ")}`
|
|
1712
|
+
});
|
|
1713
|
+
} else {
|
|
1714
|
+
types.set(type.name, type);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
if (types.size === 0) return { config, issues };
|
|
1718
|
+
const component = (tag, field, props) => {
|
|
1719
|
+
if (!tag) return void 0;
|
|
1720
|
+
const options = Object.fromEntries(
|
|
1721
|
+
(props ?? []).filter((key2) => field[key2] !== void 0).map((key2) => [key2, field[key2]])
|
|
1722
|
+
);
|
|
1723
|
+
return Object.keys(options).length ? { tag, props: options } : tag;
|
|
1724
|
+
};
|
|
1725
|
+
const convert = (fields, path) => fields.map((field, i) => {
|
|
1726
|
+
const here = `${path}[${i}]`;
|
|
1727
|
+
const own = field;
|
|
1728
|
+
const type = types.get(own.type);
|
|
1729
|
+
if (!type) {
|
|
1730
|
+
if ((field.type === "group" || field.type === "array") && Array.isArray(field.fields))
|
|
1731
|
+
return { ...field, fields: convert(field.fields, `${here}.fields`) };
|
|
1732
|
+
if (field.type === "blocks" && Array.isArray(field.blocks))
|
|
1733
|
+
return {
|
|
1734
|
+
...field,
|
|
1735
|
+
blocks: field.blocks.map((block, b) => ({
|
|
1736
|
+
...block,
|
|
1737
|
+
fields: convert(block.fields, `${here}.blocks[${b}].fields`)
|
|
1738
|
+
}))
|
|
1739
|
+
};
|
|
1740
|
+
return field;
|
|
1741
|
+
}
|
|
1742
|
+
const problem = type.checkOptions?.(own);
|
|
1743
|
+
if (problem) issues.push({ path: here, message: problem });
|
|
1744
|
+
const ownValidate = own.validate;
|
|
1745
|
+
const check = type.validate;
|
|
1746
|
+
const admin = own.admin ?? {};
|
|
1747
|
+
const input = admin.component ?? component(type.admin?.component, own, type.admin?.props);
|
|
1748
|
+
const cell = admin.cell ?? component(type.admin?.cell, own, type.admin?.props);
|
|
1749
|
+
return {
|
|
1750
|
+
...own,
|
|
1751
|
+
type: type.base,
|
|
1752
|
+
customType: type.name,
|
|
1753
|
+
...check || ownValidate ? {
|
|
1754
|
+
validate: async (value, ctx) => {
|
|
1755
|
+
const empty = value === null || value === void 0 || value === "";
|
|
1756
|
+
if (check && !empty) {
|
|
1757
|
+
const result = await check(value, { ...ctx, field: own });
|
|
1758
|
+
if (result !== true) return result;
|
|
1759
|
+
}
|
|
1760
|
+
return ownValidate ? ownValidate(value, ctx) : true;
|
|
1761
|
+
}
|
|
1762
|
+
} : {},
|
|
1763
|
+
...input || cell ? {
|
|
1764
|
+
admin: {
|
|
1765
|
+
...admin,
|
|
1766
|
+
...input ? { component: input } : {},
|
|
1767
|
+
...cell ? { cell } : {}
|
|
1768
|
+
}
|
|
1769
|
+
} : {}
|
|
1770
|
+
};
|
|
1771
|
+
});
|
|
1772
|
+
return {
|
|
1773
|
+
config: {
|
|
1774
|
+
...config,
|
|
1775
|
+
...config.collections ? {
|
|
1776
|
+
collections: config.collections.map((c, i) => ({
|
|
1777
|
+
...c,
|
|
1778
|
+
fields: convert(c.fields, `collections[${i}].fields`)
|
|
1779
|
+
}))
|
|
1780
|
+
} : {},
|
|
1781
|
+
...config.globals ? {
|
|
1782
|
+
globals: config.globals.map((g, i) => ({
|
|
1783
|
+
...g,
|
|
1784
|
+
fields: convert(g.fields, `globals[${i}].fields`)
|
|
1785
|
+
}))
|
|
1786
|
+
} : {}
|
|
1787
|
+
},
|
|
1788
|
+
issues
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1669
1792
|
// src/load-config.ts
|
|
1670
1793
|
import { existsSync } from "fs";
|
|
1671
1794
|
import { resolve } from "path";
|
|
@@ -2323,9 +2446,10 @@ function validateField(field, path, siblings, collectionSlugs, add) {
|
|
|
2323
2446
|
if (typeof admin !== "object" || admin === null) {
|
|
2324
2447
|
add(`${path}.admin`, "must be an object", "e.g. admin: { component: 'ecms-color-picker' }");
|
|
2325
2448
|
} else {
|
|
2326
|
-
const { component, after } = admin;
|
|
2449
|
+
const { component, after, cell } = admin;
|
|
2327
2450
|
if (component !== void 0) validateComponent(component, `${path}.admin.component`, add);
|
|
2328
2451
|
if (after !== void 0) validateComponents(after, `${path}.admin.after`, add);
|
|
2452
|
+
if (cell !== void 0) validateComponent(cell, `${path}.admin.cell`, add);
|
|
2329
2453
|
}
|
|
2330
2454
|
}
|
|
2331
2455
|
switch (field.type) {
|
|
@@ -2590,6 +2714,15 @@ async function applyPlugins(input) {
|
|
|
2590
2714
|
if (typeof plugin !== "function") break;
|
|
2591
2715
|
config = await plugin(config);
|
|
2592
2716
|
}
|
|
2717
|
+
const modules = fieldTypeModules(config);
|
|
2718
|
+
if (modules.length > 0)
|
|
2719
|
+
config = {
|
|
2720
|
+
...config,
|
|
2721
|
+
admin: {
|
|
2722
|
+
...config.admin,
|
|
2723
|
+
modules: [.../* @__PURE__ */ new Set([...config.admin?.modules ?? [], ...modules])]
|
|
2724
|
+
}
|
|
2725
|
+
};
|
|
2593
2726
|
return config;
|
|
2594
2727
|
}
|
|
2595
2728
|
async function resolveConfig(input) {
|
|
@@ -2598,7 +2731,9 @@ async function resolveConfig(input) {
|
|
|
2598
2731
|
if (typeof config === "object" && config !== null && Array.isArray(config.collections ?? [])) {
|
|
2599
2732
|
config = withMedia(withUsers(config));
|
|
2600
2733
|
}
|
|
2601
|
-
const
|
|
2734
|
+
const applied = applyFieldTypes(config);
|
|
2735
|
+
config = applied.config;
|
|
2736
|
+
const issues = [...applied.issues, ...validateConfig(config)];
|
|
2602
2737
|
if (issues.length > 0) throw new ConfigError(issues);
|
|
2603
2738
|
const { plugins: _plugins, ...rest } = config;
|
|
2604
2739
|
const result = {
|
|
@@ -2646,7 +2781,8 @@ async function resolveConfig(input) {
|
|
|
2646
2781
|
],
|
|
2647
2782
|
globals: config.globals ?? [],
|
|
2648
2783
|
endpoints: config.endpoints ?? [],
|
|
2649
|
-
commands: config.commands ?? []
|
|
2784
|
+
commands: config.commands ?? [],
|
|
2785
|
+
fieldTypes: config.fieldTypes ?? []
|
|
2650
2786
|
};
|
|
2651
2787
|
resolved.add(result);
|
|
2652
2788
|
return result;
|
|
@@ -4737,12 +4873,14 @@ async function serializeFields(fields, update, localized) {
|
|
|
4737
4873
|
if (!await update.allows(field)) f.readOnly = true;
|
|
4738
4874
|
if (field.localized && localized) f.localized = true;
|
|
4739
4875
|
if (field.position === "sidebar") f.position = "sidebar";
|
|
4740
|
-
if (field.admin?.component || field.admin?.after?.length) {
|
|
4876
|
+
if (field.admin?.component || field.admin?.after?.length || field.admin?.cell) {
|
|
4741
4877
|
f.admin = {
|
|
4742
4878
|
...field.admin.component ? { component: componentRef(field.admin.component) } : {},
|
|
4743
|
-
...field.admin.after?.length ? { after: field.admin.after.map(componentRef) } : {}
|
|
4879
|
+
...field.admin.after?.length ? { after: field.admin.after.map(componentRef) } : {},
|
|
4880
|
+
...field.admin.cell ? { cell: componentRef(field.admin.cell) } : {}
|
|
4744
4881
|
};
|
|
4745
4882
|
}
|
|
4883
|
+
if (field.customType) f.customType = field.customType;
|
|
4746
4884
|
switch (field.type) {
|
|
4747
4885
|
case "text":
|
|
4748
4886
|
case "textarea":
|
|
@@ -5677,9 +5815,14 @@ function names(config) {
|
|
|
5677
5815
|
}
|
|
5678
5816
|
const globals = /* @__PURE__ */ new Map();
|
|
5679
5817
|
for (const g of config.globals) globals.set(g.slug, claim(pascal(g.slug), "Global"));
|
|
5680
|
-
|
|
5818
|
+
const custom = new Map(
|
|
5819
|
+
(config.fieldTypes ?? []).filter((t) => t.typescript).map((t) => [t.name, t.typescript])
|
|
5820
|
+
);
|
|
5821
|
+
return { collections, globals, custom };
|
|
5681
5822
|
}
|
|
5682
5823
|
function fieldType(field, n, indent) {
|
|
5824
|
+
const custom = field.customType ? n.custom.get(field.customType) : void 0;
|
|
5825
|
+
if (custom) return custom;
|
|
5683
5826
|
switch (field.type) {
|
|
5684
5827
|
case "text":
|
|
5685
5828
|
case "textarea":
|
|
@@ -5833,6 +5976,7 @@ export {
|
|
|
5833
5976
|
EasyCMS,
|
|
5834
5977
|
EasyCMSError,
|
|
5835
5978
|
FIELD_TYPES,
|
|
5979
|
+
FIELD_TYPE_BASES,
|
|
5836
5980
|
ForbiddenError,
|
|
5837
5981
|
INTERNAL_COLLECTIONS,
|
|
5838
5982
|
MAX_DEPTH,
|
|
@@ -5859,6 +6003,8 @@ export {
|
|
|
5859
6003
|
createRestHandler,
|
|
5860
6004
|
createRootEndpointHandler,
|
|
5861
6005
|
defineConfig,
|
|
6006
|
+
defineFieldType,
|
|
6007
|
+
definePlugin,
|
|
5862
6008
|
fillMissing,
|
|
5863
6009
|
findConfigFile,
|
|
5864
6010
|
generateSlugs,
|