@easy-cms/core 0.13.1 → 0.16.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 +1009 -881
- package/dist/index.js +298 -17
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -56,14 +56,6 @@ declare const anyone: Access;
|
|
|
56
56
|
declare const isLoggedIn: Access;
|
|
57
57
|
declare const isAdmin: Access;
|
|
58
58
|
|
|
59
|
-
/** What a preview token opens: one document of a collection, or one global. */
|
|
60
|
-
type PreviewTarget = {
|
|
61
|
-
readonly collection: string;
|
|
62
|
-
readonly id: string;
|
|
63
|
-
} | {
|
|
64
|
-
readonly global: string;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
59
|
interface Logger {
|
|
68
60
|
info(message: string): void;
|
|
69
61
|
warn(message: string): void;
|
|
@@ -169,8 +161,29 @@ interface Database {
|
|
|
169
161
|
* do that itself (SQLite). Others (Postgres) leave it out; use their own tools.
|
|
170
162
|
*/
|
|
171
163
|
readonly backup?: ((file: string) => Promise<void>) | undefined;
|
|
164
|
+
/** Raw table access for copying a whole database to another (`easy-cms copy`). */
|
|
165
|
+
readonly transfer?: DatabaseTransfer | undefined;
|
|
172
166
|
destroy(): Promise<void>;
|
|
173
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Reads and writes stored rows as they are, ids and hidden fields included, bypassing validation
|
|
170
|
+
* and hooks. Only for moving a database: `copyDatabase()`.
|
|
171
|
+
*/
|
|
172
|
+
interface DatabaseTransfer {
|
|
173
|
+
/**
|
|
174
|
+
* Identifies the tables and columns the config produces, the same for every database kind.
|
|
175
|
+
* Two databases can be copied into each other when their hashes match.
|
|
176
|
+
*/
|
|
177
|
+
readonly schemaHash: string;
|
|
178
|
+
/** Every table, in an order that is safe to write. */
|
|
179
|
+
tables(): string[];
|
|
180
|
+
count(table: string): Promise<number>;
|
|
181
|
+
/** Rows in a stable order (by primary key), `offset` and `limit` for batches. */
|
|
182
|
+
read(table: string, offset: number, limit: number): Promise<Record<string, unknown>[]>;
|
|
183
|
+
write(table: string, rows: readonly Record<string, unknown>[]): Promise<void>;
|
|
184
|
+
/** After all writes, e.g. moves id sequences past the copied ids so new documents fit. */
|
|
185
|
+
finish(): Promise<void>;
|
|
186
|
+
}
|
|
174
187
|
/** What `sqlite()` / `postgres()` return and what `config.db` holds. */
|
|
175
188
|
interface DatabaseAdapter {
|
|
176
189
|
readonly name: string;
|
|
@@ -314,959 +327,1056 @@ type Field = TextField | TextareaField | NumberField | BooleanField | DateField
|
|
|
314
327
|
type FieldType = Field['type'];
|
|
315
328
|
declare const FIELD_TYPES: readonly ["text", "textarea", "number", "boolean", "date", "email", "json", "select", "slug", "richText", "upload", "relationship", "array", "group", "blocks"];
|
|
316
329
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
readonly
|
|
330
|
+
/** What a preview token opens: one document of a collection, or one global. */
|
|
331
|
+
type PreviewTarget = {
|
|
332
|
+
readonly collection: string;
|
|
333
|
+
readonly id: string;
|
|
334
|
+
} | {
|
|
335
|
+
readonly global: string;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
interface LoginArgs {
|
|
339
|
+
readonly email: string;
|
|
340
|
+
readonly password: string;
|
|
341
|
+
/** Client IP, used together with the email for rate limiting. */
|
|
342
|
+
readonly ip?: string | undefined;
|
|
320
343
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
344
|
+
interface Session {
|
|
345
|
+
readonly user: AuthUser;
|
|
346
|
+
/** Signed session token, the value of the session cookie. */
|
|
347
|
+
readonly token: string;
|
|
348
|
+
/** Send back in the `X-CSRF-Token` header on cookie-authenticated writes. */
|
|
349
|
+
readonly csrfToken: string;
|
|
350
|
+
readonly expiresAt: string;
|
|
351
|
+
}
|
|
352
|
+
/** Login, logout and session verification. Available as `cms.auth`. */
|
|
353
|
+
declare class Auth {
|
|
354
|
+
private readonly cms;
|
|
355
|
+
constructor(cms: EasyCMS);
|
|
356
|
+
private get db();
|
|
357
|
+
private get config();
|
|
358
|
+
/** Checks credentials and starts a session. Throws `UnauthorizedError` or `TooManyRequestsError`. */
|
|
359
|
+
login(args: LoginArgs): Promise<Session>;
|
|
360
|
+
/** Creates the first admin. Only works while there are no users. */
|
|
361
|
+
registerFirstUser(args: {
|
|
362
|
+
email: string;
|
|
363
|
+
password: string;
|
|
364
|
+
name?: string;
|
|
365
|
+
}): Promise<Session>;
|
|
366
|
+
hasUsers(): Promise<boolean>;
|
|
367
|
+
/** Ends the session for a signed token. Unknown tokens are ignored. */
|
|
368
|
+
logout(signedToken: string): Promise<void>;
|
|
333
369
|
/**
|
|
334
|
-
*
|
|
335
|
-
*
|
|
370
|
+
* Returns the user for a signed session token, or for an API key (`ecms_…`, with `apiKey` set
|
|
371
|
+
* on the user), or `null` if it is invalid or expired.
|
|
336
372
|
*/
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
|
|
373
|
+
verify(signedToken: string | undefined | null): Promise<AuthUser | null>;
|
|
374
|
+
/** The CSRF token clients must echo for a signed session token. */
|
|
375
|
+
csrfFor(signedToken: string): string | undefined;
|
|
376
|
+
/** Starts a new session for a user, e.g. after they changed their own password. */
|
|
377
|
+
createSession(userId: ID): Promise<Session>;
|
|
378
|
+
/** Signs a user out everywhere. */
|
|
379
|
+
revokeSessions(userId: ID): Promise<void>;
|
|
380
|
+
/** The owner of an API key, limited by the key; `null` when unknown, expired or disabled. */
|
|
381
|
+
private verifyApiKey;
|
|
382
|
+
private startSession;
|
|
383
|
+
private findSession;
|
|
384
|
+
private toAuthUser;
|
|
385
|
+
private deleteExpiredSessions;
|
|
386
|
+
private windowStart;
|
|
387
|
+
private checkRateLimit;
|
|
388
|
+
private recordFailure;
|
|
389
|
+
private clearFailures;
|
|
342
390
|
}
|
|
343
|
-
/** Stores files on the local disk. Suitable for servers with a persistent filesystem. */
|
|
344
|
-
declare function localStorage(options?: LocalStorageOptions): StorageAdapter;
|
|
345
391
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
type
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
readonly
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
readonly
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
392
|
+
type Simplify<T> = {
|
|
393
|
+
[K in keyof T]: T[K];
|
|
394
|
+
} & {};
|
|
395
|
+
type OptionValue<O> = O extends string ? O : O extends {
|
|
396
|
+
readonly value: infer V;
|
|
397
|
+
} ? V : never;
|
|
398
|
+
type SelectValue<F> = F extends {
|
|
399
|
+
readonly options: readonly (infer O extends SelectOption)[];
|
|
400
|
+
} ? F extends {
|
|
401
|
+
readonly hasMany: true;
|
|
402
|
+
} ? OptionValue<O>[] : OptionValue<O> : never;
|
|
403
|
+
/** The built-in users collection as seen by the type system (hidden fields left out). */
|
|
404
|
+
interface BuiltinUsersCollection {
|
|
405
|
+
readonly slug: 'users';
|
|
406
|
+
readonly fields: readonly [
|
|
407
|
+
{
|
|
408
|
+
readonly name: 'email';
|
|
409
|
+
readonly type: 'email';
|
|
410
|
+
readonly required: true;
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
readonly name: 'name';
|
|
414
|
+
readonly type: 'text';
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
readonly name: 'role';
|
|
418
|
+
readonly type: 'select';
|
|
419
|
+
readonly options: readonly string[];
|
|
420
|
+
readonly required: true;
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
readonly name: 'active';
|
|
424
|
+
readonly type: 'boolean';
|
|
425
|
+
}
|
|
426
|
+
];
|
|
377
427
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
428
|
+
/** The built-in API keys collection (`apiKeys: true`); the key's hash is left out. */
|
|
429
|
+
interface BuiltinApiKeysCollection {
|
|
430
|
+
readonly slug: 'api-keys';
|
|
431
|
+
readonly fields: readonly [
|
|
432
|
+
{
|
|
433
|
+
readonly name: 'name';
|
|
434
|
+
readonly type: 'text';
|
|
435
|
+
readonly required: true;
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
readonly name: 'permissions';
|
|
439
|
+
readonly type: 'json';
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
readonly name: 'expiresAt';
|
|
443
|
+
readonly type: 'date';
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
readonly name: 'prefix';
|
|
447
|
+
readonly type: 'text';
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
readonly name: 'user';
|
|
451
|
+
readonly type: 'relationship';
|
|
452
|
+
readonly to: 'users';
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
readonly name: 'lastUsedAt';
|
|
456
|
+
readonly type: 'date';
|
|
457
|
+
}
|
|
458
|
+
];
|
|
389
459
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
type
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
type AfterChangeHook = (args: HookBase & {
|
|
401
|
-
doc: Data$2;
|
|
402
|
-
operation: Operation;
|
|
403
|
-
previousDoc?: Data$2;
|
|
404
|
-
}) => MaybePromise<void>;
|
|
405
|
-
type BeforeDeleteHook = (args: HookBase & {
|
|
406
|
-
id: ID;
|
|
407
|
-
}) => MaybePromise<void>;
|
|
408
|
-
type AfterDeleteHook = (args: HookBase & {
|
|
409
|
-
id: ID;
|
|
410
|
-
doc: Data$2;
|
|
411
|
-
}) => MaybePromise<void>;
|
|
412
|
-
type AfterReadHook = (args: HookBase & {
|
|
413
|
-
doc: Data$2;
|
|
414
|
-
}) => MaybePromise<Data$2 | undefined>;
|
|
415
|
-
interface CollectionHooks {
|
|
416
|
-
readonly beforeValidate?: readonly BeforeValidateHook[];
|
|
417
|
-
readonly beforeChange?: readonly BeforeChangeHook[];
|
|
418
|
-
readonly afterChange?: readonly AfterChangeHook[];
|
|
419
|
-
readonly beforeDelete?: readonly BeforeDeleteHook[];
|
|
420
|
-
readonly afterDelete?: readonly AfterDeleteHook[];
|
|
421
|
-
readonly afterRead?: readonly AfterReadHook[];
|
|
460
|
+
/** `api-keys` when the config turns API keys on. */
|
|
461
|
+
type ApiKeysOf<C extends Config> = C extends {
|
|
462
|
+
readonly apiKeys: true;
|
|
463
|
+
} ? BuiltinApiKeysCollection : never;
|
|
464
|
+
/** Every collection of a config, including the built-in ones. */
|
|
465
|
+
type AllCollections<C extends Config> = NonNullable<C['collections']>[number] | BuiltinUsersCollection | BuiltinMediaCollection | ApiKeysOf<C>;
|
|
466
|
+
/** The built-in media collection; its documents are typed as `MediaDocument`. */
|
|
467
|
+
interface BuiltinMediaCollection {
|
|
468
|
+
readonly slug: 'media';
|
|
469
|
+
readonly fields: readonly [];
|
|
422
470
|
}
|
|
423
|
-
|
|
424
|
-
readonly
|
|
425
|
-
|
|
426
|
-
|
|
471
|
+
type CollectionBySlug<C extends Config, S> = Extract<AllCollections<C>, {
|
|
472
|
+
readonly slug: S;
|
|
473
|
+
}>;
|
|
474
|
+
/** Relationships are ids at depth 0 and documents when populated. */
|
|
475
|
+
type RelationValue<C extends Config, S> = [CollectionBySlug<C, S>] extends [never] ? ID | Record<string, unknown> : ID | CollectionDocument<C, S & string>;
|
|
476
|
+
interface MediaSize {
|
|
477
|
+
filename: string;
|
|
478
|
+
width: number;
|
|
479
|
+
height: number;
|
|
480
|
+
filesize: number;
|
|
481
|
+
url: string;
|
|
427
482
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
483
|
+
/** A document of the built-in `media` collection, as returned by the API. */
|
|
484
|
+
interface MediaDocument {
|
|
485
|
+
id: ID;
|
|
486
|
+
filename: string;
|
|
487
|
+
originalName?: string | null;
|
|
488
|
+
mimeType: string;
|
|
489
|
+
filesize: number;
|
|
490
|
+
width?: number | null;
|
|
491
|
+
height?: number | null;
|
|
492
|
+
alt?: string | null;
|
|
493
|
+
/** Public URL of the file. */
|
|
494
|
+
url: string;
|
|
495
|
+
/** Resized copies, when `upload.imageSizes` is set and sharp is installed. */
|
|
496
|
+
sizes: Record<string, MediaSize>;
|
|
497
|
+
createdAt: string;
|
|
498
|
+
updatedAt: string;
|
|
499
|
+
[field: string]: unknown;
|
|
435
500
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
readonly
|
|
442
|
-
|
|
443
|
-
readonly
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
readonly
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
readonly
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
readonly
|
|
457
|
-
readonly
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
readonly
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
readonly
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
501
|
+
type FieldValue<F extends Field, C extends Config = Config> = F extends {
|
|
502
|
+
readonly type: 'text' | 'textarea' | 'email' | 'slug' | 'date';
|
|
503
|
+
} ? string : F extends {
|
|
504
|
+
readonly type: 'number';
|
|
505
|
+
} ? number : F extends {
|
|
506
|
+
readonly type: 'boolean';
|
|
507
|
+
} ? boolean : F extends {
|
|
508
|
+
readonly type: 'json';
|
|
509
|
+
} ? unknown : F extends {
|
|
510
|
+
readonly type: 'select';
|
|
511
|
+
} ? SelectValue<F> : F extends {
|
|
512
|
+
readonly type: 'richText';
|
|
513
|
+
} ? RichTextDocument : F extends {
|
|
514
|
+
readonly type: 'upload';
|
|
515
|
+
} ? ID | MediaDocument : F extends {
|
|
516
|
+
readonly type: 'relationship';
|
|
517
|
+
readonly to: infer S;
|
|
518
|
+
} ? F extends {
|
|
519
|
+
readonly hasMany: true;
|
|
520
|
+
} ? RelationValue<C, S>[] : RelationValue<C, S> : F extends {
|
|
521
|
+
readonly type: 'array';
|
|
522
|
+
readonly fields: infer Sub extends readonly Field[];
|
|
523
|
+
} ? Simplify<FieldsValue<Sub, C> & {
|
|
524
|
+
id: string;
|
|
525
|
+
}>[] : F extends {
|
|
526
|
+
readonly type: 'group';
|
|
527
|
+
readonly fields: infer Sub extends readonly Field[];
|
|
528
|
+
} ? FieldsValue<Sub, C> : F extends {
|
|
529
|
+
readonly type: 'blocks';
|
|
530
|
+
readonly blocks: infer B extends readonly Block[];
|
|
531
|
+
} ? BlockValue<B[number], C>[] : never;
|
|
532
|
+
/** One row of a `blocks` field: the block's fields plus `id` and `blockType` (distributes over kinds). */
|
|
533
|
+
type BlockValue<B, C extends Config> = B extends {
|
|
534
|
+
readonly slug: infer S;
|
|
535
|
+
readonly fields: infer Sub extends readonly Field[];
|
|
536
|
+
} ? Simplify<FieldsValue<Sub, C> & {
|
|
537
|
+
id: string;
|
|
538
|
+
blockType: S;
|
|
539
|
+
}> : never;
|
|
540
|
+
type BlockInput<B> = B extends {
|
|
541
|
+
readonly slug: infer S;
|
|
542
|
+
readonly fields: infer Sub extends readonly Field[];
|
|
543
|
+
} ? Simplify<FieldsInput<Sub> & {
|
|
544
|
+
id?: string;
|
|
545
|
+
blockType: S;
|
|
546
|
+
}> : never;
|
|
547
|
+
type Visible<Fs extends readonly Field[]> = Exclude<Fs[number], {
|
|
548
|
+
readonly hidden: true;
|
|
549
|
+
}>;
|
|
550
|
+
/** Always set when read: required fields, and lists and groups (empty `[]` / `{}` otherwise). */
|
|
551
|
+
type Present = {
|
|
552
|
+
readonly required: true;
|
|
553
|
+
} | {
|
|
554
|
+
readonly type: 'array' | 'blocks' | 'group';
|
|
555
|
+
} | {
|
|
556
|
+
readonly hasMany: true;
|
|
557
|
+
};
|
|
558
|
+
type RequiredFields<Fs extends readonly Field[]> = Extract<Visible<Fs>, Present>;
|
|
559
|
+
type OptionalFields<Fs extends readonly Field[]> = Exclude<Visible<Fs>, Present>;
|
|
560
|
+
/** The shape of the data described by a list of fields. */
|
|
561
|
+
type FieldsValue<Fs extends readonly Field[], C extends Config = Config> = Simplify<{
|
|
562
|
+
-readonly [F in RequiredFields<Fs> as F['name']]: FieldValue<F, C>;
|
|
563
|
+
} & {
|
|
564
|
+
-readonly [F in OptionalFields<Fs> as F['name']]?: FieldValue<F, C> | null;
|
|
565
|
+
}>;
|
|
566
|
+
type SystemFields<T extends {
|
|
473
567
|
readonly drafts?: boolean;
|
|
568
|
+
}> = {
|
|
569
|
+
id: ID;
|
|
570
|
+
createdAt: string;
|
|
571
|
+
updatedAt: string;
|
|
572
|
+
} & (T extends {
|
|
573
|
+
readonly drafts: true;
|
|
574
|
+
} ? {
|
|
575
|
+
status: 'draft' | 'published';
|
|
576
|
+
} : unknown);
|
|
577
|
+
type InferCollection<T extends CollectionConfig, C extends Config = Config> = Simplify<SystemFields<T> & FieldsValue<T['fields'], C>>;
|
|
578
|
+
type InferGlobal<T extends GlobalConfig, C extends Config = Config> = Simplify<
|
|
579
|
+
/** `null` until the global is saved for the first time. */
|
|
580
|
+
{
|
|
581
|
+
updatedAt: string | null;
|
|
582
|
+
} & (T extends {
|
|
583
|
+
readonly drafts: true;
|
|
584
|
+
} ? {
|
|
585
|
+
status: 'draft' | 'published';
|
|
586
|
+
} : unknown) & FieldsValue<T['fields'], C>>;
|
|
587
|
+
type CollectionSlug<C extends Config> = NonNullable<C['collections']>[number]['slug'] | 'users' | 'media' | ApiKeysOf<C>['slug'];
|
|
588
|
+
type GlobalSlug<C extends Config> = NonNullable<C['globals']>[number]['slug'];
|
|
589
|
+
/** Document type of a collection, e.g. `CollectionDocument<typeof config, 'posts'>`. */
|
|
590
|
+
type CollectionDocument<C extends Config, S extends CollectionSlug<C>> = S extends 'media' ? MediaDocument : InferCollection<CollectionBySlug<C, S>, C>;
|
|
591
|
+
/** Data type of a global, e.g. `GlobalDocument<typeof config, 'site'>`. */
|
|
592
|
+
type GlobalDocument<C extends Config, S extends GlobalSlug<C>> = InferGlobal<Extract<NonNullable<C['globals']>[number], {
|
|
593
|
+
readonly slug: S;
|
|
594
|
+
}>, C>;
|
|
595
|
+
type InputValue<F extends Field> = F extends {
|
|
596
|
+
readonly type: 'relationship';
|
|
597
|
+
} ? F extends {
|
|
598
|
+
readonly hasMany: true;
|
|
599
|
+
} ? readonly ID[] : ID : F extends {
|
|
600
|
+
readonly type: 'select';
|
|
601
|
+
readonly hasMany: true;
|
|
602
|
+
} ? Readonly<SelectValue<F>> : F extends {
|
|
603
|
+
readonly type: 'upload';
|
|
604
|
+
} ? ID : F extends {
|
|
605
|
+
readonly type: 'date';
|
|
606
|
+
} ? string | Date : F extends {
|
|
607
|
+
readonly type: 'array';
|
|
608
|
+
readonly fields: infer Sub extends readonly Field[];
|
|
609
|
+
} ? readonly Simplify<FieldsInput<Sub> & {
|
|
610
|
+
id?: string;
|
|
611
|
+
}>[] : F extends {
|
|
612
|
+
readonly type: 'group';
|
|
613
|
+
readonly fields: infer Sub extends readonly Field[];
|
|
614
|
+
} ? FieldsInput<Sub> : F extends {
|
|
615
|
+
readonly type: 'blocks';
|
|
616
|
+
readonly blocks: infer B extends readonly Block[];
|
|
617
|
+
} ? readonly BlockInput<B[number]>[] : FieldValue<F>;
|
|
618
|
+
/** Required fields must be given, unless Easy CMS can fill them (default value, slug from another field). */
|
|
619
|
+
type NeedsInput<F extends Field> = F extends {
|
|
620
|
+
readonly required: true;
|
|
621
|
+
} ? F extends {
|
|
622
|
+
readonly defaultValue: unknown;
|
|
623
|
+
} ? never : F extends {
|
|
624
|
+
readonly type: 'slug';
|
|
625
|
+
readonly from: string;
|
|
626
|
+
} ? never : F : never;
|
|
627
|
+
type FieldsInput<Fs extends readonly Field[]> = Simplify<{
|
|
628
|
+
-readonly [F in NeedsInput<Visible<Fs>> as F['name']]: InputValue<F>;
|
|
629
|
+
} & {
|
|
630
|
+
-readonly [F in Exclude<Visible<Fs>, NeedsInput<Visible<Fs>>> as F['name']]?: InputValue<F> | null;
|
|
631
|
+
}>;
|
|
632
|
+
type StatusInput<T> = T extends {
|
|
633
|
+
readonly drafts: true;
|
|
634
|
+
} ? {
|
|
635
|
+
status?: 'draft' | 'published';
|
|
636
|
+
} : unknown;
|
|
637
|
+
/** Users are created with a password, which is hashed and never returned. */
|
|
638
|
+
type PasswordInput<S, Required extends boolean> = S extends 'users' ? Required extends true ? {
|
|
639
|
+
password: string;
|
|
640
|
+
} : {
|
|
641
|
+
password?: string;
|
|
642
|
+
} : unknown;
|
|
643
|
+
/** Data accepted by `create`, e.g. `CreateInput<typeof config, 'posts'>`. */
|
|
644
|
+
type CreateInput<C extends Config, S extends CollectionSlug<C>> = Simplify<FieldsInput<CollectionBySlug<C, S>['fields']> & StatusInput<CollectionBySlug<C, S>> & PasswordInput<S, true>>;
|
|
645
|
+
/** Data accepted by `update`: any subset of `CreateInput`. */
|
|
646
|
+
type UpdateInput<C extends Config, S extends CollectionSlug<C>> = Partial<CreateInput<C, S>>;
|
|
647
|
+
/** Data accepted by `updateGlobal`. */
|
|
648
|
+
type GlobalInput<C extends Config, S extends GlobalSlug<C>> = Partial<Simplify<FieldsInput<Extract<NonNullable<C['globals']>[number], {
|
|
649
|
+
readonly slug: S;
|
|
650
|
+
}>['fields']> & StatusInput<Extract<NonNullable<C['globals']>[number], {
|
|
651
|
+
readonly slug: S;
|
|
652
|
+
}>>>>;
|
|
653
|
+
|
|
654
|
+
interface StoredFile {
|
|
655
|
+
readonly body: Uint8Array;
|
|
656
|
+
readonly size: number;
|
|
657
|
+
}
|
|
658
|
+
/** Where uploaded files live. Keys are generated by Easy CMS: `<name>-<random>.<ext>`. */
|
|
659
|
+
interface StorageAdapter {
|
|
660
|
+
readonly name: string;
|
|
661
|
+
/** Called once with the project root, before any other method. */
|
|
662
|
+
init?(args: {
|
|
663
|
+
cwd: string;
|
|
664
|
+
}): void | Promise<void>;
|
|
665
|
+
put(key: string, data: Uint8Array, options: {
|
|
666
|
+
contentType: string;
|
|
667
|
+
}): Promise<void>;
|
|
668
|
+
get(key: string): Promise<StoredFile | null>;
|
|
669
|
+
delete(key: string): Promise<void>;
|
|
474
670
|
/**
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
* it is published again.
|
|
671
|
+
* Public URL of a file, or `undefined` to serve it through the REST API
|
|
672
|
+
* (`<api>/media/file/<key>`), as the local adapter does.
|
|
478
673
|
*/
|
|
479
|
-
|
|
480
|
-
/** Allow scheduling publish/unpublish for a later time (needs `drafts`). */
|
|
481
|
-
readonly schedule?: boolean;
|
|
482
|
-
/** Live preview: the page that shows a document, e.g. `({ doc }) => \`/posts/${doc.slug}\``. */
|
|
483
|
-
readonly preview?: PreviewURL;
|
|
484
|
-
readonly access?: CollectionAccess;
|
|
485
|
-
readonly hooks?: CollectionHooks;
|
|
486
|
-
/** Custom admin components. */
|
|
487
|
-
readonly admin?: ContainerAdmin;
|
|
674
|
+
url?(key: string): string | undefined;
|
|
488
675
|
}
|
|
489
|
-
interface
|
|
490
|
-
|
|
491
|
-
readonly
|
|
492
|
-
/** Icon in the admin menu. Default `settings`. */
|
|
493
|
-
readonly icon?: AdminIcon;
|
|
494
|
-
readonly fields: readonly Field[];
|
|
495
|
-
readonly drafts?: boolean;
|
|
496
|
-
/** Keep a snapshot of every save. See `CollectionConfig.versions`. */
|
|
497
|
-
readonly versions?: boolean | VersionsConfig;
|
|
498
|
-
/** Allow scheduling publish/unpublish for a later time (needs `drafts`). */
|
|
499
|
-
readonly schedule?: boolean;
|
|
500
|
-
/** Live preview: the page that shows the global. See `CollectionConfig.preview`. */
|
|
501
|
-
readonly preview?: PreviewURL;
|
|
502
|
-
readonly access?: GlobalAccess;
|
|
503
|
-
readonly hooks?: GlobalHooks;
|
|
504
|
-
/** Custom admin components. */
|
|
505
|
-
readonly admin?: ContainerAdmin;
|
|
676
|
+
interface LocalStorageOptions {
|
|
677
|
+
/** Directory, relative to the project root. Default `uploads`. */
|
|
678
|
+
readonly dir?: string;
|
|
506
679
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
interface
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
680
|
+
/** Stores files on the local disk. Suitable for servers with a persistent filesystem. */
|
|
681
|
+
declare function localStorage(options?: LocalStorageOptions): StorageAdapter;
|
|
682
|
+
|
|
683
|
+
declare const DEFAULT_MAX_VERSIONS = 50;
|
|
684
|
+
/** One saved state of a document or global. */
|
|
685
|
+
interface VersionSummary {
|
|
686
|
+
readonly id: ID;
|
|
687
|
+
/** `draft` or `published` with drafts enabled, otherwise `null`. */
|
|
688
|
+
readonly status: 'draft' | 'published' | null;
|
|
689
|
+
/** The newest version: the document's current editable state. */
|
|
690
|
+
readonly latest: boolean;
|
|
691
|
+
/** Who saved it (user id), or `null` for saves without a user (scripts, seeds). */
|
|
692
|
+
readonly author: ID | null;
|
|
693
|
+
readonly createdAt: string;
|
|
519
694
|
}
|
|
520
|
-
interface
|
|
521
|
-
/**
|
|
522
|
-
readonly
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
695
|
+
interface Version<T = Record<string, unknown>> extends VersionSummary {
|
|
696
|
+
/** The document as it was saved (hidden fields such as password hashes are never kept). */
|
|
697
|
+
readonly data: T;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
type Data$2 = Record<string, unknown>;
|
|
701
|
+
type Slug<C extends Config> = string extends CollectionSlug<C> ? string : CollectionSlug<C>;
|
|
702
|
+
type GSlug<C extends Config> = string extends GlobalSlug<C> ? string : GlobalSlug<C>;
|
|
703
|
+
type Doc<C extends Config, S> = string extends CollectionSlug<C> ? RawDocument : CollectionDocument<C, S & CollectionSlug<C>>;
|
|
704
|
+
type Create<C extends Config, S> = string extends CollectionSlug<C> ? Data$2 : CreateInput<C, S & CollectionSlug<C>>;
|
|
705
|
+
type Update<C extends Config, S> = string extends CollectionSlug<C> ? Data$2 : UpdateInput<C, S & CollectionSlug<C>>;
|
|
706
|
+
type GDoc<C extends Config, S> = string extends GlobalSlug<C> ? Data$2 : GlobalDocument<C, S & GlobalSlug<C>>;
|
|
707
|
+
type GInput<C extends Config, S> = string extends GlobalSlug<C> ? Data$2 : GlobalInput<C, S & GlobalSlug<C>>;
|
|
708
|
+
interface AccessOptions {
|
|
527
709
|
/**
|
|
528
|
-
* The
|
|
529
|
-
*
|
|
710
|
+
* The Local API trusts its caller and skips access rules by default.
|
|
711
|
+
* Pass `false` (with `user`) to apply them, as the REST API does.
|
|
530
712
|
*/
|
|
531
|
-
readonly
|
|
713
|
+
readonly overrideAccess?: boolean;
|
|
714
|
+
/** The user to check access for. `null` = not logged in. */
|
|
715
|
+
readonly user?: AuthUser | null;
|
|
716
|
+
}
|
|
717
|
+
interface DepthOptions extends AccessOptions {
|
|
718
|
+
/** How many levels of relationships to populate. Default 1, maximum 3. */
|
|
719
|
+
readonly depth?: number;
|
|
532
720
|
/**
|
|
533
|
-
*
|
|
534
|
-
*
|
|
721
|
+
* With `localization`: the locale to read, and for writes the locale the data is in. Default:
|
|
722
|
+
* the default locale. `'all'` reads (and writes) localized fields as `{ [locale]: value }`.
|
|
535
723
|
*/
|
|
536
|
-
readonly
|
|
724
|
+
readonly locale?: string;
|
|
725
|
+
}
|
|
726
|
+
interface ReadOptions extends DepthOptions {
|
|
537
727
|
/**
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
* package export (`'@easy-cms/plugin-seo/admin'`) or a file path relative to the project root
|
|
541
|
-
* (`'./admin/color-picker.js'`), resolved on the server. A module must be one self-contained ES
|
|
542
|
-
* module file. Remote URLs are not allowed.
|
|
728
|
+
* Include drafts. By default only published documents of collections with
|
|
729
|
+
* `drafts: true` are returned, including when populating relationships (FR-DRF-04).
|
|
543
730
|
*/
|
|
544
|
-
readonly
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
/** Key in `media.sizes`, e.g. `thumbnail`. */
|
|
548
|
-
readonly name: string;
|
|
549
|
-
readonly width: number;
|
|
550
|
-
readonly height?: number;
|
|
551
|
-
/** How to fit when both width and height are set. Default `cover`. */
|
|
552
|
-
readonly fit?: 'cover' | 'contain' | 'inside';
|
|
553
|
-
}
|
|
554
|
-
interface UploadConfig {
|
|
555
|
-
/** Directory for uploaded files with the default local storage, relative to the project root. Default `uploads`. */
|
|
556
|
-
readonly dir?: string;
|
|
557
|
-
/** Maximum file size in bytes. Default 10 MB. */
|
|
558
|
-
readonly maxFileSize?: number;
|
|
559
|
-
/** Allowed MIME types, detected from file contents. `image/*` style wildcards are allowed. */
|
|
560
|
-
readonly mimeTypes?: readonly string[];
|
|
561
|
-
/** Where files are stored. Default: local disk in `dir`. */
|
|
562
|
-
readonly storage?: StorageAdapter;
|
|
563
|
-
/** Resized copies generated for images when `sharp` is installed. */
|
|
564
|
-
readonly imageSizes?: readonly ImageSize[];
|
|
731
|
+
readonly draft?: boolean;
|
|
732
|
+
/** Use the default locale's value when a localized value is empty. Default: `localization.fallback`. */
|
|
733
|
+
readonly fallbackLocale?: boolean;
|
|
565
734
|
}
|
|
566
|
-
interface
|
|
567
|
-
|
|
568
|
-
|
|
735
|
+
interface FindOptions extends ReadOptions {
|
|
736
|
+
readonly where?: Where;
|
|
737
|
+
/** Field path, `-` prefix for descending. Default `-createdAt`. */
|
|
738
|
+
readonly sort?: string | readonly string[];
|
|
739
|
+
/** Default 10. `0` returns every document. */
|
|
740
|
+
readonly limit?: number;
|
|
741
|
+
/** 1-based. Default 1. */
|
|
742
|
+
readonly page?: number;
|
|
569
743
|
}
|
|
570
|
-
interface
|
|
571
|
-
/**
|
|
572
|
-
readonly
|
|
573
|
-
/** Session lifetime in seconds. Default 7 days. */
|
|
574
|
-
readonly tokenExpiration?: number;
|
|
575
|
-
/** Failed logins allowed per email (and IP) within `lockWindow`. Default 5. */
|
|
576
|
-
readonly maxLoginAttempts?: number;
|
|
577
|
-
/** Window for `maxLoginAttempts`, in seconds. Default 15 minutes. */
|
|
578
|
-
readonly lockWindow?: number;
|
|
744
|
+
interface CreateEasyCMSOptions {
|
|
745
|
+
/** Project root. Default `process.cwd()`. */
|
|
746
|
+
readonly cwd?: string;
|
|
579
747
|
/**
|
|
580
|
-
*
|
|
581
|
-
*
|
|
748
|
+
* Run due scheduled jobs and webhook retries every minute in this process. Default: on when
|
|
749
|
+
* a collection or global has `schedule`, or `webhooks` is set. Turn it off where a cron calls
|
|
750
|
+
* `runJobs` (or `GET <api>/jobs/run`) instead.
|
|
582
751
|
*/
|
|
583
|
-
readonly
|
|
752
|
+
readonly scheduler?: boolean;
|
|
753
|
+
/** Default `verify` when `NODE_ENV=production`, otherwise `push`. */
|
|
754
|
+
readonly schema?: SchemaMode;
|
|
755
|
+
readonly logger?: Logger;
|
|
756
|
+
readonly interactive?: boolean;
|
|
584
757
|
}
|
|
585
|
-
/**
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
readonly
|
|
591
|
-
/**
|
|
592
|
-
readonly
|
|
593
|
-
|
|
594
|
-
readonly
|
|
595
|
-
|
|
596
|
-
json(): Promise<Record<string, unknown>>;
|
|
758
|
+
/** Connects to the database and returns the Local API. */
|
|
759
|
+
declare function createEasyCMS<const C extends Config>(config: C, options?: CreateEasyCMSOptions): Promise<EasyCMS<C>>;
|
|
760
|
+
/** A scheduled publish or unpublish. */
|
|
761
|
+
interface ScheduledJob {
|
|
762
|
+
readonly id: ID;
|
|
763
|
+
readonly action: 'publish' | 'unpublish';
|
|
764
|
+
/** ISO time the job runs at (or after). */
|
|
765
|
+
readonly runAt: string;
|
|
766
|
+
readonly state: 'pending' | 'done' | 'failed';
|
|
767
|
+
readonly error: string | null;
|
|
768
|
+
readonly author: ID | null;
|
|
597
769
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
770
|
+
declare class EasyCMS<C extends Config = Config> {
|
|
771
|
+
readonly config: ResolvedConfig;
|
|
772
|
+
readonly db: Database;
|
|
773
|
+
readonly logger: Logger;
|
|
774
|
+
/** Login, logout and session checks. */
|
|
775
|
+
readonly auth: Auth;
|
|
776
|
+
/** Where uploaded files are stored. */
|
|
777
|
+
readonly storage: StorageAdapter;
|
|
778
|
+
/** Project root: relative paths in the config (e.g. `admin.modules`) start here. */
|
|
779
|
+
readonly cwd: string;
|
|
780
|
+
private readonly versions;
|
|
781
|
+
private readonly webhooks;
|
|
782
|
+
constructor(config: ResolvedConfig, db: Database, logger?: Logger, storage?: StorageAdapter, cwd?: string);
|
|
783
|
+
find<S extends Slug<C>>(collection: S, options?: FindOptions): Promise<PaginatedDocs<Doc<C, S>>>;
|
|
784
|
+
findById<S extends Slug<C>>(collection: S, id: ID, options?: ReadOptions): Promise<Doc<C, S> | null>;
|
|
785
|
+
count<S extends Slug<C>>(collection: S, options?: {
|
|
786
|
+
where?: Where;
|
|
787
|
+
draft?: boolean;
|
|
788
|
+
locale?: string;
|
|
789
|
+
} & AccessOptions): Promise<number>;
|
|
790
|
+
create<S extends Slug<C>>(collection: S, data: Create<C, S>, options?: DepthOptions): Promise<Doc<C, S>>;
|
|
791
|
+
/**
|
|
792
|
+
* Stores a file and creates its `media` document (FR-UPL). The type is detected from
|
|
793
|
+
* the contents; images get width/height, and resized copies when `sharp` is installed.
|
|
794
|
+
*/
|
|
795
|
+
upload(file: {
|
|
796
|
+
data: Uint8Array;
|
|
797
|
+
name: string;
|
|
798
|
+
}, data?: Record<string, unknown>, options?: DepthOptions): Promise<MediaDocument>;
|
|
799
|
+
/**
|
|
800
|
+
* Creates an API key (needs `apiKeys: true`) and returns it once: only a hash is stored.
|
|
801
|
+
* With access enforced the key belongs to `user`; trusted calls name the owner in `data.user`.
|
|
802
|
+
*/
|
|
803
|
+
createApiKey(data: {
|
|
804
|
+
name: string;
|
|
805
|
+
permissions?: ApiKeyPermissions;
|
|
806
|
+
expiresAt?: string | Date | null;
|
|
807
|
+
user?: ID;
|
|
808
|
+
}, options?: AccessOptions): Promise<{
|
|
809
|
+
key: string;
|
|
810
|
+
doc: Record<string, unknown>;
|
|
811
|
+
}>;
|
|
812
|
+
/** Public URL of a stored file. */
|
|
813
|
+
mediaURL(key: string): string;
|
|
814
|
+
update<S extends Slug<C>>(collection: S, id: ID, data: Update<C, S>, options?: DepthOptions): Promise<Doc<C, S>>;
|
|
815
|
+
/**
|
|
816
|
+
* Takes a document off the site: its status becomes `draft`. With versions and drafts, a plain
|
|
817
|
+
* `update` with `status: 'draft'` only saves a draft and leaves the published document live;
|
|
818
|
+
* this is how to unpublish it.
|
|
819
|
+
*/
|
|
820
|
+
unpublish<S extends Slug<C>>(collection: S, id: ID, options?: DepthOptions): Promise<Doc<C, S>>;
|
|
821
|
+
/** Throws away the unpublished draft of a published document, going back to what is live. */
|
|
822
|
+
discardDraft<S extends Slug<C>>(collection: S, id: ID, options?: DepthOptions): Promise<Doc<C, S>>;
|
|
823
|
+
/** Saved versions of a document, newest first. Needs update access to the document. */
|
|
824
|
+
findVersions<S extends Slug<C>>(collection: S, id: ID, options?: AccessOptions & {
|
|
825
|
+
limit?: number;
|
|
826
|
+
page?: number;
|
|
827
|
+
}): Promise<PaginatedDocs<VersionSummary>>;
|
|
828
|
+
/** One saved version with the document as it was then. */
|
|
829
|
+
findVersion<S extends Slug<C>>(collection: S, id: ID, versionId: ID, options?: DepthOptions): Promise<Version<Doc<C, S>> | null>;
|
|
830
|
+
/**
|
|
831
|
+
* Makes an old version the current content, as a new version. With drafts it is restored as
|
|
832
|
+
* a draft (publish it to put it live); access, hooks and validation run as for `update`.
|
|
833
|
+
*/
|
|
834
|
+
restoreVersion<S extends Slug<C>>(collection: S, id: ID, versionId: ID, options?: DepthOptions): Promise<Doc<C, S>>;
|
|
835
|
+
/**
|
|
836
|
+
* Live preview: the document as it would be read if `data` were saved on top of its current
|
|
837
|
+
* state (or as a new document when `id` is `null`), with relationships populated and
|
|
838
|
+
* `afterRead` hooks applied. Nothing is written. Also returns the page URL from `preview`.
|
|
839
|
+
*/
|
|
840
|
+
preview<S extends Slug<C>>(collection: S, id: ID | null, data: Record<string, unknown>, options?: DepthOptions): Promise<LivePreview<Doc<C, S>>>;
|
|
841
|
+
/**
|
|
842
|
+
* A token that lets a page read one document's current draft for a while (default one hour),
|
|
843
|
+
* without a login: `GET /api/cms/:collection/:id?preview=<token>`. The admin adds one to the
|
|
844
|
+
* preview URL as `easy-cms-preview`, for sites on another origin.
|
|
845
|
+
*/
|
|
846
|
+
createPreviewToken(target: {
|
|
847
|
+
collection: string;
|
|
848
|
+
id: ID;
|
|
849
|
+
} | {
|
|
850
|
+
global: string;
|
|
851
|
+
}, options?: {
|
|
852
|
+
expiresIn?: number;
|
|
853
|
+
}): string;
|
|
854
|
+
/** What a preview token opens, or `null` when it is invalid or expired. */
|
|
855
|
+
verifyPreviewToken(token: string | null | undefined): (PreviewTarget & {
|
|
856
|
+
expiresAt: number;
|
|
857
|
+
}) | null;
|
|
858
|
+
/** Live preview of a global. See `preview`. */
|
|
859
|
+
previewGlobal<S extends GSlug<C>>(slug: S, data: Record<string, unknown>, options?: DepthOptions): Promise<LivePreview<GDoc<C, S>>>;
|
|
860
|
+
/** Coerces preview input like a save would, keeping values that would fail validation. */
|
|
861
|
+
private previewDoc;
|
|
862
|
+
private previewURL;
|
|
863
|
+
private versionTarget;
|
|
864
|
+
private updateDocument;
|
|
865
|
+
delete<S extends Slug<C>>(collection: S, id: ID, options?: AccessOptions): Promise<Doc<C, S>>;
|
|
866
|
+
findGlobal<S extends GSlug<C>>(slug: S, options?: ReadOptions): Promise<GDoc<C, S>>;
|
|
867
|
+
updateGlobal<S extends GSlug<C>>(slug: S, data: GInput<C, S>, options?: DepthOptions): Promise<GDoc<C, S>>;
|
|
868
|
+
/** Takes a global off the site (`status: 'draft'`). See `unpublish`. */
|
|
869
|
+
unpublishGlobal<S extends GSlug<C>>(slug: S, options?: DepthOptions): Promise<GDoc<C, S>>;
|
|
870
|
+
/** Throws away the unpublished draft of a published global. */
|
|
871
|
+
discardGlobalDraft<S extends GSlug<C>>(slug: S, options?: DepthOptions): Promise<GDoc<C, S>>;
|
|
872
|
+
/** Saved versions of a global, newest first. Needs update access. */
|
|
873
|
+
findGlobalVersions<S extends GSlug<C>>(slug: S, options?: AccessOptions & {
|
|
874
|
+
limit?: number;
|
|
875
|
+
page?: number;
|
|
876
|
+
}): Promise<PaginatedDocs<VersionSummary>>;
|
|
877
|
+
findGlobalVersion<S extends GSlug<C>>(slug: S, versionId: ID, options?: DepthOptions): Promise<Version<GDoc<C, S>> | null>;
|
|
878
|
+
/** Makes an old version of a global current (as a draft when the global has drafts). */
|
|
879
|
+
restoreGlobalVersion<S extends GSlug<C>>(slug: S, versionId: ID, options?: DepthOptions): Promise<GDoc<C, S>>;
|
|
880
|
+
private globalVersionTarget;
|
|
881
|
+
/** The global's pending draft when it has one (see `withDrafts`), otherwise `saved`. */
|
|
882
|
+
private globalDraft;
|
|
883
|
+
private saveGlobal;
|
|
603
884
|
/**
|
|
604
|
-
*
|
|
605
|
-
*
|
|
885
|
+
* What `user` may do with one document. Resolves `where`-style access
|
|
886
|
+
* against the document, so the admin UI can hide actions precisely.
|
|
606
887
|
*/
|
|
607
|
-
|
|
608
|
-
|
|
888
|
+
documentPermissions(collection: Slug<C>, id: ID, user: AuthUser | null): Promise<{
|
|
889
|
+
update: boolean;
|
|
890
|
+
delete: boolean;
|
|
891
|
+
}>;
|
|
892
|
+
/** Closes the database connection. */
|
|
609
893
|
/**
|
|
610
|
-
*
|
|
611
|
-
*
|
|
894
|
+
* Waits until webhook deliveries in progress are done. Call it before a serverless function
|
|
895
|
+
* returns, which may otherwise stop them.
|
|
612
896
|
*/
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
readonly secret: string;
|
|
620
|
-
readonly db: DatabaseAdapter;
|
|
897
|
+
flushWebhooks(): Promise<void>;
|
|
898
|
+
destroy(): Promise<void>;
|
|
899
|
+
private schedulerTimer;
|
|
900
|
+
private running;
|
|
901
|
+
/** Runs `runJobs` every `interval` ms in this process (`createEasyCMS` does this by default). */
|
|
902
|
+
startScheduler(interval?: number): void;
|
|
621
903
|
/**
|
|
622
|
-
*
|
|
623
|
-
*
|
|
904
|
+
* Runs due scheduled publishes and unpublishes, and retries failed webhook deliveries. What the
|
|
905
|
+
* scheduler and the cron endpoint (`GET <api>/jobs/run`) call.
|
|
624
906
|
*/
|
|
625
|
-
|
|
907
|
+
runJobs(now?: Date): Promise<{
|
|
908
|
+
ran: number;
|
|
909
|
+
failed: number;
|
|
910
|
+
webhooks: {
|
|
911
|
+
sent: number;
|
|
912
|
+
failed: number;
|
|
913
|
+
};
|
|
914
|
+
}>;
|
|
626
915
|
/**
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
* `auth.trustedOrigins` are always allowed, with cookies. Default: none.
|
|
916
|
+
* Tries webhook deliveries that are due for another attempt. Deliveries are kept in the
|
|
917
|
+
* database until they succeed, so they survive restarts.
|
|
630
918
|
*/
|
|
631
|
-
|
|
919
|
+
retryWebhooks(now?: Date): Promise<{
|
|
920
|
+
sent: number;
|
|
921
|
+
failed: number;
|
|
922
|
+
}>;
|
|
923
|
+
/** Webhook deliveries until they succeed, stored in `webhook-deliveries`. */
|
|
924
|
+
private webhookQueue;
|
|
632
925
|
/**
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
* convention of Vercel Cron). Logged-in admins can always run jobs.
|
|
926
|
+
* Publishes or unpublishes documents whose scheduled time has come. Safe to call often, e.g.
|
|
927
|
+
* from a cron job (`GET <api>/jobs/run`) where no process keeps running.
|
|
636
928
|
*/
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
929
|
+
runScheduled(now?: Date): Promise<{
|
|
930
|
+
ran: number;
|
|
931
|
+
failed: number;
|
|
932
|
+
}>;
|
|
933
|
+
/** Schedules a publish or unpublish, replacing a pending job with the same action. */
|
|
934
|
+
schedule<S extends Slug<C>>(collection: S, id: ID, job: {
|
|
935
|
+
action: 'publish' | 'unpublish';
|
|
936
|
+
at: Date | string;
|
|
937
|
+
}, options?: AccessOptions): Promise<ScheduledJob>;
|
|
938
|
+
/** Pending jobs of a document, soonest first. */
|
|
939
|
+
scheduled<S extends Slug<C>>(collection: S, id: ID, options?: AccessOptions): Promise<ScheduledJob[]>;
|
|
940
|
+
cancelSchedule<S extends Slug<C>>(collection: S, id: ID, jobId: ID, options?: AccessOptions): Promise<void>;
|
|
941
|
+
scheduleGlobal<S extends GSlug<C>>(slug: S, job: {
|
|
942
|
+
action: 'publish' | 'unpublish';
|
|
943
|
+
at: Date | string;
|
|
944
|
+
}, options?: AccessOptions): Promise<ScheduledJob>;
|
|
945
|
+
scheduledGlobal<S extends GSlug<C>>(slug: S, options?: AccessOptions): Promise<ScheduledJob[]>;
|
|
946
|
+
cancelGlobalSchedule<S extends GSlug<C>>(slug: S, jobId: ID, options?: AccessOptions): Promise<void>;
|
|
947
|
+
private scheduleTarget;
|
|
948
|
+
private globalScheduleTarget;
|
|
949
|
+
private addJob;
|
|
646
950
|
/**
|
|
647
|
-
*
|
|
648
|
-
*
|
|
951
|
+
* The next pending jobs across all collections and globals, soonest first; with access
|
|
952
|
+
* enforced, only those the user may update.
|
|
649
953
|
*/
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
/**
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
/**
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
readonly type: 'boolean';
|
|
708
|
-
}
|
|
709
|
-
];
|
|
710
|
-
}
|
|
711
|
-
/** Every collection of a config, including the built-in users collection. */
|
|
712
|
-
type AllCollections<C extends Config> = NonNullable<C['collections']>[number] | BuiltinUsersCollection | BuiltinMediaCollection;
|
|
713
|
-
/** The built-in media collection; its documents are typed as `MediaDocument`. */
|
|
714
|
-
interface BuiltinMediaCollection {
|
|
715
|
-
readonly slug: 'media';
|
|
716
|
-
readonly fields: readonly [];
|
|
717
|
-
}
|
|
718
|
-
type CollectionBySlug<C extends Config, S> = Extract<AllCollections<C>, {
|
|
719
|
-
readonly slug: S;
|
|
720
|
-
}>;
|
|
721
|
-
/** Relationships are ids at depth 0 and documents when populated. */
|
|
722
|
-
type RelationValue<C extends Config, S> = [CollectionBySlug<C, S>] extends [never] ? ID | Record<string, unknown> : ID | CollectionDocument<C, S & string>;
|
|
723
|
-
interface MediaSize {
|
|
724
|
-
filename: string;
|
|
725
|
-
width: number;
|
|
726
|
-
height: number;
|
|
727
|
-
filesize: number;
|
|
728
|
-
url: string;
|
|
954
|
+
upcomingJobs(options?: AccessOptions & {
|
|
955
|
+
limit?: number;
|
|
956
|
+
}): Promise<(ScheduledJob & {
|
|
957
|
+
collection?: string;
|
|
958
|
+
global?: string;
|
|
959
|
+
doc: ID;
|
|
960
|
+
})[]>;
|
|
961
|
+
private pendingJobs;
|
|
962
|
+
private removeJob;
|
|
963
|
+
/** @internal */
|
|
964
|
+
collection(slug: string): CollectionConfig;
|
|
965
|
+
private global;
|
|
966
|
+
/** Refuses what a request's API key does not allow (see `ApiKeyPermissions`). */
|
|
967
|
+
private checkKey;
|
|
968
|
+
/** The query constraint read access adds, or `undefined` when there is none. Throws when denied. */
|
|
969
|
+
private readWhere;
|
|
970
|
+
private checkDocumentAccess;
|
|
971
|
+
private checkGlobalAccess;
|
|
972
|
+
private fieldChecker;
|
|
973
|
+
/** Runs afterRead hooks, populates relationships and removes what the caller may not see. */
|
|
974
|
+
private output;
|
|
975
|
+
/** Creates a document: access, input filtering, hooks, validation, then afterChange. */
|
|
976
|
+
private createDocument;
|
|
977
|
+
/** The locale for an operation; `undefined` without localization. Throws on unknown locales. */
|
|
978
|
+
private localeOf;
|
|
979
|
+
/** Stored localized maps → the values of the requested locale. */
|
|
980
|
+
private pick;
|
|
981
|
+
/** Input for one locale → stored localized maps, keeping the other locales from `current`. */
|
|
982
|
+
private toMaps;
|
|
983
|
+
/** Makes `where` match localized fields in the operation's locale. */
|
|
984
|
+
private whereFor;
|
|
985
|
+
/** Drafts are kept as versions (instead of unpublishing) when both are enabled. */
|
|
986
|
+
private separateDrafts;
|
|
987
|
+
/** Replaces published documents that have a newer draft with that draft. */
|
|
988
|
+
private withDrafts;
|
|
989
|
+
/** Sends webhook events for a change; `before` is the stored status before it, if any. */
|
|
990
|
+
private emit;
|
|
991
|
+
/** Records a version after a save, when the collection or global keeps versions. */
|
|
992
|
+
private saveVersion;
|
|
993
|
+
private hookArgs;
|
|
994
|
+
/**
|
|
995
|
+
* Runs hooks that may replace a value, passed as `data` (before hooks) or `doc` (afterRead).
|
|
996
|
+
* Returning `undefined` keeps the value; a throw cancels the operation.
|
|
997
|
+
*/
|
|
998
|
+
private transform;
|
|
999
|
+
/** Runs hooks after the change is saved: failures are logged, never undo the save (NFR-REL-03). */
|
|
1000
|
+
private notify;
|
|
1001
|
+
/** Writes resized copies with sharp, when installed and configured. */
|
|
1002
|
+
private resizeImage;
|
|
1003
|
+
/** Validates, checks uniqueness and references. Returns clean data or throws `ValidationError`. */
|
|
1004
|
+
private prepare;
|
|
1005
|
+
/** Refuses changes that would leave no active admin (and lock everyone out). */
|
|
1006
|
+
private guardLastAdmin;
|
|
1007
|
+
private makeSlugsUnique;
|
|
1008
|
+
private checkUnique;
|
|
1009
|
+
private isTaken;
|
|
1010
|
+
private checkReferences;
|
|
729
1011
|
}
|
|
730
|
-
/** A document
|
|
731
|
-
interface
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
mimeType: string;
|
|
736
|
-
filesize: number;
|
|
737
|
-
width?: number | null;
|
|
738
|
-
height?: number | null;
|
|
739
|
-
alt?: string | null;
|
|
740
|
-
/** Public URL of the file. */
|
|
741
|
-
url: string;
|
|
742
|
-
/** Resized copies, when `upload.imageSizes` is set and sharp is installed. */
|
|
743
|
-
sizes: Record<string, MediaSize>;
|
|
744
|
-
createdAt: string;
|
|
745
|
-
updatedAt: string;
|
|
746
|
-
[field: string]: unknown;
|
|
1012
|
+
/** A document as live preview shows it, and the page to show it on. */
|
|
1013
|
+
interface LivePreview<T> {
|
|
1014
|
+
readonly doc: T;
|
|
1015
|
+
/** From the collection's `preview` function; `null` without one. */
|
|
1016
|
+
readonly url: string | null;
|
|
747
1017
|
}
|
|
748
|
-
type FieldValue<F extends Field, C extends Config = Config> = F extends {
|
|
749
|
-
readonly type: 'text' | 'textarea' | 'email' | 'slug' | 'date';
|
|
750
|
-
} ? string : F extends {
|
|
751
|
-
readonly type: 'number';
|
|
752
|
-
} ? number : F extends {
|
|
753
|
-
readonly type: 'boolean';
|
|
754
|
-
} ? boolean : F extends {
|
|
755
|
-
readonly type: 'json';
|
|
756
|
-
} ? unknown : F extends {
|
|
757
|
-
readonly type: 'select';
|
|
758
|
-
} ? SelectValue<F> : F extends {
|
|
759
|
-
readonly type: 'richText';
|
|
760
|
-
} ? RichTextDocument : F extends {
|
|
761
|
-
readonly type: 'upload';
|
|
762
|
-
} ? ID | MediaDocument : F extends {
|
|
763
|
-
readonly type: 'relationship';
|
|
764
|
-
readonly to: infer S;
|
|
765
|
-
} ? F extends {
|
|
766
|
-
readonly hasMany: true;
|
|
767
|
-
} ? RelationValue<C, S>[] : RelationValue<C, S> : F extends {
|
|
768
|
-
readonly type: 'array';
|
|
769
|
-
readonly fields: infer Sub extends readonly Field[];
|
|
770
|
-
} ? Simplify<FieldsValue<Sub, C> & {
|
|
771
|
-
id: string;
|
|
772
|
-
}>[] : F extends {
|
|
773
|
-
readonly type: 'group';
|
|
774
|
-
readonly fields: infer Sub extends readonly Field[];
|
|
775
|
-
} ? FieldsValue<Sub, C> : F extends {
|
|
776
|
-
readonly type: 'blocks';
|
|
777
|
-
readonly blocks: infer B extends readonly Block[];
|
|
778
|
-
} ? BlockValue<B[number], C>[] : never;
|
|
779
|
-
/** One row of a `blocks` field: the block's fields plus `id` and `blockType` (distributes over kinds). */
|
|
780
|
-
type BlockValue<B, C extends Config> = B extends {
|
|
781
|
-
readonly slug: infer S;
|
|
782
|
-
readonly fields: infer Sub extends readonly Field[];
|
|
783
|
-
} ? Simplify<FieldsValue<Sub, C> & {
|
|
784
|
-
id: string;
|
|
785
|
-
blockType: S;
|
|
786
|
-
}> : never;
|
|
787
|
-
type BlockInput<B> = B extends {
|
|
788
|
-
readonly slug: infer S;
|
|
789
|
-
readonly fields: infer Sub extends readonly Field[];
|
|
790
|
-
} ? Simplify<FieldsInput<Sub> & {
|
|
791
|
-
id?: string;
|
|
792
|
-
blockType: S;
|
|
793
|
-
}> : never;
|
|
794
|
-
type Visible<Fs extends readonly Field[]> = Exclude<Fs[number], {
|
|
795
|
-
readonly hidden: true;
|
|
796
|
-
}>;
|
|
797
|
-
/** Always set when read: required fields, and lists and groups (empty `[]` / `{}` otherwise). */
|
|
798
|
-
type Present = {
|
|
799
|
-
readonly required: true;
|
|
800
|
-
} | {
|
|
801
|
-
readonly type: 'array' | 'blocks' | 'group';
|
|
802
|
-
} | {
|
|
803
|
-
readonly hasMany: true;
|
|
804
|
-
};
|
|
805
|
-
type RequiredFields<Fs extends readonly Field[]> = Extract<Visible<Fs>, Present>;
|
|
806
|
-
type OptionalFields<Fs extends readonly Field[]> = Exclude<Visible<Fs>, Present>;
|
|
807
|
-
/** The shape of the data described by a list of fields. */
|
|
808
|
-
type FieldsValue<Fs extends readonly Field[], C extends Config = Config> = Simplify<{
|
|
809
|
-
-readonly [F in RequiredFields<Fs> as F['name']]: FieldValue<F, C>;
|
|
810
|
-
} & {
|
|
811
|
-
-readonly [F in OptionalFields<Fs> as F['name']]?: FieldValue<F, C> | null;
|
|
812
|
-
}>;
|
|
813
|
-
type SystemFields<T extends {
|
|
814
|
-
readonly drafts?: boolean;
|
|
815
|
-
}> = {
|
|
816
|
-
id: ID;
|
|
817
|
-
createdAt: string;
|
|
818
|
-
updatedAt: string;
|
|
819
|
-
} & (T extends {
|
|
820
|
-
readonly drafts: true;
|
|
821
|
-
} ? {
|
|
822
|
-
status: 'draft' | 'published';
|
|
823
|
-
} : unknown);
|
|
824
|
-
type InferCollection<T extends CollectionConfig, C extends Config = Config> = Simplify<SystemFields<T> & FieldsValue<T['fields'], C>>;
|
|
825
|
-
type InferGlobal<T extends GlobalConfig, C extends Config = Config> = Simplify<
|
|
826
|
-
/** `null` until the global is saved for the first time. */
|
|
827
|
-
{
|
|
828
|
-
updatedAt: string | null;
|
|
829
|
-
} & (T extends {
|
|
830
|
-
readonly drafts: true;
|
|
831
|
-
} ? {
|
|
832
|
-
status: 'draft' | 'published';
|
|
833
|
-
} : unknown) & FieldsValue<T['fields'], C>>;
|
|
834
|
-
type CollectionSlug<C extends Config> = NonNullable<C['collections']>[number]['slug'] | 'users' | 'media';
|
|
835
|
-
type GlobalSlug<C extends Config> = NonNullable<C['globals']>[number]['slug'];
|
|
836
|
-
/** Document type of a collection, e.g. `CollectionDocument<typeof config, 'posts'>`. */
|
|
837
|
-
type CollectionDocument<C extends Config, S extends CollectionSlug<C>> = S extends 'media' ? MediaDocument : InferCollection<CollectionBySlug<C, S>, C>;
|
|
838
|
-
/** Data type of a global, e.g. `GlobalDocument<typeof config, 'site'>`. */
|
|
839
|
-
type GlobalDocument<C extends Config, S extends GlobalSlug<C>> = InferGlobal<Extract<NonNullable<C['globals']>[number], {
|
|
840
|
-
readonly slug: S;
|
|
841
|
-
}>, C>;
|
|
842
|
-
type InputValue<F extends Field> = F extends {
|
|
843
|
-
readonly type: 'relationship';
|
|
844
|
-
} ? F extends {
|
|
845
|
-
readonly hasMany: true;
|
|
846
|
-
} ? readonly ID[] : ID : F extends {
|
|
847
|
-
readonly type: 'select';
|
|
848
|
-
readonly hasMany: true;
|
|
849
|
-
} ? Readonly<SelectValue<F>> : F extends {
|
|
850
|
-
readonly type: 'upload';
|
|
851
|
-
} ? ID : F extends {
|
|
852
|
-
readonly type: 'date';
|
|
853
|
-
} ? string | Date : F extends {
|
|
854
|
-
readonly type: 'array';
|
|
855
|
-
readonly fields: infer Sub extends readonly Field[];
|
|
856
|
-
} ? readonly Simplify<FieldsInput<Sub> & {
|
|
857
|
-
id?: string;
|
|
858
|
-
}>[] : F extends {
|
|
859
|
-
readonly type: 'group';
|
|
860
|
-
readonly fields: infer Sub extends readonly Field[];
|
|
861
|
-
} ? FieldsInput<Sub> : F extends {
|
|
862
|
-
readonly type: 'blocks';
|
|
863
|
-
readonly blocks: infer B extends readonly Block[];
|
|
864
|
-
} ? readonly BlockInput<B[number]>[] : FieldValue<F>;
|
|
865
|
-
/** Required fields must be given, unless Easy CMS can fill them (default value, slug from another field). */
|
|
866
|
-
type NeedsInput<F extends Field> = F extends {
|
|
867
|
-
readonly required: true;
|
|
868
|
-
} ? F extends {
|
|
869
|
-
readonly defaultValue: unknown;
|
|
870
|
-
} ? never : F extends {
|
|
871
|
-
readonly type: 'slug';
|
|
872
|
-
readonly from: string;
|
|
873
|
-
} ? never : F : never;
|
|
874
|
-
type FieldsInput<Fs extends readonly Field[]> = Simplify<{
|
|
875
|
-
-readonly [F in NeedsInput<Visible<Fs>> as F['name']]: InputValue<F>;
|
|
876
|
-
} & {
|
|
877
|
-
-readonly [F in Exclude<Visible<Fs>, NeedsInput<Visible<Fs>>> as F['name']]?: InputValue<F> | null;
|
|
878
|
-
}>;
|
|
879
|
-
type StatusInput<T> = T extends {
|
|
880
|
-
readonly drafts: true;
|
|
881
|
-
} ? {
|
|
882
|
-
status?: 'draft' | 'published';
|
|
883
|
-
} : unknown;
|
|
884
|
-
/** Users are created with a password, which is hashed and never returned. */
|
|
885
|
-
type PasswordInput<S, Required extends boolean> = S extends 'users' ? Required extends true ? {
|
|
886
|
-
password: string;
|
|
887
|
-
} : {
|
|
888
|
-
password?: string;
|
|
889
|
-
} : unknown;
|
|
890
|
-
/** Data accepted by `create`, e.g. `CreateInput<typeof config, 'posts'>`. */
|
|
891
|
-
type CreateInput<C extends Config, S extends CollectionSlug<C>> = Simplify<FieldsInput<CollectionBySlug<C, S>['fields']> & StatusInput<CollectionBySlug<C, S>> & PasswordInput<S, true>>;
|
|
892
|
-
/** Data accepted by `update`: any subset of `CreateInput`. */
|
|
893
|
-
type UpdateInput<C extends Config, S extends CollectionSlug<C>> = Partial<CreateInput<C, S>>;
|
|
894
|
-
/** Data accepted by `updateGlobal`. */
|
|
895
|
-
type GlobalInput<C extends Config, S extends GlobalSlug<C>> = Partial<Simplify<FieldsInput<Extract<NonNullable<C['globals']>[number], {
|
|
896
|
-
readonly slug: S;
|
|
897
|
-
}>['fields']> & StatusInput<Extract<NonNullable<C['globals']>[number], {
|
|
898
|
-
readonly slug: S;
|
|
899
|
-
}>>>>;
|
|
900
1018
|
|
|
901
|
-
declare const
|
|
902
|
-
/**
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
/**
|
|
910
|
-
readonly
|
|
911
|
-
|
|
1019
|
+
declare const WEBHOOK_EVENTS: readonly ["create", "update", "delete", "publish", "unpublish", "draft"];
|
|
1020
|
+
/**
|
|
1021
|
+
* `create`, `update`, `delete`: the stored document changed. `publish` / `unpublish`: its status
|
|
1022
|
+
* changed (sent with `create` or `update`). `draft`: only a draft was saved; what is live did not
|
|
1023
|
+
* change (collections with versions and drafts).
|
|
1024
|
+
*/
|
|
1025
|
+
type WebhookEvent = (typeof WEBHOOK_EVENTS)[number];
|
|
1026
|
+
interface WebhookConfig {
|
|
1027
|
+
/** Where to POST events. */
|
|
1028
|
+
readonly url: string;
|
|
1029
|
+
/** Events to send. Default: all. */
|
|
1030
|
+
readonly events?: readonly WebhookEvent[];
|
|
1031
|
+
/** Collections to send events for. Default: all; `[]` for none. */
|
|
1032
|
+
readonly collections?: readonly string[];
|
|
1033
|
+
/** Globals to send events for. Default: all; `[]` for none. */
|
|
1034
|
+
readonly globals?: readonly string[];
|
|
1035
|
+
/** Signs each body: `x-easy-cms-signature: sha256=<hex HMAC-SHA256 of the body>`. */
|
|
1036
|
+
readonly secret?: string;
|
|
1037
|
+
/** Extra request headers, e.g. a token the receiver checks. */
|
|
1038
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
912
1039
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
readonly
|
|
1040
|
+
/** What a webhook receives as JSON. */
|
|
1041
|
+
interface WebhookPayload {
|
|
1042
|
+
readonly event: WebhookEvent;
|
|
1043
|
+
readonly collection?: string;
|
|
1044
|
+
readonly global?: string;
|
|
1045
|
+
readonly id?: ID;
|
|
1046
|
+
/** The stored document (every locale, no hidden fields); for `delete`, as it was. */
|
|
1047
|
+
readonly doc: Record<string, unknown>;
|
|
1048
|
+
/** ISO time of the change. */
|
|
1049
|
+
readonly timestamp: string;
|
|
916
1050
|
}
|
|
917
1051
|
|
|
918
1052
|
type Data$1 = Record<string, unknown>;
|
|
919
|
-
type
|
|
920
|
-
type
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
* The Local API trusts its caller and skips access rules by default.
|
|
929
|
-
* Pass `false` (with `user`) to apply them, as the REST API does.
|
|
930
|
-
*/
|
|
931
|
-
readonly overrideAccess?: boolean;
|
|
932
|
-
/** The user to check access for. `null` = not logged in. */
|
|
933
|
-
readonly user?: AuthUser | null;
|
|
1053
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
1054
|
+
type Operation = 'create' | 'update';
|
|
1055
|
+
interface HookBase {
|
|
1056
|
+
/** The user the operation runs for; `null` for anonymous or trusted Local API calls. */
|
|
1057
|
+
readonly user: AuthUser | null;
|
|
1058
|
+
/** The Local API, e.g. to query other collections or trigger a revalidation. */
|
|
1059
|
+
readonly cms: EasyCMS;
|
|
1060
|
+
/** Slug of the collection or global. */
|
|
1061
|
+
readonly slug: string;
|
|
934
1062
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1063
|
+
type BeforeValidateHook = (args: HookBase & {
|
|
1064
|
+
data: Data$1;
|
|
1065
|
+
operation: Operation;
|
|
1066
|
+
originalDoc?: Data$1;
|
|
1067
|
+
}) => MaybePromise<Data$1 | undefined>;
|
|
1068
|
+
type BeforeChangeHook = (args: HookBase & {
|
|
1069
|
+
data: Data$1;
|
|
1070
|
+
operation: Operation;
|
|
1071
|
+
originalDoc?: Data$1;
|
|
1072
|
+
}) => MaybePromise<Data$1 | undefined>;
|
|
1073
|
+
type AfterChangeHook = (args: HookBase & {
|
|
1074
|
+
doc: Data$1;
|
|
1075
|
+
operation: Operation;
|
|
1076
|
+
previousDoc?: Data$1;
|
|
1077
|
+
}) => MaybePromise<void>;
|
|
1078
|
+
type BeforeDeleteHook = (args: HookBase & {
|
|
1079
|
+
id: ID;
|
|
1080
|
+
}) => MaybePromise<void>;
|
|
1081
|
+
type AfterDeleteHook = (args: HookBase & {
|
|
1082
|
+
id: ID;
|
|
1083
|
+
doc: Data$1;
|
|
1084
|
+
}) => MaybePromise<void>;
|
|
1085
|
+
type AfterReadHook = (args: HookBase & {
|
|
1086
|
+
doc: Data$1;
|
|
1087
|
+
}) => MaybePromise<Data$1 | undefined>;
|
|
1088
|
+
interface CollectionHooks {
|
|
1089
|
+
readonly beforeValidate?: readonly BeforeValidateHook[];
|
|
1090
|
+
readonly beforeChange?: readonly BeforeChangeHook[];
|
|
1091
|
+
readonly afterChange?: readonly AfterChangeHook[];
|
|
1092
|
+
readonly beforeDelete?: readonly BeforeDeleteHook[];
|
|
1093
|
+
readonly afterDelete?: readonly AfterDeleteHook[];
|
|
1094
|
+
readonly afterRead?: readonly AfterReadHook[];
|
|
943
1095
|
}
|
|
944
|
-
interface
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
*/
|
|
949
|
-
readonly draft?: boolean;
|
|
950
|
-
/** Use the default locale's value when a localized value is empty. Default: `localization.fallback`. */
|
|
951
|
-
readonly fallbackLocale?: boolean;
|
|
1096
|
+
interface GlobalHooks {
|
|
1097
|
+
readonly beforeChange?: readonly BeforeChangeHook[];
|
|
1098
|
+
readonly afterChange?: readonly AfterChangeHook[];
|
|
1099
|
+
readonly afterRead?: readonly AfterReadHook[];
|
|
952
1100
|
}
|
|
953
|
-
interface
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
readonly page?: number;
|
|
1101
|
+
interface LocalizationConfig {
|
|
1102
|
+
/** Content locales, e.g. `['th', 'en']`. */
|
|
1103
|
+
readonly locales: readonly string[];
|
|
1104
|
+
/** The locale used when none is given; its values also fill empty ones. Default: the first. */
|
|
1105
|
+
readonly defaultLocale?: string;
|
|
1106
|
+
/** Reads return the default locale's value when a locale's value is empty. Default true. */
|
|
1107
|
+
readonly fallback?: boolean;
|
|
961
1108
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
readonly
|
|
974
|
-
readonly interactive?: boolean;
|
|
1109
|
+
/**
|
|
1110
|
+
* Where a document is shown on the site, for live preview in the admin: an absolute URL or a
|
|
1111
|
+
* path on the site's origin. Return `null` when the document has no page.
|
|
1112
|
+
*/
|
|
1113
|
+
type PreviewURL = (args: {
|
|
1114
|
+
readonly doc: Record<string, unknown>;
|
|
1115
|
+
/** The content locale being edited (with `localization`), otherwise the admin locale. */
|
|
1116
|
+
readonly locale: string;
|
|
1117
|
+
}) => string | null | undefined;
|
|
1118
|
+
interface VersionsConfig {
|
|
1119
|
+
/** Versions kept per document; older ones are deleted. Default 50. */
|
|
1120
|
+
readonly max?: number;
|
|
975
1121
|
}
|
|
976
|
-
/**
|
|
977
|
-
|
|
978
|
-
/**
|
|
979
|
-
|
|
980
|
-
readonly id: ID;
|
|
981
|
-
readonly action: 'publish' | 'unpublish';
|
|
982
|
-
/** ISO time the job runs at (or after). */
|
|
983
|
-
readonly runAt: string;
|
|
984
|
-
readonly state: 'pending' | 'done' | 'failed';
|
|
985
|
-
readonly error: string | null;
|
|
986
|
-
readonly author: ID | null;
|
|
1122
|
+
/** Custom admin components for a collection's or global's edit page. */
|
|
1123
|
+
interface ContainerAdmin {
|
|
1124
|
+
/** Panels in the edit page's side column, below publishing (components from admin modules). */
|
|
1125
|
+
readonly sidebar?: readonly AdminComponent[];
|
|
987
1126
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
readonly
|
|
991
|
-
readonly
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
readonly cwd: string;
|
|
998
|
-
private readonly versions;
|
|
999
|
-
private readonly webhooks;
|
|
1000
|
-
constructor(config: ResolvedConfig, db: Database, logger?: Logger, storage?: StorageAdapter, cwd?: string);
|
|
1001
|
-
find<S extends Slug<C>>(collection: S, options?: FindOptions): Promise<PaginatedDocs<Doc<C, S>>>;
|
|
1002
|
-
findById<S extends Slug<C>>(collection: S, id: ID, options?: ReadOptions): Promise<Doc<C, S> | null>;
|
|
1003
|
-
count<S extends Slug<C>>(collection: S, options?: {
|
|
1004
|
-
where?: Where;
|
|
1005
|
-
draft?: boolean;
|
|
1006
|
-
locale?: string;
|
|
1007
|
-
} & AccessOptions): Promise<number>;
|
|
1008
|
-
create<S extends Slug<C>>(collection: S, data: Create<C, S>, options?: DepthOptions): Promise<Doc<C, S>>;
|
|
1127
|
+
interface CollectionConfig {
|
|
1128
|
+
/** URL and table name. Lowercase letters, digits, `-` and `_`. */
|
|
1129
|
+
readonly slug: string;
|
|
1130
|
+
readonly labels?: {
|
|
1131
|
+
readonly singular?: Label;
|
|
1132
|
+
readonly plural?: Label;
|
|
1133
|
+
};
|
|
1134
|
+
/** Icon in the admin menu. Default `file-text`. */
|
|
1135
|
+
readonly icon?: AdminIcon;
|
|
1009
1136
|
/**
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1137
|
+
* How the admin opens documents from the list: `drawer` slides a panel over the list, handy
|
|
1138
|
+
* for small collections (categories, tags). Default `page`. Collections with drafts, versions
|
|
1139
|
+
* or live preview always use the page.
|
|
1012
1140
|
*/
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
/**
|
|
1018
|
-
|
|
1019
|
-
update<S extends Slug<C>>(collection: S, id: ID, data: Update<C, S>, options?: DepthOptions): Promise<Doc<C, S>>;
|
|
1141
|
+
readonly editIn?: 'page' | 'drawer';
|
|
1142
|
+
readonly fields: readonly Field[];
|
|
1143
|
+
/** Top-level field shown as the document title in the admin UI. */
|
|
1144
|
+
readonly useAsTitle?: string;
|
|
1145
|
+
/** Adds a `status` field (`draft` | `published`). */
|
|
1146
|
+
readonly drafts?: boolean;
|
|
1020
1147
|
/**
|
|
1021
|
-
*
|
|
1022
|
-
*
|
|
1023
|
-
*
|
|
1148
|
+
* Keep a snapshot of every save, to view history and restore. With `drafts`, a draft saved
|
|
1149
|
+
* on a published document is kept as a version and the published document stays live until
|
|
1150
|
+
* it is published again.
|
|
1024
1151
|
*/
|
|
1025
|
-
|
|
1026
|
-
/**
|
|
1027
|
-
|
|
1028
|
-
/**
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1152
|
+
readonly versions?: boolean | VersionsConfig;
|
|
1153
|
+
/** Allow scheduling publish/unpublish for a later time (needs `drafts`). */
|
|
1154
|
+
readonly schedule?: boolean;
|
|
1155
|
+
/** Live preview: the page that shows a document, e.g. `({ doc }) => \`/posts/${doc.slug}\``. */
|
|
1156
|
+
readonly preview?: PreviewURL;
|
|
1157
|
+
readonly access?: CollectionAccess;
|
|
1158
|
+
readonly hooks?: CollectionHooks;
|
|
1159
|
+
/** Custom admin components. */
|
|
1160
|
+
readonly admin?: ContainerAdmin;
|
|
1161
|
+
}
|
|
1162
|
+
interface GlobalConfig {
|
|
1163
|
+
readonly slug: string;
|
|
1164
|
+
readonly label?: Label;
|
|
1165
|
+
/** Icon in the admin menu. Default `settings`. */
|
|
1166
|
+
readonly icon?: AdminIcon;
|
|
1167
|
+
readonly fields: readonly Field[];
|
|
1168
|
+
readonly drafts?: boolean;
|
|
1169
|
+
/** Keep a snapshot of every save. See `CollectionConfig.versions`. */
|
|
1170
|
+
readonly versions?: boolean | VersionsConfig;
|
|
1171
|
+
/** Allow scheduling publish/unpublish for a later time (needs `drafts`). */
|
|
1172
|
+
readonly schedule?: boolean;
|
|
1173
|
+
/** Live preview: the page that shows the global. See `CollectionConfig.preview`. */
|
|
1174
|
+
readonly preview?: PreviewURL;
|
|
1175
|
+
readonly access?: GlobalAccess;
|
|
1176
|
+
readonly hooks?: GlobalHooks;
|
|
1177
|
+
/** Custom admin components. */
|
|
1178
|
+
readonly admin?: ContainerAdmin;
|
|
1179
|
+
}
|
|
1180
|
+
type AdminLocale = 'en' | 'th';
|
|
1181
|
+
/** Icons a collection or global can show in the admin menu ([Lucide](https://lucide.dev) names). */
|
|
1182
|
+
declare const ADMIN_ICONS: readonly ["file-text", "newspaper", "book-open", "notebook", "folder", "tag", "tags", "image", "images", "video", "music", "file", "users", "user", "building", "store", "shopping-bag", "shopping-cart", "package", "box", "calendar", "calendar-days", "map-pin", "globe", "house", "layout-grid", "layers", "star", "heart", "message-square", "mail", "phone", "briefcase", "graduation-cap", "utensils", "car", "settings", "sliders-horizontal", "palette", "megaphone", "bell", "link", "quote", "circle-help", "award", "ticket", "camera", "key"];
|
|
1183
|
+
type AdminIcon = (typeof ADMIN_ICONS)[number];
|
|
1184
|
+
/** Your or your client's brand in the admin UI. */
|
|
1185
|
+
interface AdminBrand {
|
|
1186
|
+
/** Shown in the menu, on the login page and in the browser tab. Default "Easy CMS". */
|
|
1187
|
+
readonly name?: string;
|
|
1188
|
+
/** Logo URL: a path on your site (`/logo.svg`) or an `https://` address. */
|
|
1189
|
+
readonly logo?: string;
|
|
1190
|
+
/** Main color as `#rrggbb`; lighter and darker shades are derived from it. */
|
|
1191
|
+
readonly color?: string;
|
|
1192
|
+
}
|
|
1193
|
+
interface AdminConfig {
|
|
1194
|
+
/** Where the admin UI is served. Default `/admin`. */
|
|
1195
|
+
readonly path?: string;
|
|
1196
|
+
/** Default admin UI language. Default `en`. */
|
|
1197
|
+
readonly locale?: AdminLocale;
|
|
1198
|
+
/** Name, logo and color of the admin UI. */
|
|
1199
|
+
readonly brand?: AdminBrand;
|
|
1035
1200
|
/**
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1201
|
+
* The public site, for the admin's "View site" link: a path (`/`) or an `https://` URL.
|
|
1202
|
+
* Default `/` with Nuxt and Next.js; none with the standalone server.
|
|
1038
1203
|
*/
|
|
1039
|
-
|
|
1204
|
+
readonly siteUrl?: string;
|
|
1040
1205
|
/**
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
* `afterRead` hooks applied. Nothing is written. Also returns the page URL from `preview`.
|
|
1206
|
+
* Order of collections in the admin menu, by slug, e.g. `['posts', 'categories', 'media']`.
|
|
1207
|
+
* Collections not listed follow in config order, with the media library last.
|
|
1044
1208
|
*/
|
|
1045
|
-
|
|
1209
|
+
readonly menu?: readonly string[];
|
|
1046
1210
|
/**
|
|
1047
|
-
*
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
1211
|
+
* JavaScript modules the admin loads after login, which define Web Components used by
|
|
1212
|
+
* fields (`admin.component`, `admin.after`) and edit pages (`admin.sidebar`). Each entry is a
|
|
1213
|
+
* package export (`'@easy-cms/plugin-seo/admin'`) or a file path relative to the project root
|
|
1214
|
+
* (`'./admin/color-picker.js'`), resolved on the server. A module must be one self-contained ES
|
|
1215
|
+
* module file. Remote URLs are not allowed.
|
|
1050
1216
|
*/
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
/** The global's pending draft when it has one (see `withDrafts`), otherwise `saved`. */
|
|
1087
|
-
private globalDraft;
|
|
1088
|
-
private saveGlobal;
|
|
1217
|
+
readonly modules?: readonly string[];
|
|
1218
|
+
}
|
|
1219
|
+
interface ImageSize {
|
|
1220
|
+
/** Key in `media.sizes`, e.g. `thumbnail`. */
|
|
1221
|
+
readonly name: string;
|
|
1222
|
+
readonly width: number;
|
|
1223
|
+
readonly height?: number;
|
|
1224
|
+
/** How to fit when both width and height are set. Default `cover`. */
|
|
1225
|
+
readonly fit?: 'cover' | 'contain' | 'inside';
|
|
1226
|
+
}
|
|
1227
|
+
interface UploadConfig {
|
|
1228
|
+
/** Directory for uploaded files with the default local storage, relative to the project root. Default `uploads`. */
|
|
1229
|
+
readonly dir?: string;
|
|
1230
|
+
/** Maximum file size in bytes. Default 10 MB. */
|
|
1231
|
+
readonly maxFileSize?: number;
|
|
1232
|
+
/** Allowed MIME types, detected from file contents. `image/*` style wildcards are allowed. */
|
|
1233
|
+
readonly mimeTypes?: readonly string[];
|
|
1234
|
+
/** Where files are stored. Default: local disk in `dir`. */
|
|
1235
|
+
readonly storage?: StorageAdapter;
|
|
1236
|
+
/** Resized copies generated for images when `sharp` is installed. */
|
|
1237
|
+
readonly imageSizes?: readonly ImageSize[];
|
|
1238
|
+
}
|
|
1239
|
+
interface RoutesConfig {
|
|
1240
|
+
/** Where the REST API is served. Default `/api/cms`. */
|
|
1241
|
+
readonly api?: string;
|
|
1242
|
+
}
|
|
1243
|
+
interface AuthConfig {
|
|
1244
|
+
/** Roles a user can have. Must include `admin`. Default `['admin', 'editor']`. */
|
|
1245
|
+
readonly roles?: readonly string[];
|
|
1246
|
+
/** Session lifetime in seconds. Default 7 days. */
|
|
1247
|
+
readonly tokenExpiration?: number;
|
|
1248
|
+
/** Failed logins allowed per email (and IP) within `lockWindow`. Default 5. */
|
|
1249
|
+
readonly maxLoginAttempts?: number;
|
|
1250
|
+
/** Window for `maxLoginAttempts`, in seconds. Default 15 minutes. */
|
|
1251
|
+
readonly lockWindow?: number;
|
|
1089
1252
|
/**
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
1253
|
+
* Origins allowed to send cookie-authenticated requests, besides the API's own origin.
|
|
1254
|
+
* Needed when the admin or frontend is served from another origin, or behind a proxy that rewrites the host.
|
|
1092
1255
|
*/
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1256
|
+
readonly trustedOrigins?: readonly string[];
|
|
1257
|
+
}
|
|
1258
|
+
/** What an endpoint's handler receives. */
|
|
1259
|
+
interface EndpointRequest {
|
|
1260
|
+
readonly request: Request;
|
|
1261
|
+
readonly url: URL;
|
|
1262
|
+
/** Values of `:name` segments in the endpoint's path. */
|
|
1263
|
+
readonly params: Readonly<Record<string, string>>;
|
|
1264
|
+
/** The logged-in user (session cookie or Bearer token), or `null`. */
|
|
1265
|
+
readonly user: AuthUser | null;
|
|
1266
|
+
/** The Local API. Pass `{ user, overrideAccess: false }` to apply the user's access rules. */
|
|
1267
|
+
readonly cms: EasyCMS;
|
|
1268
|
+
/** The request's JSON body, which must be an object (at most 1 MB). */
|
|
1269
|
+
json(): Promise<Record<string, unknown>>;
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* A custom REST endpoint, served under `routes.api`. Writes from the browser pass the same
|
|
1273
|
+
* CSRF check as the built-in endpoints.
|
|
1274
|
+
*/
|
|
1275
|
+
interface Endpoint {
|
|
1098
1276
|
/**
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1277
|
+
* Path under the API, e.g. `/seo/generate` or `/stats/:collection`. The first segment must
|
|
1278
|
+
* not be a collection slug or one of `users`, `globals`, `admin`, `jobs`.
|
|
1101
1279
|
*/
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
private schedulerTimer;
|
|
1105
|
-
private running;
|
|
1106
|
-
/** Runs `runJobs` every `interval` ms in this process (`createEasyCMS` does this by default). */
|
|
1107
|
-
startScheduler(interval?: number): void;
|
|
1280
|
+
readonly path: string;
|
|
1281
|
+
readonly method: 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
1108
1282
|
/**
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
1283
|
+
* Returns a `Response`, or a value that is sent as JSON. Throw an Easy CMS error
|
|
1284
|
+
* (`UnauthorizedError`, `ForbiddenError`, `ValidationError`, `NotFoundError`) for error responses.
|
|
1111
1285
|
*/
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1286
|
+
readonly handler: (request: EndpointRequest) => MaybePromise<unknown>;
|
|
1287
|
+
}
|
|
1288
|
+
/** Receives the config and returns a modified copy. Runs before validation. */
|
|
1289
|
+
type Plugin = (config: Config) => MaybePromise<Config>;
|
|
1290
|
+
interface Config {
|
|
1291
|
+
/** Signs sessions. At least 32 characters; read it from `process.env.EASY_CMS_SECRET`. */
|
|
1292
|
+
readonly secret: string;
|
|
1293
|
+
readonly db: DatabaseAdapter;
|
|
1120
1294
|
/**
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1295
|
+
* Public origin of the app, e.g. `https://example.com`. When set, media URLs are absolute
|
|
1296
|
+
* so frontends on other origins can use them. Default: relative URLs.
|
|
1123
1297
|
*/
|
|
1124
|
-
|
|
1125
|
-
sent: number;
|
|
1126
|
-
failed: number;
|
|
1127
|
-
}>;
|
|
1128
|
-
/** Webhook deliveries until they succeed, stored in `webhook-deliveries`. */
|
|
1129
|
-
private webhookQueue;
|
|
1298
|
+
readonly serverURL?: string;
|
|
1130
1299
|
/**
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1300
|
+
* Origins whose browser code may call the REST API (CORS), e.g. a Vite or React app on
|
|
1301
|
+
* another origin. `'*'` allows any origin for anonymous requests. Origins in
|
|
1302
|
+
* `auth.trustedOrigins` are always allowed, with cookies. Default: none.
|
|
1133
1303
|
*/
|
|
1134
|
-
|
|
1135
|
-
ran: number;
|
|
1136
|
-
failed: number;
|
|
1137
|
-
}>;
|
|
1138
|
-
/** Schedules a publish or unpublish, replacing a pending job with the same action. */
|
|
1139
|
-
schedule<S extends Slug<C>>(collection: S, id: ID, job: {
|
|
1140
|
-
action: 'publish' | 'unpublish';
|
|
1141
|
-
at: Date | string;
|
|
1142
|
-
}, options?: AccessOptions): Promise<ScheduledJob>;
|
|
1143
|
-
/** Pending jobs of a document, soonest first. */
|
|
1144
|
-
scheduled<S extends Slug<C>>(collection: S, id: ID, options?: AccessOptions): Promise<ScheduledJob[]>;
|
|
1145
|
-
cancelSchedule<S extends Slug<C>>(collection: S, id: ID, jobId: ID, options?: AccessOptions): Promise<void>;
|
|
1146
|
-
scheduleGlobal<S extends GSlug<C>>(slug: S, job: {
|
|
1147
|
-
action: 'publish' | 'unpublish';
|
|
1148
|
-
at: Date | string;
|
|
1149
|
-
}, options?: AccessOptions): Promise<ScheduledJob>;
|
|
1150
|
-
scheduledGlobal<S extends GSlug<C>>(slug: S, options?: AccessOptions): Promise<ScheduledJob[]>;
|
|
1151
|
-
cancelGlobalSchedule<S extends GSlug<C>>(slug: S, jobId: ID, options?: AccessOptions): Promise<void>;
|
|
1152
|
-
private scheduleTarget;
|
|
1153
|
-
private globalScheduleTarget;
|
|
1154
|
-
private addJob;
|
|
1304
|
+
readonly cors?: readonly string[] | '*';
|
|
1155
1305
|
/**
|
|
1156
|
-
*
|
|
1157
|
-
*
|
|
1306
|
+
* Lets a cron service run due scheduled jobs: `GET <api>/jobs/run` with
|
|
1307
|
+
* `Authorization: Bearer <cronSecret>`. Default: the `CRON_SECRET` environment variable (the
|
|
1308
|
+
* convention of Vercel Cron). Logged-in admins can always run jobs.
|
|
1158
1309
|
*/
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
/** @internal */
|
|
1169
|
-
collection(slug: string): CollectionConfig;
|
|
1170
|
-
private global;
|
|
1171
|
-
/** The query constraint read access adds, or `undefined` when there is none. Throws when denied. */
|
|
1172
|
-
private readWhere;
|
|
1173
|
-
private checkDocumentAccess;
|
|
1174
|
-
private checkGlobalAccess;
|
|
1175
|
-
private fieldChecker;
|
|
1176
|
-
/** Runs afterRead hooks, populates relationships and removes what the caller may not see. */
|
|
1177
|
-
private output;
|
|
1178
|
-
/** Creates a document: access, input filtering, hooks, validation, then afterChange. */
|
|
1179
|
-
private createDocument;
|
|
1180
|
-
/** The locale for an operation; `undefined` without localization. Throws on unknown locales. */
|
|
1181
|
-
private localeOf;
|
|
1182
|
-
/** Stored localized maps → the values of the requested locale. */
|
|
1183
|
-
private pick;
|
|
1184
|
-
/** Input for one locale → stored localized maps, keeping the other locales from `current`. */
|
|
1185
|
-
private toMaps;
|
|
1186
|
-
/** Makes `where` match localized fields in the operation's locale. */
|
|
1187
|
-
private whereFor;
|
|
1188
|
-
/** Drafts are kept as versions (instead of unpublishing) when both are enabled. */
|
|
1189
|
-
private separateDrafts;
|
|
1190
|
-
/** Replaces published documents that have a newer draft with that draft. */
|
|
1191
|
-
private withDrafts;
|
|
1192
|
-
/** Sends webhook events for a change; `before` is the stored status before it, if any. */
|
|
1193
|
-
private emit;
|
|
1194
|
-
/** Records a version after a save, when the collection or global keeps versions. */
|
|
1195
|
-
private saveVersion;
|
|
1196
|
-
private hookArgs;
|
|
1310
|
+
readonly cronSecret?: string;
|
|
1311
|
+
/** Endpoints notified when content changes, e.g. to rebuild a static site. */
|
|
1312
|
+
readonly webhooks?: readonly WebhookConfig[];
|
|
1313
|
+
/** Content in several languages: fields with `localized: true` hold one value per locale. */
|
|
1314
|
+
readonly localization?: LocalizationConfig | null;
|
|
1315
|
+
readonly routes?: RoutesConfig;
|
|
1316
|
+
readonly admin?: AdminConfig;
|
|
1317
|
+
readonly upload?: UploadConfig;
|
|
1318
|
+
readonly auth?: AuthConfig;
|
|
1197
1319
|
/**
|
|
1198
|
-
*
|
|
1199
|
-
*
|
|
1320
|
+
* Collections. A collection with slug `users` adds fields, access or hooks
|
|
1321
|
+
* to the built-in users collection.
|
|
1200
1322
|
*/
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
private checkUnique;
|
|
1212
|
-
private isTaken;
|
|
1213
|
-
private checkReferences;
|
|
1323
|
+
readonly collections?: readonly CollectionConfig[];
|
|
1324
|
+
readonly globals?: readonly GlobalConfig[];
|
|
1325
|
+
/** Custom REST endpoints, e.g. from plugins. */
|
|
1326
|
+
readonly endpoints?: readonly Endpoint[];
|
|
1327
|
+
/**
|
|
1328
|
+
* API keys for scripts and other apps (`Authorization: Bearer ecms_…`), managed in the admin
|
|
1329
|
+
* under Settings. A key acts as its owner, limited to the collections and operations it lists.
|
|
1330
|
+
*/
|
|
1331
|
+
readonly apiKeys?: boolean;
|
|
1332
|
+
readonly plugins?: readonly Plugin[];
|
|
1214
1333
|
}
|
|
1215
|
-
/**
|
|
1216
|
-
interface
|
|
1217
|
-
readonly
|
|
1218
|
-
/**
|
|
1219
|
-
readonly
|
|
1334
|
+
/** The config after plugins ran, validation passed and defaults were applied. */
|
|
1335
|
+
interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes' | 'admin' | 'upload' | 'auth' | 'collections' | 'globals' | 'endpoints' | 'plugins'> {
|
|
1336
|
+
readonly cors: readonly string[] | '*';
|
|
1337
|
+
/** `null` without localization. */
|
|
1338
|
+
readonly localization: Required<LocalizationConfig> | null;
|
|
1339
|
+
readonly routes: Required<RoutesConfig>;
|
|
1340
|
+
readonly admin: Required<AdminConfig>;
|
|
1341
|
+
readonly upload: Required<Omit<UploadConfig, 'storage'>> & Pick<UploadConfig, 'storage'>;
|
|
1342
|
+
readonly auth: Required<AuthConfig>;
|
|
1343
|
+
readonly collections: readonly CollectionConfig[];
|
|
1344
|
+
readonly globals: readonly GlobalConfig[];
|
|
1345
|
+
readonly endpoints: readonly Endpoint[];
|
|
1220
1346
|
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Declares the Easy CMS config. Returns it unchanged, keeping literal types
|
|
1349
|
+
* so document types can be inferred from it.
|
|
1350
|
+
*/
|
|
1351
|
+
declare function defineConfig<const TConfig extends Config>(config: TConfig): TConfig;
|
|
1221
1352
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
readonly
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
constructor(cms: EasyCMS);
|
|
1240
|
-
private get db();
|
|
1241
|
-
private get config();
|
|
1242
|
-
/** Checks credentials and starts a session. Throws `UnauthorizedError` or `TooManyRequestsError`. */
|
|
1243
|
-
login(args: LoginArgs): Promise<Session>;
|
|
1244
|
-
/** Creates the first admin. Only works while there are no users. */
|
|
1245
|
-
registerFirstUser(args: {
|
|
1246
|
-
email: string;
|
|
1247
|
-
password: string;
|
|
1248
|
-
name?: string;
|
|
1249
|
-
}): Promise<Session>;
|
|
1250
|
-
hasUsers(): Promise<boolean>;
|
|
1251
|
-
/** Ends the session for a signed token. Unknown tokens are ignored. */
|
|
1252
|
-
logout(signedToken: string): Promise<void>;
|
|
1253
|
-
/** Returns the user for a signed session token, or `null` if it is invalid or expired. */
|
|
1254
|
-
verify(signedToken: string | undefined | null): Promise<AuthUser | null>;
|
|
1255
|
-
/** The CSRF token clients must echo for a signed session token. */
|
|
1256
|
-
csrfFor(signedToken: string): string | undefined;
|
|
1257
|
-
/** Starts a new session for a user, e.g. after they changed their own password. */
|
|
1258
|
-
createSession(userId: ID): Promise<Session>;
|
|
1259
|
-
/** Signs a user out everywhere. */
|
|
1260
|
-
revokeSessions(userId: ID): Promise<void>;
|
|
1261
|
-
private startSession;
|
|
1262
|
-
private findSession;
|
|
1263
|
-
private toAuthUser;
|
|
1264
|
-
private deleteExpiredSessions;
|
|
1265
|
-
private windowStart;
|
|
1266
|
-
private checkRateLimit;
|
|
1267
|
-
private recordFailure;
|
|
1268
|
-
private clearFailures;
|
|
1353
|
+
declare const API_KEYS = "api-keys";
|
|
1354
|
+
/** What a key may do with a collection. `create` on `media` means uploading. */
|
|
1355
|
+
declare const API_KEY_OPERATIONS: readonly ["read", "create", "update", "delete", "publish"];
|
|
1356
|
+
type ApiKeyOperation = (typeof API_KEY_OPERATIONS)[number];
|
|
1357
|
+
/** What a key may do with a global. */
|
|
1358
|
+
declare const API_KEY_GLOBAL_OPERATIONS: readonly ["read", "update", "publish"];
|
|
1359
|
+
type ApiKeyGlobalOperation = (typeof API_KEY_GLOBAL_OPERATIONS)[number];
|
|
1360
|
+
/** Stored in the key's `permissions` field. Anything not listed is not allowed. */
|
|
1361
|
+
interface ApiKeyPermissions {
|
|
1362
|
+
readonly collections?: Readonly<Record<string, readonly ApiKeyOperation[]>>;
|
|
1363
|
+
readonly globals?: Readonly<Record<string, readonly ApiKeyGlobalOperation[]>>;
|
|
1364
|
+
}
|
|
1365
|
+
/** Set on `AuthUser.apiKey` when a request authenticated with an API key. */
|
|
1366
|
+
interface ApiKeyContext {
|
|
1367
|
+
readonly id: number | string;
|
|
1368
|
+
readonly name: string;
|
|
1369
|
+
readonly permissions: ApiKeyPermissions;
|
|
1269
1370
|
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Whether a request's API key allows an operation. Always true without a key (sessions are
|
|
1373
|
+
* governed by access rules alone); a key only narrows what its user may do.
|
|
1374
|
+
*/
|
|
1375
|
+
declare function keyAllows(user: AuthUser | null, target: {
|
|
1376
|
+
collection: string;
|
|
1377
|
+
} | {
|
|
1378
|
+
global: string;
|
|
1379
|
+
}, operation: ApiKeyOperation): boolean;
|
|
1270
1380
|
|
|
1271
1381
|
declare const MIN_PASSWORD_LENGTH = 8;
|
|
1272
1382
|
/** Hashes a password as `scrypt$N$r$p$salt$hash` (base64url). */
|
|
@@ -1278,6 +1388,24 @@ declare const MEDIA = "media";
|
|
|
1278
1388
|
/** Collections Easy CMS uses internally. Not exposed over REST or in the admin UI. */
|
|
1279
1389
|
declare const INTERNAL_COLLECTIONS: ReadonlySet<string>;
|
|
1280
1390
|
|
|
1391
|
+
interface CopyProgress {
|
|
1392
|
+
readonly table: string;
|
|
1393
|
+
readonly rows: number;
|
|
1394
|
+
}
|
|
1395
|
+
interface CopyResult {
|
|
1396
|
+
readonly tables: number;
|
|
1397
|
+
readonly rows: number;
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Copies every stored row from `source` into `target` (another kind of database is fine, e.g.
|
|
1401
|
+
* SQLite to Postgres), keeping ids, so relationships, versions and users stay as they are.
|
|
1402
|
+
* Both must come from the same collections and fields, and the target must be empty.
|
|
1403
|
+
* Uploaded files are not copied: they stay in the storage they are in.
|
|
1404
|
+
*/
|
|
1405
|
+
declare function copyDatabase(source: Database, target: Database, options?: {
|
|
1406
|
+
onTable?: (progress: CopyProgress) => void;
|
|
1407
|
+
}): Promise<CopyResult>;
|
|
1408
|
+
|
|
1281
1409
|
interface ConfigIssue {
|
|
1282
1410
|
/** Where the problem is, e.g. `collections.posts.fields.slug.from`. */
|
|
1283
1411
|
readonly path: string;
|
|
@@ -1577,4 +1705,4 @@ declare const BUILTIN_COLLECTIONS: readonly ["users", "media"];
|
|
|
1577
1705
|
/** Checks a config and returns every problem found. An empty array means it is valid. */
|
|
1578
1706
|
declare function validateConfig(config: Config): ConfigIssue[];
|
|
1579
1707
|
|
|
1580
|
-
export { ADMIN_ICONS, 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 ArrayField, Auth, type AuthConfig, type AuthUser, BUILTIN_COLLECTIONS, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinMediaCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CollectionAccess, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_TOKEN_EXPIRATION, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DateField, type DepthOptions, EXTENSIONS, EasyCMS, EasyCMSError, type EmailField, 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 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, 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 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, consoleLogger, createEasyCMS, createRestHandler, defineConfig, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isLoggedIn, loadConfig, localStorage, mergeForUpdate, mimeAllowed, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|
|
1708
|
+
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 CollectionAccess, type CollectionConfig, type CollectionDocument, type CollectionHooks, 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_TOKEN_EXPIRATION, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DepthOptions, EXTENSIONS, EasyCMS, EasyCMSError, type EmailField, 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 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, 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 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, consoleLogger, copyDatabase, createEasyCMS, createRestHandler, defineConfig, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isLoggedIn, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|