@easy-cms/core 0.48.0 → 0.49.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/chunk-4PGEDNBY.js +11067 -0
- package/dist/chunk-CGVNZUW4.js +534 -0
- package/dist/chunk-K6GTTRN3.js +6 -0
- package/dist/{chunk-C4KJ5TFK.js → chunk-U266LV4M.js} +4 -1
- package/dist/conditions.d.ts +4 -2
- package/dist/conditions.js +1 -1
- package/dist/config-jzFGsNL6.d.ts +3209 -0
- package/dist/handler-CMA1LIq7.d.ts +188 -0
- package/dist/index.d.ts +8 -3831
- package/dist/index.js +54 -11307
- package/dist/internal.d.ts +136 -0
- package/dist/internal.js +310 -0
- package/dist/plugin-EX9OHwR3.d.ts +326 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +34 -0
- package/package.json +9 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3495 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
type Role = 'admin' | 'editor' | (string & {});
|
|
7
|
-
/** The logged-in admin user, as seen by access functions and hooks. */
|
|
8
|
-
interface AuthUser {
|
|
9
|
-
readonly id: ID;
|
|
10
|
-
readonly email: string;
|
|
11
|
-
readonly role: Role;
|
|
12
|
-
/**
|
|
13
|
-
* Set by `onRequest` (e.g. a multi-tenant plugin) when `role` holds only in part of the site,
|
|
14
|
-
* such as one tenant: an `admin` there administers that part, not the whole system (Settings,
|
|
15
|
-
* backups, roles, other users' API keys).
|
|
16
|
-
*/
|
|
17
|
-
readonly scoped?: boolean;
|
|
18
|
-
/**
|
|
19
|
-
* A member of the site, e.g. a customer (`auth.members.roles`): signs in on the site, never to
|
|
20
|
-
* the admin, and `isLoggedIn` doesn't count them.
|
|
21
|
-
*/
|
|
22
|
-
readonly member?: boolean;
|
|
23
|
-
readonly [field: string]: unknown;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* An admin of the whole system: role `admin`, signed in (not an API key), and not an admin of a
|
|
27
|
-
* part only (`scoped`).
|
|
28
|
-
*/
|
|
29
|
-
declare const isSystemAdmin: (user: AuthUser | null | undefined) => boolean;
|
|
30
|
-
interface WhereOperators {
|
|
31
|
-
readonly equals?: unknown;
|
|
32
|
-
readonly not_equals?: unknown;
|
|
33
|
-
readonly in?: readonly unknown[];
|
|
34
|
-
readonly not_in?: readonly unknown[];
|
|
35
|
-
readonly gt?: number | string;
|
|
36
|
-
readonly gte?: number | string;
|
|
37
|
-
readonly lt?: number | string;
|
|
38
|
-
readonly lte?: number | string;
|
|
39
|
-
readonly like?: string;
|
|
40
|
-
readonly exists?: boolean;
|
|
41
|
-
}
|
|
42
|
-
type Where = {
|
|
43
|
-
readonly and?: readonly Where[];
|
|
44
|
-
readonly or?: readonly Where[];
|
|
45
|
-
} & {
|
|
46
|
-
readonly [field: string]: WhereOperators | readonly Where[] | undefined;
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* What a request is about besides its user, e.g. the site or tenant it works in: set by
|
|
50
|
-
* `onRequest` in the config (usually a plugin's), or passed to the Local API as `context`.
|
|
51
|
-
*/
|
|
52
|
-
type RequestContext = Readonly<Record<string, unknown>>;
|
|
53
|
-
interface AccessArgs {
|
|
54
|
-
/** `null` when the request is not logged in. */
|
|
55
|
-
readonly user: AuthUser | null;
|
|
56
|
-
/** The request's context (`onRequest`), `{}` when there is none. */
|
|
57
|
-
readonly context?: RequestContext;
|
|
58
|
-
readonly id?: ID;
|
|
59
|
-
readonly data?: Readonly<Record<string, unknown>>;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Decides whether an operation is allowed.
|
|
63
|
-
* Return a `Where` to allow it only for the documents that match.
|
|
64
|
-
*/
|
|
65
|
-
type Access = (args: AccessArgs) => boolean | Where | Promise<boolean | Where>;
|
|
66
|
-
interface CollectionAccess {
|
|
67
|
-
readonly read?: Access;
|
|
68
|
-
readonly create?: Access;
|
|
69
|
-
readonly update?: Access;
|
|
70
|
-
readonly delete?: Access;
|
|
71
|
-
}
|
|
72
|
-
interface GlobalAccess {
|
|
73
|
-
readonly read?: Access;
|
|
74
|
-
readonly update?: Access;
|
|
75
|
-
}
|
|
76
|
-
type FieldAccessFn = (args: AccessArgs) => boolean | Promise<boolean>;
|
|
77
|
-
interface FieldAccess {
|
|
78
|
-
readonly read?: FieldAccessFn;
|
|
79
|
-
readonly update?: FieldAccessFn;
|
|
80
|
-
}
|
|
81
|
-
declare const anyone: Access;
|
|
82
|
-
/**
|
|
83
|
-
* A signed-in user of the admin: not a site member (`auth.members`). What collections allow by
|
|
84
|
-
* default.
|
|
85
|
-
*/
|
|
86
|
-
declare const isLoggedIn: Access;
|
|
87
|
-
/** Anyone signed in, site members (e.g. customers) included. */
|
|
88
|
-
declare const isSignedIn: Access;
|
|
89
|
-
declare const isAdmin: Access;
|
|
90
|
-
|
|
91
|
-
/** What a preview token opens: one document of a collection, or one global. */
|
|
92
|
-
type PreviewTarget = {
|
|
93
|
-
readonly collection: string;
|
|
94
|
-
readonly id: string;
|
|
95
|
-
} | {
|
|
96
|
-
readonly global: string;
|
|
97
|
-
};
|
|
98
|
-
/**
|
|
99
|
-
* Why a link was sent: a forgotten password, an invitation to a new account, or (`verify`) to
|
|
100
|
-
* confirm the email of an account made by signing up.
|
|
101
|
-
*/
|
|
102
|
-
type PasswordPurpose = 'reset' | 'invite' | 'verify';
|
|
103
|
-
|
|
104
|
-
/** The cookie that carries a sign-in through the provider and back. */
|
|
105
|
-
declare const SSO_COOKIE = "ecms-sso";
|
|
106
|
-
/** A provider as the login page shows it (`GET <api>/users/init`). */
|
|
107
|
-
interface SsoProviderRef {
|
|
108
|
-
id: string;
|
|
109
|
-
name: string;
|
|
110
|
-
icon: string;
|
|
111
|
-
}
|
|
112
|
-
/** Settings → SSO (`GET <api>/admin/sso`). */
|
|
113
|
-
interface AdminSso {
|
|
114
|
-
providers: (SsoProviderRef & {
|
|
115
|
-
/** Give this to the provider as the redirect (callback) URL. */
|
|
116
|
-
callbackURL: string | null;
|
|
117
|
-
details: Record<string, string>;
|
|
118
|
-
})[];
|
|
119
|
-
/** Who may sign in with a password: everyone, or only admins. */
|
|
120
|
-
password: 'everyone' | 'admins';
|
|
121
|
-
/** Email domains whose people get an account when they first sign in. */
|
|
122
|
-
signUp: {
|
|
123
|
-
domains: string[];
|
|
124
|
-
role: string;
|
|
125
|
-
} | null;
|
|
126
|
-
/** Without `serverURL`, sign-in only works in development. */
|
|
127
|
-
serverURL: boolean;
|
|
128
|
-
}
|
|
129
|
-
/** An outside account a user signs in with (`GET <api>/auth/identities`). */
|
|
130
|
-
interface UserIdentity {
|
|
131
|
-
id: ID;
|
|
132
|
-
provider: string;
|
|
133
|
-
/** The provider's name, e.g. `Google`. */
|
|
134
|
-
name: string;
|
|
135
|
-
email: string | null;
|
|
136
|
-
createdAt: string;
|
|
137
|
-
lastUsedAt: string | null;
|
|
138
|
-
}
|
|
139
|
-
/** Why a sign-in did not work, as `?sso=` on the page it ends on. */
|
|
140
|
-
type SsoOutcome = 'linked' | 'cancelled' | 'expired' | 'failed' | 'no-account' | 'link-first' | 'unverified' | 'inactive' | 'taken';
|
|
141
|
-
/** Single sign-on with `auth.providers`. Available as `cms.auth.sso`. */
|
|
142
|
-
declare class SingleSignOn {
|
|
143
|
-
private readonly cms;
|
|
144
|
-
constructor(cms: EasyCMS);
|
|
145
|
-
private get config();
|
|
146
|
-
get enabled(): boolean;
|
|
147
|
-
providers(): SsoProviderRef[];
|
|
148
|
-
/** Whether a user may sign in with a password (`auth.password: false` leaves only admins). */
|
|
149
|
-
passwordAllowed(user: {
|
|
150
|
-
readonly [key: string]: unknown;
|
|
151
|
-
}): boolean;
|
|
152
|
-
/** `<serverURL><api>/auth/<id>/callback`, or `null` without `serverURL` in production. */
|
|
153
|
-
callbackURL(provider: string, origin?: string): string | null;
|
|
154
|
-
/**
|
|
155
|
-
* Starts signing in (or, with `user`, linking an account): the provider's URL to send the
|
|
156
|
-
* browser to, and the sealed value of the cookie that must come back with it.
|
|
157
|
-
*/
|
|
158
|
-
start(providerId: string, options: {
|
|
159
|
-
origin?: string;
|
|
160
|
-
redirect?: string | null;
|
|
161
|
-
user?: AuthUser;
|
|
162
|
-
}): Promise<{
|
|
163
|
-
url: string;
|
|
164
|
-
cookie: string;
|
|
165
|
-
}>;
|
|
166
|
-
/**
|
|
167
|
-
* Finishes at `<api>/auth/<id>/callback`: checks the response, finds (or makes) the user and
|
|
168
|
-
* starts a session. Returns where to send the browser; failures end on the login page (or the
|
|
169
|
-
* account page, when linking) with `?sso=<outcome>`.
|
|
170
|
-
*/
|
|
171
|
-
finish(providerId: string, options: {
|
|
172
|
-
url: URL;
|
|
173
|
-
cookie: string | undefined;
|
|
174
|
-
origin?: string;
|
|
175
|
-
user: AuthUser | null;
|
|
176
|
-
}): Promise<{
|
|
177
|
-
redirect: string;
|
|
178
|
-
session?: Session;
|
|
179
|
-
}>;
|
|
180
|
-
/** A user's outside accounts. */
|
|
181
|
-
identities(userId: ID): Promise<UserIdentity[]>;
|
|
182
|
-
/**
|
|
183
|
-
* Unlinks an outside account: the user's own, or anyone's for admins. Refuses to unlink the
|
|
184
|
-
* last way a user has to sign in.
|
|
185
|
-
*/
|
|
186
|
-
unlink(actor: AuthUser, identityId: ID): Promise<void>;
|
|
187
|
-
/** Forgets a deleted user's outside accounts. */
|
|
188
|
-
forget(userId: ID): Promise<void>;
|
|
189
|
-
settings(origin?: string): AdminSso;
|
|
190
|
-
private provider;
|
|
191
|
-
/** Only pages of the admin: anything else (another site) goes to the admin's home. */
|
|
192
|
-
private adminPage;
|
|
193
|
-
private identity;
|
|
194
|
-
private addIdentity;
|
|
195
|
-
private userByEmail;
|
|
196
|
-
/** A new account for people from `allowSignUp.domains`. */
|
|
197
|
-
private signUp;
|
|
198
|
-
/** A site member (`auth.members`): matched by email, like any shop or site account. */
|
|
199
|
-
private isMember;
|
|
200
|
-
private defaultRole;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
interface LoginArgs {
|
|
204
|
-
readonly email: string;
|
|
205
|
-
readonly password: string;
|
|
206
|
-
/** Client IP, used together with the email for rate limiting. */
|
|
207
|
-
readonly ip?: string | undefined;
|
|
208
|
-
}
|
|
209
|
-
interface Session {
|
|
210
|
-
readonly user: AuthUser;
|
|
211
|
-
/** Signed session token, the value of the session cookie. */
|
|
212
|
-
readonly token: string;
|
|
213
|
-
/** Send back in the `X-CSRF-Token` header on cookie-authenticated writes. */
|
|
214
|
-
readonly csrfToken: string;
|
|
215
|
-
readonly expiresAt: string;
|
|
216
|
-
}
|
|
217
|
-
interface SignupArgs {
|
|
218
|
-
readonly email: string;
|
|
219
|
-
readonly password: string;
|
|
220
|
-
readonly name?: string | undefined;
|
|
221
|
-
/** From `GET <api>/users/signup`: the form was loaded a moment ago. */
|
|
222
|
-
readonly token?: unknown;
|
|
223
|
-
/** The hidden field; bots fill it in. */
|
|
224
|
-
readonly honeypot?: unknown;
|
|
225
|
-
/** The Cloudflare Turnstile response, with `signup.turnstile`. */
|
|
226
|
-
readonly turnstile?: unknown;
|
|
227
|
-
readonly ip?: string | undefined;
|
|
228
|
-
/** The request's origin: used for the link only in development, without a site URL. */
|
|
229
|
-
readonly origin?: string | undefined;
|
|
230
|
-
readonly locale?: string | undefined;
|
|
231
|
-
/** The request's context (`onRequest`), e.g. the tenant: given to the users' hooks. */
|
|
232
|
-
readonly context?: RequestContext;
|
|
233
|
-
}
|
|
234
|
-
/** What signing up did: a link was emailed to confirm, or (without `verifyEmail`) a session. */
|
|
235
|
-
type SignupResult = {
|
|
236
|
-
readonly verify: true;
|
|
237
|
-
} | (Session & {
|
|
238
|
-
readonly verify: false;
|
|
239
|
-
});
|
|
240
|
-
/** Where a password link is opened from, and the language of its email. */
|
|
241
|
-
interface PasswordLinkOptions {
|
|
242
|
-
/** The request's origin: used for the link only in development, when `serverURL` is not set. */
|
|
243
|
-
readonly origin?: string | undefined;
|
|
244
|
-
/** `en` or `th`. Default: `admin.locale`. */
|
|
245
|
-
readonly locale?: string | undefined;
|
|
246
|
-
}
|
|
247
|
-
/** Login, logout and session verification. Available as `cms.auth`. */
|
|
248
|
-
declare class Auth {
|
|
249
|
-
private readonly cms;
|
|
250
|
-
/** Signing in with outside accounts (`auth.providers`). */
|
|
251
|
-
readonly sso: SingleSignOn;
|
|
252
|
-
constructor(cms: EasyCMS);
|
|
253
|
-
private get db();
|
|
254
|
-
private get config();
|
|
255
|
-
/** Checks credentials and starts a session. Throws `UnauthorizedError` or `TooManyRequestsError`. */
|
|
256
|
-
login(args: LoginArgs): Promise<Session>;
|
|
257
|
-
/**
|
|
258
|
-
* Creates the first admin. Only works while there are no users, and with `auth.setupCode`
|
|
259
|
-
* (`EASY_CMS_SETUP_CODE`) only with that code.
|
|
260
|
-
*/
|
|
261
|
-
registerFirstUser(args: {
|
|
262
|
-
email: string;
|
|
263
|
-
password: string;
|
|
264
|
-
name?: string;
|
|
265
|
-
setupCode?: string;
|
|
266
|
-
ip?: string | undefined;
|
|
267
|
-
}): Promise<Session>;
|
|
268
|
-
/** What a sign-up form needs: a token proving when it was loaded, and Turnstile's site key. */
|
|
269
|
-
signupForm(): {
|
|
270
|
-
token: string;
|
|
271
|
-
turnstile: string | null;
|
|
272
|
-
verifyEmail: boolean;
|
|
273
|
-
};
|
|
274
|
-
/**
|
|
275
|
-
* Creates a member's account (`auth.members.signup`). With `verifyEmail` (the default), emails
|
|
276
|
-
* a link to confirm the address and answers the same whether or not the email has an account;
|
|
277
|
-
* otherwise starts a session. Throws `ValidationError` for spam checks and bad input.
|
|
278
|
-
*/
|
|
279
|
-
signup(args: SignupArgs): Promise<SignupResult>;
|
|
280
|
-
/** Confirms a member's email from the link, and signs them in. */
|
|
281
|
-
verifyEmail(args: {
|
|
282
|
-
token: string;
|
|
283
|
-
ip?: string | undefined;
|
|
284
|
-
}): Promise<Session>;
|
|
285
|
-
private mailVerifyLink;
|
|
286
|
-
/**
|
|
287
|
-
* A link to one of the site's pages for members (`auth.members.pages`), else to the admin's
|
|
288
|
-
* page of the same purpose.
|
|
289
|
-
*/
|
|
290
|
-
private memberLink;
|
|
291
|
-
hasUsers(): Promise<boolean>;
|
|
292
|
-
private warnedNoClientIp;
|
|
293
|
-
/**
|
|
294
|
-
* Without the client's address, failed logins count per email only, so anyone can lock an
|
|
295
|
-
* account for a while. Said once, in production.
|
|
296
|
-
*/
|
|
297
|
-
private warnNoClientIp;
|
|
298
|
-
/** Ends the session for a signed token. Unknown tokens are ignored. */
|
|
299
|
-
logout(signedToken: string): Promise<void>;
|
|
300
|
-
/**
|
|
301
|
-
* The user a request's headers carry: a Bearer token (session or API key) or the session
|
|
302
|
-
* cookie; `null` when there is none or it is not valid. For frameworks' pages and handlers.
|
|
303
|
-
*/
|
|
304
|
-
userFromHeaders(headers: {
|
|
305
|
-
get(name: string): string | null | undefined;
|
|
306
|
-
}): Promise<AuthUser | null>;
|
|
307
|
-
/**
|
|
308
|
-
* Returns the user for a signed session token, or for an API key (`ecms_…`, with `apiKey` set
|
|
309
|
-
* on the user), or `null` if it is invalid or expired.
|
|
310
|
-
*/
|
|
311
|
-
verify(signedToken: string | undefined | null): Promise<AuthUser | null>;
|
|
312
|
-
/** The CSRF token clients must echo for a signed session token. */
|
|
313
|
-
csrfFor(signedToken: string): string | undefined;
|
|
314
|
-
/** Starts a new session for a user, e.g. after they changed their own password. */
|
|
315
|
-
createSession(userId: ID): Promise<Session>;
|
|
316
|
-
/** Signs a user out everywhere. */
|
|
317
|
-
revokeSessions(userId: ID): Promise<void>;
|
|
318
|
-
/**
|
|
319
|
-
* The origin password links point to: `serverURL`, or the request's origin in development. In
|
|
320
|
-
* production the request's Host can't be trusted (a forged one would send the link elsewhere).
|
|
321
|
-
*/
|
|
322
|
-
passwordLinkBase(origin?: string): string | null;
|
|
323
|
-
/** Whether "forgot password" and invitations work: `email` is set and links have an origin. */
|
|
324
|
-
canSendPasswordLinks(origin?: string): boolean;
|
|
325
|
-
/**
|
|
326
|
-
* Emails a link to set a new password, if an active account has this email. Always resolves the
|
|
327
|
-
* same way, so callers can't tell whether the email has an account; extra requests within
|
|
328
|
-
* `lockWindow` are ignored.
|
|
329
|
-
*/
|
|
330
|
-
requestPasswordReset(args: PasswordLinkOptions & {
|
|
331
|
-
email: string;
|
|
332
|
-
ip?: string | undefined;
|
|
333
|
-
}): Promise<void>;
|
|
334
|
-
/**
|
|
335
|
-
* Emails a user a link to set their password: an invitation if they have none yet, else a
|
|
336
|
-
* reset link. For admins (the REST API checks the role). Returns which one was sent.
|
|
337
|
-
*/
|
|
338
|
-
sendPasswordLink(userId: ID, options?: PasswordLinkOptions): Promise<PasswordPurpose>;
|
|
339
|
-
/** The user and purpose of a valid, unused password link, or `null`. */
|
|
340
|
-
checkPasswordToken(token: string): Promise<{
|
|
341
|
-
user: AuthUser;
|
|
342
|
-
purpose: PasswordPurpose;
|
|
343
|
-
} | null>;
|
|
344
|
-
/**
|
|
345
|
-
* Sets the password from a link, ends every session of the account and starts one here.
|
|
346
|
-
* Throws `ValidationError` when the link expired or was used, or the password is too short.
|
|
347
|
-
*/
|
|
348
|
-
resetPassword(args: {
|
|
349
|
-
token: string;
|
|
350
|
-
password: string;
|
|
351
|
-
ip?: string | undefined;
|
|
352
|
-
locale?: string | undefined;
|
|
353
|
-
}): Promise<Session & {
|
|
354
|
-
purpose: PasswordPurpose;
|
|
355
|
-
}>;
|
|
356
|
-
private passwordTokenUser;
|
|
357
|
-
private mailPasswordLink;
|
|
358
|
-
private localeOf;
|
|
359
|
-
private findByEmail;
|
|
360
|
-
/** The owner of an API key, limited by the key; `null` when unknown, expired or disabled. */
|
|
361
|
-
private verifyApiKey;
|
|
362
|
-
private startSession;
|
|
363
|
-
private findSession;
|
|
364
|
-
private toAuthUser;
|
|
365
|
-
private deleteExpiredSessions;
|
|
366
|
-
private windowStart;
|
|
367
|
-
private checkRateLimit;
|
|
368
|
-
private recordFailure;
|
|
369
|
-
private clearFailures;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
interface Logger {
|
|
373
|
-
info(message: string): void;
|
|
374
|
-
warn(message: string): void;
|
|
375
|
-
error(message: string): void;
|
|
376
|
-
}
|
|
377
|
-
declare const consoleLogger: Logger;
|
|
378
|
-
declare const silentLogger: Logger;
|
|
379
|
-
|
|
380
|
-
/** A document as stored: relationships and uploads are ids, never populated. */
|
|
381
|
-
type RawDocument = Record<string, unknown> & {
|
|
382
|
-
id: ID;
|
|
383
|
-
};
|
|
384
|
-
interface PaginatedDocs<T> {
|
|
385
|
-
docs: T[];
|
|
386
|
-
totalDocs: number;
|
|
387
|
-
limit: number;
|
|
388
|
-
page: number;
|
|
389
|
-
totalPages: number;
|
|
390
|
-
hasNextPage: boolean;
|
|
391
|
-
hasPrevPage: boolean;
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* How the adapter treats the database schema on startup.
|
|
395
|
-
* - `push`: apply schema changes directly (development).
|
|
396
|
-
* - `verify`: fail if migrations are pending or the config changed without a migration (production).
|
|
397
|
-
* - `skip`: do nothing (used by the migration CLI).
|
|
398
|
-
*/
|
|
399
|
-
type SchemaMode = 'push' | 'verify' | 'skip';
|
|
400
|
-
interface DatabaseInitArgs {
|
|
401
|
-
readonly config: ResolvedConfig;
|
|
402
|
-
/** Project root; relative paths (database file, migrations) resolve from here. */
|
|
403
|
-
readonly cwd: string;
|
|
404
|
-
readonly schema: SchemaMode;
|
|
405
|
-
readonly logger: Logger;
|
|
406
|
-
/** Whether the process can prompt the user (the CLI in a terminal). */
|
|
407
|
-
readonly interactive?: boolean;
|
|
408
|
-
}
|
|
409
|
-
interface FindArgs {
|
|
410
|
-
readonly collection: string;
|
|
411
|
-
readonly where?: Where | undefined;
|
|
412
|
-
/** Field paths; prefix with `-` for descending. */
|
|
413
|
-
readonly sort: readonly string[];
|
|
414
|
-
/** `0` returns every matching document. */
|
|
415
|
-
readonly limit: number;
|
|
416
|
-
readonly page: number;
|
|
417
|
-
}
|
|
418
|
-
interface MigrationInfo {
|
|
419
|
-
readonly name: string;
|
|
420
|
-
readonly applied: boolean;
|
|
421
|
-
}
|
|
422
|
-
interface CreateMigrationResult {
|
|
423
|
-
readonly name: string;
|
|
424
|
-
readonly file: string;
|
|
425
|
-
readonly statements: readonly string[];
|
|
426
|
-
}
|
|
427
|
-
/** A connected database bound to one config. Returned by `DatabaseAdapter.init`. */
|
|
428
|
-
interface Database {
|
|
429
|
-
find(args: FindArgs): Promise<PaginatedDocs<RawDocument>>;
|
|
430
|
-
findById(args: {
|
|
431
|
-
collection: string;
|
|
432
|
-
id: ID;
|
|
433
|
-
}): Promise<RawDocument | null>;
|
|
434
|
-
/** Documents of one collection by id, in any order. Used to populate relationships. */
|
|
435
|
-
findByIds(args: {
|
|
436
|
-
collection: string;
|
|
437
|
-
ids: readonly ID[];
|
|
438
|
-
}): Promise<RawDocument[]>;
|
|
439
|
-
count(args: {
|
|
440
|
-
collection: string;
|
|
441
|
-
where?: Where | undefined;
|
|
442
|
-
}): Promise<number>;
|
|
443
|
-
/** `data` holds every field, `createdAt` and `updatedAt`. Runs in one transaction. */
|
|
444
|
-
create(args: {
|
|
445
|
-
collection: string;
|
|
446
|
-
data: Record<string, unknown>;
|
|
447
|
-
}): Promise<RawDocument>;
|
|
448
|
-
/**
|
|
449
|
-
* `data` holds the whole document after merging. Runs in one transaction. With `where`, only
|
|
450
|
-
* when the stored document matches it at the moment of writing (compare and set): `null` when
|
|
451
|
-
* it doesn't, and nothing is written.
|
|
452
|
-
*/
|
|
453
|
-
update(args: {
|
|
454
|
-
collection: string;
|
|
455
|
-
id: ID;
|
|
456
|
-
data: Record<string, unknown>;
|
|
457
|
-
}): Promise<RawDocument>;
|
|
458
|
-
update(args: {
|
|
459
|
-
collection: string;
|
|
460
|
-
id: ID;
|
|
461
|
-
data: Record<string, unknown>;
|
|
462
|
-
where: Where;
|
|
463
|
-
}): Promise<RawDocument | null>;
|
|
464
|
-
/**
|
|
465
|
-
* Adds `by` to a top-level number field in one statement, safe against concurrent calls; an
|
|
466
|
-
* empty value counts as 0. With `min` / `max`, only when the result stays within them. Returns
|
|
467
|
-
* the new value, or `null` when it would leave the bounds or the document is gone.
|
|
468
|
-
*/
|
|
469
|
-
increment(args: {
|
|
470
|
-
collection: string;
|
|
471
|
-
id: ID;
|
|
472
|
-
field: string;
|
|
473
|
-
by: number;
|
|
474
|
-
min?: number | undefined;
|
|
475
|
-
max?: number | undefined;
|
|
476
|
-
}): Promise<number | null>;
|
|
477
|
-
delete(args: {
|
|
478
|
-
collection: string;
|
|
479
|
-
id: ID;
|
|
480
|
-
}): Promise<void>;
|
|
481
|
-
findGlobal(args: {
|
|
482
|
-
slug: string;
|
|
483
|
-
}): Promise<Record<string, unknown> | null>;
|
|
484
|
-
updateGlobal(args: {
|
|
485
|
-
slug: string;
|
|
486
|
-
data: Record<string, unknown>;
|
|
487
|
-
}): Promise<Record<string, unknown>>;
|
|
488
|
-
/** Writes a migration file for the difference between the config and the latest migration. `null` when nothing changed. */
|
|
489
|
-
createMigration(args: {
|
|
490
|
-
name: string;
|
|
491
|
-
}): Promise<CreateMigrationResult | null>;
|
|
492
|
-
/** Applies pending migrations in order. Returns the names applied. */
|
|
493
|
-
migrate(): Promise<string[]>;
|
|
494
|
-
migrationStatus(): Promise<MigrationInfo[]>;
|
|
495
|
-
/**
|
|
496
|
-
* Copies the database to `file` (an absolute path) while it is in use, when the database can
|
|
497
|
-
* do that itself (SQLite). Others (Postgres) leave it out; use their own tools.
|
|
498
|
-
*/
|
|
499
|
-
readonly backup?: ((file: string) => Promise<void>) | undefined;
|
|
500
|
-
/** Raw table access for copying a whole database to another (`easy-cms copy`). */
|
|
501
|
-
readonly transfer?: DatabaseTransfer | undefined;
|
|
502
|
-
/** The prefix of its tables (`tablePrefix`), so a copy (a backup) can use the same. */
|
|
503
|
-
readonly tablePrefix?: string | undefined;
|
|
504
|
-
destroy(): Promise<void>;
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* Reads and writes stored rows as they are, ids and hidden fields included, bypassing validation
|
|
508
|
-
* and hooks. Only for moving a database: `copyDatabase()`.
|
|
509
|
-
*/
|
|
510
|
-
interface DatabaseTransfer {
|
|
511
|
-
/**
|
|
512
|
-
* Identifies the tables and columns the config produces, the same for every database kind.
|
|
513
|
-
* Two databases can be copied into each other when their hashes match.
|
|
514
|
-
*/
|
|
515
|
-
readonly schemaHash: string;
|
|
516
|
-
/** Every table, in an order that is safe to write. */
|
|
517
|
-
tables(): string[];
|
|
518
|
-
count(table: string): Promise<number>;
|
|
519
|
-
/** Rows in a stable order (by primary key), `offset` and `limit` for batches. */
|
|
520
|
-
read(table: string, offset: number, limit: number): Promise<Record<string, unknown>[]>;
|
|
521
|
-
write(table: string, rows: readonly Record<string, unknown>[]): Promise<void>;
|
|
522
|
-
/** After all writes, e.g. moves id sequences past the copied ids so new documents fit. */
|
|
523
|
-
finish(): Promise<void>;
|
|
524
|
-
}
|
|
525
|
-
/** What `sqlite()` / `postgres()` return and what `config.db` holds. */
|
|
526
|
-
interface DatabaseAdapter {
|
|
527
|
-
readonly name: string;
|
|
528
|
-
init(args: DatabaseInitArgs): Promise<Database>;
|
|
529
|
-
/** Hints for bundlers (Nitro, Next.js output tracing) about files static analysis cannot find. */
|
|
530
|
-
readonly bundle?: BundleHints;
|
|
531
|
-
}
|
|
532
|
-
interface BundleHints {
|
|
533
|
-
/** Absolute paths of files to ship with the server build, e.g. native binaries loaded dynamically. */
|
|
534
|
-
readonly traceInclude: readonly string[];
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/** An email to send. Addresses may be `name <address>`. */
|
|
538
|
-
interface EmailMessage {
|
|
539
|
-
readonly to: string | readonly string[];
|
|
540
|
-
readonly cc?: string | readonly string[];
|
|
541
|
-
readonly bcc?: string | readonly string[];
|
|
542
|
-
/** Default: the adapter's `from`. */
|
|
543
|
-
readonly from?: string;
|
|
544
|
-
readonly replyTo?: string;
|
|
545
|
-
readonly subject: string;
|
|
546
|
-
readonly html?: string;
|
|
547
|
-
readonly text?: string;
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* Sends email for Easy CMS and its plugins (form notifications, and more later). Set one in
|
|
551
|
-
* the config as `email`, e.g. `smtp()` from `@easy-cms/email-smtp`, or `consoleEmail()` in
|
|
552
|
-
* development. Throw to report a failure: the message is retried later.
|
|
553
|
-
*/
|
|
554
|
-
interface EmailAdapter {
|
|
555
|
-
/** What sends the email, e.g. `smtp`; shown to admins on the dashboard (System). */
|
|
556
|
-
readonly name?: string;
|
|
557
|
-
/** The sender when a message has none, e.g. `Easy CMS <no-reply@example.com>`. */
|
|
558
|
-
readonly from?: string | undefined;
|
|
559
|
-
send(message: EmailMessage & {
|
|
560
|
-
readonly from: string;
|
|
561
|
-
}): Promise<void>;
|
|
562
|
-
/**
|
|
563
|
-
* The settings in use, shown to admins on Settings → Email. Never return secrets: say whether
|
|
564
|
-
* a password is set, not what it is.
|
|
565
|
-
*/
|
|
566
|
-
describe?(): readonly EmailSetting[];
|
|
567
|
-
/** Checks the connection without sending (Settings → Email); throws with the reason. */
|
|
568
|
-
verify?(): Promise<void>;
|
|
569
|
-
}
|
|
570
|
-
/** One setting an email adapter shows to admins (`describe()`). */
|
|
571
|
-
interface EmailSetting {
|
|
572
|
-
/**
|
|
573
|
-
* `host`, `port`, `secure`, `user`, `password`, `from` and `delivery` (`log`: printed, not sent)
|
|
574
|
-
* are translated in the admin; others are shown as they are.
|
|
575
|
-
*/
|
|
576
|
-
readonly key: string;
|
|
577
|
-
/** `null`: not set. */
|
|
578
|
-
readonly value: string | null;
|
|
579
|
-
/** Where it comes from, e.g. `SMTP_HOST` or `smtp({ host })`. */
|
|
580
|
-
readonly source?: string;
|
|
581
|
-
}
|
|
582
|
-
/** Prints emails to the log instead of sending them: for development and tests. */
|
|
583
|
-
declare function consoleEmail(options?: {
|
|
584
|
-
from?: string;
|
|
585
|
-
log?: (text: string) => void;
|
|
586
|
-
}): EmailAdapter & {
|
|
587
|
-
readonly sent: (EmailMessage & {
|
|
588
|
-
from: string;
|
|
589
|
-
})[];
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
type Simplify<T> = {
|
|
593
|
-
[K in keyof T]: T[K];
|
|
594
|
-
} & {};
|
|
595
|
-
type OptionValue<O> = O extends string ? O : O extends {
|
|
596
|
-
readonly value: infer V;
|
|
597
|
-
} ? V : never;
|
|
598
|
-
type SelectValue<F> = F extends {
|
|
599
|
-
readonly options: readonly (infer O extends SelectOption)[];
|
|
600
|
-
} ? F extends {
|
|
601
|
-
readonly hasMany: true;
|
|
602
|
-
} ? OptionValue<O>[] : OptionValue<O> : never;
|
|
603
|
-
/** The built-in users collection as seen by the type system (hidden fields left out). */
|
|
604
|
-
interface BuiltinUsersCollection {
|
|
605
|
-
readonly slug: 'users';
|
|
606
|
-
readonly fields: readonly [
|
|
607
|
-
{
|
|
608
|
-
readonly name: 'email';
|
|
609
|
-
readonly type: 'email';
|
|
610
|
-
readonly required: true;
|
|
611
|
-
},
|
|
612
|
-
{
|
|
613
|
-
readonly name: 'name';
|
|
614
|
-
readonly type: 'text';
|
|
615
|
-
},
|
|
616
|
-
{
|
|
617
|
-
readonly name: 'role';
|
|
618
|
-
readonly type: 'select';
|
|
619
|
-
readonly options: readonly string[];
|
|
620
|
-
readonly required: true;
|
|
621
|
-
},
|
|
622
|
-
{
|
|
623
|
-
readonly name: 'active';
|
|
624
|
-
readonly type: 'boolean';
|
|
625
|
-
}
|
|
626
|
-
];
|
|
627
|
-
}
|
|
628
|
-
/** The built-in API keys collection (`apiKeys: true`); the key's hash is left out. */
|
|
629
|
-
interface BuiltinApiKeysCollection {
|
|
630
|
-
readonly slug: 'api-keys';
|
|
631
|
-
readonly fields: readonly [
|
|
632
|
-
{
|
|
633
|
-
readonly name: 'name';
|
|
634
|
-
readonly type: 'text';
|
|
635
|
-
readonly required: true;
|
|
636
|
-
},
|
|
637
|
-
{
|
|
638
|
-
readonly name: 'permissions';
|
|
639
|
-
readonly type: 'json';
|
|
640
|
-
},
|
|
641
|
-
{
|
|
642
|
-
readonly name: 'expiresAt';
|
|
643
|
-
readonly type: 'date';
|
|
644
|
-
},
|
|
645
|
-
{
|
|
646
|
-
readonly name: 'prefix';
|
|
647
|
-
readonly type: 'text';
|
|
648
|
-
},
|
|
649
|
-
{
|
|
650
|
-
readonly name: 'user';
|
|
651
|
-
readonly type: 'relationship';
|
|
652
|
-
readonly to: 'users';
|
|
653
|
-
},
|
|
654
|
-
{
|
|
655
|
-
readonly name: 'lastUsedAt';
|
|
656
|
-
readonly type: 'date';
|
|
657
|
-
}
|
|
658
|
-
];
|
|
659
|
-
}
|
|
660
|
-
/** The built-in media folders (`upload.folders: true`). */
|
|
661
|
-
interface BuiltinMediaFoldersCollection {
|
|
662
|
-
readonly slug: 'media-folders';
|
|
663
|
-
readonly fields: readonly [
|
|
664
|
-
{
|
|
665
|
-
readonly name: 'name';
|
|
666
|
-
readonly type: 'text';
|
|
667
|
-
readonly required: true;
|
|
668
|
-
},
|
|
669
|
-
{
|
|
670
|
-
readonly name: 'parent';
|
|
671
|
-
readonly type: 'relationship';
|
|
672
|
-
readonly to: 'media-folders';
|
|
673
|
-
},
|
|
674
|
-
{
|
|
675
|
-
readonly name: 'key';
|
|
676
|
-
readonly type: 'text';
|
|
677
|
-
},
|
|
678
|
-
{
|
|
679
|
-
readonly name: 'private';
|
|
680
|
-
readonly type: 'boolean';
|
|
681
|
-
},
|
|
682
|
-
{
|
|
683
|
-
readonly name: 'permissions';
|
|
684
|
-
readonly type: 'json';
|
|
685
|
-
}
|
|
686
|
-
];
|
|
687
|
-
}
|
|
688
|
-
/** `media-folders` when the config turns folders on. */
|
|
689
|
-
type FoldersOf<C extends Config> = C extends {
|
|
690
|
-
readonly upload: {
|
|
691
|
-
readonly folders: true;
|
|
692
|
-
};
|
|
693
|
-
} ? BuiltinMediaFoldersCollection : never;
|
|
694
|
-
/** `api-keys` when the config turns API keys on. */
|
|
695
|
-
type ApiKeysOf<C extends Config> = C extends {
|
|
696
|
-
readonly apiKeys: true;
|
|
697
|
-
} ? BuiltinApiKeysCollection : never;
|
|
698
|
-
/** The `PluginTypes` of each plugin in the config (a union). */
|
|
699
|
-
type PluginTypesOf<C extends Config> = C extends {
|
|
700
|
-
readonly plugins: readonly (infer P)[];
|
|
701
|
-
} ? P extends TypedPlugin<infer T extends PluginTypes> ? T : never : never;
|
|
702
|
-
/** Fields that plugins add to the collection (or global) `S`, as a union. */
|
|
703
|
-
type AddedFields<T, S> = T extends {
|
|
704
|
-
readonly [K in 'fields' | 'globalFields']?: infer M;
|
|
705
|
-
} ? M extends {
|
|
706
|
-
readonly [slug: string]: readonly Field[];
|
|
707
|
-
} ? string extends keyof M ? never : S extends keyof M ? M[S][number] : never : never : never;
|
|
708
|
-
type PluginFields<C extends Config, S, Key extends 'fields' | 'globalFields'> = AddedFields<PluginTypesOf<C> extends infer T ? T extends PluginTypes ? Pick<T, Key & keyof T> : never : never, S>;
|
|
709
|
-
/** Collections that plugins add. */
|
|
710
|
-
type PluginCollections<C extends Config> = PluginTypesOf<C> extends infer T ? T extends {
|
|
711
|
-
readonly collections: readonly (infer K extends CollectionConfig)[];
|
|
712
|
-
} ? K : never : never;
|
|
713
|
-
/** A collection or global with the fields plugins add to it. */
|
|
714
|
-
type WithPluginFields<C extends Config, T extends {
|
|
715
|
-
readonly slug: string;
|
|
716
|
-
readonly fields: readonly Field[];
|
|
717
|
-
}, Key extends 'fields' | 'globalFields'> = [PluginFields<C, T['slug'], Key>] extends [never] ? T : Omit<T, 'fields'> & {
|
|
718
|
-
readonly fields: readonly (T['fields'][number] | PluginFields<C, T['slug'], Key>)[];
|
|
719
|
-
};
|
|
720
|
-
/**
|
|
721
|
-
* The config's own collections. A literal config without `collections` has none (only plugins
|
|
722
|
-
* add some); a loosely typed `Config` has any.
|
|
723
|
-
*/
|
|
724
|
-
type OwnCollections<C extends Config> = 'collections' extends keyof C ? NonNullable<C['collections']>[number] : never;
|
|
725
|
-
/** Every collection of a config, including the built-in ones and those plugins add. */
|
|
726
|
-
type AllCollections<C extends Config> = OwnCollections<C> | BuiltinUsersCollection | BuiltinMediaCollection | ApiKeysOf<C> | FoldersOf<C> | PluginCollections<C>;
|
|
727
|
-
/** The built-in media collection; its documents are typed as `MediaDocument`. */
|
|
728
|
-
interface BuiltinMediaCollection {
|
|
729
|
-
readonly slug: 'media';
|
|
730
|
-
readonly fields: readonly [];
|
|
731
|
-
}
|
|
732
|
-
type CollectionBySlug<C extends Config, S> = WithPluginFields<C, Extract<AllCollections<C>, {
|
|
733
|
-
readonly slug: S;
|
|
734
|
-
}>, 'fields'>;
|
|
735
|
-
type GlobalBySlug<C extends Config, S> = WithPluginFields<C, Extract<NonNullable<C['globals']>[number], {
|
|
736
|
-
readonly slug: S;
|
|
737
|
-
}>, 'globalFields'>;
|
|
738
|
-
/** Relationships are ids at depth 0 and documents when populated. */
|
|
739
|
-
type RelationValue<C extends Config, S> = [CollectionBySlug<C, S>] extends [never] ? ID | Record<string, unknown> : ID | CollectionDocument<C, S & string>;
|
|
740
|
-
interface MediaSize {
|
|
741
|
-
filename: string;
|
|
742
|
-
width: number;
|
|
743
|
-
height: number;
|
|
744
|
-
filesize: number;
|
|
745
|
-
url: string;
|
|
746
|
-
}
|
|
747
|
-
/** A document of the built-in `media` collection, as returned by the API. */
|
|
748
|
-
interface MediaDocument {
|
|
749
|
-
id: ID;
|
|
750
|
-
filename: string;
|
|
751
|
-
originalName?: string | null;
|
|
752
|
-
mimeType: string;
|
|
753
|
-
filesize: number;
|
|
754
|
-
width?: number | null;
|
|
755
|
-
height?: number | null;
|
|
756
|
-
alt?: string | null;
|
|
757
|
-
/** Its media folder (`upload.folders`), when it is in one. */
|
|
758
|
-
folder?: ID | null;
|
|
759
|
-
/** In a private folder: `url` needs a signed-in user who may see it, or `cms.signedMediaURL()`. */
|
|
760
|
-
private?: boolean | null;
|
|
761
|
-
/** URL of the file: public, or for private files the API's, which checks who asks. */
|
|
762
|
-
url: string;
|
|
763
|
-
/** Resized copies, when `upload.imageSizes` is set and sharp is installed. */
|
|
764
|
-
sizes: Record<string, MediaSize>;
|
|
765
|
-
createdAt: string;
|
|
766
|
-
updatedAt: string;
|
|
767
|
-
[field: string]: unknown;
|
|
768
|
-
}
|
|
769
|
-
type FieldValue<F extends Field, C extends Config = Config> = F extends {
|
|
770
|
-
readonly type: 'text' | 'textarea' | 'email' | 'slug' | 'date';
|
|
771
|
-
} ? string : F extends {
|
|
772
|
-
readonly type: 'number';
|
|
773
|
-
} ? number : F extends {
|
|
774
|
-
readonly type: 'boolean';
|
|
775
|
-
} ? boolean : F extends {
|
|
776
|
-
readonly type: 'json';
|
|
777
|
-
} ? unknown : F extends {
|
|
778
|
-
readonly type: 'select';
|
|
779
|
-
} ? SelectValue<F> : F extends {
|
|
780
|
-
readonly type: 'richText';
|
|
781
|
-
} ? RichTextDocument : F extends {
|
|
782
|
-
readonly type: 'upload';
|
|
783
|
-
} ? F extends {
|
|
784
|
-
readonly hasMany: true;
|
|
785
|
-
} ? (ID | MediaDocument)[] : ID | MediaDocument : F extends {
|
|
786
|
-
readonly type: 'relationship';
|
|
787
|
-
readonly to: infer S;
|
|
788
|
-
} ? F extends {
|
|
789
|
-
readonly hasMany: true;
|
|
790
|
-
} ? RelationValue<C, S>[] : RelationValue<C, S> : F extends {
|
|
791
|
-
readonly type: 'array';
|
|
792
|
-
readonly fields: infer Sub extends readonly Field[];
|
|
793
|
-
} ? Simplify<FieldsValue<Sub, C> & {
|
|
794
|
-
id: string;
|
|
795
|
-
}>[] : F extends {
|
|
796
|
-
readonly type: 'group';
|
|
797
|
-
readonly fields: infer Sub extends readonly Field[];
|
|
798
|
-
} ? FieldsValue<Sub, C> : F extends {
|
|
799
|
-
readonly type: 'blocks';
|
|
800
|
-
readonly blocks: infer B extends readonly Block[];
|
|
801
|
-
} ? BlockValue<B[number], C>[] : CustomValue<F>;
|
|
802
|
-
/** The value of a field of an added type (`CustomFieldTypes`). */
|
|
803
|
-
type CustomValue<F> = F extends {
|
|
804
|
-
readonly type: infer T extends keyof CustomFieldTypes;
|
|
805
|
-
} ? CustomFieldTypes[T] extends {
|
|
806
|
-
value: infer V;
|
|
807
|
-
} ? V : unknown : never;
|
|
808
|
-
/** One row of a `blocks` field: the block's fields plus `id` and `blockType` (distributes over kinds). */
|
|
809
|
-
type BlockValue<B, C extends Config> = B extends {
|
|
810
|
-
readonly slug: infer S;
|
|
811
|
-
readonly fields: infer Sub extends readonly Field[];
|
|
812
|
-
} ? Simplify<FieldsValue<Sub, C> & {
|
|
813
|
-
id: string;
|
|
814
|
-
blockType: S;
|
|
815
|
-
}> : never;
|
|
816
|
-
type BlockInput<B> = B extends {
|
|
817
|
-
readonly slug: infer S;
|
|
818
|
-
readonly fields: infer Sub extends readonly Field[];
|
|
819
|
-
} ? Simplify<FieldsInput<Sub> & {
|
|
820
|
-
id?: string;
|
|
821
|
-
blockType: S;
|
|
822
|
-
}> : never;
|
|
823
|
-
type Visible<Fs extends readonly Field[]> = Exclude<Fs[number], {
|
|
824
|
-
readonly hidden: true;
|
|
825
|
-
}>;
|
|
826
|
-
/** Always set when read: required fields, and lists and groups (empty `[]` / `{}` otherwise). */
|
|
827
|
-
type Present = {
|
|
828
|
-
readonly required: true;
|
|
829
|
-
} | {
|
|
830
|
-
readonly type: 'array' | 'blocks' | 'group';
|
|
831
|
-
} | {
|
|
832
|
-
readonly hasMany: true;
|
|
833
|
-
};
|
|
834
|
-
type RequiredFields<Fs extends readonly Field[]> = Extract<Visible<Fs>, Present>;
|
|
835
|
-
type OptionalFields<Fs extends readonly Field[]> = Exclude<Visible<Fs>, Present>;
|
|
836
|
-
/** The shape of the data described by a list of fields. */
|
|
837
|
-
type FieldsValue<Fs extends readonly Field[], C extends Config = Config> = Simplify<{
|
|
838
|
-
-readonly [F in RequiredFields<Fs> as F['name']]: FieldValue<F, C>;
|
|
839
|
-
} & {
|
|
840
|
-
-readonly [F in OptionalFields<Fs> as F['name']]?: FieldValue<F, C> | null;
|
|
841
|
-
}>;
|
|
842
|
-
type SystemFields<T extends {
|
|
843
|
-
readonly drafts?: unknown;
|
|
844
|
-
}> = {
|
|
845
|
-
id: ID;
|
|
846
|
-
createdAt: string;
|
|
847
|
-
updatedAt: string;
|
|
848
|
-
} & (T extends {
|
|
849
|
-
readonly drafts: true;
|
|
850
|
-
} ? {
|
|
851
|
-
status: 'draft' | 'published';
|
|
852
|
-
} : unknown);
|
|
853
|
-
/** What document types are inferred from: a collection's or global's fields and drafts. */
|
|
854
|
-
type FieldsOwner = {
|
|
855
|
-
readonly fields: readonly Field[];
|
|
856
|
-
readonly drafts?: unknown;
|
|
857
|
-
};
|
|
858
|
-
type InferCollection<T extends FieldsOwner, C extends Config = Config> = Simplify<SystemFields<T> & FieldsValue<T['fields'], C>>;
|
|
859
|
-
type InferGlobal<T extends FieldsOwner, C extends Config = Config> = Simplify<
|
|
860
|
-
/** `null` until the global is saved for the first time. */
|
|
861
|
-
{
|
|
862
|
-
updatedAt: string | null;
|
|
863
|
-
} & (T extends {
|
|
864
|
-
readonly drafts: true;
|
|
865
|
-
} ? {
|
|
866
|
-
status: 'draft' | 'published';
|
|
867
|
-
} : unknown) & FieldsValue<T['fields'], C>>;
|
|
868
|
-
type CollectionSlug<C extends Config> = OwnCollections<C>['slug'] | 'users' | 'media' | ApiKeysOf<C>['slug'] | FoldersOf<C>['slug'] | PluginCollections<C>['slug'];
|
|
869
|
-
type GlobalSlug<C extends Config> = NonNullable<C['globals']>[number]['slug'];
|
|
870
|
-
/** Document type of a collection, e.g. `CollectionDocument<typeof config, 'posts'>`. */
|
|
871
|
-
type CollectionDocument<C extends Config, S extends CollectionSlug<C>> = S extends 'media' ? MediaDocument : InferCollection<CollectionBySlug<C, S>, C>;
|
|
872
|
-
/** Data type of a global, e.g. `GlobalDocument<typeof config, 'site'>`. */
|
|
873
|
-
type GlobalDocument<C extends Config, S extends GlobalSlug<C>> = InferGlobal<GlobalBySlug<C, S>, C>;
|
|
874
|
-
type InputValue<F extends Field> = F extends {
|
|
875
|
-
readonly type: 'relationship';
|
|
876
|
-
} ? F extends {
|
|
877
|
-
readonly hasMany: true;
|
|
878
|
-
} ? readonly ID[] : ID : F extends {
|
|
879
|
-
readonly type: 'select';
|
|
880
|
-
readonly hasMany: true;
|
|
881
|
-
} ? Readonly<SelectValue<F>> : F extends {
|
|
882
|
-
readonly type: 'upload';
|
|
883
|
-
} ? F extends {
|
|
884
|
-
readonly hasMany: true;
|
|
885
|
-
} ? readonly ID[] : ID : F extends {
|
|
886
|
-
readonly type: 'date';
|
|
887
|
-
} ? string | Date : F extends {
|
|
888
|
-
readonly type: 'array';
|
|
889
|
-
readonly fields: infer Sub extends readonly Field[];
|
|
890
|
-
} ? readonly Simplify<FieldsInput<Sub> & {
|
|
891
|
-
id?: string;
|
|
892
|
-
}>[] : F extends {
|
|
893
|
-
readonly type: 'group';
|
|
894
|
-
readonly fields: infer Sub extends readonly Field[];
|
|
895
|
-
} ? FieldsInput<Sub> : F extends {
|
|
896
|
-
readonly type: 'blocks';
|
|
897
|
-
readonly blocks: infer B extends readonly Block[];
|
|
898
|
-
} ? readonly BlockInput<B[number]>[] : FieldValue<F>;
|
|
899
|
-
/**
|
|
900
|
-
* Required fields must be given, unless Easy CMS can fill them (default value, slug from another
|
|
901
|
-
* field) or a condition may hide them (`admin.condition`).
|
|
902
|
-
*/
|
|
903
|
-
type NeedsInput<F extends Field> = F extends {
|
|
904
|
-
readonly required: true;
|
|
905
|
-
} ? F extends {
|
|
906
|
-
readonly defaultValue: unknown;
|
|
907
|
-
} ? never : F extends {
|
|
908
|
-
readonly type: 'slug';
|
|
909
|
-
readonly from: string;
|
|
910
|
-
} ? never : F extends {
|
|
911
|
-
readonly admin: {
|
|
912
|
-
readonly condition: object;
|
|
913
|
-
};
|
|
914
|
-
} ? never : F : never;
|
|
915
|
-
type FieldsInput<Fs extends readonly Field[]> = Simplify<{
|
|
916
|
-
-readonly [F in NeedsInput<Visible<Fs>> as F['name']]: InputValue<F>;
|
|
917
|
-
} & {
|
|
918
|
-
-readonly [F in Exclude<Visible<Fs>, NeedsInput<Visible<Fs>>> as F['name']]?: InputValue<F> | null;
|
|
919
|
-
}>;
|
|
920
|
-
type StatusInput<T> = T extends {
|
|
921
|
-
readonly drafts: true;
|
|
922
|
-
} ? {
|
|
923
|
-
status?: 'draft' | 'published';
|
|
924
|
-
} : unknown;
|
|
925
|
-
/**
|
|
926
|
-
* A user's password, hashed and never returned. Optional when creating: a user without one sets
|
|
927
|
-
* it from an invitation link.
|
|
928
|
-
*/
|
|
929
|
-
type PasswordInput<S, Required extends boolean> = S extends 'users' ? Required extends true ? {
|
|
930
|
-
password: string;
|
|
931
|
-
} : {
|
|
932
|
-
password?: string;
|
|
933
|
-
} : unknown;
|
|
934
|
-
/** Data accepted by `create`, e.g. `CreateInput<typeof config, 'posts'>`. */
|
|
935
|
-
type CreateInput<C extends Config, S extends CollectionSlug<C>> = Simplify<FieldsInput<CollectionBySlug<C, S>['fields']> & StatusInput<CollectionBySlug<C, S>> & PasswordInput<S, false>>;
|
|
936
|
-
/** Data accepted by `update`: any subset of `CreateInput`. */
|
|
937
|
-
type UpdateInput<C extends Config, S extends CollectionSlug<C>> = Partial<CreateInput<C, S>>;
|
|
938
|
-
/** Data accepted by `updateGlobal`. */
|
|
939
|
-
type GlobalInput<C extends Config, S extends GlobalSlug<C>> = Partial<Simplify<FieldsInput<GlobalBySlug<C, S>['fields']> & StatusInput<GlobalBySlug<C, S>>>>;
|
|
940
|
-
|
|
941
|
-
/** What a role may do with a collection or global, ticked in Settings → Roles. */
|
|
942
|
-
declare const ROLE_OPERATIONS: readonly ["read", "create", "update", "delete", "publish"];
|
|
943
|
-
type RoleOperation = (typeof ROLE_OPERATIONS)[number];
|
|
944
|
-
/** A field a role may only read, or not see at all; fields not listed follow the collection. */
|
|
945
|
-
type FieldRule = 'read' | 'hidden';
|
|
946
|
-
type FieldRules = Readonly<Record<string, Readonly<Record<string, FieldRule>>>>;
|
|
947
|
-
/**
|
|
948
|
-
* Stored in a role's `permissions`. Anything not listed is not allowed. A slug listed with no
|
|
949
|
-
* operations has been looked at and given nothing (it is not "new" in Settings → Roles).
|
|
950
|
-
*/
|
|
951
|
-
interface RolePermissions {
|
|
952
|
-
readonly collections?: Readonly<Record<string, readonly RoleOperation[]>>;
|
|
953
|
-
readonly globals?: Readonly<Record<string, readonly RoleOperation[]>>;
|
|
954
|
-
/** Admin pages: `status`, `deliveries`, `page:<path>` (`admin.pages`), `widget:<tag>` (`admin.dashboard`). */
|
|
955
|
-
readonly admin?: readonly string[];
|
|
956
|
-
/** Operations of `collections` allowed only on the role's own documents (see `admin.ownerField`). */
|
|
957
|
-
readonly own?: Readonly<Record<string, readonly RoleOperation[]>>;
|
|
958
|
-
/** Top-level fields the role may only read or not see, by collection or global slug. */
|
|
959
|
-
readonly fields?: {
|
|
960
|
-
readonly collections?: FieldRules;
|
|
961
|
-
readonly globals?: FieldRules;
|
|
962
|
-
};
|
|
963
|
-
}
|
|
964
|
-
/** One role as Settings → Roles shows it (`GET <api>/admin/roles`). */
|
|
965
|
-
interface AdminRole {
|
|
966
|
-
id: ID;
|
|
967
|
-
key: string;
|
|
968
|
-
/** The name shown in the admin; empty for roles from the config that were not renamed. */
|
|
969
|
-
name: string;
|
|
970
|
-
/** From `auth.roles`: can't be deleted. */
|
|
971
|
-
system: boolean;
|
|
972
|
-
/** Users with this role. */
|
|
973
|
-
users: number;
|
|
974
|
-
permissions: RolePermissions;
|
|
975
|
-
updatedAt: string;
|
|
976
|
-
}
|
|
977
|
-
/** A top-level field whose permission can be set per role. */
|
|
978
|
-
interface AdminRoleField {
|
|
979
|
-
name: string;
|
|
980
|
-
label?: Label;
|
|
981
|
-
/** Required without a default value: roles that create documents must be able to fill it. */
|
|
982
|
-
required: boolean;
|
|
983
|
-
/** The collection's owner field (`admin.ownerField`). */
|
|
984
|
-
owner?: boolean;
|
|
985
|
-
}
|
|
986
|
-
/** A row of the permissions table. */
|
|
987
|
-
interface AdminRoleTarget {
|
|
988
|
-
slug: string;
|
|
989
|
-
/** Operations that make sense here (`publish` with drafts). */
|
|
990
|
-
ops: RoleOperation[];
|
|
991
|
-
/** No role has been given anything here yet (added to the config since). */
|
|
992
|
-
new: boolean;
|
|
993
|
-
fields: AdminRoleField[];
|
|
994
|
-
}
|
|
995
|
-
/** A page or dashboard panel that can be given to roles. */
|
|
996
|
-
interface AdminRoleView {
|
|
997
|
-
id: string;
|
|
998
|
-
/** For plugin pages and panels; the admin names `status` and `deliveries` itself. */
|
|
999
|
-
label?: Label;
|
|
1000
|
-
}
|
|
1001
|
-
/** Settings → Roles (`GET <api>/admin/roles`). */
|
|
1002
|
-
interface AdminRoles {
|
|
1003
|
-
roles: AdminRole[];
|
|
1004
|
-
collections: (AdminRoleTarget & {
|
|
1005
|
-
/** Collections its relationship and upload fields point to. */
|
|
1006
|
-
references: string[];
|
|
1007
|
-
/** Can be limited to own documents: the field that names the owner (`createdBy` by default). */
|
|
1008
|
-
owner?: string;
|
|
1009
|
-
})[];
|
|
1010
|
-
globals: AdminRoleTarget[];
|
|
1011
|
-
views: AdminRoleView[];
|
|
1012
|
-
}
|
|
1013
|
-
/** One change to a role (`GET <api>/admin/roles/:id/history`). */
|
|
1014
|
-
interface AdminRoleChange {
|
|
1015
|
-
id: ID;
|
|
1016
|
-
/** Email of who saved it, or `null` when it was the system (first start). */
|
|
1017
|
-
author: string | null;
|
|
1018
|
-
createdAt: string;
|
|
1019
|
-
name: string;
|
|
1020
|
-
permissions: RolePermissions;
|
|
1021
|
-
}
|
|
1022
|
-
/**
|
|
1023
|
-
* Roles and their permissions (`auth.rbac`), on top of access rules: an operation is allowed
|
|
1024
|
-
* when both allow it. Admins may do everything; requests that are not logged in are left to
|
|
1025
|
-
* access rules alone.
|
|
1026
|
-
*/
|
|
1027
|
-
declare class Roles {
|
|
1028
|
-
private readonly config;
|
|
1029
|
-
private readonly db;
|
|
1030
|
-
private readonly log?;
|
|
1031
|
-
/** Writes to the audit log (`audit`). */
|
|
1032
|
-
private readonly audit?;
|
|
1033
|
-
readonly enabled: boolean;
|
|
1034
|
-
private readonly versions;
|
|
1035
|
-
private cache;
|
|
1036
|
-
private synced;
|
|
1037
|
-
constructor(config: ResolvedConfig, db: Database, log?: ((message: string) => void) | undefined,
|
|
1038
|
-
/** Writes to the audit log (`audit`). */
|
|
1039
|
-
audit?: ((entry: {
|
|
1040
|
-
action: string;
|
|
1041
|
-
target: string;
|
|
1042
|
-
doc: ID;
|
|
1043
|
-
title: string;
|
|
1044
|
-
changes?: {
|
|
1045
|
-
field: string;
|
|
1046
|
-
before?: unknown;
|
|
1047
|
-
after?: unknown;
|
|
1048
|
-
}[];
|
|
1049
|
-
}) => Promise<void>) | undefined);
|
|
1050
|
-
/**
|
|
1051
|
-
* Whether the user's role allows an operation: `true`, `false`, or for their own account
|
|
1052
|
-
* (users without read or update on `users`) a constraint to it.
|
|
1053
|
-
*/
|
|
1054
|
-
allows(user: AuthUser | null, target: {
|
|
1055
|
-
collection: string;
|
|
1056
|
-
} | {
|
|
1057
|
-
global: string;
|
|
1058
|
-
}, operation: RoleOperation): Promise<boolean | Where>;
|
|
1059
|
-
/**
|
|
1060
|
-
* Whether the user may open an admin page: admins always; with roles, those whose role has
|
|
1061
|
-
* it; without roles, nobody else (`status`, `deliveries`) or everybody (plugin pages).
|
|
1062
|
-
*/
|
|
1063
|
-
canView(user: AuthUser, id: string): Promise<boolean>;
|
|
1064
|
-
/**
|
|
1065
|
-
* The field that names a collection's owner, for "own documents only": `admin.ownerField`, or
|
|
1066
|
-
* `createdBy`. `undefined` without roles, and for users and collections roles don't cover.
|
|
1067
|
-
*/
|
|
1068
|
-
ownerOf(slug: string): string | undefined;
|
|
1069
|
-
/**
|
|
1070
|
-
* The user's field rules (top-level fields their role may only read, or not see), for field
|
|
1071
|
-
* access checks. Roles that may update only their own documents can't change the owner field.
|
|
1072
|
-
*/
|
|
1073
|
-
fieldRules(user: AuthUser | null): Promise<ReadonlyMap<Field, FieldRule> | undefined>;
|
|
1074
|
-
/** A new document is its creator's: sets `createdBy`, and an empty owner field. */
|
|
1075
|
-
fillOwner(slug: string, data: Record<string, unknown>, user: AuthUser | null): void;
|
|
1076
|
-
/** Documents a user owns, by collection (`createdBy` and owner fields), for deleting them. */
|
|
1077
|
-
owned(userId: ID): Promise<{
|
|
1078
|
-
collection: string;
|
|
1079
|
-
count: number;
|
|
1080
|
-
}[]>;
|
|
1081
|
-
/** Gives a deleted user's documents to another user, or to nobody (`null`). */
|
|
1082
|
-
transfer(from: ID, to: ID | null): Promise<void>;
|
|
1083
|
-
/** Forgets cached permissions, after a role changed. */
|
|
1084
|
-
invalidate(): void;
|
|
1085
|
-
/**
|
|
1086
|
-
* Adds the roles of `auth.roles` that are missing; on the first start, gives them everything.
|
|
1087
|
-
* Runs once, on first use (not on startup: `easy-cms migrate` opens the CMS before the table exists).
|
|
1088
|
-
*/
|
|
1089
|
-
sync(): Promise<void>;
|
|
1090
|
-
private addMissing;
|
|
1091
|
-
/**
|
|
1092
|
-
* Documents from before roles have no `createdBy`: it is filled from who saved their first
|
|
1093
|
-
* version, for collections with versions. Only while no document of the collection has one,
|
|
1094
|
-
* so it runs once (and finds nothing to do afterwards).
|
|
1095
|
-
*/
|
|
1096
|
-
private backfillCreators;
|
|
1097
|
-
/** Whether a role exists (users can only be given existing roles). */
|
|
1098
|
-
exists(key: string): Promise<boolean>;
|
|
1099
|
-
/** Roles a user can be given, those of `auth.roles` first; `name` may be empty. */
|
|
1100
|
-
options(): Promise<{
|
|
1101
|
-
key: string;
|
|
1102
|
-
name: string;
|
|
1103
|
-
}[]>;
|
|
1104
|
-
list(): Promise<AdminRoles>;
|
|
1105
|
-
create(input: {
|
|
1106
|
-
key?: unknown;
|
|
1107
|
-
name?: unknown;
|
|
1108
|
-
permissions?: unknown;
|
|
1109
|
-
}, author: AuthUser): Promise<AdminRole>;
|
|
1110
|
-
update(id: ID, input: {
|
|
1111
|
-
name?: unknown;
|
|
1112
|
-
permissions?: unknown;
|
|
1113
|
-
}, author: AuthUser): Promise<AdminRole>;
|
|
1114
|
-
delete(id: ID): Promise<void>;
|
|
1115
|
-
history(id: ID): Promise<AdminRoleChange[]>;
|
|
1116
|
-
/** Pages and panels roles can be given. */
|
|
1117
|
-
private views;
|
|
1118
|
-
/** Every collection and global, and the plugin pages and panels: what roles had before roles. */
|
|
1119
|
-
private everything;
|
|
1120
|
-
/** Keeps only known collections, globals, operations and pages; `read` comes with the others. */
|
|
1121
|
-
private clean;
|
|
1122
|
-
/**
|
|
1123
|
-
* Keeps rules for known top-level fields. A field that must be filled in (required, without a
|
|
1124
|
-
* default) can't be read-only or hidden for a role that creates documents.
|
|
1125
|
-
*/
|
|
1126
|
-
private cleanRules;
|
|
1127
|
-
/** Fields that name a collection's owner (`createdBy`, and `admin.ownerField`). */
|
|
1128
|
-
private ownerColumns;
|
|
1129
|
-
/** Sets one field of every matching document, keeping the rest as stored. */
|
|
1130
|
-
private setWhere;
|
|
1131
|
-
private cleanName;
|
|
1132
|
-
private permissionsOf;
|
|
1133
|
-
private load;
|
|
1134
|
-
private rows;
|
|
1135
|
-
private row;
|
|
1136
|
-
private users;
|
|
1137
|
-
private insert;
|
|
1138
|
-
/** Keeps the role as saved, for its history. */
|
|
1139
|
-
private record;
|
|
1140
|
-
private toRole;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
/** What a role may do in a folder (`upload.folders` with `auth.rbac`), each including the last. */
|
|
1144
|
-
declare const FOLDER_LEVELS: readonly ["view", "edit", "manage"];
|
|
1145
|
-
/**
|
|
1146
|
-
* `view`: see its files and use them in documents. `edit`: also upload, rename, move and delete
|
|
1147
|
-
* files. `manage`: also create, rename, move and delete subfolders.
|
|
1148
|
-
*/
|
|
1149
|
-
type FolderLevel = (typeof FOLDER_LEVELS)[number];
|
|
1150
|
-
/** A folder's `permissions`: each role's level; roles not listed get nothing. */
|
|
1151
|
-
type FolderPermissions = Readonly<Record<string, FolderLevel>>;
|
|
1152
|
-
/**
|
|
1153
|
-
* Folders of the media library and their permissions. A folder without its own permissions
|
|
1154
|
-
* follows its parent; the top level is open to every role that may use `media`. Folder
|
|
1155
|
-
* permissions only narrow what roles may do with `media`, for signed-in users (not API keys).
|
|
1156
|
-
*/
|
|
1157
|
-
declare class MediaFolders {
|
|
1158
|
-
private readonly db;
|
|
1159
|
-
private readonly roles;
|
|
1160
|
-
readonly enabled: boolean;
|
|
1161
|
-
private cache;
|
|
1162
|
-
constructor(config: ResolvedConfig, db: Database, roles: Roles);
|
|
1163
|
-
/** Whether folder permissions apply to this user. */
|
|
1164
|
-
applies(user: AuthUser | null): user is AuthUser;
|
|
1165
|
-
/** Forgets cached folders, after one changed. */
|
|
1166
|
-
invalidate(): void;
|
|
1167
|
-
private rows;
|
|
1168
|
-
/** A role's level in a folder (`null`: the top level): the nearest permissions up the tree. */
|
|
1169
|
-
private levelIn;
|
|
1170
|
-
/** Whether files in a folder (`null`: the top level) are private: it or a folder above is. */
|
|
1171
|
-
private privateIn;
|
|
1172
|
-
/** Whether files put in this folder are private. */
|
|
1173
|
-
isPrivate(id: ID | null): Promise<boolean>;
|
|
1174
|
-
/** The folder and every folder inside it. */
|
|
1175
|
-
private within;
|
|
1176
|
-
/** Folders whose privacy follows this one: it and those inside, with their privacy now. */
|
|
1177
|
-
subtree(id: ID): Promise<{
|
|
1178
|
-
id: ID;
|
|
1179
|
-
private: boolean;
|
|
1180
|
-
}[]>;
|
|
1181
|
-
/**
|
|
1182
|
-
* The privacy each folder of a subtree would have after a change to its top folder: made
|
|
1183
|
-
* private or public, moved, or (as `private: false`) deleted with its contents moving up.
|
|
1184
|
-
*/
|
|
1185
|
-
privacyAfter(id: ID, change: {
|
|
1186
|
-
private?: boolean;
|
|
1187
|
-
parent?: ID | null;
|
|
1188
|
-
}): Promise<{
|
|
1189
|
-
id: ID;
|
|
1190
|
-
private: boolean;
|
|
1191
|
-
}[]>;
|
|
1192
|
-
/**
|
|
1193
|
-
* The folder with this key (`folder: 'banners'` on upload fields), made at the top level when
|
|
1194
|
-
* there is none yet.
|
|
1195
|
-
*/
|
|
1196
|
-
/**
|
|
1197
|
-
* The folder with this key, made when first needed. `scope`: the values the key is unique
|
|
1198
|
-
* within (`uniqueWithin` of the `key` field, e.g. the tenant), given to a new folder too.
|
|
1199
|
-
* `null` when there is none and `create` is false.
|
|
1200
|
-
*/
|
|
1201
|
-
keyed(key: string, scope?: Readonly<Record<string, unknown>>): Promise<ID>;
|
|
1202
|
-
keyed(key: string, scope: Readonly<Record<string, unknown>>, create: boolean): Promise<ID | null>;
|
|
1203
|
-
/** Whether a file in `folder` is in the folder with this key, or below it. */
|
|
1204
|
-
inKeyed(key: string, folder: ID | null, scope?: Readonly<Record<string, unknown>>): Promise<boolean>;
|
|
1205
|
-
/** The user's level in a folder (`null`: the top level). */
|
|
1206
|
-
level(user: AuthUser | null, id: ID | null): Promise<FolderLevel | 'none'>;
|
|
1207
|
-
/**
|
|
1208
|
-
* The constraint folder permissions add to an operation on files or folders, or `true` when
|
|
1209
|
-
* they add none (no folder limits this user).
|
|
1210
|
-
*/
|
|
1211
|
-
where(user: AuthUser | null, collection: string, operation: string): Promise<true | Where>;
|
|
1212
|
-
/** Refuses putting a file (`media`) or a subfolder in a folder the user may not. */
|
|
1213
|
-
checkTarget(user: AuthUser | null, collection: string, folder: ID | null): Promise<void>;
|
|
1214
|
-
/**
|
|
1215
|
-
* Checks a folder before it is saved: a name unique among its siblings (ignoring case), a
|
|
1216
|
-
* parent that is not itself or inside it, and permissions for roles that exist.
|
|
1217
|
-
*/
|
|
1218
|
-
validate(data: Record<string, unknown>, self: ID | undefined): Promise<void>;
|
|
1219
|
-
/**
|
|
1220
|
-
* Before a folder is deleted: its files and subfolders move up to its parent. Returns how many
|
|
1221
|
-
* moved.
|
|
1222
|
-
*/
|
|
1223
|
-
release(folder: {
|
|
1224
|
-
id: ID;
|
|
1225
|
-
parent?: unknown;
|
|
1226
|
-
}): Promise<{
|
|
1227
|
-
files: number;
|
|
1228
|
-
folders: number;
|
|
1229
|
-
}>;
|
|
1230
|
-
private fresh;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
interface StoredFile {
|
|
1234
|
-
readonly body: Uint8Array;
|
|
1235
|
-
readonly size: number;
|
|
1236
|
-
/** The type the storage serves the file with, when it says (checked after direct uploads). */
|
|
1237
|
-
readonly contentType?: string;
|
|
1238
|
-
}
|
|
1239
|
-
/** Where uploaded files live. Keys are generated by Easy CMS: `<name>-<random>.<ext>`. */
|
|
1240
|
-
interface StorageAdapter {
|
|
1241
|
-
readonly name: string;
|
|
1242
|
-
/** Called once with the project root, before any other method. */
|
|
1243
|
-
init?(args: {
|
|
1244
|
-
cwd: string;
|
|
1245
|
-
}): void | Promise<void>;
|
|
1246
|
-
put(key: string, data: Uint8Array, options: {
|
|
1247
|
-
contentType: string;
|
|
1248
|
-
}): Promise<void>;
|
|
1249
|
-
get(key: string): Promise<StoredFile | null>;
|
|
1250
|
-
delete(key: string): Promise<void>;
|
|
1251
|
-
/**
|
|
1252
|
-
* Public URL of a file, or `undefined` to serve it through the REST API
|
|
1253
|
-
* (`<api>/media/file/<key>`), as the local adapter does.
|
|
1254
|
-
*/
|
|
1255
|
-
url?(key: string): string | undefined;
|
|
1256
|
-
/**
|
|
1257
|
-
* Lets a browser send a file straight to the storage (large files, past a host's request
|
|
1258
|
-
* limit): where to send it and how. The URL must work for `expiresIn` seconds and accept at
|
|
1259
|
-
* most `size` bytes. Without it, files are uploaded through the server.
|
|
1260
|
-
*/
|
|
1261
|
-
uploadURL?(key: string, options: {
|
|
1262
|
-
contentType: string;
|
|
1263
|
-
size: number;
|
|
1264
|
-
expiresIn: number;
|
|
1265
|
-
}): Promise<DirectUpload>;
|
|
1266
|
-
/**
|
|
1267
|
-
* The first `bytes` of a file and its whole size, without reading it all (to check a file
|
|
1268
|
-
* uploaded straight to the storage); `null` when there is no such file. Default: `get`.
|
|
1269
|
-
*/
|
|
1270
|
-
getStart?(key: string, bytes: number): Promise<StoredFile | null>;
|
|
1271
|
-
}
|
|
1272
|
-
/** Where and how a browser sends a file straight to the storage (`StorageAdapter.uploadURL`). */
|
|
1273
|
-
interface DirectUpload {
|
|
1274
|
-
readonly url: string;
|
|
1275
|
-
readonly method: 'PUT' | 'POST';
|
|
1276
|
-
/** Headers to send with the file. */
|
|
1277
|
-
readonly headers: Readonly<Record<string, string>>;
|
|
1278
|
-
}
|
|
1279
|
-
interface LocalStorageOptions {
|
|
1280
|
-
/** Directory, relative to the project root. Default `uploads`. */
|
|
1281
|
-
readonly dir?: string;
|
|
1282
|
-
}
|
|
1283
|
-
/** Stores files on the local disk. Suitable for servers with a persistent filesystem. */
|
|
1284
|
-
declare function localStorage(options?: LocalStorageOptions): StorageAdapter;
|
|
1285
|
-
|
|
1286
|
-
declare const DEFAULT_MAX_VERSIONS = 50;
|
|
1287
|
-
/** One saved state of a document or global. */
|
|
1288
|
-
interface VersionSummary {
|
|
1289
|
-
readonly id: ID;
|
|
1290
|
-
/** `draft` or `published` with drafts enabled, otherwise `null`. */
|
|
1291
|
-
readonly status: 'draft' | 'published' | null;
|
|
1292
|
-
/** The newest version: the document's current editable state. */
|
|
1293
|
-
readonly latest: boolean;
|
|
1294
|
-
/** Who saved it (user id), or `null` for saves without a user (scripts, seeds). */
|
|
1295
|
-
readonly author: ID | null;
|
|
1296
|
-
readonly createdAt: string;
|
|
1297
|
-
}
|
|
1298
|
-
interface Version<T = Record<string, unknown>> extends VersionSummary {
|
|
1299
|
-
/** The document as it was saved (hidden fields such as password hashes are never kept). */
|
|
1300
|
-
readonly data: T;
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
type Data$2 = Record<string, unknown>;
|
|
1304
|
-
type Slug<C extends Config> = string extends CollectionSlug<C> ? string : CollectionSlug<C>;
|
|
1305
|
-
type GSlug<C extends Config> = string extends GlobalSlug<C> ? string : GlobalSlug<C>;
|
|
1306
|
-
type Doc<C extends Config, S> = string extends CollectionSlug<C> ? RawDocument : CollectionDocument<C, S & CollectionSlug<C>>;
|
|
1307
|
-
type Create<C extends Config, S> = string extends CollectionSlug<C> ? Data$2 : CreateInput<C, S & CollectionSlug<C>>;
|
|
1308
|
-
type Update<C extends Config, S> = string extends CollectionSlug<C> ? Data$2 : UpdateInput<C, S & CollectionSlug<C>>;
|
|
1309
|
-
type GDoc<C extends Config, S> = string extends GlobalSlug<C> ? Data$2 : GlobalDocument<C, S & GlobalSlug<C>>;
|
|
1310
|
-
type GInput<C extends Config, S> = string extends GlobalSlug<C> ? Data$2 : GlobalInput<C, S & GlobalSlug<C>>;
|
|
1311
|
-
/** A collection slug of the config `C` (any string when the config isn't a literal). */
|
|
1312
|
-
type SlugOf<C extends Config> = Slug<C>;
|
|
1313
|
-
/**
|
|
1314
|
-
* A document of collection `S` as the Local API returns it, e.g. for helpers that take an
|
|
1315
|
-
* `EasyCMS<C>`: inferred from a literal config, loose otherwise.
|
|
1316
|
-
*/
|
|
1317
|
-
type DocumentOf<C extends Config, S> = Doc<C, S>;
|
|
1318
|
-
interface AccessOptions {
|
|
1319
|
-
/**
|
|
1320
|
-
* The Local API trusts its caller and skips access rules by default.
|
|
1321
|
-
* Pass `false` (with `user`) to apply them, as the REST API does.
|
|
1322
|
-
*/
|
|
1323
|
-
readonly overrideAccess?: boolean;
|
|
1324
|
-
/** The user to check access for. `null` = not logged in. */
|
|
1325
|
-
readonly user?: AuthUser | null;
|
|
1326
|
-
/**
|
|
1327
|
-
* What the call is about besides its user, e.g. the tenant (see `onRequest`): given to access
|
|
1328
|
-
* functions, hooks and `filterOptions`.
|
|
1329
|
-
*/
|
|
1330
|
-
readonly context?: RequestContext;
|
|
1331
|
-
}
|
|
1332
|
-
interface DepthOptions extends AccessOptions {
|
|
1333
|
-
/** How many levels of relationships to populate. Default 1, maximum 3. */
|
|
1334
|
-
readonly depth?: number;
|
|
1335
|
-
/**
|
|
1336
|
-
* With `localization`: the locale to read, and for writes the locale the data is in. Default:
|
|
1337
|
-
* the default locale. `'all'` reads (and writes) localized fields as `{ [locale]: value }`.
|
|
1338
|
-
*/
|
|
1339
|
-
readonly locale?: string;
|
|
1340
|
-
}
|
|
1341
|
-
interface UpdateOptions extends DepthOptions {
|
|
1342
|
-
/**
|
|
1343
|
-
* Upkeep of the live document, e.g. values a plugin keeps up to date (a page's path): with
|
|
1344
|
-
* drafts and versions a pending draft stays as it is (edits go to it by default), the status
|
|
1345
|
-
* stays, and no version is added to the history.
|
|
1346
|
-
*/
|
|
1347
|
-
readonly live?: boolean;
|
|
1348
|
-
/**
|
|
1349
|
-
* Saves only when the stored document still matches this at the moment of writing (compare and
|
|
1350
|
-
* set), e.g. `{ status: { equals: 'pending' } }` so that one of two concurrent calls wins.
|
|
1351
|
-
* `update()` then returns `null` when it doesn't match, after its `before*` hooks ran.
|
|
1352
|
-
*/
|
|
1353
|
-
readonly where?: Where;
|
|
1354
|
-
}
|
|
1355
|
-
interface IncrementOptions {
|
|
1356
|
-
/** Only when the result stays at or above this, e.g. `0` for stock. */
|
|
1357
|
-
readonly min?: number;
|
|
1358
|
-
/** Only when the result stays at or below this. */
|
|
1359
|
-
readonly max?: number;
|
|
1360
|
-
}
|
|
1361
|
-
interface ReadOptions extends DepthOptions {
|
|
1362
|
-
/**
|
|
1363
|
-
* Include drafts. By default only published documents of collections with
|
|
1364
|
-
* `drafts: true` are returned, including when populating relationships (FR-DRF-04).
|
|
1365
|
-
*/
|
|
1366
|
-
readonly draft?: boolean;
|
|
1367
|
-
/** Use the default locale's value when a localized value is empty. Default: `localization.fallback`. */
|
|
1368
|
-
readonly fallbackLocale?: boolean;
|
|
1369
|
-
}
|
|
1370
|
-
interface FindOptions extends ReadOptions {
|
|
1371
|
-
readonly where?: Where;
|
|
1372
|
-
/** Field path, `-` prefix for descending. Default `-createdAt`. */
|
|
1373
|
-
readonly sort?: string | readonly string[];
|
|
1374
|
-
/** Default 10. `0` returns every document. */
|
|
1375
|
-
readonly limit?: number;
|
|
1376
|
-
/** 1-based. Default 1. */
|
|
1377
|
-
readonly page?: number;
|
|
1378
|
-
}
|
|
1379
|
-
interface CreateEasyCMSOptions {
|
|
1380
|
-
/** Project root. Default `process.cwd()`. */
|
|
1381
|
-
readonly cwd?: string;
|
|
1382
|
-
/**
|
|
1383
|
-
* Run due scheduled jobs and webhook retries every minute in this process. Default: on when
|
|
1384
|
-
* a collection or global has `schedule`, or `webhooks`, `email` or `jobs` is set. Turn it off
|
|
1385
|
-
* where a cron calls `runJobs` (or `GET <api>/jobs/run`) instead.
|
|
1386
|
-
*/
|
|
1387
|
-
readonly scheduler?: boolean;
|
|
1388
|
-
/** Default `verify` when `NODE_ENV=production`, otherwise `push`. */
|
|
1389
|
-
readonly schema?: SchemaMode;
|
|
1390
|
-
readonly logger?: Logger;
|
|
1391
|
-
readonly interactive?: boolean;
|
|
1392
|
-
}
|
|
1393
|
-
/** Connects to the database and returns the Local API. */
|
|
1394
|
-
declare function createEasyCMS<const C extends Config>(config: C, options?: CreateEasyCMSOptions): Promise<EasyCMS<C>>;
|
|
1395
|
-
/** A scheduled publish or unpublish. */
|
|
1396
|
-
interface ScheduledJob {
|
|
1397
|
-
readonly id: ID;
|
|
1398
|
-
readonly action: 'publish' | 'unpublish';
|
|
1399
|
-
/** ISO time the job runs at (or after). */
|
|
1400
|
-
readonly runAt: string;
|
|
1401
|
-
readonly state: 'pending' | 'done' | 'failed';
|
|
1402
|
-
readonly error: string | null;
|
|
1403
|
-
readonly author: ID | null;
|
|
1404
|
-
}
|
|
1405
|
-
declare class EasyCMS<C extends Config = Config> {
|
|
1406
|
-
readonly config: ResolvedConfig;
|
|
1407
|
-
readonly db: Database;
|
|
1408
|
-
readonly logger: Logger;
|
|
1409
|
-
/** Login, logout and session checks. */
|
|
1410
|
-
readonly auth: Auth;
|
|
1411
|
-
/** Where uploaded files are stored. */
|
|
1412
|
-
readonly storage: StorageAdapter;
|
|
1413
|
-
/**
|
|
1414
|
-
* Where files in private folders are stored (`upload.privateStorage`, else `storage` when it has
|
|
1415
|
-
* no public URLs); `null` when private folders can't be used.
|
|
1416
|
-
*/
|
|
1417
|
-
readonly privateStorage: StorageAdapter | null;
|
|
1418
|
-
/** Project root: relative paths in the config (e.g. `admin.modules`) start here. */
|
|
1419
|
-
readonly cwd: string;
|
|
1420
|
-
/** Roles and their permissions (`auth.rbac`, Settings → Roles). */
|
|
1421
|
-
readonly roles: Roles;
|
|
1422
|
-
/** Who did what (`audit`, Settings → Audit log). */
|
|
1423
|
-
readonly audit: AuditLog;
|
|
1424
|
-
/** Folders of the media library and their permissions (`upload.folders`). */
|
|
1425
|
-
readonly folders: MediaFolders;
|
|
1426
|
-
private readonly versions;
|
|
1427
|
-
private readonly webhooks;
|
|
1428
|
-
private readonly mailer;
|
|
1429
|
-
constructor(config: ResolvedConfig, db: Database, logger?: Logger, storage?: StorageAdapter, cwd?: string, privateStorage?: StorageAdapter | null);
|
|
1430
|
-
/**
|
|
1431
|
-
* The user and context of a request, as the REST API works them out: its session cookie or
|
|
1432
|
-
* Bearer token, then `onRequest`. For framework routes and server code that act for a visitor;
|
|
1433
|
-
* pass both to the Local API with `overrideAccess: false`.
|
|
1434
|
-
*/
|
|
1435
|
-
forRequest(request: Request | Headers): Promise<{
|
|
1436
|
-
user: AuthUser | null;
|
|
1437
|
-
context: RequestContext;
|
|
1438
|
-
}>;
|
|
1439
|
-
/** @internal `onRequest` of the config, for a request whose user is known. */
|
|
1440
|
-
applyOnRequest(headers: Headers, url: URL | undefined, user: AuthUser | null): Promise<{
|
|
1441
|
-
user: AuthUser | null;
|
|
1442
|
-
context: RequestContext;
|
|
1443
|
-
}>;
|
|
1444
|
-
find<S extends Slug<C>>(collection: S, options?: FindOptions): Promise<PaginatedDocs<Doc<C, S>>>;
|
|
1445
|
-
findById<S extends Slug<C>>(collection: S, id: ID, options?: ReadOptions): Promise<Doc<C, S> | null>;
|
|
1446
|
-
count<S extends Slug<C>>(collection: S, options?: {
|
|
1447
|
-
where?: Where;
|
|
1448
|
-
draft?: boolean;
|
|
1449
|
-
locale?: string;
|
|
1450
|
-
} & AccessOptions): Promise<number>;
|
|
1451
|
-
create<S extends Slug<C>>(collection: S, data: Create<C, S>, options?: DepthOptions): Promise<Doc<C, S>>;
|
|
1452
|
-
/**
|
|
1453
|
-
* Stores a file and creates its `media` document (FR-UPL). The type is detected from
|
|
1454
|
-
* the contents; images get width/height, and resized copies when `sharp` is installed.
|
|
1455
|
-
*/
|
|
1456
|
-
upload(file: {
|
|
1457
|
-
data: Uint8Array;
|
|
1458
|
-
name: string;
|
|
1459
|
-
}, data?: Record<string, unknown>, options?: DepthOptions): Promise<MediaDocument>;
|
|
1460
|
-
/**
|
|
1461
|
-
* Starts an upload straight to the storage, for files too large to send through the server
|
|
1462
|
-
* (`POST <api>/media/uploads`): checks who uploads what where, as `upload()` does, and returns
|
|
1463
|
-
* a signed ticket and where to send the file. `upload` is `null` when the storage can't take
|
|
1464
|
-
* files directly: send it to `upload()` instead. Then `completeUpload(ticket)`.
|
|
1465
|
-
*/
|
|
1466
|
-
createUpload(file: {
|
|
1467
|
-
name: string;
|
|
1468
|
-
size: number;
|
|
1469
|
-
type?: string;
|
|
1470
|
-
}, data?: Record<string, unknown>, options?: AccessOptions): Promise<{
|
|
1471
|
-
ticket: string;
|
|
1472
|
-
upload: DirectUpload | null;
|
|
1473
|
-
}>;
|
|
1474
|
-
/**
|
|
1475
|
-
* Finishes an upload made with `createUpload()` (`POST <api>/media/uploads/complete`): checks
|
|
1476
|
-
* the file in the storage like any upload (its size, and its type from its contents), then
|
|
1477
|
-
* makes its media document. A file that fails is deleted.
|
|
1478
|
-
*/
|
|
1479
|
-
completeUpload(ticket: string, options?: DepthOptions): Promise<MediaDocument>;
|
|
1480
|
-
/** Who may upload, and into which folder: `upload()` and direct uploads. */
|
|
1481
|
-
private checkUpload;
|
|
1482
|
-
/** A value with its HMAC (`secret`), for `purpose` only: `<base64url JSON>.<signature>`. */
|
|
1483
|
-
private sign;
|
|
1484
|
-
/** The value of a token made by `sign`, or `undefined` when it isn't genuine. */
|
|
1485
|
-
private verify;
|
|
1486
|
-
/**
|
|
1487
|
-
* Downloads a file from an `http(s)` link and uploads it like `upload()`. Private network
|
|
1488
|
-
* addresses are refused unless `upload.fromURL.allowPrivate`. Calls on behalf of a user (the
|
|
1489
|
-
* REST API, `overrideAccess: false`) also need `upload.fromURL` and a host in its `allowedHosts`.
|
|
1490
|
-
*/
|
|
1491
|
-
uploadFromURL(url: string, data?: Record<string, unknown>, options?: DepthOptions): Promise<MediaDocument>;
|
|
1492
|
-
/**
|
|
1493
|
-
* Creates an API key (needs `apiKeys: true`) and returns it once: only a hash is stored.
|
|
1494
|
-
* With access enforced the key belongs to `user`; trusted calls name the owner in `data.user`.
|
|
1495
|
-
*/
|
|
1496
|
-
createApiKey(data: {
|
|
1497
|
-
name: string;
|
|
1498
|
-
permissions?: ApiKeyPermissions;
|
|
1499
|
-
expiresAt?: string | Date | null;
|
|
1500
|
-
user?: ID;
|
|
1501
|
-
}, options?: AccessOptions): Promise<{
|
|
1502
|
-
key: string;
|
|
1503
|
-
doc: Record<string, unknown>;
|
|
1504
|
-
}>;
|
|
1505
|
-
/** Public URL of a stored file. */
|
|
1506
|
-
mediaURL(key: string): string;
|
|
1507
|
-
/**
|
|
1508
|
-
* A link to a private file that works without signing in until it expires: `expiresIn` in
|
|
1509
|
-
* seconds or as `'30m'`, `'1h'` (the default), `'7d'` (the most). Public files get their usual
|
|
1510
|
-
* URL. `size`: one of the file's resized copies. Links stop working when `secret` changes or
|
|
1511
|
-
* the file stops being private.
|
|
1512
|
-
*/
|
|
1513
|
-
signedMediaURL(doc: {
|
|
1514
|
-
filename?: unknown;
|
|
1515
|
-
private?: unknown;
|
|
1516
|
-
sizes?: unknown;
|
|
1517
|
-
}, options?: {
|
|
1518
|
-
expiresIn?: number | string;
|
|
1519
|
-
size?: string;
|
|
1520
|
-
}): string;
|
|
1521
|
-
/** Whether a signed link to a private file is genuine and not expired. */
|
|
1522
|
-
verifyMediaSignature(key: string, expires: string | null, signature: string | null): boolean;
|
|
1523
|
-
private mediaSignature;
|
|
1524
|
-
/**
|
|
1525
|
-
* How many documents use any of these files in a top-level upload field (rich text and fields
|
|
1526
|
-
* inside groups, arrays and blocks are not counted). Drafts count too.
|
|
1527
|
-
*/
|
|
1528
|
-
mediaUsage(ids: readonly ID[]): Promise<number>;
|
|
1529
|
-
/** The storage for public files, or for private ones. */
|
|
1530
|
-
storageFor(isPrivate: boolean): StorageAdapter;
|
|
1531
|
-
/**
|
|
1532
|
-
* Copies a file and its resized copies to the other storage under their new names (`.private`
|
|
1533
|
-
* added or removed). Returns the fields to save, and `cleanUp` to delete the old copies once
|
|
1534
|
-
* the document points to the new ones.
|
|
1535
|
-
*/
|
|
1536
|
-
private relocate;
|
|
1537
|
-
/** A folder's name, parent and permissions; private only with somewhere private to keep files. */
|
|
1538
|
-
private validateFolder;
|
|
1539
|
-
/**
|
|
1540
|
-
* Refuses a change that would move more than 200 files between storages in one request: they
|
|
1541
|
-
* are copied one by one, which could outlast a serverless function.
|
|
1542
|
-
*/
|
|
1543
|
-
private checkMoveLimit;
|
|
1544
|
-
/** After a folder became private or public (or moved): its files follow, in every subfolder. */
|
|
1545
|
-
private settlePrivacy;
|
|
1546
|
-
/** Files at the top level are public. */
|
|
1547
|
-
private settleTopLevel;
|
|
1548
|
-
private settleFiles;
|
|
1549
|
-
update<S extends Slug<C>>(collection: S, id: ID, data: Update<C, S>, options: UpdateOptions & {
|
|
1550
|
-
where: Where;
|
|
1551
|
-
}): Promise<Doc<C, S> | null>;
|
|
1552
|
-
update<S extends Slug<C>>(collection: S, id: ID, data: Update<C, S>, options?: UpdateOptions & {
|
|
1553
|
-
where?: undefined;
|
|
1554
|
-
}): Promise<Doc<C, S>>;
|
|
1555
|
-
/**
|
|
1556
|
-
* Adds `by` (negative to take away) to a top-level number field in one database statement, so
|
|
1557
|
-
* concurrent calls never lose a change, e.g. stock or a counter. Skips hooks, validation, access
|
|
1558
|
-
* and versions. Returns the new value, or `null` when `min` / `max` would be passed.
|
|
1559
|
-
*/
|
|
1560
|
-
increment<S extends Slug<C>>(collection: S, id: ID, field: string, by: number, options?: IncrementOptions): Promise<number | null>;
|
|
1561
|
-
/**
|
|
1562
|
-
* Takes a document off the site: its status becomes `draft`. With versions and drafts, a plain
|
|
1563
|
-
* `update` with `status: 'draft'` only saves a draft and leaves the published document live;
|
|
1564
|
-
* this is how to unpublish it.
|
|
1565
|
-
*/
|
|
1566
|
-
unpublish<S extends Slug<C>>(collection: S, id: ID, options?: DepthOptions): Promise<Doc<C, S>>;
|
|
1567
|
-
/** Throws away the unpublished draft of a published document, going back to what is live. */
|
|
1568
|
-
discardDraft<S extends Slug<C>>(collection: S, id: ID, options?: DepthOptions): Promise<Doc<C, S>>;
|
|
1569
|
-
/** Saved versions of a document, newest first. Needs update access to the document. */
|
|
1570
|
-
findVersions<S extends Slug<C>>(collection: S, id: ID, options?: AccessOptions & {
|
|
1571
|
-
limit?: number;
|
|
1572
|
-
page?: number;
|
|
1573
|
-
}): Promise<PaginatedDocs<VersionSummary>>;
|
|
1574
|
-
/** One saved version with the document as it was then. */
|
|
1575
|
-
findVersion<S extends Slug<C>>(collection: S, id: ID, versionId: ID, options?: DepthOptions): Promise<Version<Doc<C, S>> | null>;
|
|
1576
|
-
/**
|
|
1577
|
-
* Makes an old version the current content, as a new version. With drafts it is restored as
|
|
1578
|
-
* a draft (publish it to put it live); access, hooks and validation run as for `update`.
|
|
1579
|
-
*/
|
|
1580
|
-
restoreVersion<S extends Slug<C>>(collection: S, id: ID, versionId: ID, options?: DepthOptions): Promise<Doc<C, S>>;
|
|
1581
|
-
/**
|
|
1582
|
-
* Live preview: the document as it would be read if `data` were saved on top of its current
|
|
1583
|
-
* state (or as a new document when `id` is `null`), with relationships populated and
|
|
1584
|
-
* `afterRead` hooks applied. Nothing is written. Also returns the page URL from `preview`.
|
|
1585
|
-
*/
|
|
1586
|
-
preview<S extends Slug<C>>(collection: S, id: ID | null, data: Record<string, unknown>, options?: DepthOptions): Promise<LivePreview<Doc<C, S>>>;
|
|
1587
|
-
/**
|
|
1588
|
-
* A token that lets a page read one document's current draft for a while (default one hour),
|
|
1589
|
-
* without a login: `GET /api/cms/:collection/:id?preview=<token>`. The admin adds one to the
|
|
1590
|
-
* preview URL as `easy-cms-preview`, for sites on another origin.
|
|
1591
|
-
*/
|
|
1592
|
-
createPreviewToken(target: {
|
|
1593
|
-
collection: string;
|
|
1594
|
-
id: ID;
|
|
1595
|
-
} | {
|
|
1596
|
-
global: string;
|
|
1597
|
-
}, options?: {
|
|
1598
|
-
expiresIn?: number;
|
|
1599
|
-
}): string;
|
|
1600
|
-
/** What a preview token opens, or `null` when it is invalid or expired. */
|
|
1601
|
-
verifyPreviewToken(token: string | null | undefined): (PreviewTarget & {
|
|
1602
|
-
expiresAt: number;
|
|
1603
|
-
}) | null;
|
|
1604
|
-
/** Live preview of a global. See `preview`. */
|
|
1605
|
-
previewGlobal<S extends GSlug<C>>(slug: S, data: Record<string, unknown>, options?: DepthOptions): Promise<LivePreview<GDoc<C, S>>>;
|
|
1606
|
-
/** Coerces preview input like a save would, keeping values that would fail validation. */
|
|
1607
|
-
private previewDoc;
|
|
1608
|
-
private previewURL;
|
|
1609
|
-
private versionTarget;
|
|
1610
|
-
private updateDocument;
|
|
1611
|
-
/**
|
|
1612
|
-
* Deletes a document. Deleting a user with roles from the admin (`auth.rbac`): `transferTo`
|
|
1613
|
-
* gives the documents they own to another user; without it they have no owner.
|
|
1614
|
-
*/
|
|
1615
|
-
delete<S extends Slug<C>>(collection: S, id: ID, options?: AccessOptions & {
|
|
1616
|
-
transferTo?: ID | null;
|
|
1617
|
-
}): Promise<Doc<C, S>>;
|
|
1618
|
-
findGlobal<S extends GSlug<C>>(slug: S, options?: ReadOptions): Promise<GDoc<C, S>>;
|
|
1619
|
-
/** A global as stored under `key` (see `globalKey`); empty when there is no key. */
|
|
1620
|
-
private readGlobal;
|
|
1621
|
-
/**
|
|
1622
|
-
* Where a global is kept: its slug, or `<slug>@<scope>` for globals with `scope` (e.g. one per
|
|
1623
|
-
* tenant). `null` when the call has no scope: reads give the global empty. `scope` is given by
|
|
1624
|
-
* scheduled jobs, which have no request.
|
|
1625
|
-
*/
|
|
1626
|
-
private globalKey;
|
|
1627
|
-
/** `globalKey` for changes, which need a scope. */
|
|
1628
|
-
private writeKey;
|
|
1629
|
-
updateGlobal<S extends GSlug<C>>(slug: S, data: GInput<C, S>, options?: DepthOptions): Promise<GDoc<C, S>>;
|
|
1630
|
-
/** Takes a global off the site (`status: 'draft'`). See `unpublish`. */
|
|
1631
|
-
unpublishGlobal<S extends GSlug<C>>(slug: S, options?: DepthOptions): Promise<GDoc<C, S>>;
|
|
1632
|
-
/** Throws away the unpublished draft of a published global. */
|
|
1633
|
-
discardGlobalDraft<S extends GSlug<C>>(slug: S, options?: DepthOptions): Promise<GDoc<C, S>>;
|
|
1634
|
-
/** Saved versions of a global, newest first. Needs update access. */
|
|
1635
|
-
findGlobalVersions<S extends GSlug<C>>(slug: S, options?: AccessOptions & {
|
|
1636
|
-
limit?: number;
|
|
1637
|
-
page?: number;
|
|
1638
|
-
}): Promise<PaginatedDocs<VersionSummary>>;
|
|
1639
|
-
findGlobalVersion<S extends GSlug<C>>(slug: S, versionId: ID, options?: DepthOptions): Promise<Version<GDoc<C, S>> | null>;
|
|
1640
|
-
/** Makes an old version of a global current (as a draft when the global has drafts). */
|
|
1641
|
-
restoreGlobalVersion<S extends GSlug<C>>(slug: S, versionId: ID, options?: DepthOptions): Promise<GDoc<C, S>>;
|
|
1642
|
-
private globalVersionTarget;
|
|
1643
|
-
/** The global's pending draft when it has one (see `withDrafts`), otherwise `saved`. */
|
|
1644
|
-
private globalDraft;
|
|
1645
|
-
private saveGlobal;
|
|
1646
|
-
/**
|
|
1647
|
-
* Users changing their own password or email say their current password first, so a session
|
|
1648
|
-
* someone else got hold of can't lock them out. Admins changing other users don't.
|
|
1649
|
-
*/
|
|
1650
|
-
private checkOwnCredentials;
|
|
1651
|
-
/**
|
|
1652
|
-
* What `user` may do with one document. Resolves `where`-style access
|
|
1653
|
-
* against the document, so the admin UI can hide actions precisely.
|
|
1654
|
-
*/
|
|
1655
|
-
documentPermissions(collection: Slug<C>, id: ID, user: AuthUser | null, context?: RequestContext): Promise<{
|
|
1656
|
-
update: boolean;
|
|
1657
|
-
delete: boolean;
|
|
1658
|
-
}>;
|
|
1659
|
-
/** Closes the database connection. */
|
|
1660
|
-
/**
|
|
1661
|
-
* Waits until webhook deliveries in progress are done. Call it before a serverless function
|
|
1662
|
-
* returns, which may otherwise stop them.
|
|
1663
|
-
*/
|
|
1664
|
-
flushWebhooks(): Promise<void>;
|
|
1665
|
-
/**
|
|
1666
|
-
* Sends an email with the config's `email` adapter, e.g. from a hook or plugin. Resolves once
|
|
1667
|
-
* the email is queued; it is sent in the background and retried from the queue if sending
|
|
1668
|
-
* fails. Without `email` in the config, the email is skipped with a warning.
|
|
1669
|
-
*/
|
|
1670
|
-
sendEmail(message: EmailMessage): Promise<void>;
|
|
1671
|
-
/** Waits for emails being sent, e.g. before a serverless function returns. */
|
|
1672
|
-
flushEmails(): Promise<void>;
|
|
1673
|
-
/** Emails queued until sent, stored in `email-deliveries`. */
|
|
1674
|
-
private emailQueue;
|
|
1675
|
-
destroy(): Promise<void>;
|
|
1676
|
-
private schedulerTimer;
|
|
1677
|
-
private running;
|
|
1678
|
-
/** Runs `runJobs` every `interval` ms in this process (`createEasyCMS` does this by default). */
|
|
1679
|
-
startScheduler(interval?: number): void;
|
|
1680
|
-
/**
|
|
1681
|
-
* Runs due scheduled publishes and unpublishes, and retries failed webhook deliveries. What the
|
|
1682
|
-
* scheduler and the cron endpoint (`GET <api>/jobs/run`) call.
|
|
1683
|
-
*/
|
|
1684
|
-
runJobs(now?: Date): Promise<{
|
|
1685
|
-
ran: number;
|
|
1686
|
-
failed: number;
|
|
1687
|
-
webhooks: {
|
|
1688
|
-
sent: number;
|
|
1689
|
-
failed: number;
|
|
1690
|
-
};
|
|
1691
|
-
emails: {
|
|
1692
|
-
sent: number;
|
|
1693
|
-
failed: number;
|
|
1694
|
-
};
|
|
1695
|
-
jobs: {
|
|
1696
|
-
ran: number;
|
|
1697
|
-
failed: number;
|
|
1698
|
-
};
|
|
1699
|
-
}>;
|
|
1700
|
-
/** `jobs` of the config that are due; when each last ran is kept in the database. */
|
|
1701
|
-
private runConfigJobs;
|
|
1702
|
-
/**
|
|
1703
|
-
* Sends an event of the app or a plugin (`events` in the config, e.g. `order.paid`) to the
|
|
1704
|
-
* webhooks that list it. Returns at once; deliveries are queued and retried like content changes.
|
|
1705
|
-
*/
|
|
1706
|
-
emit(event: string, data: Record<string, unknown>, about?: {
|
|
1707
|
-
collection: string;
|
|
1708
|
-
id: ID;
|
|
1709
|
-
}): void;
|
|
1710
|
-
/**
|
|
1711
|
-
* Tries webhook deliveries that are due for another attempt. Deliveries are kept in the
|
|
1712
|
-
* database until they succeed, so they survive restarts.
|
|
1713
|
-
*/
|
|
1714
|
-
retryWebhooks(now?: Date): Promise<{
|
|
1715
|
-
sent: number;
|
|
1716
|
-
failed: number;
|
|
1717
|
-
}>;
|
|
1718
|
-
/** Webhook deliveries until they succeed, stored in `webhook-deliveries`. */
|
|
1719
|
-
private webhookQueue;
|
|
1720
|
-
/**
|
|
1721
|
-
* Publishes or unpublishes documents whose scheduled time has come. Safe to call often, e.g.
|
|
1722
|
-
* from a cron job (`GET <api>/jobs/run`) where no process keeps running.
|
|
1723
|
-
*/
|
|
1724
|
-
runScheduled(now?: Date): Promise<{
|
|
1725
|
-
ran: number;
|
|
1726
|
-
failed: number;
|
|
1727
|
-
}>;
|
|
1728
|
-
/** Schedules a publish or unpublish, replacing a pending job with the same action. */
|
|
1729
|
-
schedule<S extends Slug<C>>(collection: S, id: ID, job: {
|
|
1730
|
-
action: 'publish' | 'unpublish';
|
|
1731
|
-
at: Date | string;
|
|
1732
|
-
}, options?: AccessOptions): Promise<ScheduledJob>;
|
|
1733
|
-
/** Pending jobs of a document, soonest first. */
|
|
1734
|
-
scheduled<S extends Slug<C>>(collection: S, id: ID, options?: AccessOptions): Promise<ScheduledJob[]>;
|
|
1735
|
-
cancelSchedule<S extends Slug<C>>(collection: S, id: ID, jobId: ID, options?: AccessOptions): Promise<void>;
|
|
1736
|
-
scheduleGlobal<S extends GSlug<C>>(slug: S, job: {
|
|
1737
|
-
action: 'publish' | 'unpublish';
|
|
1738
|
-
at: Date | string;
|
|
1739
|
-
}, options?: AccessOptions): Promise<ScheduledJob>;
|
|
1740
|
-
scheduledGlobal<S extends GSlug<C>>(slug: S, options?: AccessOptions): Promise<ScheduledJob[]>;
|
|
1741
|
-
cancelGlobalSchedule<S extends GSlug<C>>(slug: S, jobId: ID, options?: AccessOptions): Promise<void>;
|
|
1742
|
-
private scheduleTarget;
|
|
1743
|
-
private globalScheduleTarget;
|
|
1744
|
-
private addJob;
|
|
1745
|
-
/**
|
|
1746
|
-
* The next pending jobs across all collections and globals, soonest first; with access
|
|
1747
|
-
* enforced, only those the user may update.
|
|
1748
|
-
*/
|
|
1749
|
-
upcomingJobs(options?: AccessOptions & {
|
|
1750
|
-
limit?: number;
|
|
1751
|
-
}): Promise<(ScheduledJob & {
|
|
1752
|
-
collection?: string;
|
|
1753
|
-
global?: string;
|
|
1754
|
-
doc: ID;
|
|
1755
|
-
})[]>;
|
|
1756
|
-
private pendingJobs;
|
|
1757
|
-
private removeJob;
|
|
1758
|
-
/**
|
|
1759
|
-
* @internal One attempt now for a saved webhook delivery or email (the admin's Retry): removed
|
|
1760
|
-
* when it is sent, otherwise kept as failed with the new error.
|
|
1761
|
-
*/
|
|
1762
|
-
retryDelivery(kind: 'webhook' | 'email', id: ID): Promise<{
|
|
1763
|
-
ok: true;
|
|
1764
|
-
} | {
|
|
1765
|
-
ok: false;
|
|
1766
|
-
error: string;
|
|
1767
|
-
}>;
|
|
1768
|
-
/** Removes webhook deliveries and emails that failed more than 30 days ago. */
|
|
1769
|
-
private pruneFailedDeliveries;
|
|
1770
|
-
/** @internal */
|
|
1771
|
-
collection(slug: string): CollectionConfig;
|
|
1772
|
-
private global;
|
|
1773
|
-
/**
|
|
1774
|
-
* Refuses what a request's API key (see `ApiKeyPermissions`) or the user's role (`auth.rbac`)
|
|
1775
|
-
* does not allow. Returns a constraint when the role allows only the user's own account.
|
|
1776
|
-
*/
|
|
1777
|
-
private checkGrant;
|
|
1778
|
-
/** The query constraint read access adds, or `undefined` when there is none. Throws when denied. */
|
|
1779
|
-
private readWhere;
|
|
1780
|
-
private checkDocumentAccess;
|
|
1781
|
-
private checkGlobalAccess;
|
|
1782
|
-
private fieldChecker;
|
|
1783
|
-
/**
|
|
1784
|
-
* Refuses filtering or sorting by fields the caller may not read: the results would tell
|
|
1785
|
-
* their values. Checks each field along a dotted path, through groups, arrays and blocks.
|
|
1786
|
-
*/
|
|
1787
|
-
private checkQueryFields;
|
|
1788
|
-
/** `checkGrant` for one document: a role limited to its own documents must own it. */
|
|
1789
|
-
private checkDocumentGrant;
|
|
1790
|
-
/** Runs afterRead hooks, populates relationships and removes what the caller may not see. */
|
|
1791
|
-
private output;
|
|
1792
|
-
/** Creates a document: access, input filtering, hooks, validation, then afterChange. */
|
|
1793
|
-
private createDocument;
|
|
1794
|
-
/** The locale for an operation; `undefined` without localization. Throws on unknown locales. */
|
|
1795
|
-
private localeOf;
|
|
1796
|
-
/** Stored localized maps → the values of the requested locale. */
|
|
1797
|
-
private pick;
|
|
1798
|
-
/** Input for one locale → stored localized maps, keeping the other locales from `current`. */
|
|
1799
|
-
private toMaps;
|
|
1800
|
-
/** Makes `where` match localized fields in the operation's locale. */
|
|
1801
|
-
private whereFor;
|
|
1802
|
-
/** Drafts are kept as versions (instead of unpublishing) when both are enabled. */
|
|
1803
|
-
private separateDrafts;
|
|
1804
|
-
/** Replaces published documents that have a newer draft with that draft. */
|
|
1805
|
-
private withDrafts;
|
|
1806
|
-
/** Sends webhook events for a change; `before` is the stored status before it, if any. */
|
|
1807
|
-
private emitChange;
|
|
1808
|
-
/**
|
|
1809
|
-
* Writes the audit log entry for a change (`audit`): publishing, unpublishing and restoring
|
|
1810
|
-
* are told apart from other saves.
|
|
1811
|
-
*/
|
|
1812
|
-
private audited;
|
|
1813
|
-
/** Records a version after a save, when the collection or global keeps versions. */
|
|
1814
|
-
private saveVersion;
|
|
1815
|
-
private hookArgs;
|
|
1816
|
-
/**
|
|
1817
|
-
* Runs hooks that may replace a value, passed as `data` (before hooks) or `doc` (afterRead).
|
|
1818
|
-
* Returning `undefined` keeps the value; a throw cancels the operation.
|
|
1819
|
-
*/
|
|
1820
|
-
private transform;
|
|
1821
|
-
/** Runs hooks after the change is saved: failures are logged, never undo the save (NFR-REL-03). */
|
|
1822
|
-
private notify;
|
|
1823
|
-
/** Writes resized copies with sharp, when installed and configured. */
|
|
1824
|
-
private resizeImage;
|
|
1825
|
-
/** Validates, checks uniqueness and references. Returns clean data or throws `ValidationError`. */
|
|
1826
|
-
private prepare;
|
|
1827
|
-
/** Refuses changes that would leave no active admin (and lock everyone out). */
|
|
1828
|
-
private guardLastAdmin;
|
|
1829
|
-
private makeSlugsUnique;
|
|
1830
|
-
private checkUnique;
|
|
1831
|
-
private isTaken;
|
|
1832
|
-
/**
|
|
1833
|
-
* What folder keys are unique within for a call (`uniqueWithin` of the `key` field of media
|
|
1834
|
-
* folders, e.g. the tenant): the value a folder made by this call would get from its hooks.
|
|
1835
|
-
*/
|
|
1836
|
-
private folderScope;
|
|
1837
|
-
/**
|
|
1838
|
-
* Where a unique field's values must differ for a call (`uniqueWithin`, e.g. the tenant): the
|
|
1839
|
-
* value a document created by this call would get there from its hooks, as `{ [field]: value }`;
|
|
1840
|
-
* `{}` when the field is unique everywhere. For finding documents by such a field, e.g. a page
|
|
1841
|
-
* by its path in the current tenant.
|
|
1842
|
-
*/
|
|
1843
|
-
uniqueScope(collection: string, field: string, options?: Pick<AccessOptions, 'user' | 'context'>): Promise<Record<string, unknown>>;
|
|
1844
|
-
/**
|
|
1845
|
-
* @internal The id of the media folder with this key (`folder` of upload fields), made when
|
|
1846
|
-
* needed; `null` when the call has no scope to make it in (e.g. all tenants) and none exists.
|
|
1847
|
-
*/
|
|
1848
|
-
keyedFolder(key: string, options?: AccessOptions): Promise<ID | null>;
|
|
1849
|
-
/** Each reference is one its relationship's `filterOptions` allow. */
|
|
1850
|
-
private checkFilterOptions;
|
|
1851
|
-
private checkReferences;
|
|
1852
|
-
}
|
|
1853
|
-
/** A document as live preview shows it, and the page to show it on. */
|
|
1854
|
-
interface LivePreview<T> {
|
|
1855
|
-
readonly doc: T;
|
|
1856
|
-
/** From the collection's `preview` function; `null` without one. */
|
|
1857
|
-
readonly url: string | null;
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
/** A label shown in the admin UI. Either one string or one string per admin locale. */
|
|
1861
|
-
type Label = string | {
|
|
1862
|
-
readonly [locale: string]: string;
|
|
1863
|
-
};
|
|
1864
|
-
interface FieldValidateContext {
|
|
1865
|
-
/** The whole document being validated, including sibling fields. */
|
|
1866
|
-
readonly data: Readonly<Record<string, unknown>>;
|
|
1867
|
-
readonly operation: 'create' | 'update';
|
|
1868
|
-
}
|
|
1869
|
-
/** Return `true` when valid, or an error message to show on the field. */
|
|
1870
|
-
type FieldValidate<TValue> = (value: TValue | null | undefined, ctx: FieldValidateContext) => true | string | Promise<true | string>;
|
|
1871
|
-
/**
|
|
1872
|
-
* A Web Component from an admin module (`admin.modules`), by its tag name, which must start
|
|
1873
|
-
* with `ecms-`. `props` (plain JSON) reach the element as its `options` property.
|
|
1874
|
-
*/
|
|
1875
|
-
type AdminComponent = string | {
|
|
1876
|
-
readonly tag: string;
|
|
1877
|
-
readonly props?: Readonly<Record<string, unknown>>;
|
|
1878
|
-
};
|
|
1879
|
-
/** How the admin shows a field, with components from admin modules. */
|
|
1880
|
-
interface FieldAdmin {
|
|
1881
|
-
/** Help below the field's label. */
|
|
1882
|
-
readonly description?: Label;
|
|
1883
|
-
/** Its share of a row (`admin.layout`'s `row`). Default: an equal share. */
|
|
1884
|
-
readonly width?: '1/4' | '1/3' | '1/2' | '2/3' | '3/4' | 'full';
|
|
1885
|
-
/**
|
|
1886
|
-
* Shown only when its sibling fields match, e.g. `{ field: 'linkType', equals: 'external' }`.
|
|
1887
|
-
* A hidden field isn't required. See `FieldCondition`.
|
|
1888
|
-
*/
|
|
1889
|
-
readonly condition?: FieldCondition;
|
|
1890
|
-
/** Shown instead of the field's input; the admin keeps the label and error messages. */
|
|
1891
|
-
readonly component?: AdminComponent;
|
|
1892
|
-
/** Shown below the field, e.g. a length meter or a preview. */
|
|
1893
|
-
readonly after?: readonly AdminComponent[];
|
|
1894
|
-
/** Shows the value in the admin's lists (e.g. a color swatch). Default: the plain value. */
|
|
1895
|
-
readonly cell?: AdminComponent;
|
|
1896
|
-
/**
|
|
1897
|
-
* A column of the list at first (each user can change it): `true`, or per request, e.g.
|
|
1898
|
-
* only when the admin shows every tenant.
|
|
1899
|
-
*/
|
|
1900
|
-
readonly column?: boolean | ((args: AdminFieldArgs) => boolean | Promise<boolean>);
|
|
1901
|
-
/** Relationships: offer to create the related document in place. Default true. */
|
|
1902
|
-
readonly allowCreate?: boolean;
|
|
1903
|
-
/**
|
|
1904
|
-
* The value a new document's form starts with, worked out for each user, e.g. from the
|
|
1905
|
-
* request's context. The server's own default (`defaultValue`, hooks) still applies.
|
|
1906
|
-
*/
|
|
1907
|
-
readonly defaultValue?: (args: AdminFieldArgs) => unknown;
|
|
1908
|
-
}
|
|
1909
|
-
/** What per-request admin options of a field receive. */
|
|
1910
|
-
interface AdminFieldArgs {
|
|
1911
|
-
readonly user: AuthUser;
|
|
1912
|
-
readonly context: RequestContext;
|
|
1913
|
-
}
|
|
1914
|
-
/**
|
|
1915
|
-
* Field types that packages add (`fieldTypes` in the config), for the types inferred from the
|
|
1916
|
-
* config. A package declares its own by augmenting this interface:
|
|
1917
|
-
*
|
|
1918
|
-
* ```ts
|
|
1919
|
-
* declare module '@easy-cms/core' {
|
|
1920
|
-
* interface CustomFieldTypes {
|
|
1921
|
-
* color: { value: string; options: { readonly presets?: readonly string[] } }
|
|
1922
|
-
* }
|
|
1923
|
-
* }
|
|
1924
|
-
* ```
|
|
1925
|
-
*/
|
|
1926
|
-
interface CustomFieldTypes {
|
|
1927
|
-
}
|
|
1928
|
-
/** A field of a type from `CustomFieldTypes`: the common options, plus the type's own. */
|
|
1929
|
-
type CustomField = {
|
|
1930
|
-
[K in keyof CustomFieldTypes]: BaseField<K & string, CustomFieldTypes[K] extends {
|
|
1931
|
-
value: infer V;
|
|
1932
|
-
} ? V : unknown> & (CustomFieldTypes[K] extends {
|
|
1933
|
-
options: infer O;
|
|
1934
|
-
} ? O : unknown);
|
|
1935
|
-
}[keyof CustomFieldTypes];
|
|
1936
|
-
interface BaseField<TType extends string, TValue> {
|
|
1937
|
-
readonly type: TType;
|
|
1938
|
-
readonly name: string;
|
|
1939
|
-
readonly label?: Label;
|
|
1940
|
-
readonly required?: boolean;
|
|
1941
|
-
readonly unique?: boolean;
|
|
1942
|
-
/**
|
|
1943
|
-
* With `unique` (or on a slug field): a sibling field (e.g. `parent` or `tenant`), or several,
|
|
1944
|
-
* whose documents only need to differ among themselves, so `/about/team` and `/careers/team`
|
|
1945
|
-
* can both be `team`. Single, unlocalized relationship, select, text or number fields.
|
|
1946
|
-
*/
|
|
1947
|
-
readonly uniqueWithin?: string | readonly string[];
|
|
1948
|
-
readonly index?: boolean;
|
|
1949
|
-
readonly defaultValue?: TValue;
|
|
1950
|
-
readonly validate?: FieldValidate<TValue>;
|
|
1951
|
-
readonly access?: FieldAccess;
|
|
1952
|
-
/** Stored but never returned by the API nor accepted as input (e.g. a password hash). */
|
|
1953
|
-
readonly hidden?: boolean;
|
|
1954
|
-
/**
|
|
1955
|
-
* One value per locale (needs `localization` in the config). Not for groups, arrays or
|
|
1956
|
-
* hasMany fields: localize the fields inside a group or array instead.
|
|
1957
|
-
*/
|
|
1958
|
-
readonly localized?: boolean;
|
|
1959
|
-
/** `sidebar`: shown in the edit page's side panel instead of the main form (top-level fields). */
|
|
1960
|
-
readonly position?: 'sidebar';
|
|
1961
|
-
/** Custom admin components for this field. */
|
|
1962
|
-
readonly admin?: FieldAdmin;
|
|
1963
|
-
/** Set by Easy CMS on fields of an added type (`fieldTypes`): the type's name, e.g. `color`. */
|
|
1964
|
-
readonly customType?: string;
|
|
1965
|
-
}
|
|
1966
|
-
interface TextField extends BaseField<'text', string> {
|
|
1967
|
-
readonly minLength?: number;
|
|
1968
|
-
readonly maxLength?: number;
|
|
1969
|
-
}
|
|
1970
|
-
interface TextareaField extends BaseField<'textarea', string> {
|
|
1971
|
-
readonly minLength?: number;
|
|
1972
|
-
readonly maxLength?: number;
|
|
1973
|
-
}
|
|
1974
|
-
interface NumberField extends BaseField<'number', number> {
|
|
1975
|
-
readonly min?: number;
|
|
1976
|
-
readonly max?: number;
|
|
1977
|
-
}
|
|
1978
|
-
interface BooleanField extends BaseField<'boolean', boolean> {
|
|
1979
|
-
}
|
|
1980
|
-
/** Stored and returned as an ISO 8601 string. */
|
|
1981
|
-
interface DateField extends BaseField<'date', string> {
|
|
1982
|
-
}
|
|
1983
|
-
interface EmailField extends BaseField<'email', string> {
|
|
1984
|
-
}
|
|
1985
|
-
interface JsonField extends BaseField<'json', unknown> {
|
|
1986
|
-
}
|
|
1987
|
-
type SelectOption = string | {
|
|
1988
|
-
readonly label: Label;
|
|
1989
|
-
readonly value: string;
|
|
1990
|
-
};
|
|
1991
|
-
interface SelectField extends BaseField<'select', string | readonly string[]> {
|
|
1992
|
-
readonly options: readonly SelectOption[];
|
|
1993
|
-
readonly hasMany?: boolean;
|
|
1994
|
-
}
|
|
1995
|
-
interface SlugField extends BaseField<'slug', string> {
|
|
1996
|
-
/** Name of a sibling `text` field to generate the slug from. */
|
|
1997
|
-
readonly from?: string;
|
|
1998
|
-
}
|
|
1999
|
-
/** Tiptap / ProseMirror JSON document. */
|
|
2000
|
-
interface RichTextDocument {
|
|
2001
|
-
readonly type: 'doc';
|
|
2002
|
-
readonly content?: readonly unknown[];
|
|
2003
|
-
}
|
|
2004
|
-
interface RichTextField extends BaseField<'richText', RichTextDocument> {
|
|
2005
|
-
}
|
|
2006
|
-
/** References a document in the built-in `media` collection: a file or an image. */
|
|
2007
|
-
interface UploadField extends BaseField<'upload', never> {
|
|
2008
|
-
/** Several files, e.g. a gallery: the value is a list, in the order editors arrange. */
|
|
2009
|
-
readonly hasMany?: boolean;
|
|
2010
|
-
/** With `hasMany`: the fewest files. */
|
|
2011
|
-
readonly minRows?: number;
|
|
2012
|
-
/** With `hasMany`: the most files. */
|
|
2013
|
-
readonly maxRows?: number;
|
|
2014
|
-
/**
|
|
2015
|
-
* The file types allowed, e.g. `['image/*']` or `['image/*', 'application/pdf']`. The admin
|
|
2016
|
-
* offers only these, and saving refuses others.
|
|
2017
|
-
*/
|
|
2018
|
-
readonly mimeTypes?: readonly string[];
|
|
2019
|
-
/**
|
|
2020
|
-
* With `upload.folders`: the `key` of the media folder this field's files go in, e.g.
|
|
2021
|
-
* `'banners'`. Its picker opens there and its uploads land there; the folder is made when it
|
|
2022
|
-
* is first needed.
|
|
2023
|
-
*/
|
|
2024
|
-
readonly folder?: string;
|
|
2025
|
-
/** With `folder`: only files in that folder (and its subfolders) may be chosen. */
|
|
2026
|
-
readonly folderOnly?: boolean;
|
|
2027
|
-
/** Which media documents may be chosen, as for relationships: offered and checked on save. */
|
|
2028
|
-
readonly filterOptions?: FilterOptions;
|
|
2029
|
-
}
|
|
2030
|
-
interface FilterOptionsArgs {
|
|
2031
|
-
/** The document being edited; `undefined` while it is being created. */
|
|
2032
|
-
readonly id: ID | undefined;
|
|
2033
|
-
readonly user: AuthUser | null;
|
|
2034
|
-
readonly cms: EasyCMS;
|
|
2035
|
-
/** The request's context (`onRequest`), `{}` when there is none. */
|
|
2036
|
-
readonly context: RequestContext;
|
|
2037
|
-
}
|
|
2038
|
-
/**
|
|
2039
|
-
* Which documents a relationship may point to: a `where` on the target collection, or `true`
|
|
2040
|
-
* for any. Runs on the server: the admin's picker offers only these, and saving checks them.
|
|
2041
|
-
*/
|
|
2042
|
-
type FilterOptions = (args: FilterOptionsArgs) => Where | true | Promise<Where | true>;
|
|
2043
|
-
interface RelationshipField extends BaseField<'relationship', never> {
|
|
2044
|
-
/** Slug of the target collection. */
|
|
2045
|
-
readonly to: string;
|
|
2046
|
-
readonly hasMany?: boolean;
|
|
2047
|
-
/** With `hasMany`: the fewest documents. */
|
|
2048
|
-
readonly minRows?: number;
|
|
2049
|
-
/** With `hasMany`: the most documents. */
|
|
2050
|
-
readonly maxRows?: number;
|
|
2051
|
-
readonly filterOptions?: FilterOptions;
|
|
2052
|
-
}
|
|
2053
|
-
interface ArrayField extends BaseField<'array', never> {
|
|
2054
|
-
readonly fields: readonly Field[];
|
|
2055
|
-
readonly minRows?: number;
|
|
2056
|
-
readonly maxRows?: number;
|
|
2057
|
-
}
|
|
2058
|
-
interface GroupField extends BaseField<'group', never> {
|
|
2059
|
-
readonly fields: readonly Field[];
|
|
2060
|
-
}
|
|
2061
|
-
/** One kind of block in a `blocks` field. */
|
|
2062
|
-
interface Block {
|
|
2063
|
-
/** Stored in each row as `blockType`. */
|
|
2064
|
-
readonly slug: string;
|
|
2065
|
-
readonly labels?: {
|
|
2066
|
-
readonly singular?: Label;
|
|
2067
|
-
readonly plural?: Label;
|
|
2068
|
-
};
|
|
2069
|
-
readonly fields: readonly Field[];
|
|
2070
|
-
}
|
|
2071
|
-
/**
|
|
2072
|
-
* A list of rows, each one of several block kinds (e.g. hero, text, gallery), for pages that
|
|
2073
|
-
* editors lay out themselves. Rows are `{ id, blockType, ...fields }`, stored as JSON.
|
|
2074
|
-
*/
|
|
2075
|
-
interface BlocksField extends BaseField<'blocks', never> {
|
|
2076
|
-
readonly blocks: readonly Block[];
|
|
2077
|
-
readonly minRows?: number;
|
|
2078
|
-
readonly maxRows?: number;
|
|
2079
|
-
}
|
|
2080
|
-
/** The fields `uniqueWithin` names, as a list. */
|
|
2081
|
-
declare const uniqueWithinOf: (field: Field) => readonly string[];
|
|
2082
|
-
/** Fields with `hasMany`: their value is a list of options, documents or files. */
|
|
2083
|
-
declare const isHasMany: (field: Field) => boolean;
|
|
2084
|
-
/**
|
|
2085
|
-
* Whether a MIME type is one of `patterns` (`image/*` matches every image but SVG, which can
|
|
2086
|
-
* carry scripts and is allowed only when listed by name: `image/svg+xml`).
|
|
2087
|
-
*/
|
|
2088
|
-
declare function mimeAllowedBy(mimeType: string, patterns: readonly string[]): boolean;
|
|
2089
|
-
/** Fields whose value is a list of rows: `array` and `blocks`. */
|
|
2090
|
-
declare const hasRows: (field: Field) => field is ArrayField | BlocksField;
|
|
2091
|
-
/** The fields of one row of an `array` or `blocks` field (`undefined` for an unknown block). */
|
|
2092
|
-
declare function rowFields(field: ArrayField | BlocksField, row: unknown): readonly Field[] | undefined;
|
|
2093
|
-
type Field = CustomField | TextField | TextareaField | NumberField | BooleanField | DateField | EmailField | JsonField | SelectField | SlugField | RichTextField | UploadField | RelationshipField | ArrayField | GroupField | BlocksField;
|
|
2094
|
-
type FieldType = Field['type'];
|
|
2095
|
-
declare const FIELD_TYPES: readonly ["text", "textarea", "number", "boolean", "date", "email", "json", "select", "slug", "richText", "upload", "relationship", "array", "group", "blocks"];
|
|
2096
|
-
|
|
2097
|
-
/** Audit log settings (`audit`). */
|
|
2098
|
-
interface AuditConfig {
|
|
2099
|
-
/** Days to keep entries; older ones are deleted. `0`: keep them all. Default 365. */
|
|
2100
|
-
readonly keep?: number;
|
|
2101
|
-
/** Keep the values before and after a change, not only which fields changed. Default `true`. */
|
|
2102
|
-
readonly values?: boolean;
|
|
2103
|
-
/** Failed logins within an hour that the dashboard warns about. Default 20. */
|
|
2104
|
-
readonly failedLogins?: number;
|
|
2105
|
-
/**
|
|
2106
|
-
* The part of the site an entry belongs to, from the request's context, e.g. its tenant
|
|
2107
|
-
* (usually set by a plugin). Users whose role holds in a part only (`scoped`) see that part's
|
|
2108
|
-
* entries; others see the chosen part's, or all.
|
|
2109
|
-
*/
|
|
2110
|
-
readonly scope?: ((context: RequestContext) => string | null | undefined) | null;
|
|
2111
|
-
}
|
|
2112
|
-
/** A field that changed: values before and after, unless they are not kept. */
|
|
2113
|
-
interface AuditChange {
|
|
2114
|
-
field: string;
|
|
2115
|
-
before?: unknown;
|
|
2116
|
-
after?: unknown;
|
|
2117
|
-
}
|
|
2118
|
-
/** One entry (`GET <api>/admin/audit`). */
|
|
2119
|
-
interface AuditEntry {
|
|
2120
|
-
id: ID;
|
|
2121
|
-
/** When, as an ISO date. */
|
|
2122
|
-
at: string;
|
|
2123
|
-
/** E.g. `create`, `update`, `publish`, `delete`, `login`, `login.failed`, `role.update`. */
|
|
2124
|
-
action: string;
|
|
2125
|
-
/** A collection slug, `global:<slug>`, or an area such as `auth`, `roles`, `backups`. */
|
|
2126
|
-
target: string | null;
|
|
2127
|
-
/** The document's id. */
|
|
2128
|
-
doc: string | null;
|
|
2129
|
-
/** The document's title then (kept after it is deleted). */
|
|
2130
|
-
title: string | null;
|
|
2131
|
-
actor: {
|
|
2132
|
-
id: string | null;
|
|
2133
|
-
email: string | null;
|
|
2134
|
-
/** `user` (admin or REST), `api-key`, `system` (code), `scheduler`. */
|
|
2135
|
-
via: string;
|
|
2136
|
-
ip: string | null;
|
|
2137
|
-
userAgent: string | null;
|
|
2138
|
-
};
|
|
2139
|
-
changes: AuditChange[] | null;
|
|
2140
|
-
detail: Record<string, unknown> | null;
|
|
2141
|
-
}
|
|
2142
|
-
/** `GET <api>/admin/audit`: a page of entries, newest first. */
|
|
2143
|
-
interface AuditPage {
|
|
2144
|
-
docs: AuditEntry[];
|
|
2145
|
-
page: number;
|
|
2146
|
-
totalPages: number;
|
|
2147
|
-
totalDocs: number;
|
|
2148
|
-
}
|
|
2149
|
-
interface AuditFilter {
|
|
2150
|
-
action?: string | null;
|
|
2151
|
-
target?: string | null;
|
|
2152
|
-
doc?: string | null;
|
|
2153
|
-
/** An email, or part of one. */
|
|
2154
|
-
actor?: string | null;
|
|
2155
|
-
/** ISO dates. */
|
|
2156
|
-
from?: string | null;
|
|
2157
|
-
to?: string | null;
|
|
2158
|
-
/** Only entries of this part of the site (`scope`). */
|
|
2159
|
-
scope?: string | null;
|
|
2160
|
-
}
|
|
2161
|
-
/** `POST <api>/admin/audit/verify`. */
|
|
2162
|
-
interface AuditVerification {
|
|
2163
|
-
checked: number;
|
|
2164
|
-
/** Entries whose signature does not match: changed after they were written. */
|
|
2165
|
-
invalid: ID[];
|
|
2166
|
-
/** Missing ids between entries: deleted entries, or (rarely) inserts that failed. */
|
|
2167
|
-
gaps: number;
|
|
2168
|
-
at: string;
|
|
2169
|
-
}
|
|
2170
|
-
/**
|
|
2171
|
-
* The audit log (`audit` in the config): who did what, when, from where. Entries can't be
|
|
2172
|
-
* changed through Easy CMS; each is signed with the secret, so changes in the database show.
|
|
2173
|
-
* Available as `cms.audit`.
|
|
2174
|
-
*/
|
|
2175
|
-
declare class AuditLog {
|
|
2176
|
-
private readonly cms;
|
|
2177
|
-
/** Entries that could not be written since the server started. */
|
|
2178
|
-
failures: number;
|
|
2179
|
-
constructor(cms: EasyCMS);
|
|
2180
|
-
get enabled(): boolean;
|
|
2181
|
-
private get settings();
|
|
2182
|
-
/**
|
|
2183
|
-
* Writes an entry. The user defaults to the request's. Never throws: a failure is logged and
|
|
2184
|
-
* counted, and the dashboard says so.
|
|
2185
|
-
*/
|
|
2186
|
-
record(entry: {
|
|
2187
|
-
action: string;
|
|
2188
|
-
target?: string | null;
|
|
2189
|
-
doc?: ID | null;
|
|
2190
|
-
title?: string | null;
|
|
2191
|
-
user?: AuthUser | null;
|
|
2192
|
-
/** Who it was when there is no user, e.g. the email of a failed login. */
|
|
2193
|
-
email?: string | null;
|
|
2194
|
-
changes?: AuditChange[] | null;
|
|
2195
|
-
detail?: Record<string, unknown> | null;
|
|
2196
|
-
/** The call's context, when it is not the request's. */
|
|
2197
|
-
context?: RequestContext;
|
|
2198
|
-
}): Promise<void>;
|
|
2199
|
-
/**
|
|
2200
|
-
* A change to a document or global: what changed between `before` and `after`. Internal
|
|
2201
|
-
* collections (sessions, versions…) are not logged.
|
|
2202
|
-
*/
|
|
2203
|
-
content(config: CollectionConfig | GlobalConfig, global: boolean, action: string, after: Record<string, unknown> | undefined, before: Record<string, unknown> | undefined, user: AuthUser | null, context?: RequestContext): Promise<void>;
|
|
2204
|
-
/** Fields whose stored value changed. Hidden ones never (the password only as "changed"). */
|
|
2205
|
-
diff(fields: readonly Field[], before: Record<string, unknown>, after: Record<string, unknown>): AuditChange[];
|
|
2206
|
-
list(filter: AuditFilter, page?: number): Promise<AuditPage>;
|
|
2207
|
-
/** The filtered entries as CSV (the newest 10,000). */
|
|
2208
|
-
csv(filter: AuditFilter): Promise<string>;
|
|
2209
|
-
/** Checks every entry's signature, and that no ids are missing. */
|
|
2210
|
-
verify(): Promise<AuditVerification>;
|
|
2211
|
-
/**
|
|
2212
|
-
* Daily upkeep from `runJobs`: deletes entries older than `keep` days, then verifies the log.
|
|
2213
|
-
* Runs when the last check is more than a day old.
|
|
2214
|
-
*/
|
|
2215
|
-
upkeep(now?: Date): Promise<void>;
|
|
2216
|
-
/** What the dashboard warns about: many failed logins, entries not written, a failed check. */
|
|
2217
|
-
attention(now?: Date): Promise<({
|
|
2218
|
-
id: 'failed-logins';
|
|
2219
|
-
count: number;
|
|
2220
|
-
} | {
|
|
2221
|
-
id: 'audit-failures';
|
|
2222
|
-
count: number;
|
|
2223
|
-
} | {
|
|
2224
|
-
id: 'audit-tampered';
|
|
2225
|
-
invalid: number;
|
|
2226
|
-
at: string;
|
|
2227
|
-
})[]>;
|
|
2228
|
-
/** The part of the site a context is in (`scope`), or `null`. */
|
|
2229
|
-
scopeOf(context: RequestContext | undefined): string | null;
|
|
2230
|
-
private sign;
|
|
2231
|
-
private lastVerification;
|
|
2232
|
-
private where;
|
|
2233
|
-
}
|
|
2234
|
-
|
|
2235
|
-
/** What the email functions in `auth.emails` receive. */
|
|
2236
|
-
interface PasswordEmailArgs {
|
|
2237
|
-
readonly user: AuthUser;
|
|
2238
|
-
/** The page that sets the password; absent for `passwordChanged`. */
|
|
2239
|
-
readonly url?: string;
|
|
2240
|
-
/** The admin language for the text: `en` or `th`. */
|
|
2241
|
-
readonly locale: string;
|
|
2242
|
-
/** When the link stops working. */
|
|
2243
|
-
readonly expiresAt?: Date;
|
|
2244
|
-
/**
|
|
2245
|
-
* Invitations when passwords are off (`auth.password: false`): the providers to sign in with,
|
|
2246
|
-
* e.g. `['Google']`; `url` is then the login page.
|
|
2247
|
-
*/
|
|
2248
|
-
readonly sso?: readonly string[];
|
|
2249
|
-
}
|
|
2250
|
-
/** An email's content: a subject, and text and/or HTML. */
|
|
2251
|
-
interface PasswordEmail {
|
|
2252
|
-
readonly subject: string;
|
|
2253
|
-
readonly text: string;
|
|
2254
|
-
readonly html?: string;
|
|
2255
|
-
}
|
|
2256
|
-
type PasswordEmailFn = (args: PasswordEmailArgs) => PasswordEmail | Promise<PasswordEmail>;
|
|
2257
|
-
/** The default emails, in English and Thai. */
|
|
2258
|
-
declare const DEFAULT_PASSWORD_EMAILS: {
|
|
2259
|
-
resetPassword: PasswordEmailFn;
|
|
2260
|
-
invite: PasswordEmailFn;
|
|
2261
|
-
passwordChanged: PasswordEmailFn;
|
|
2262
|
-
verifyEmail: PasswordEmailFn;
|
|
2263
|
-
};
|
|
2264
|
-
|
|
2265
|
-
/**
|
|
2266
|
-
* Signing in to the admin with an outside account (`auth.providers`): Google, Microsoft, GitHub
|
|
2267
|
-
* or any OpenID Connect provider. `@easy-cms/auth-oauth` makes these; Easy CMS runs the rest
|
|
2268
|
-
* (the redirect, matching users, sessions).
|
|
2269
|
-
*/
|
|
2270
|
-
/** Values bound to one sign-in, checked when the browser comes back. */
|
|
2271
|
-
interface AuthProviderRequest {
|
|
2272
|
-
/** Where the provider sends the browser back: `<serverURL><api>/auth/<id>/callback`. */
|
|
2273
|
-
readonly redirectUri: string;
|
|
2274
|
-
readonly state: string;
|
|
2275
|
-
/** For PKCE: the provider gets its S256 challenge. */
|
|
2276
|
-
readonly codeVerifier: string;
|
|
2277
|
-
/** For OpenID Connect: must come back in the ID token. */
|
|
2278
|
-
readonly nonce: string;
|
|
2279
|
-
}
|
|
2280
|
-
/** Who signed in, as the provider says. */
|
|
2281
|
-
interface AuthProviderProfile {
|
|
2282
|
-
/** The provider's stable id for the account (`sub`). */
|
|
2283
|
-
readonly subject: string;
|
|
2284
|
-
readonly email: string | null;
|
|
2285
|
-
/** The provider vouches for the email. Only verified emails are matched to users. */
|
|
2286
|
-
readonly emailVerified: boolean;
|
|
2287
|
-
readonly name?: string | undefined;
|
|
2288
|
-
}
|
|
2289
|
-
interface AuthProvider {
|
|
2290
|
-
/** In URLs and stored identities, e.g. `google`: lowercase letters, digits and `-`. */
|
|
2291
|
-
readonly id: string;
|
|
2292
|
-
/** On the sign-in button, e.g. `Google`. */
|
|
2293
|
-
readonly name: string;
|
|
2294
|
-
/** The button's icon in the admin. Default `key`. */
|
|
2295
|
-
readonly icon?: 'google' | 'microsoft' | 'github' | 'key';
|
|
2296
|
-
/** The provider's page to send the browser to. */
|
|
2297
|
-
authorizationURL(request: AuthProviderRequest): Promise<URL>;
|
|
2298
|
-
/**
|
|
2299
|
-
* After the browser comes back to `redirectUri` (`url`, with its query): checks the response
|
|
2300
|
-
* and exchanges the code. Throws when anything does not check out.
|
|
2301
|
-
*/
|
|
2302
|
-
callback(request: AuthProviderRequest & {
|
|
2303
|
-
readonly url: URL;
|
|
2304
|
-
}): Promise<AuthProviderProfile>;
|
|
2305
|
-
/** For Settings → SSO: what it connects to, never secrets. */
|
|
2306
|
-
describe?(): Readonly<Record<string, string>>;
|
|
2307
|
-
/**
|
|
2308
|
-
* Signs staff who have an account in by their verified email the first time, without linking
|
|
2309
|
-
* the provider from their account page. Only for a provider whose emails your organization
|
|
2310
|
-
* controls (e.g. its Google Workspace). Default `false`; site members are always matched.
|
|
2311
|
-
*/
|
|
2312
|
-
readonly linkByEmail?: boolean;
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
|
-
interface ConfigIssue {
|
|
2316
|
-
/** Where the problem is, e.g. `collections.posts.fields.slug.from`. */
|
|
2317
|
-
readonly path: string;
|
|
2318
|
-
readonly message: string;
|
|
2319
|
-
/** How to fix it. */
|
|
2320
|
-
readonly hint?: string;
|
|
2321
|
-
}
|
|
2322
|
-
declare class ConfigError extends Error {
|
|
2323
|
-
readonly issues: readonly ConfigIssue[];
|
|
2324
|
-
constructor(issues: readonly ConfigIssue[]);
|
|
2325
|
-
static format(issues: readonly ConfigIssue[]): string;
|
|
2326
|
-
}
|
|
2327
|
-
/** Base class for errors that map to an HTTP status in the REST API. */
|
|
2328
|
-
declare class EasyCMSError extends Error {
|
|
2329
|
-
readonly status: number;
|
|
2330
|
-
constructor(message: string, status: number);
|
|
2331
|
-
}
|
|
2332
|
-
interface FieldError {
|
|
2333
|
-
/** Dotted path, e.g. `links.0.url`. */
|
|
2334
|
-
readonly field: string;
|
|
2335
|
-
readonly message: string;
|
|
2336
|
-
}
|
|
2337
|
-
declare class ValidationError extends EasyCMSError {
|
|
2338
|
-
readonly errors: readonly FieldError[];
|
|
2339
|
-
constructor(collection: string, errors: readonly FieldError[]);
|
|
2340
|
-
}
|
|
2341
|
-
declare class NotFoundError extends EasyCMSError {
|
|
2342
|
-
constructor(collection: string, id: unknown);
|
|
2343
|
-
}
|
|
2344
|
-
/** A bad `where`, `sort` or unknown collection/global. */
|
|
2345
|
-
declare class QueryError extends EasyCMSError {
|
|
2346
|
-
constructor(message: string);
|
|
2347
|
-
}
|
|
2348
|
-
/** The database schema does not match the config (pending or missing migrations). */
|
|
2349
|
-
declare class SchemaError extends EasyCMSError {
|
|
2350
|
-
constructor(message: string);
|
|
2351
|
-
}
|
|
2352
|
-
/** Not logged in, or the session is invalid. */
|
|
2353
|
-
declare class UnauthorizedError extends EasyCMSError {
|
|
2354
|
-
constructor(message?: string);
|
|
2355
|
-
}
|
|
2356
|
-
/** Logged in, but not allowed to do this. */
|
|
2357
|
-
declare class ForbiddenError extends EasyCMSError {
|
|
2358
|
-
constructor(message?: string);
|
|
2359
|
-
}
|
|
2360
|
-
declare class TooManyRequestsError extends EasyCMSError {
|
|
2361
|
-
constructor(message: string);
|
|
2362
|
-
}
|
|
2363
|
-
declare class PayloadTooLargeError extends EasyCMSError {
|
|
2364
|
-
constructor(message: string);
|
|
2365
|
-
}
|
|
2366
|
-
|
|
2367
|
-
/** Built-in types a field type can be stored as. */
|
|
2368
|
-
declare const FIELD_TYPE_BASES: readonly ["text", "textarea", "email", "number", "boolean", "date", "json"];
|
|
2369
|
-
type FieldTypeBase = (typeof FIELD_TYPE_BASES)[number];
|
|
2370
|
-
/** What a field type's `validate` gets besides the value: the field, with its own options. */
|
|
2371
|
-
interface FieldTypeValidateContext extends FieldValidateContext {
|
|
2372
|
-
readonly field: Readonly<Record<string, unknown>> & {
|
|
2373
|
-
readonly name: string;
|
|
2374
|
-
};
|
|
2375
|
-
}
|
|
2376
|
-
/**
|
|
2377
|
-
* A field type a package adds, e.g. `color`: stored, queried and translated like its `base`
|
|
2378
|
-
* type, with its own checks and admin components. List it in the config's `fieldTypes`.
|
|
2379
|
-
*/
|
|
2380
|
-
interface FieldTypeDefinition {
|
|
2381
|
-
/** The `type` fields use, e.g. `color`. Lowercase; not one of the built-in types. */
|
|
2382
|
-
readonly name: string;
|
|
2383
|
-
/** How values are stored and queried: one of the built-in scalar types. */
|
|
2384
|
-
readonly base: FieldTypeBase;
|
|
2385
|
-
/** Checks a value (not empty ones; `required` covers those). Return `true` or a message. */
|
|
2386
|
-
readonly validate?: (value: unknown, ctx: FieldTypeValidateContext) => true | string | Promise<true | string>;
|
|
2387
|
-
/** Checks a field's own options when the config loads; return a message for a mistake. */
|
|
2388
|
-
readonly checkOptions?: (field: Readonly<Record<string, unknown>>) => string | undefined;
|
|
2389
|
-
readonly admin?: {
|
|
2390
|
-
/** The input: a Web Component from `module`. */
|
|
2391
|
-
readonly component?: string;
|
|
2392
|
-
/** Shown in the admin's lists. */
|
|
2393
|
-
readonly cell?: string;
|
|
2394
|
-
/** The admin module (package export or file) that defines the components. */
|
|
2395
|
-
readonly module?: string;
|
|
2396
|
-
/** The field options its components get (as their `options`), e.g. `['presets']`. */
|
|
2397
|
-
readonly props?: readonly string[];
|
|
2398
|
-
};
|
|
2399
|
-
/** The value's TypeScript type for `generate:types`. Default: the base type's. */
|
|
2400
|
-
readonly typescript?: string;
|
|
2401
|
-
}
|
|
2402
|
-
/** Declares a field type; returns it unchanged. */
|
|
2403
|
-
declare function defineFieldType(definition: FieldTypeDefinition): FieldTypeDefinition;
|
|
2404
|
-
|
|
2405
|
-
declare const WEBHOOK_EVENTS: readonly ["create", "update", "delete", "publish", "unpublish", "draft"];
|
|
2406
|
-
/**
|
|
2407
|
-
* `create`, `update`, `delete`: the stored document changed. `publish` / `unpublish`: its status
|
|
2408
|
-
* changed (sent with `create` or `update`). `draft`: only a draft was saved; what is live did not
|
|
2409
|
-
* change (collections with versions and drafts).
|
|
2410
|
-
*/
|
|
2411
|
-
type WebhookEvent = (typeof WEBHOOK_EVENTS)[number];
|
|
2412
|
-
interface WebhookConfig {
|
|
2413
|
-
/** Where to POST events. */
|
|
2414
|
-
readonly url: string;
|
|
2415
|
-
/**
|
|
2416
|
-
* Events to send. Default: every content change. Events of the app or its plugins (`events`
|
|
2417
|
-
* in the config, e.g. `order.paid`) are sent only when listed here.
|
|
2418
|
-
*/
|
|
2419
|
-
readonly events?: readonly (WebhookEvent | (string & {}))[];
|
|
2420
|
-
/** Collections to send events for. Default: all; `[]` for none. */
|
|
2421
|
-
readonly collections?: readonly string[];
|
|
2422
|
-
/** Globals to send events for. Default: all; `[]` for none. */
|
|
2423
|
-
readonly globals?: readonly string[];
|
|
2424
|
-
/** Signs each body: `x-easy-cms-signature: sha256=<hex HMAC-SHA256 of the body>`. */
|
|
2425
|
-
readonly secret?: string;
|
|
2426
|
-
/** Extra request headers, e.g. a token the receiver checks. */
|
|
2427
|
-
readonly headers?: Readonly<Record<string, string>>;
|
|
2428
|
-
}
|
|
2429
|
-
/** What a webhook receives as JSON. */
|
|
2430
|
-
interface WebhookPayload {
|
|
2431
|
-
/** A content change (`WebhookEvent`), or an event of the app or a plugin, e.g. `order.paid`. */
|
|
2432
|
-
readonly event: string;
|
|
2433
|
-
readonly collection?: string;
|
|
2434
|
-
readonly global?: string;
|
|
2435
|
-
readonly id?: ID;
|
|
2436
|
-
/**
|
|
2437
|
-
* The stored document (every locale, no hidden fields); for `delete`, as it was. For an event of
|
|
2438
|
-
* the app or a plugin: what `cms.emit()` was given.
|
|
2439
|
-
*/
|
|
2440
|
-
readonly doc: Record<string, unknown>;
|
|
2441
|
-
/** ISO time of the change. */
|
|
2442
|
-
readonly timestamp: string;
|
|
2443
|
-
}
|
|
2444
|
-
|
|
2445
|
-
type Data$1 = Record<string, unknown>;
|
|
2446
|
-
type MaybePromise<T> = T | Promise<T>;
|
|
2447
|
-
type Operation = 'create' | 'update';
|
|
2448
|
-
interface HookBase {
|
|
2449
|
-
/** The user the operation runs for; `null` for anonymous or trusted Local API calls. */
|
|
2450
|
-
readonly user: AuthUser | null;
|
|
2451
|
-
/** The Local API, e.g. to query other collections or trigger a revalidation. */
|
|
2452
|
-
readonly cms: EasyCMS;
|
|
2453
|
-
/** Slug of the collection or global. */
|
|
2454
|
-
readonly slug: string;
|
|
2455
|
-
/** The request's context (`onRequest`, or `context` of a Local API call); `{}` when there is none. */
|
|
2456
|
-
readonly context: RequestContext;
|
|
2457
|
-
}
|
|
2458
|
-
type BeforeValidateHook = (args: HookBase & {
|
|
2459
|
-
data: Data$1;
|
|
2460
|
-
operation: Operation;
|
|
2461
|
-
originalDoc?: Data$1;
|
|
2462
|
-
}) => MaybePromise<Data$1 | undefined>;
|
|
2463
|
-
type BeforeChangeHook = (args: HookBase & {
|
|
2464
|
-
data: Data$1;
|
|
2465
|
-
operation: Operation;
|
|
2466
|
-
originalDoc?: Data$1;
|
|
2467
|
-
}) => MaybePromise<Data$1 | undefined>;
|
|
2468
|
-
type AfterChangeHook = (args: HookBase & {
|
|
2469
|
-
doc: Data$1;
|
|
2470
|
-
operation: Operation;
|
|
2471
|
-
previousDoc?: Data$1;
|
|
2472
|
-
}) => MaybePromise<void>;
|
|
2473
|
-
type BeforeDeleteHook = (args: HookBase & {
|
|
2474
|
-
id: ID;
|
|
2475
|
-
}) => MaybePromise<void>;
|
|
2476
|
-
type AfterDeleteHook = (args: HookBase & {
|
|
2477
|
-
id: ID;
|
|
2478
|
-
doc: Data$1;
|
|
2479
|
-
}) => MaybePromise<void>;
|
|
2480
|
-
type AfterReadHook = (args: HookBase & {
|
|
2481
|
-
doc: Data$1;
|
|
2482
|
-
}) => MaybePromise<Data$1 | undefined>;
|
|
2483
|
-
interface CollectionHooks {
|
|
2484
|
-
readonly beforeValidate?: readonly BeforeValidateHook[];
|
|
2485
|
-
readonly beforeChange?: readonly BeforeChangeHook[];
|
|
2486
|
-
readonly afterChange?: readonly AfterChangeHook[];
|
|
2487
|
-
readonly beforeDelete?: readonly BeforeDeleteHook[];
|
|
2488
|
-
readonly afterDelete?: readonly AfterDeleteHook[];
|
|
2489
|
-
readonly afterRead?: readonly AfterReadHook[];
|
|
2490
|
-
}
|
|
2491
|
-
interface GlobalHooks {
|
|
2492
|
-
readonly beforeChange?: readonly BeforeChangeHook[];
|
|
2493
|
-
readonly afterChange?: readonly AfterChangeHook[];
|
|
2494
|
-
readonly afterRead?: readonly AfterReadHook[];
|
|
2495
|
-
}
|
|
2496
|
-
interface LocalizationConfig {
|
|
2497
|
-
/** Content locales, e.g. `['th', 'en']`. */
|
|
2498
|
-
readonly locales: readonly string[];
|
|
2499
|
-
/** The locale used when none is given; its values also fill empty ones. Default: the first. */
|
|
2500
|
-
readonly defaultLocale?: string;
|
|
2501
|
-
/** Reads return the default locale's value when a locale's value is empty. Default true. */
|
|
2502
|
-
readonly fallback?: boolean;
|
|
2503
|
-
}
|
|
2504
|
-
/**
|
|
2505
|
-
* Where a document is shown on the site, for live preview in the admin: an absolute URL or a
|
|
2506
|
-
* path on the site's origin. Return `null` when the document has no page.
|
|
2507
|
-
*/
|
|
2508
|
-
type PreviewURL = (args: {
|
|
2509
|
-
readonly doc: Record<string, unknown>;
|
|
2510
|
-
/** The content locale being edited (with `localization`), otherwise the admin locale. */
|
|
2511
|
-
readonly locale: string;
|
|
2512
|
-
}) => string | null | undefined;
|
|
2513
|
-
interface VersionsConfig {
|
|
2514
|
-
/** Versions kept per document; older ones are deleted. Default 50. */
|
|
2515
|
-
readonly max?: number;
|
|
2516
|
-
}
|
|
2517
|
-
/** A panel of an edit page's side column: a component, at the top or (default) below the rest. */
|
|
2518
|
-
type SidebarPanel = AdminComponent | {
|
|
2519
|
-
readonly tag: string;
|
|
2520
|
-
readonly props?: Readonly<Record<string, unknown>>;
|
|
2521
|
-
/** `top`: right below publishing, above the side fields. Default: below them. */
|
|
2522
|
-
readonly position?: 'top';
|
|
2523
|
-
};
|
|
2524
|
-
/**
|
|
2525
|
-
* Where the edit page puts its fields: tabs, collapsible sections and rows, by field name. Only
|
|
2526
|
-
* how they look: the data stays as the fields say. Fields not placed follow at the end (in the
|
|
2527
|
-
* first tab). Top-level fields; sidebar fields stay in the side column.
|
|
2528
|
-
*
|
|
2529
|
-
* ```ts
|
|
2530
|
-
* layout: [
|
|
2531
|
-
* { tab: 'Content', fields: ['title', { row: ['author', 'category'] }, 'body'] },
|
|
2532
|
-
* { tab: 'SEO', fields: ['meta'] },
|
|
2533
|
-
* ]
|
|
2534
|
-
* ```
|
|
2535
|
-
*/
|
|
2536
|
-
type LayoutNode = string | {
|
|
2537
|
-
readonly row: readonly string[];
|
|
2538
|
-
} | {
|
|
2539
|
-
readonly collapsible: Label;
|
|
2540
|
-
/** Starts folded. */
|
|
2541
|
-
readonly collapsed?: boolean;
|
|
2542
|
-
readonly description?: Label;
|
|
2543
|
-
readonly fields: readonly LayoutNode[];
|
|
2544
|
-
} | {
|
|
2545
|
-
readonly tab: Label;
|
|
2546
|
-
readonly description?: Label;
|
|
2547
|
-
readonly fields: readonly LayoutNode[];
|
|
2548
|
-
};
|
|
2549
|
-
/** Custom admin components for a collection's or global's edit page. */
|
|
2550
|
-
interface ContainerAdmin {
|
|
2551
|
-
/** Panels in the edit page's side column (components from admin modules). */
|
|
2552
|
-
readonly sidebar?: readonly SidebarPanel[];
|
|
2553
|
-
/** Tabs, collapsible sections and rows of the edit page. See `LayoutNode`. */
|
|
2554
|
-
readonly layout?: readonly LayoutNode[];
|
|
2555
|
-
/**
|
|
2556
|
-
* Its menu group: a group's id (`admin.nav`), e.g. `shop` or `shop.catalog`; `settings` for
|
|
2557
|
-
* Settings; or a label, which makes a group of that name. Default: Content for collections,
|
|
2558
|
-
* Settings › Site for globals.
|
|
2559
|
-
*/
|
|
2560
|
-
readonly group?: 'settings' | Label;
|
|
2561
|
-
}
|
|
2562
|
-
interface CollectionAdmin extends ContainerAdmin {
|
|
2563
|
-
/**
|
|
2564
|
-
* A number that needs attention, next to its menu item: the documents matching `where` (that
|
|
2565
|
-
* the user may read), e.g. orders to send. `tone`: `accent` (default), `warning` or `danger`.
|
|
2566
|
-
*/
|
|
2567
|
-
readonly badge?: {
|
|
2568
|
-
readonly where: Where;
|
|
2569
|
-
readonly tone?: 'accent' | 'warning' | 'danger';
|
|
2570
|
-
/** What the number counts, for screen readers, e.g. `{ en: 'to send', th: 'รอจัดส่ง' }`. */
|
|
2571
|
-
readonly label?: Label;
|
|
2572
|
-
};
|
|
2573
|
-
/** The number of documents beside its menu item. Default `true`. */
|
|
2574
|
-
readonly count?: boolean;
|
|
2575
|
-
/** The list with no documents yet: what to say, and a link, e.g. to a guide. */
|
|
2576
|
-
readonly empty?: {
|
|
2577
|
-
readonly description?: Label;
|
|
2578
|
-
readonly link?: {
|
|
2579
|
-
readonly label: Label;
|
|
2580
|
-
readonly href: string;
|
|
2581
|
-
};
|
|
2582
|
-
};
|
|
2583
|
-
readonly list?: CollectionListAdmin;
|
|
2584
|
-
/**
|
|
2585
|
-
* With `auth.rbac`: the relationship field to `users` that names a document's owner, e.g.
|
|
2586
|
-
* `author`, for roles given "own documents only". Default: who created it (`createdBy`).
|
|
2587
|
-
*/
|
|
2588
|
-
readonly ownerField?: string;
|
|
2589
|
-
/**
|
|
2590
|
-
* Deleting asks more than "Are you sure?", for documents that take much with them (e.g. a
|
|
2591
|
-
* tenant): `typeTitle` has the user type the document's title; `impact` is a path under the
|
|
2592
|
-
* API, called with `?id=`, that returns `{ message }` (a label) saying what will be lost.
|
|
2593
|
-
* Documents are then deleted one at a time, not from a selection.
|
|
2594
|
-
*/
|
|
2595
|
-
readonly confirmDelete?: {
|
|
2596
|
-
readonly typeTitle?: boolean;
|
|
2597
|
-
readonly impact?: string;
|
|
2598
|
-
};
|
|
2599
|
-
}
|
|
2600
|
-
interface CollectionListAdmin {
|
|
2601
|
-
/**
|
|
2602
|
-
* Show the list as a tree: the name of a relationship field to this same collection (e.g.
|
|
2603
|
-
* `parent`). Top-level documents come first; their children open below them.
|
|
2604
|
-
*/
|
|
2605
|
-
readonly tree?: string;
|
|
2606
|
-
/** The list's default order, e.g. `title` or `-updatedAt` (the default). */
|
|
2607
|
-
readonly sort?: string;
|
|
2608
|
-
}
|
|
2609
|
-
interface CollectionConfig {
|
|
2610
|
-
/** URL and table name. Lowercase letters, digits, `-` and `_`. */
|
|
2611
|
-
readonly slug: string;
|
|
2612
|
-
readonly labels?: {
|
|
2613
|
-
readonly singular?: Label;
|
|
2614
|
-
readonly plural?: Label;
|
|
2615
|
-
};
|
|
2616
|
-
/** Icon in the admin menu. Default `file-text`. */
|
|
2617
|
-
readonly icon?: AdminIcon;
|
|
2618
|
-
/**
|
|
2619
|
-
* How the admin opens documents from the list: `drawer` slides a panel over the list, handy
|
|
2620
|
-
* for small collections (categories, tags). Default `page`. Collections with drafts, versions
|
|
2621
|
-
* or live preview always use the page.
|
|
2622
|
-
*/
|
|
2623
|
-
readonly editIn?: 'page' | 'drawer';
|
|
2624
|
-
readonly fields: readonly Field[];
|
|
2625
|
-
/** Top-level field shown as the document title in the admin UI. */
|
|
2626
|
-
readonly useAsTitle?: string;
|
|
2627
|
-
/** Adds a `status` field (`draft` | `published`). */
|
|
2628
|
-
readonly drafts?: boolean;
|
|
2629
|
-
/**
|
|
2630
|
-
* Keep a snapshot of every save, to view history and restore. With `drafts`, a draft saved
|
|
2631
|
-
* on a published document is kept as a version and the published document stays live until
|
|
2632
|
-
* it is published again.
|
|
2633
|
-
*/
|
|
2634
|
-
readonly versions?: boolean | VersionsConfig;
|
|
2635
|
-
/** Allow scheduling publish/unpublish for a later time (needs `drafts`). */
|
|
2636
|
-
readonly schedule?: boolean;
|
|
2637
|
-
/** Live preview: the page that shows a document, e.g. `({ doc }) => \`/posts/${doc.slug}\``. */
|
|
2638
|
-
readonly preview?: PreviewURL;
|
|
2639
|
-
readonly access?: CollectionAccess;
|
|
2640
|
-
readonly hooks?: CollectionHooks;
|
|
2641
|
-
/** Custom admin components, and where the collection is in the menu. */
|
|
2642
|
-
readonly admin?: CollectionAdmin;
|
|
2643
|
-
}
|
|
2644
|
-
interface GlobalConfig {
|
|
2645
|
-
readonly slug: string;
|
|
2646
|
-
readonly label?: Label;
|
|
2647
|
-
/** Icon in the admin menu. Default `settings`. */
|
|
2648
|
-
readonly icon?: AdminIcon;
|
|
2649
|
-
readonly fields: readonly Field[];
|
|
2650
|
-
readonly drafts?: boolean;
|
|
2651
|
-
/** Keep a snapshot of every save. See `CollectionConfig.versions`. */
|
|
2652
|
-
readonly versions?: boolean | VersionsConfig;
|
|
2653
|
-
/** Allow scheduling publish/unpublish for a later time (needs `drafts`). */
|
|
2654
|
-
readonly schedule?: boolean;
|
|
2655
|
-
/** Live preview: the page that shows the global. See `CollectionConfig.preview`. */
|
|
2656
|
-
readonly preview?: PreviewURL;
|
|
2657
|
-
readonly access?: GlobalAccess;
|
|
2658
|
-
readonly hooks?: GlobalHooks;
|
|
2659
|
-
/**
|
|
2660
|
-
* One value per scope, e.g. per tenant (usually set by a plugin): the scope of a call, from its
|
|
2661
|
-
* context (`onRequest`). A string keeps a value of its own; `undefined` the shared value; `null`
|
|
2662
|
-
* means the call has none, so reads give the global empty and changes are refused.
|
|
2663
|
-
*/
|
|
2664
|
-
readonly scope?: (args: {
|
|
2665
|
-
readonly context: RequestContext;
|
|
2666
|
-
readonly user: AuthUser | null;
|
|
2667
|
-
}) => string | null | undefined;
|
|
2668
|
-
/** Custom admin components. */
|
|
2669
|
-
readonly admin?: ContainerAdmin;
|
|
2670
|
-
}
|
|
2671
|
-
type AdminLocale = 'en' | 'th';
|
|
2672
|
-
/** Icons a collection or global can show in the admin menu ([Lucide](https://lucide.dev) names). */
|
|
2673
|
-
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", "chart-column", "chart-line"];
|
|
2674
|
-
type AdminIcon = (typeof ADMIN_ICONS)[number];
|
|
2675
|
-
/** Your or your client's brand in the admin UI. */
|
|
2676
|
-
interface AdminBrand {
|
|
2677
|
-
/** Shown in the menu, on the login page and in the browser tab. Default "Easy CMS". */
|
|
2678
|
-
readonly name?: string;
|
|
2679
|
-
/** Logo URL: a path on your site (`/logo.svg`) or an `https://` address. */
|
|
2680
|
-
readonly logo?: string;
|
|
2681
|
-
/** Main color as `#rrggbb`; lighter and darker shades are derived from it. */
|
|
2682
|
-
readonly color?: string;
|
|
2683
|
-
}
|
|
2684
|
-
/** A group of the admin menu (`admin.nav`). */
|
|
2685
|
-
interface NavGroup {
|
|
2686
|
-
/** Lowercase letters, digits and `-`; children are named `<parent>.<child>`, e.g. `shop.catalog`. */
|
|
2687
|
-
readonly id: string;
|
|
2688
|
-
readonly label: Label;
|
|
2689
|
-
readonly icon?: AdminIcon;
|
|
2690
|
-
/** Its place among the groups: lower first. Content is 0, the media library 10, Settings 1000. */
|
|
2691
|
-
readonly order?: number;
|
|
2692
|
-
/** Groups inside it, one level. */
|
|
2693
|
-
readonly children?: readonly Omit<NavGroup, 'children'>[];
|
|
2694
|
-
}
|
|
2695
|
-
/** An entry of the command palette (⌘K) that opens a page of the admin. */
|
|
2696
|
-
interface AdminCommand {
|
|
2697
|
-
readonly label: Label;
|
|
2698
|
-
/** A path in the admin, e.g. `/collections/orders?f_status=paid`. */
|
|
2699
|
-
readonly to: string;
|
|
2700
|
-
readonly icon?: AdminIcon;
|
|
2701
|
-
/** Words that find it besides its label. */
|
|
2702
|
-
readonly keywords?: readonly string[];
|
|
2703
|
-
}
|
|
2704
|
-
/** Who may see an admin page or dashboard widget. Default: every logged-in user. */
|
|
2705
|
-
type AdminViewAccess = (args: {
|
|
2706
|
-
readonly user: AuthUser;
|
|
2707
|
-
}) => boolean | Promise<boolean>;
|
|
2708
|
-
/** A page of its own in the admin, e.g. a plugin's report, at `<admin>/p/<path>`. */
|
|
2709
|
-
interface AdminPage {
|
|
2710
|
-
/** Its address under `<admin>/p/`: lowercase letters, digits and `-`, e.g. `forms-overview`. */
|
|
2711
|
-
readonly path: string;
|
|
2712
|
-
/** The page's body: a Web Component from an admin module. The admin draws the header. */
|
|
2713
|
-
readonly component: AdminComponent;
|
|
2714
|
-
/** Its title in the header and the menu. */
|
|
2715
|
-
readonly label: Label;
|
|
2716
|
-
/** Icon in the admin menu. Default `file-text`. */
|
|
2717
|
-
readonly icon?: AdminIcon;
|
|
2718
|
-
/**
|
|
2719
|
-
* Its menu group: `content` (default), `settings`, a group's id (`admin.nav`) or a label; `false`:
|
|
2720
|
-
* not listed, reached by links.
|
|
2721
|
-
*/
|
|
2722
|
-
readonly group?: 'content' | 'settings' | false | Label;
|
|
2723
|
-
/** Who may open it. Pages a user may not open are left out of their admin. */
|
|
2724
|
-
readonly access?: AdminViewAccess;
|
|
2725
|
-
}
|
|
2726
|
-
/** A panel on the admin's dashboard, after the built-in ones. */
|
|
2727
|
-
interface DashboardWidget {
|
|
2728
|
-
/** A Web Component from an admin module. */
|
|
2729
|
-
readonly component: AdminComponent;
|
|
2730
|
-
/** `half` (default): the side column, beside drafts and recent edits; `full`: below both. */
|
|
2731
|
-
readonly width?: 'half' | 'full';
|
|
2732
|
-
/** Its name in Settings → Roles (`auth.rbac`). Default: the component's tag. */
|
|
2733
|
-
readonly label?: Label;
|
|
2734
|
-
/** Who sees it. */
|
|
2735
|
-
readonly access?: AdminViewAccess;
|
|
2736
|
-
}
|
|
2737
|
-
interface AdminConfig {
|
|
2738
|
-
/** Where the admin UI is served. Default `/admin`. */
|
|
2739
|
-
readonly path?: string;
|
|
2740
|
-
/** Default admin UI language. Default `en`. */
|
|
2741
|
-
readonly locale?: AdminLocale;
|
|
2742
|
-
/** Name, logo and color of the admin UI. */
|
|
2743
|
-
readonly brand?: AdminBrand;
|
|
2744
|
-
/**
|
|
2745
|
-
* The public site, for the admin's "View site" link: a path (`/`) or an `https://` URL.
|
|
2746
|
-
* Default `/` with Nuxt and Next.js; none with the standalone server.
|
|
2747
|
-
*/
|
|
2748
|
-
readonly siteUrl?: string;
|
|
2749
|
-
/**
|
|
2750
|
-
* Order of collections in the admin menu, by slug, e.g. `['posts', 'categories', 'media']`.
|
|
2751
|
-
* Collections not listed follow in config order, with the media library last.
|
|
2752
|
-
*/
|
|
2753
|
-
readonly menu?: readonly string[];
|
|
2754
|
-
/**
|
|
2755
|
-
* The admin menu's groups, which collections, globals and pages name in `admin.group`, e.g.
|
|
2756
|
-
* `{ id: 'shop', label: 'Shop', icon: 'store', children: [{ id: 'catalog', label: 'Catalog' }] }`.
|
|
2757
|
-
* Built in: `content` and `settings` (with `site`, `people` and `system`); declaring one of
|
|
2758
|
-
* their ids changes its label, icon or order. Plugins declare their own.
|
|
2759
|
-
*/
|
|
2760
|
-
readonly nav?: readonly NavGroup[];
|
|
2761
|
-
/** More entries of the command palette (⌘K), e.g. a plugin's page with a filter. */
|
|
2762
|
-
readonly commands?: readonly AdminCommand[];
|
|
2763
|
-
/**
|
|
2764
|
-
* JavaScript modules the admin loads after login, which define Web Components used by
|
|
2765
|
-
* fields (`admin.component`, `admin.after`) and edit pages (`admin.sidebar`). Each entry is a
|
|
2766
|
-
* package export (`'@easy-cms/plugin-seo/admin'`) or a file path relative to the project root
|
|
2767
|
-
* (`'./admin/color-picker.js'`), resolved on the server. A module must be one self-contained ES
|
|
2768
|
-
* module file. Remote URLs are not allowed.
|
|
2769
|
-
*/
|
|
2770
|
-
readonly modules?: readonly string[];
|
|
2771
|
-
/** Pages of their own, e.g. from plugins, at `<admin>/p/<path>`. */
|
|
2772
|
-
readonly pages?: readonly AdminPage[];
|
|
2773
|
-
/** Panels on the dashboard, after the built-in ones. */
|
|
2774
|
-
readonly dashboard?: readonly DashboardWidget[];
|
|
2775
|
-
/**
|
|
2776
|
-
* A choice at the top of the menu that applies to everything in the admin, e.g. the tenant
|
|
2777
|
-
* (usually set by a plugin). The choice is kept in a cookie, which every request of the admin
|
|
2778
|
-
* sends; `onRequest` reads it.
|
|
2779
|
-
*/
|
|
2780
|
-
readonly switcher?: AdminSwitcher | null;
|
|
2781
|
-
}
|
|
2782
|
-
/** See `AdminConfig.switcher`. */
|
|
2783
|
-
interface AdminSwitcher {
|
|
2784
|
-
/** Cookie that keeps the choice, e.g. `ecms-tenant`. */
|
|
2785
|
-
readonly cookie: string;
|
|
2786
|
-
readonly label: Label;
|
|
2787
|
-
/**
|
|
2788
|
-
* Path under the API that lists the choices for the logged-in user:
|
|
2789
|
-
* `{ options: [{ value, label }], all?: Label }`, with `all` to offer "all of them" (value `*`).
|
|
2790
|
-
*/
|
|
2791
|
-
readonly options: string;
|
|
2792
|
-
}
|
|
2793
|
-
interface ImageSize {
|
|
2794
|
-
/** Key in `media.sizes`, e.g. `thumbnail`. */
|
|
2795
|
-
readonly name: string;
|
|
2796
|
-
readonly width: number;
|
|
2797
|
-
readonly height?: number;
|
|
2798
|
-
/** How to fit when both width and height are set. Default `cover`. */
|
|
2799
|
-
readonly fit?: 'cover' | 'contain' | 'inside';
|
|
2800
|
-
}
|
|
2801
|
-
/**
|
|
2802
|
-
* Uploads from a link: the server downloads the file (`cms.uploadFromURL`, `POST <api>/media`
|
|
2803
|
-
* with `{ url }`, "From a link" in the admin).
|
|
2804
|
-
*/
|
|
2805
|
-
interface UploadFromURLConfig {
|
|
2806
|
-
/**
|
|
2807
|
-
* Hosts files may come from: `images.example.com`, `*.example.com` (its subdomains, not
|
|
2808
|
-
* `example.com` itself) or `*` for any public host. Checked at every redirect too.
|
|
2809
|
-
*/
|
|
2810
|
-
readonly allowedHosts: readonly string[];
|
|
2811
|
-
/**
|
|
2812
|
-
* Also download from private network addresses (`10.x`, `192.168.x`, `localhost`…), e.g. an
|
|
2813
|
-
* intranet. Off by default: links could then reach services that are not meant to be public.
|
|
2814
|
-
*/
|
|
2815
|
-
readonly allowPrivate?: boolean;
|
|
2816
|
-
}
|
|
2817
|
-
interface UploadConfig {
|
|
2818
|
-
/** Directory for uploaded files with the default local storage, relative to the project root. Default `uploads`. */
|
|
2819
|
-
readonly dir?: string;
|
|
2820
|
-
/** Maximum file size in bytes. Default 10 MB. */
|
|
2821
|
-
readonly maxFileSize?: number;
|
|
2822
|
-
/** Allowed MIME types, detected from file contents. `image/*` style wildcards are allowed. */
|
|
2823
|
-
readonly mimeTypes?: readonly string[];
|
|
2824
|
-
/** Where files are stored. Default: local disk in `dir`. */
|
|
2825
|
-
readonly storage?: StorageAdapter;
|
|
2826
|
-
/** Resized copies generated for images when `sharp` is installed. */
|
|
2827
|
-
readonly imageSizes?: readonly ImageSize[];
|
|
2828
|
-
/** Lets users upload from a link. Off by default. */
|
|
2829
|
-
readonly fromURL?: UploadFromURLConfig;
|
|
2830
|
-
/**
|
|
2831
|
-
* Folders in the media library (`media-folders`; a file is in one folder at most). With
|
|
2832
|
-
* `auth.rbac`, admins can also choose which roles see and change each folder. Off by default.
|
|
2833
|
-
*/
|
|
2834
|
-
readonly folders?: boolean;
|
|
2835
|
-
/**
|
|
2836
|
-
* Where files in private folders are kept: never served publicly, only through the API to
|
|
2837
|
-
* users who may see them or with `cms.signedMediaURL()`. Default: `storage`, when it has no
|
|
2838
|
-
* public URL (the local disk, Netlify Blobs); a storage with public URLs (S3 with `publicUrl`,
|
|
2839
|
-
* a public Vercel Blob store) needs one, e.g. `vercelBlobStorage({ access: 'private' })`.
|
|
2840
|
-
*/
|
|
2841
|
-
readonly privateStorage?: StorageAdapter;
|
|
2842
|
-
}
|
|
2843
|
-
interface RoutesConfig {
|
|
2844
|
-
/** Where the REST API is served. Default `/api/cms`. */
|
|
2845
|
-
readonly api?: string;
|
|
2846
|
-
}
|
|
2847
|
-
/**
|
|
2848
|
-
* People who sign in on the site rather than to the admin, e.g. customers of a shop.
|
|
2849
|
-
*/
|
|
2850
|
-
interface MembersConfig {
|
|
2851
|
-
/**
|
|
2852
|
-
* Their roles, also listed in `roles`. Members never get into the admin, and access that
|
|
2853
|
-
* defaults to logged-in users (`isLoggedIn`) doesn't count them: give them access in the
|
|
2854
|
-
* collections they use, e.g. with `isSignedIn`.
|
|
2855
|
-
*/
|
|
2856
|
-
readonly roles: readonly string[];
|
|
2857
|
-
/** Visitors create their own account: `POST <api>/users/signup`. */
|
|
2858
|
-
readonly signup?: MembersSignup;
|
|
2859
|
-
/**
|
|
2860
|
-
* The site's pages that open the links of members' emails, given `?token=`: paths on
|
|
2861
|
-
* `admin.siteUrl` (else `serverURL`), or full URLs. Without them, links go to the admin's pages.
|
|
2862
|
-
*/
|
|
2863
|
-
readonly pages?: {
|
|
2864
|
-
/** Confirms the email of a new account: `POST <api>/users/verify-email` with the token. */
|
|
2865
|
-
readonly verifyEmail?: string;
|
|
2866
|
-
/** Sets a forgotten password: `POST <api>/users/reset-password` with the token. */
|
|
2867
|
-
readonly resetPassword?: string;
|
|
2868
|
-
};
|
|
2869
|
-
/** Your own text for the email that confirms an address. Default: English or Thai. */
|
|
2870
|
-
readonly emails?: {
|
|
2871
|
-
readonly verifyEmail?: PasswordEmailFn;
|
|
2872
|
-
};
|
|
2873
|
-
}
|
|
2874
|
-
interface MembersSignup {
|
|
2875
|
-
/** The role new accounts get: one of `members.roles`. */
|
|
2876
|
-
readonly role: string;
|
|
2877
|
-
/** New accounts confirm their email with a link before signing in. Default `true`. */
|
|
2878
|
-
readonly verifyEmail?: boolean;
|
|
2879
|
-
/** Cloudflare Turnstile, checked on every sign-up when set. */
|
|
2880
|
-
readonly turnstile?: {
|
|
2881
|
-
readonly siteKey: string;
|
|
2882
|
-
readonly secretKey: string;
|
|
2883
|
-
};
|
|
2884
|
-
}
|
|
2885
|
-
interface AuthConfig {
|
|
2886
|
-
/**
|
|
2887
|
-
* Roles a user can have. Must include `admin`. Default `['admin', 'editor']`. With `rbac`, these
|
|
2888
|
-
* are the roles that always exist; admins can add more in Settings → Roles.
|
|
2889
|
-
*/
|
|
2890
|
-
readonly roles?: readonly string[];
|
|
2891
|
-
/**
|
|
2892
|
-
* Roles and their permissions from the admin (Settings → Roles): admins tick what each role
|
|
2893
|
-
* may do with each collection, global and admin page, on top of access rules. Default `false`.
|
|
2894
|
-
*/
|
|
2895
|
-
readonly rbac?: boolean;
|
|
2896
|
-
/** Session lifetime in seconds. Default 7 days. */
|
|
2897
|
-
readonly tokenExpiration?: number;
|
|
2898
|
-
/** Failed logins allowed per email (and IP) within `lockWindow`. Default 5. */
|
|
2899
|
-
readonly maxLoginAttempts?: number;
|
|
2900
|
-
/** Window for `maxLoginAttempts`, in seconds. Default 15 minutes. */
|
|
2901
|
-
readonly lockWindow?: number;
|
|
2902
|
-
/**
|
|
2903
|
-
* Origins allowed to send cookie-authenticated requests, besides the API's own origin.
|
|
2904
|
-
* Needed when the admin or frontend is served from another origin, or behind a proxy that rewrites the host.
|
|
2905
|
-
*/
|
|
2906
|
-
readonly trustedOrigins?: readonly string[];
|
|
2907
|
-
/**
|
|
2908
|
-
* How long a "forgot password" link works, in seconds. Default 1 hour. Links work once: setting
|
|
2909
|
-
* the password ends them. Needs `email` and, in production, `serverURL` for the link.
|
|
2910
|
-
*/
|
|
2911
|
-
readonly resetPasswordExpiration?: number;
|
|
2912
|
-
/** How long an invitation link works, in seconds. Default 7 days. */
|
|
2913
|
-
readonly inviteExpiration?: number;
|
|
2914
|
-
/**
|
|
2915
|
-
* Signing in to the admin with outside accounts, e.g. `[google({ clientId, clientSecret })]`
|
|
2916
|
-
* from `@easy-cms/auth-oauth`. Users are matched by their verified email.
|
|
2917
|
-
*/
|
|
2918
|
-
readonly providers?: readonly AuthProvider[];
|
|
2919
|
-
/**
|
|
2920
|
-
* With `providers`: people from these email domains who sign in for the first time get an
|
|
2921
|
-
* account with `role` (default: as new users). Without it, only existing users can sign in.
|
|
2922
|
-
*/
|
|
2923
|
-
readonly allowSignUp?: {
|
|
2924
|
-
readonly domains: readonly string[];
|
|
2925
|
-
readonly role?: string;
|
|
2926
|
-
};
|
|
2927
|
-
/**
|
|
2928
|
-
* `false`: only admins may sign in with a password (a way in when the provider is down);
|
|
2929
|
-
* everyone else signs in with `providers`. Default `true`.
|
|
2930
|
-
*/
|
|
2931
|
-
readonly password?: boolean;
|
|
2932
|
-
/**
|
|
2933
|
-
* A code the first admin must enter to create their account, so nobody else can claim a
|
|
2934
|
-
* freshly deployed site. Default: the `EASY_CMS_SETUP_CODE` environment variable; without it,
|
|
2935
|
-
* no code is asked.
|
|
2936
|
-
*/
|
|
2937
|
-
readonly setupCode?: string;
|
|
2938
|
-
/**
|
|
2939
|
-
* People who sign in on the site, not to the admin, e.g. customers: their roles, signing up,
|
|
2940
|
-
* and the pages their email links open.
|
|
2941
|
-
*/
|
|
2942
|
-
readonly members?: MembersConfig;
|
|
2943
|
-
/** Your own text for the password emails, e.g. in your brand's voice. Default: English or Thai. */
|
|
2944
|
-
readonly emails?: {
|
|
2945
|
-
readonly resetPassword?: PasswordEmailFn;
|
|
2946
|
-
readonly invite?: PasswordEmailFn;
|
|
2947
|
-
readonly passwordChanged?: PasswordEmailFn;
|
|
2948
|
-
};
|
|
2949
|
-
}
|
|
2950
|
-
/** What `onRequest` receives. */
|
|
2951
|
-
interface OnRequestArgs {
|
|
2952
|
-
readonly headers: Headers;
|
|
2953
|
-
/** The request's URL; `undefined` when only headers are known (`cms.forRequest(headers)`). */
|
|
2954
|
-
readonly url: URL | undefined;
|
|
2955
|
-
/** The user of the session or API key, before `onRequest` changes it; `null` when anonymous. */
|
|
2956
|
-
readonly user: AuthUser | null;
|
|
2957
|
-
readonly cms: EasyCMS;
|
|
2958
|
-
}
|
|
2959
|
-
/**
|
|
2960
|
-
* Runs on each API request (REST, plugin endpoints, `cms.forRequest`) after the user is known:
|
|
2961
|
-
* returns the request's `context` (e.g. the tenant it works in, from a header) and may change the
|
|
2962
|
-
* user (e.g. their role in that tenant, `scoped`). Plugins that set it call the one before.
|
|
2963
|
-
*/
|
|
2964
|
-
type OnRequest = (args: OnRequestArgs) => MaybePromise<{
|
|
2965
|
-
readonly user?: AuthUser | null;
|
|
2966
|
-
readonly context?: RequestContext;
|
|
2967
|
-
} | undefined>;
|
|
2968
|
-
/** What an endpoint's handler receives. */
|
|
2969
|
-
interface EndpointRequest {
|
|
2970
|
-
readonly request: Request;
|
|
2971
|
-
readonly url: URL;
|
|
2972
|
-
/** Values of `:name` segments in the endpoint's path. */
|
|
2973
|
-
readonly params: Readonly<Record<string, string>>;
|
|
2974
|
-
/** The logged-in user (session cookie or Bearer token), or `null`. */
|
|
2975
|
-
readonly user: AuthUser | null;
|
|
2976
|
-
/** The request's context (`onRequest`): pass it to the Local API with `user`. */
|
|
2977
|
-
readonly context: RequestContext;
|
|
2978
|
-
/** The client's IP address, when the adapter knows it (e.g. to rate-limit a public form). */
|
|
2979
|
-
readonly ip: string | undefined;
|
|
2980
|
-
/** The Local API. Pass `{ user, overrideAccess: false }` to apply the user's access rules. */
|
|
2981
|
-
readonly cms: EasyCMS;
|
|
2982
|
-
/** The request's JSON body, which must be an object (at most 1 MB). */
|
|
2983
|
-
json(): Promise<Record<string, unknown>>;
|
|
2984
|
-
/** The request's body as text, as sent (at most 1 MB), e.g. to check a webhook's signature. */
|
|
2985
|
-
text(): Promise<string>;
|
|
2986
|
-
}
|
|
2987
|
-
/**
|
|
2988
|
-
* Work that runs with the scheduled jobs: every minute in a server, or each time a cron calls
|
|
2989
|
-
* `GET <api>/jobs/run`. E.g. a plugin's clean-up.
|
|
2990
|
-
*/
|
|
2991
|
-
interface JobConfig {
|
|
2992
|
-
/** Unique, e.g. `ecommerce:carts`. */
|
|
2993
|
-
readonly name: string;
|
|
2994
|
-
/**
|
|
2995
|
-
* At most this often, in seconds. Default: every run. The last run is kept in the database, so
|
|
2996
|
-
* it holds across processes and serverless invocations.
|
|
2997
|
-
*/
|
|
2998
|
-
readonly every?: number;
|
|
2999
|
-
readonly run: (args: {
|
|
3000
|
-
readonly cms: EasyCMS;
|
|
3001
|
-
readonly now: Date;
|
|
3002
|
-
}) => MaybePromise<unknown>;
|
|
3003
|
-
}
|
|
3004
|
-
/** An `easy-cms <name> [args…]` command. Built-in commands keep their names. */
|
|
3005
|
-
interface CliCommand {
|
|
3006
|
-
/** Lowercase, e.g. `nested:rebuild`. */
|
|
3007
|
-
readonly name: string;
|
|
3008
|
-
/** One line for `easy-cms --help`. */
|
|
3009
|
-
readonly description: string;
|
|
3010
|
-
/** Shown by `easy-cms <name> --help`. */
|
|
3011
|
-
readonly help?: string;
|
|
3012
|
-
/** Runs with the CMS open (no schema push); return a non-zero exit code on failure. */
|
|
3013
|
-
readonly run: (args: {
|
|
3014
|
-
readonly cms: EasyCMS;
|
|
3015
|
-
/** Positional arguments after the command name. */
|
|
3016
|
-
readonly args: readonly string[];
|
|
3017
|
-
readonly log: (line: string) => void;
|
|
3018
|
-
}) => MaybePromise<number | undefined>;
|
|
3019
|
-
}
|
|
3020
|
-
/**
|
|
3021
|
-
* A custom REST endpoint, served under `routes.api`. Writes from the browser pass the same
|
|
3022
|
-
* CSRF check as the built-in endpoints.
|
|
3023
|
-
*/
|
|
3024
|
-
interface Endpoint {
|
|
3025
|
-
/**
|
|
3026
|
-
* Path under the API, e.g. `/seo/generate` or `/stats/:collection`. The first segment must
|
|
3027
|
-
* not be a collection slug or one of `users`, `globals`, `admin`, `jobs`. With `root`, the
|
|
3028
|
-
* path from the site's root, outside the API and the admin.
|
|
3029
|
-
*/
|
|
3030
|
-
readonly path: string;
|
|
3031
|
-
readonly method: 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
3032
|
-
/**
|
|
3033
|
-
* Serve the path from the site's root instead of under `routes.api`, e.g. `/robots.txt`.
|
|
3034
|
-
* Only the standalone server (`easy-cms serve`) serves these; a Nuxt or Next.js app owns its
|
|
3035
|
-
* root, so plugins also offer a helper to use in the app's own route. Default false.
|
|
3036
|
-
*/
|
|
3037
|
-
readonly root?: boolean;
|
|
3038
|
-
/**
|
|
3039
|
-
* Returns a `Response`, or a value that is sent as JSON. Throw an Easy CMS error
|
|
3040
|
-
* (`UnauthorizedError`, `ForbiddenError`, `ValidationError`, `NotFoundError`) for error responses.
|
|
3041
|
-
*/
|
|
3042
|
-
readonly handler: (request: EndpointRequest) => MaybePromise<unknown>;
|
|
3043
|
-
}
|
|
3044
|
-
/** A plugin's package and version, listed for admins on the dashboard (System). */
|
|
3045
|
-
interface PluginInfo {
|
|
3046
|
-
/** Usually the package name, e.g. `@acme/easy-cms-plugin-stats`. */
|
|
3047
|
-
readonly name: string;
|
|
3048
|
-
readonly version?: string;
|
|
3049
|
-
}
|
|
3050
|
-
/** Receives the config and returns a modified copy. Runs before validation. */
|
|
3051
|
-
type Plugin = ((config: Config) => MaybePromise<Config>) & {
|
|
3052
|
-
/** Set by `definePlugin(plugin, info)`. */
|
|
3053
|
-
readonly info?: PluginInfo;
|
|
3054
|
-
};
|
|
3055
|
-
/** Holds a plugin's `PluginTypes` at the type level only: nothing is stored at runtime. */
|
|
3056
|
-
declare const PLUGIN_TYPES: unique symbol;
|
|
3057
|
-
/**
|
|
3058
|
-
* What a plugin adds, for the document types inferred from the config: fields on collections
|
|
3059
|
-
* and globals (by slug), and whole collections. Field types are written like config fields,
|
|
3060
|
-
* e.g. `{ fields: { posts: readonly [{ readonly name: 'meta'; readonly type: 'group'; … }] } }`.
|
|
3061
|
-
*/
|
|
3062
|
-
interface PluginTypes {
|
|
3063
|
-
readonly fields?: {
|
|
3064
|
-
readonly [collection: string]: readonly Field[];
|
|
3065
|
-
};
|
|
3066
|
-
readonly globalFields?: {
|
|
3067
|
-
readonly [global: string]: readonly Field[];
|
|
3068
|
-
};
|
|
3069
|
-
readonly collections?: readonly CollectionConfig[];
|
|
3070
|
-
}
|
|
3071
|
-
/** A plugin that tells the type system what it adds (see `definePlugin`). */
|
|
3072
|
-
type TypedPlugin<T extends PluginTypes> = Plugin & {
|
|
3073
|
-
readonly [PLUGIN_TYPES]?: T;
|
|
3074
|
-
};
|
|
3075
|
-
/**
|
|
3076
|
-
* Returns the plugin with what it adds as types, so `cms.find()` and `CollectionDocument` know
|
|
3077
|
-
* the plugin's fields; `info` (its name and version) is listed for admins on the dashboard:
|
|
3078
|
-
*
|
|
3079
|
-
* ```ts
|
|
3080
|
-
* export const colorPlugin = (options: { collections: readonly string[] }) =>
|
|
3081
|
-
* definePlugin<{ fields: { posts: readonly [{ readonly name: 'color'; readonly type: 'text' }] } }>(
|
|
3082
|
-
* (config) => ({ ...config, collections: … }),
|
|
3083
|
-
* { name: '@acme/easy-cms-plugin-color', version: '1.2.0' },
|
|
3084
|
-
* )
|
|
3085
|
-
* ```
|
|
3086
|
-
*/
|
|
3087
|
-
declare function definePlugin<const T extends PluginTypes = Record<never, never>>(plugin: Plugin, info?: PluginInfo): TypedPlugin<T>;
|
|
3088
|
-
/** Database backups (Settings → Backups): one compressed SQLite file each. */
|
|
3089
|
-
interface BackupsConfig {
|
|
3090
|
-
/** Back up automatically every day or week. Default: only by hand. */
|
|
3091
|
-
readonly every?: 'day' | 'week';
|
|
3092
|
-
/** When, as `HH:MM` in the server's time zone. Default `03:00`. */
|
|
3093
|
-
readonly at?: string;
|
|
3094
|
-
/** How many finished backups to keep; older ones are deleted. Default 7. */
|
|
3095
|
-
readonly keep?: number;
|
|
3096
|
-
/** Folder for the default local storage, from the project root. Default `backups`. Never served publicly. */
|
|
3097
|
-
readonly dir?: string;
|
|
3098
|
-
/** Where backups are stored instead, e.g. `s3Storage()` with a private bucket. */
|
|
3099
|
-
readonly storage?: StorageAdapter;
|
|
3100
|
-
/**
|
|
3101
|
-
* For Postgres: the SQLite adapter that writes the backup file, `sqlite` from
|
|
3102
|
-
* `@easy-cms/db-sqlite`. SQLite databases back themselves up and don't need it.
|
|
3103
|
-
*/
|
|
3104
|
-
readonly sqlite?: (options: {
|
|
3105
|
-
url: string;
|
|
3106
|
-
tablePrefix?: string;
|
|
3107
|
-
}) => DatabaseAdapter;
|
|
3108
|
-
}
|
|
3109
|
-
interface Config {
|
|
3110
|
-
/** Signs sessions. At least 32 characters; read it from `process.env.EASY_CMS_SECRET`. */
|
|
3111
|
-
readonly secret: string;
|
|
3112
|
-
readonly db: DatabaseAdapter;
|
|
3113
|
-
/**
|
|
3114
|
-
* Public origin of the app, e.g. `https://example.com`. When set, media URLs are absolute
|
|
3115
|
-
* so frontends on other origins can use them. Default: relative URLs.
|
|
3116
|
-
*/
|
|
3117
|
-
readonly serverURL?: string;
|
|
3118
|
-
/**
|
|
3119
|
-
* Origins whose browser code may call the REST API (CORS), e.g. a Vite or React app on
|
|
3120
|
-
* another origin. `'*'` allows any origin for anonymous requests. Origins in
|
|
3121
|
-
* `auth.trustedOrigins` are always allowed, with cookies. Default: none.
|
|
3122
|
-
*/
|
|
3123
|
-
readonly cors?: readonly string[] | '*';
|
|
3124
|
-
/**
|
|
3125
|
-
* Lets a cron service run due scheduled jobs: `GET <api>/jobs/run` with
|
|
3126
|
-
* `Authorization: Bearer <cronSecret>`. Default: the `CRON_SECRET` environment variable (the
|
|
3127
|
-
* convention of Vercel Cron). Logged-in admins can always run jobs.
|
|
3128
|
-
*/
|
|
3129
|
-
readonly cronSecret?: string;
|
|
3130
|
-
/** Endpoints notified when content changes, e.g. to rebuild a static site. */
|
|
3131
|
-
readonly webhooks?: readonly WebhookConfig[];
|
|
3132
|
-
/**
|
|
3133
|
-
* Events of the app or its plugins besides content changes, e.g. `order.paid`: sent with
|
|
3134
|
-
* `cms.emit()` to the webhooks that list them in `events`. Named `<area>.<what>`.
|
|
3135
|
-
*/
|
|
3136
|
-
readonly events?: readonly string[];
|
|
3137
|
-
/** Work run with the scheduled jobs, e.g. a plugin's clean-up. */
|
|
3138
|
-
readonly jobs?: readonly JobConfig[];
|
|
3139
|
-
/** Content in several languages: fields with `localized: true` hold one value per locale. */
|
|
3140
|
-
readonly localization?: LocalizationConfig | null;
|
|
3141
|
-
readonly routes?: RoutesConfig;
|
|
3142
|
-
readonly admin?: AdminConfig;
|
|
3143
|
-
readonly upload?: UploadConfig;
|
|
3144
|
-
readonly auth?: AuthConfig;
|
|
3145
|
-
/**
|
|
3146
|
-
* Collections. A collection with slug `users` adds fields, access or hooks
|
|
3147
|
-
* to the built-in users collection.
|
|
3148
|
-
*/
|
|
3149
|
-
readonly collections?: readonly CollectionConfig[];
|
|
3150
|
-
readonly globals?: readonly GlobalConfig[];
|
|
3151
|
-
/** Custom REST endpoints, e.g. from plugins. */
|
|
3152
|
-
readonly endpoints?: readonly Endpoint[];
|
|
3153
|
-
/** Extra `easy-cms <name>` CLI commands, e.g. from plugins. */
|
|
3154
|
-
readonly commands?: readonly CliCommand[];
|
|
3155
|
-
/** The request's context and user, worked out on each API request (usually by a plugin). */
|
|
3156
|
-
readonly onRequest?: OnRequest;
|
|
3157
|
-
/**
|
|
3158
|
-
* Field types from packages, e.g. `color` from `@easy-cms/fields`: fields then use
|
|
3159
|
-
* `type: 'color'`. See `defineFieldType`.
|
|
3160
|
-
*/
|
|
3161
|
-
readonly fieldTypes?: readonly FieldTypeDefinition[];
|
|
3162
|
-
/**
|
|
3163
|
-
* API keys for scripts and other apps (`Authorization: Bearer ecms_…`), managed in the admin
|
|
3164
|
-
* under Settings. A key acts as its owner, limited to the collections and operations it lists.
|
|
3165
|
-
*/
|
|
3166
|
-
readonly apiKeys?: boolean;
|
|
3167
|
-
/**
|
|
3168
|
-
* Sends email for plugins such as the form builder, e.g. `smtp()` from
|
|
3169
|
-
* `@easy-cms/email-smtp`, or `consoleEmail()` in development. Emails are queued in the
|
|
3170
|
-
* database and retried until sent.
|
|
3171
|
-
*/
|
|
3172
|
-
readonly email?: EmailAdapter;
|
|
3173
|
-
/**
|
|
3174
|
-
* Database backups, made on a schedule and from the admin (Settings → Backups). Without
|
|
3175
|
-
* `every`, admins can still back up by hand.
|
|
3176
|
-
*/
|
|
3177
|
-
readonly backups?: BackupsConfig;
|
|
3178
|
-
/**
|
|
3179
|
-
* The audit log (Settings → Audit log): who changed what and when, sign-ins, and admin
|
|
3180
|
-
* actions, kept for `keep` days. `true` for the defaults.
|
|
3181
|
-
*/
|
|
3182
|
-
readonly audit?: boolean | AuditConfig;
|
|
3183
|
-
readonly plugins?: readonly Plugin[];
|
|
3184
|
-
}
|
|
3185
|
-
/** The config after plugins ran, validation passed and defaults were applied. */
|
|
3186
|
-
interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes' | 'admin' | 'upload' | 'auth' | 'collections' | 'globals' | 'endpoints' | 'plugins' | 'audit'> {
|
|
3187
|
-
readonly cors: readonly string[] | '*';
|
|
3188
|
-
/** `null` without localization. */
|
|
3189
|
-
readonly localization: Required<LocalizationConfig> | null;
|
|
3190
|
-
readonly routes: Required<RoutesConfig>;
|
|
3191
|
-
readonly admin: Required<AdminConfig>;
|
|
3192
|
-
readonly upload: Required<Omit<UploadConfig, 'storage' | 'fromURL' | 'privateStorage'>> & Pick<UploadConfig, 'storage' | 'fromURL' | 'privateStorage'>;
|
|
3193
|
-
readonly auth: Required<AuthConfig>;
|
|
3194
|
-
/** `false` without an audit log. */
|
|
3195
|
-
readonly audit: Required<AuditConfig> | false;
|
|
3196
|
-
readonly collections: readonly CollectionConfig[];
|
|
3197
|
-
readonly globals: readonly GlobalConfig[];
|
|
3198
|
-
readonly endpoints: readonly Endpoint[];
|
|
3199
|
-
readonly commands: readonly CliCommand[];
|
|
3200
|
-
readonly events: readonly string[];
|
|
3201
|
-
readonly jobs: readonly JobConfig[];
|
|
3202
|
-
readonly fieldTypes: readonly FieldTypeDefinition[];
|
|
3203
|
-
/** The config's plugins in order, with their `info` where they give one. */
|
|
3204
|
-
readonly installedPlugins: readonly Partial<PluginInfo>[];
|
|
3205
|
-
}
|
|
3206
|
-
/**
|
|
3207
|
-
* Declares the Easy CMS config. Returns it unchanged, keeping literal types
|
|
3208
|
-
* so document types can be inferred from it.
|
|
3209
|
-
*/
|
|
3210
|
-
declare function defineConfig<const TConfig extends Config>(config: TConfig): TConfig;
|
|
3211
|
-
|
|
3212
|
-
declare const API_KEYS = "api-keys";
|
|
3213
|
-
/** What a key may do with a collection. `create` on `media` means uploading. */
|
|
3214
|
-
declare const API_KEY_OPERATIONS: readonly ["read", "create", "update", "delete", "publish"];
|
|
3215
|
-
type ApiKeyOperation = (typeof API_KEY_OPERATIONS)[number];
|
|
3216
|
-
/** What a key may do with a global. */
|
|
3217
|
-
declare const API_KEY_GLOBAL_OPERATIONS: readonly ["read", "update", "publish"];
|
|
3218
|
-
type ApiKeyGlobalOperation = (typeof API_KEY_GLOBAL_OPERATIONS)[number];
|
|
3219
|
-
/** Stored in the key's `permissions` field. Anything not listed is not allowed. */
|
|
3220
|
-
interface ApiKeyPermissions {
|
|
3221
|
-
readonly collections?: Readonly<Record<string, readonly ApiKeyOperation[]>>;
|
|
3222
|
-
readonly globals?: Readonly<Record<string, readonly ApiKeyGlobalOperation[]>>;
|
|
3223
|
-
/**
|
|
3224
|
-
* With `upload.folders`: the media folders (ids) the key may use, with their subfolders. Empty
|
|
3225
|
-
* or missing: every folder.
|
|
3226
|
-
*/
|
|
3227
|
-
readonly folders?: readonly (string | number)[];
|
|
3228
|
-
/**
|
|
3229
|
-
* The context the key was created in (`onRequest`, e.g. its tenant), kept for good: requests
|
|
3230
|
-
* with the key get it as `user.apiKey.permissions.context`. Set by Easy CMS, not by input.
|
|
3231
|
-
*/
|
|
3232
|
-
readonly context?: RequestContext;
|
|
3233
|
-
}
|
|
3234
|
-
/** Set on `AuthUser.apiKey` when a request authenticated with an API key. */
|
|
3235
|
-
interface ApiKeyContext {
|
|
3236
|
-
readonly id: number | string;
|
|
3237
|
-
readonly name: string;
|
|
3238
|
-
readonly permissions: ApiKeyPermissions;
|
|
3239
|
-
}
|
|
3240
|
-
/**
|
|
3241
|
-
* Whether a request's API key allows an operation. Always true without a key (sessions are
|
|
3242
|
-
* governed by access rules alone); a key only narrows what its user may do.
|
|
3243
|
-
*/
|
|
3244
|
-
declare function keyAllows(user: AuthUser | null, target: {
|
|
3245
|
-
collection: string;
|
|
3246
|
-
} | {
|
|
3247
|
-
global: string;
|
|
3248
|
-
}, operation: ApiKeyOperation): boolean;
|
|
3249
|
-
|
|
3250
|
-
declare const MIN_PASSWORD_LENGTH = 8;
|
|
3251
|
-
/** Hashes a password as `scrypt$N$r$p$salt$hash` (base64url). */
|
|
3252
|
-
declare function hashPassword(password: string): Promise<string>;
|
|
3253
|
-
/** Checks a password against a hash in constant time. Malformed hashes never match. */
|
|
3254
|
-
declare function verifyPassword(password: string, stored: string): Promise<boolean>;
|
|
3255
|
-
|
|
3256
|
-
type BackupState = 'pending' | 'running' | 'done' | 'failed';
|
|
3257
|
-
/** One backup as the admin shows it (`GET <api>/admin/backups`). */
|
|
3258
|
-
interface AdminBackup {
|
|
3259
|
-
id: ID;
|
|
3260
|
-
state: BackupState;
|
|
3261
|
-
trigger: 'manual' | 'scheduled';
|
|
3262
|
-
/** The file's name, e.g. `my-site-2026-10-04-0300.db.gz`. */
|
|
3263
|
-
filename: string | null;
|
|
3264
|
-
/** Bytes, compressed. */
|
|
3265
|
-
size: number | null;
|
|
3266
|
-
startedAt: string | null;
|
|
3267
|
-
finishedAt: string | null;
|
|
3268
|
-
error: string | null;
|
|
3269
|
-
/** Who started it (manual backups). */
|
|
3270
|
-
author: string | null;
|
|
3271
|
-
downloadedBy: string | null;
|
|
3272
|
-
downloadedAt: string | null;
|
|
3273
|
-
}
|
|
3274
|
-
/** Settings → Backups (`GET <api>/admin/backups`). */
|
|
3275
|
-
interface AdminBackups {
|
|
3276
|
-
settings: {
|
|
3277
|
-
every: 'day' | 'week' | null;
|
|
3278
|
-
at: string;
|
|
3279
|
-
keep: number;
|
|
3280
|
-
/** The storage adapter's name, e.g. `local` or `s3`. */
|
|
3281
|
-
storage: string;
|
|
3282
|
-
/** The folder, for local storage. */
|
|
3283
|
-
dir: string | null;
|
|
3284
|
-
database: string;
|
|
3285
|
-
/** Why backups can't be made with this setup, if they can't. */
|
|
3286
|
-
unavailable: string | null;
|
|
3287
|
-
};
|
|
3288
|
-
backups: AdminBackup[];
|
|
3289
|
-
}
|
|
3290
|
-
/**
|
|
3291
|
-
* Writes a consistent copy of the database to `file` as SQLite, while the CMS keeps running:
|
|
3292
|
-
* SQLite copies itself; other databases are copied into a new SQLite file (`backups.sqlite`).
|
|
3293
|
-
*/
|
|
3294
|
-
declare function writeBackupFile<C extends Config>(cms: EasyCMS<C>, file: string): Promise<void>;
|
|
3295
|
-
|
|
3296
|
-
declare const MEDIA = "media";
|
|
3297
|
-
declare const MEDIA_FOLDERS = "media-folders";
|
|
3298
|
-
/** Collections Easy CMS uses internally. Not exposed over REST or in the admin UI. */
|
|
3299
|
-
declare const INTERNAL_COLLECTIONS: ReadonlySet<string>;
|
|
3300
|
-
|
|
3301
|
-
/**
|
|
3302
|
-
* A string that is the same for configs with the same structure: collections, fields, options
|
|
3303
|
-
* and every other plain value. Functions count only by where they are, not by their code, since
|
|
3304
|
-
* bundlers rename variables differently each time they compile the same source.
|
|
3305
|
-
*
|
|
3306
|
-
* Frameworks may load the config module more than once (Next.js bundles it into each server
|
|
3307
|
-
* layer), which gives different objects for the same config; this tells those apart from edits.
|
|
3308
|
-
*/
|
|
3309
|
-
declare function configSignature(config: Config): string;
|
|
3310
|
-
|
|
3311
|
-
interface CopyProgress {
|
|
3312
|
-
readonly table: string;
|
|
3313
|
-
readonly rows: number;
|
|
3314
|
-
}
|
|
3315
|
-
interface CopyResult {
|
|
3316
|
-
readonly tables: number;
|
|
3317
|
-
readonly rows: number;
|
|
3318
|
-
}
|
|
3319
|
-
/**
|
|
3320
|
-
* Copies every stored row from `source` into `target` (another kind of database is fine, e.g.
|
|
3321
|
-
* SQLite to Postgres), keeping ids, so relationships, versions and users stay as they are.
|
|
3322
|
-
* Both must come from the same collections and fields, and the target must be empty.
|
|
3323
|
-
* Uploaded files are not copied: they stay in the storage they are in.
|
|
3324
|
-
*/
|
|
3325
|
-
declare function copyDatabase(source: Database, target: Database, options?: {
|
|
3326
|
-
onTable?: (progress: CopyProgress) => void;
|
|
3327
|
-
}): Promise<CopyResult>;
|
|
3328
|
-
|
|
3329
|
-
type Localization = Required<LocalizationConfig>;
|
|
3330
|
-
|
|
3331
|
-
type Data = Record<string, unknown>;
|
|
3332
|
-
/**
|
|
3333
|
-
* Turns text into a URL slug. Keeps letters and digits of any script,
|
|
3334
|
-
* so Thai titles stay readable: "สวัสดี World!" → "สวัสดี-world".
|
|
3335
|
-
*/
|
|
3336
|
-
declare function slugify(text: string): string;
|
|
3337
|
-
/** Fills in `defaultValue` for fields that are missing, including inside groups and array rows. */
|
|
3338
|
-
declare function applyDefaults(fields: readonly Field[], data: Data, localization?: Localization | null): Data;
|
|
3339
|
-
/** Gives every field a value (`null`, `[]` or a nested object) so documents always have the same shape. */
|
|
3340
|
-
declare function fillMissing(fields: readonly Field[], data: Data): Data;
|
|
3341
|
-
/** Fills empty `slug` fields from their `from` field. Does not check uniqueness. */
|
|
3342
|
-
declare function generateSlugs(fields: readonly Field[], data: Data, localization?: Localization | null): Data;
|
|
3343
|
-
/**
|
|
3344
|
-
* Merges an update into the stored document. Top-level fields and group
|
|
3345
|
-
* fields merge key by key; arrays and hasMany values are replaced.
|
|
3346
|
-
*/
|
|
3347
|
-
declare function mergeForUpdate(fields: readonly Field[], existing: Data, patch: Data): Data;
|
|
3348
|
-
/** Parses an id from user input. Integer ids may arrive as strings from URLs. */
|
|
3349
|
-
declare function parseId(value: unknown): ID | undefined;
|
|
3350
|
-
/** A reference to another document found while validating, checked against the database afterwards. */
|
|
3351
|
-
interface Reference {
|
|
3352
|
-
readonly field: string;
|
|
3353
|
-
readonly collection: string;
|
|
3354
|
-
readonly id: ID;
|
|
3355
|
-
/** The relationship's `filterOptions`, checked when saving. */
|
|
3356
|
-
readonly filterOptions?: FilterOptions;
|
|
3357
|
-
/** The upload's `mimeTypes`, checked when saving. */
|
|
3358
|
-
readonly mimeTypes?: readonly string[];
|
|
3359
|
-
/** The upload's `folder` with `folderOnly`: the file must be in it, checked when saving. */
|
|
3360
|
-
readonly folderOnly?: string;
|
|
3361
|
-
}
|
|
3362
|
-
interface ValidateOptions {
|
|
3363
|
-
readonly operation: 'create' | 'update';
|
|
3364
|
-
/** Drafts may be incomplete: skip `required`, but still check types (FR-DRF-02). */
|
|
3365
|
-
readonly skipRequired?: boolean;
|
|
3366
|
-
/** The whole top-level document, passed to custom `validate` functions. */
|
|
3367
|
-
readonly root: Data;
|
|
3368
|
-
/** Localized fields hold `{ [locale]: value }`; each value is validated. */
|
|
3369
|
-
readonly localization?: Localization | null;
|
|
3370
|
-
/**
|
|
3371
|
-
* The locale being written: `required` and custom `validate` apply to its values, and its
|
|
3372
|
-
* errors use the plain field path. Default: the default locale.
|
|
3373
|
-
*/
|
|
3374
|
-
readonly locale?: string;
|
|
3375
|
-
}
|
|
3376
|
-
interface ValidateResult {
|
|
3377
|
-
/** Only known fields, with values normalized (ids parsed, dates as ISO strings, row ids filled). */
|
|
3378
|
-
readonly data: Data;
|
|
3379
|
-
readonly errors: FieldError[];
|
|
3380
|
-
readonly references: Reference[];
|
|
3381
|
-
}
|
|
3382
|
-
/** Checks and normalizes data against fields. Unknown keys are dropped. */
|
|
3383
|
-
declare function validateFields(fields: readonly Field[], input: Data, options: ValidateOptions, prefix?: string): Promise<ValidateResult>;
|
|
3384
|
-
|
|
3385
|
-
/**
|
|
3386
|
-
* The Easy CMS instance for this server, made on first use and kept across hot reloads.
|
|
3387
|
-
*
|
|
3388
|
-
* Configs are compared by structure, not identity: frameworks bundle the config into each server
|
|
3389
|
-
* layer, so the same config can arrive as several objects. A change to fields or options (in
|
|
3390
|
-
* development) closes the old instance and makes a new one; a change to a hook's code alone needs
|
|
3391
|
-
* a restart. A failed start is not kept: the next call tries again.
|
|
3392
|
-
*/
|
|
3393
|
-
declare function sharedEasyCMS<const C extends Config>(config: C, options?: CreateEasyCMSOptions): Promise<EasyCMS<C>>;
|
|
3394
|
-
interface ApiHandlerOptions {
|
|
3395
|
-
/** Where the API is mounted. Default: `routes.api` of the config. */
|
|
3396
|
-
readonly basePath?: string;
|
|
3397
|
-
/**
|
|
3398
|
-
* The client's IP (login rate limiting, the audit log), from the framework's request object.
|
|
3399
|
-
* Takes precedence over `trustProxy`.
|
|
3400
|
-
*/
|
|
3401
|
-
readonly getClientIp?: (request: Request) => string | undefined;
|
|
3402
|
-
/**
|
|
3403
|
-
* Behind a proxy that adds the client's address to `X-Forwarded-For`: the last address in it
|
|
3404
|
-
* (the one the proxy added; earlier ones come from the client and can say anything). Vercel
|
|
3405
|
-
* and Netlify are recognized without it.
|
|
3406
|
-
*/
|
|
3407
|
-
readonly trustProxy?: boolean;
|
|
3408
|
-
}
|
|
3409
|
-
/**
|
|
3410
|
-
* The client's address from `X-Forwarded-For` as the nearest proxy wrote it: its last entry.
|
|
3411
|
-
* Earlier entries were sent by the client, so they can't be trusted.
|
|
3412
|
-
*/
|
|
3413
|
-
declare function forwardedClientIp(value: string | null | undefined): string | undefined;
|
|
3414
|
-
/** The client's address on hosting platforms that set it themselves: Vercel, Netlify. */
|
|
3415
|
-
declare function platformClientIp(headers: Headers): string | undefined;
|
|
3416
|
-
/**
|
|
3417
|
-
* The REST API as a `(Request) => Response` handler for a framework's route, on the shared
|
|
3418
|
-
* instance (`sharedEasyCMS`). Mount it at `routes.api` (default `/api/cms`), for every method.
|
|
3419
|
-
*/
|
|
3420
|
-
declare function createApiHandler(config: Config, options?: ApiHandlerOptions): (request: Request) => Promise<Response>;
|
|
3421
|
-
|
|
3422
|
-
declare const CONFIG_FILE_NAMES: readonly ["easy-cms.config.ts", "easy-cms.config.mts", "easy-cms.config.js", "easy-cms.config.mjs"];
|
|
3423
|
-
interface LoadConfigOptions {
|
|
3424
|
-
/** Directory to search. Default `process.cwd()`. */
|
|
3425
|
-
readonly cwd?: string;
|
|
3426
|
-
/** Explicit config file path, relative to `cwd`. */
|
|
3427
|
-
readonly configFile?: string;
|
|
3428
|
-
}
|
|
3429
|
-
/** Finds the config file in `cwd`, returning its absolute path or `undefined`. */
|
|
3430
|
-
declare function findConfigFile(cwd?: string): string | undefined;
|
|
3431
|
-
/**
|
|
3432
|
-
* Imports a config file without resolving or validating it.
|
|
3433
|
-
* Build tools use this to read hints (e.g. `db.bundle`) when env vars may be missing.
|
|
3434
|
-
*/
|
|
3435
|
-
declare function importConfig(file: string): Promise<Config | undefined>;
|
|
3436
|
-
/** Loads `easy-cms.config.ts` (or `.mts`/`.js`/`.mjs`) and resolves it. */
|
|
3437
|
-
declare function loadConfig(options?: LoadConfigOptions): Promise<ResolvedConfig>;
|
|
3438
|
-
|
|
3439
|
-
/** File types Easy CMS recognizes from their contents (FR-UPL-03). */
|
|
3440
|
-
declare const EXTENSIONS: Readonly<Record<string, string>>;
|
|
3441
|
-
/**
|
|
3442
|
-
* Names for several types at once, usable in `upload.mimeTypes` and an upload field's
|
|
3443
|
-
* `mimeTypes`: `documents` (PDF, Word, Excel, PowerPoint, OpenDocument, CSV, text), `office`
|
|
3444
|
-
* (docx, xlsx, pptx) and `archives` (zip).
|
|
3445
|
-
*/
|
|
3446
|
-
declare const MIME_GROUPS: Readonly<Record<string, readonly string[]>>;
|
|
3447
|
-
/** Patterns with group names (`documents`…) replaced by their types. */
|
|
3448
|
-
declare function expandMimeTypes(patterns: readonly string[]): string[];
|
|
3449
|
-
/**
|
|
3450
|
-
* Detects the MIME type from file contents, ignoring the type the client sent. The name only
|
|
3451
|
-
* tells CSV from plain text, which have the same contents.
|
|
3452
|
-
*/
|
|
3453
|
-
declare function sniffMimeType(data: Uint8Array, name?: string): string | undefined;
|
|
3454
|
-
/**
|
|
3455
|
-
* `image/*` style matching; group names (`documents`…) stand for their types. A wildcard leaves
|
|
3456
|
-
* out SVG, which can carry scripts: it is allowed only when listed as `image/svg+xml`.
|
|
3457
|
-
*/
|
|
3458
|
-
declare function mimeAllowed(type: string, allowed: readonly string[]): boolean;
|
|
3459
|
-
/** Width and height of PNG, GIF, JPEG and WebP images without decoding them. */
|
|
3460
|
-
declare function imageDimensions(data: Uint8Array, type: string): {
|
|
3461
|
-
width: number;
|
|
3462
|
-
height: number;
|
|
3463
|
-
} | undefined;
|
|
3464
|
-
|
|
3465
|
-
/** The menu's groups that are always there. Declaring their ids changes labels, icons, order. */
|
|
3466
|
-
declare const BUILTIN_NAV: readonly NavGroup[];
|
|
3467
|
-
|
|
3468
|
-
declare const DEFAULT_DEPTH = 1;
|
|
3469
|
-
declare const MAX_DEPTH = 3;
|
|
3470
|
-
/** Loads the documents of one collection that the caller may see. */
|
|
3471
|
-
type Loader = (collection: CollectionConfig, ids: readonly ID[]) => Promise<RawDocument[]>;
|
|
3472
|
-
/**
|
|
3473
|
-
* Replaces relationship and upload ids with documents, `depth` levels deep.
|
|
3474
|
-
* Missing (or unreadable) documents become `null` (single) or are dropped (hasMany).
|
|
3475
|
-
* Returns new objects; the input is not modified.
|
|
3476
|
-
*/
|
|
3477
|
-
declare function populate(load: Loader, collections: readonly CollectionConfig[], fields: readonly Field[], docs: RawDocument[], depth: number): Promise<RawDocument[]>;
|
|
3478
|
-
|
|
3479
|
-
declare const DEFAULT_ADMIN_PATH = "/admin";
|
|
3480
|
-
declare const DEFAULT_API_PATH = "/api/cms";
|
|
3481
|
-
declare const DEFAULT_MAX_FILE_SIZE: number;
|
|
3482
|
-
declare const DEFAULT_TOKEN_EXPIRATION: number;
|
|
3483
|
-
/**
|
|
3484
|
-
* Runs the config's plugins in order, without validating the result. Build tools use it to
|
|
3485
|
-
* read what plugins add (e.g. `admin.modules`) when the config is not complete yet.
|
|
3486
|
-
*/
|
|
3487
|
-
declare function applyPlugins(input: Config): Promise<Config>;
|
|
3488
|
-
/**
|
|
3489
|
-
* Runs plugins in order, adds the built-in collections, validates the result
|
|
3490
|
-
* and fills in defaults. Throws `ConfigError`. Resolving twice is a no-op.
|
|
3491
|
-
*/
|
|
3492
|
-
declare function resolveConfig(input: Config | ResolvedConfig): Promise<ResolvedConfig>;
|
|
1
|
+
import { P as PaginatedDocs, I as ID, E as EmailSetting } from './config-jzFGsNL6.js';
|
|
2
|
+
export { A as ADAPTER_API_VERSION, a as ADMIN_ICONS, b as Access, c as AccessArgs, d as AccessOptions, e as AdminBrand, f as AdminCommand, g as AdminComponent, h as AdminConfig, i as AdminFieldArgs, j as AdminIcon, k as AdminLocale, l as AdminPage, m as AdminRole, n as AdminRoleChange, o as AdminRoleField, p as AdminRoleTarget, q as AdminRoleView, r as AdminRoles, s as AdminSso, t as AdminSwitcher, u as AdminViewAccess, v as AfterChangeHook, w as AfterDeleteHook, x as AfterReadHook, y as ApiKeyContext, z as ApiKeyGlobalOperation, B as ApiKeyOperation, C as ApiKeyPermissions, D as ArrayField, F as AuditChange, G as AuditConfig, H as AuditEntry, J as AuditLog, K as AuditPage, L as AuditVerification, M as Auth, N as AuthConfig, O as AuthProvider, Q as AuthProviderProfile, R as AuthProviderRequest, S as AuthUser, T as BackupsConfig, U as BeforeChangeHook, V as BeforeDeleteHook, W as BeforeValidateHook, X as Block, Y as BlocksField, Z as BooleanField, _ as BuiltinApiKeysCollection, $ as BuiltinMediaCollection, a0 as BuiltinMediaFoldersCollection, a1 as BuiltinUsersCollection, a2 as BundleHints, a3 as CliCommand, a4 as CollectionAccess, a5 as CollectionAdmin, a6 as CollectionConfig, a7 as CollectionDocument, a8 as CollectionHooks, a9 as CollectionListAdmin, aa as CollectionSlug, ab as Config, ac as ConfigError, ad as ConfigIssue, ae as ContainerAdmin, af as CreateEasyCMSOptions, ag as CreateInput, ah as CreateMigrationResult, ai as CustomField, aj as CustomFieldTypes, ak as DashboardWidget, al as Database, am as DatabaseAdapter, an as DatabaseInitArgs, ao as DatabaseTransfer, ap as DateField, aq as DepthOptions, ar as DirectUpload, as as DiskStorageOptions, at as DocumentOf, au as EasyCMS, av as EasyCMSError, aw as EmailAdapter, ax as EmailField, ay as EmailMessage, az as Endpoint, aA as EndpointRequest, aB as ErrorCode, aC as FIELD_TYPES, aD as Field, aE as FieldAccess, aF as FieldAccessFn, aG as FieldAdmin, aH as FieldError, aI as FieldRule, aJ as FieldType, aK as FieldTypeBase, aL as FieldTypeDefinition, aM as FieldTypeValidateContext, aN as FieldValidate, aO as FieldValidateContext, aP as FieldValue, aQ as FieldsInput, aR as FieldsValue, aS as FilterOptions, aT as FilterOptionsArgs, aU as FindArgs, aV as FindOptions, aW as FolderLevel, aX as FolderPermissions, aY as ForbiddenError, aZ as GlobalAccess, a_ as GlobalConfig, a$ as GlobalDocument, b0 as GlobalHooks, b1 as GlobalInput, b2 as GlobalSlug, b3 as GroupField, b4 as ImageSize, b5 as IncrementOptions, b6 as InferCollection, b7 as InferGlobal, b8 as JobConfig, b9 as JsonField, ba as Label, bb as LayoutNode, bc as LivePreview, bd as LocalizationConfig, be as Logger, bf as LoginArgs, bg as MediaDocument, bh as MediaSize, bi as MembersConfig, bj as MembersSignup, bk as MigrationInfo, bl as NavGroup, bm as NotFoundError, bn as NumberField, bo as OnRequest, bp as OnRequestArgs, bq as Operation, br as PLUGIN_API_VERSION, bs as PasswordEmail, bt as PasswordEmailArgs, bu as PasswordEmailFn, bv as PasswordLinkOptions, bw as PayloadTooLargeError, bx as Plugin, by as PluginInfo, bz as PluginTypes, bA as PreviewTarget, bB as PreviewURL, bC as QueryError, bD as ROLE_OPERATIONS, bE as RawDocument, bF as ReadOptions, bG as RelationshipField, bH as RequestContext, bI as ResolvedConfig, bJ as RichTextDocument, bK as RichTextField, bL as Role, bM as RoleOperation, bN as RolePermissions, bO as Roles, bP as RoutesConfig, bQ as ScheduledPublish, bR as SchemaError, bS as SchemaMode, bT as SelectField, bU as SelectOption, bV as Session, bW as SidebarPanel, bX as SignupArgs, bY as SignupResult, bZ as SingleSignOn, b_ as SlugField, b$ as SlugOf, c0 as SsoOutcome, c1 as SsoProviderRef, c2 as StorageAdapter, c3 as StoredFile, c4 as TextField, c5 as TextareaField, c6 as TooManyRequestsError, c7 as TypedPlugin, c8 as UnauthorizedError, c9 as UpdateInput, ca as UpdateOptions, cb as UploadConfig, cc as UploadField, cd as UploadFromURLConfig, ce as UserIdentity, cf as ValidationError, cg as Version, ch as VersionSummary, ci as VersionsConfig, cj as WEBHOOK_EVENTS, ck as WebhookConfig, cl as WebhookEvent, cm as WebhookPayload, cn as Where, co as WhereOperators, cp as anyone, cq as consoleEmail, cr as consoleLogger, cs as createEasyCMS, ct as defineConfig, cu as defineFieldType, cv as definePlugin, cw as diskStorage, cx as hasRows, cy as isAdmin, cz as isHasMany, cA as isSignedIn, cB as isStaff, cC as isSystemAdmin, cD as mimeAllowedBy, cE as rowFields, cF as silentLogger, cG as uniqueWithinOf } from './config-jzFGsNL6.js';
|
|
3
|
+
export { A as ADMIN_API_VERSION, a as AdminCollection, b as AdminComponentRef, c as AdminElementProps, d as AdminField, e as AdminGlobal, f as AdminLayoutNode, g as AdminNavGroup, h as AdminNavItem, i as AdminNavNode, j as AdminPageRef, k as AdminPageRoute, l as AdminSchema, m as AdminWidgetRef, S as SetFieldDetail } from './plugin-EX9OHwR3.js';
|
|
4
|
+
export { A as AdminBackup, a as AdminBackups, b as ApiHandlerOptions, B as BackupState, L as LoadConfigOptions, R as RestHandler, c as RestHandlerOptions, d as createApiHandler, e as createRestHandler, l as loadConfig, r as resolveConfig, s as sharedEasyCMS, f as slugify } from './handler-CMA1LIq7.js';
|
|
5
|
+
export { FieldCondition, matchesCondition } from './conditions.js';
|
|
3493
6
|
|
|
3494
7
|
type DeliveryKind = 'webhook' | 'email';
|
|
3495
8
|
type DeliveryState = 'failed' | 'pending';
|
|
@@ -3523,11 +36,8 @@ interface AdminEmailDelivery extends DeliveryBase {
|
|
|
3523
36
|
subject: string;
|
|
3524
37
|
}
|
|
3525
38
|
type AdminDelivery = AdminWebhookDelivery | AdminEmailDelivery;
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
totalDocs: number;
|
|
3529
|
-
page: number;
|
|
3530
|
-
totalPages: number;
|
|
39
|
+
/** A page of deliveries: the list shape of the rest of the API, and counts for the filter. */
|
|
40
|
+
interface AdminDeliveries extends PaginatedDocs<AdminDelivery> {
|
|
3531
41
|
/** How many there are of each state, for the filter. */
|
|
3532
42
|
counts: Record<DeliveryState, number>;
|
|
3533
43
|
}
|
|
@@ -3552,281 +62,6 @@ type EmailCheck = {
|
|
|
3552
62
|
error: string;
|
|
3553
63
|
};
|
|
3554
64
|
|
|
3555
|
-
/**
|
|
3556
|
-
* Finds the file of an admin module (`admin.modules`): a package export or a path relative to
|
|
3557
|
-
* the project root. Looks from the project root first, then from the server's entry file
|
|
3558
|
-
* (a Nuxt `.output/server` build keeps traced packages next to it). `undefined` when not found.
|
|
3559
|
-
*/
|
|
3560
|
-
declare function resolveAdminModule(specifier: string, cwd?: string): string | undefined;
|
|
3561
|
-
|
|
3562
|
-
/** A Web Component from an admin module, with its `props` (plain JSON). */
|
|
3563
|
-
interface AdminComponentRef {
|
|
3564
|
-
tag: string;
|
|
3565
|
-
props?: Record<string, unknown>;
|
|
3566
|
-
/** A side panel at the top of the side column (`position: 'top'`). */
|
|
3567
|
-
position?: 'top';
|
|
3568
|
-
}
|
|
3569
|
-
/** Where the edit page puts its fields (`admin.layout`), with unplaced fields filled in. */
|
|
3570
|
-
type AdminLayoutNode = {
|
|
3571
|
-
type: 'field';
|
|
3572
|
-
name: string;
|
|
3573
|
-
} | {
|
|
3574
|
-
type: 'row';
|
|
3575
|
-
fields: string[];
|
|
3576
|
-
} | {
|
|
3577
|
-
type: 'collapsible';
|
|
3578
|
-
label: Label;
|
|
3579
|
-
collapsed?: boolean;
|
|
3580
|
-
description?: Label;
|
|
3581
|
-
nodes: AdminLayoutNode[];
|
|
3582
|
-
} | {
|
|
3583
|
-
type: 'tab';
|
|
3584
|
-
label: Label;
|
|
3585
|
-
description?: Label;
|
|
3586
|
-
nodes: AdminLayoutNode[];
|
|
3587
|
-
};
|
|
3588
|
-
/** An entry of the admin menu. */
|
|
3589
|
-
type AdminNavItem = {
|
|
3590
|
-
kind: 'collection';
|
|
3591
|
-
slug: string;
|
|
3592
|
-
} | {
|
|
3593
|
-
kind: 'global';
|
|
3594
|
-
slug: string;
|
|
3595
|
-
} | {
|
|
3596
|
-
kind: 'page';
|
|
3597
|
-
path: string;
|
|
3598
|
-
} | {
|
|
3599
|
-
kind: 'view';
|
|
3600
|
-
view: 'roles' | 'sso' | 'backups' | 'email' | 'deliveries' | 'audit';
|
|
3601
|
-
};
|
|
3602
|
-
/** A group of the admin menu (`admin.nav`), with what this user may open in it. */
|
|
3603
|
-
interface AdminNavGroup {
|
|
3604
|
-
kind: 'group';
|
|
3605
|
-
id: string;
|
|
3606
|
-
label: Label;
|
|
3607
|
-
icon?: string;
|
|
3608
|
-
items: AdminNavNode[];
|
|
3609
|
-
}
|
|
3610
|
-
type AdminNavNode = AdminNavItem | AdminNavGroup;
|
|
3611
|
-
/** A field as the admin UI sees it: plain JSON, no functions. */
|
|
3612
|
-
interface AdminField {
|
|
3613
|
-
name: string;
|
|
3614
|
-
type: Field['type'];
|
|
3615
|
-
label?: Label;
|
|
3616
|
-
required?: boolean;
|
|
3617
|
-
unique?: boolean;
|
|
3618
|
-
defaultValue?: unknown;
|
|
3619
|
-
minLength?: number;
|
|
3620
|
-
maxLength?: number;
|
|
3621
|
-
min?: number;
|
|
3622
|
-
max?: number;
|
|
3623
|
-
options?: {
|
|
3624
|
-
label: Label;
|
|
3625
|
-
value: string;
|
|
3626
|
-
}[];
|
|
3627
|
-
hasMany?: boolean;
|
|
3628
|
-
to?: string;
|
|
3629
|
-
/** An upload's allowed file types, e.g. `image/*`. */
|
|
3630
|
-
mimeTypes?: string[];
|
|
3631
|
-
/** An upload's media folder (`folder`): where its picker opens; `only`: nowhere else. */
|
|
3632
|
-
folder?: {
|
|
3633
|
-
id: ID;
|
|
3634
|
-
only: boolean;
|
|
3635
|
-
};
|
|
3636
|
-
/** A relationship with `filterOptions`: the picker asks the server which documents fit. */
|
|
3637
|
-
filtered?: boolean;
|
|
3638
|
-
from?: string;
|
|
3639
|
-
minRows?: number;
|
|
3640
|
-
maxRows?: number;
|
|
3641
|
-
fields?: AdminField[];
|
|
3642
|
-
/** Block kinds of a `blocks` field. */
|
|
3643
|
-
blocks?: {
|
|
3644
|
-
slug: string;
|
|
3645
|
-
labels?: {
|
|
3646
|
-
singular?: Label;
|
|
3647
|
-
plural?: Label;
|
|
3648
|
-
};
|
|
3649
|
-
fields: AdminField[];
|
|
3650
|
-
}[];
|
|
3651
|
-
/** The current user may not change this field. */
|
|
3652
|
-
readOnly?: boolean;
|
|
3653
|
-
/** One value per content locale. */
|
|
3654
|
-
localized?: boolean;
|
|
3655
|
-
/** Shown in the edit page's side panel. */
|
|
3656
|
-
position?: 'sidebar';
|
|
3657
|
-
/** Components from admin modules: instead of the input, below the field, and in lists. */
|
|
3658
|
-
admin?: {
|
|
3659
|
-
component?: AdminComponentRef;
|
|
3660
|
-
after?: AdminComponentRef[];
|
|
3661
|
-
cell?: AdminComponentRef;
|
|
3662
|
-
};
|
|
3663
|
-
/** A column of the list at first (`admin.column`). */
|
|
3664
|
-
column?: boolean;
|
|
3665
|
-
/** A relationship that offers no "Create" in place (`admin.allowCreate: false`). */
|
|
3666
|
-
noCreate?: boolean;
|
|
3667
|
-
/** A field of an added type (`fieldTypes`), e.g. `color`; `type` is the base it's stored as. */
|
|
3668
|
-
customType?: string;
|
|
3669
|
-
/** Help below its label (`admin.description`). */
|
|
3670
|
-
description?: Label;
|
|
3671
|
-
/** Its share of a row (`admin.width`). */
|
|
3672
|
-
width?: string;
|
|
3673
|
-
/** Shown only when its siblings match (`admin.condition`). */
|
|
3674
|
-
condition?: FieldCondition;
|
|
3675
|
-
}
|
|
3676
|
-
interface AdminCollection {
|
|
3677
|
-
slug: string;
|
|
3678
|
-
labels?: {
|
|
3679
|
-
singular?: Label;
|
|
3680
|
-
plural?: Label;
|
|
3681
|
-
};
|
|
3682
|
-
icon?: string;
|
|
3683
|
-
useAsTitle?: string;
|
|
3684
|
-
/** Documents open in a panel over the list. */
|
|
3685
|
-
editIn?: 'drawer';
|
|
3686
|
-
drafts: boolean;
|
|
3687
|
-
versions: boolean;
|
|
3688
|
-
/** Has live preview (`preview` in the config). */
|
|
3689
|
-
preview: boolean;
|
|
3690
|
-
/** Publishing can be scheduled. */
|
|
3691
|
-
schedule: boolean;
|
|
3692
|
-
fields: AdminField[];
|
|
3693
|
-
/** Panels from admin modules in the edit page's side column. */
|
|
3694
|
-
sidebar?: AdminComponentRef[];
|
|
3695
|
-
/** Tabs, sections and rows of the edit page (`admin.layout`). */
|
|
3696
|
-
layout?: AdminLayoutNode[];
|
|
3697
|
-
/** A number in the menu (`admin.badge`); the menu's counts come from `GET <api>/admin/counts`. */
|
|
3698
|
-
badge?: {
|
|
3699
|
-
tone: 'accent' | 'warning' | 'danger';
|
|
3700
|
-
label?: Label;
|
|
3701
|
-
};
|
|
3702
|
-
/** No document count beside it in the menu (`admin.count: false`). */
|
|
3703
|
-
noCount?: boolean;
|
|
3704
|
-
/** What the list says while it has no documents (`admin.empty`). */
|
|
3705
|
-
empty?: {
|
|
3706
|
-
description?: Label;
|
|
3707
|
-
link?: {
|
|
3708
|
-
label: Label;
|
|
3709
|
-
href: string;
|
|
3710
|
-
};
|
|
3711
|
-
};
|
|
3712
|
-
/** The list is a tree along this relationship field (`admin.list.tree`). */
|
|
3713
|
-
tree?: string;
|
|
3714
|
-
/** The list's default order (`admin.list.sort`). */
|
|
3715
|
-
defaultSort?: string;
|
|
3716
|
-
permissions: {
|
|
3717
|
-
read: boolean;
|
|
3718
|
-
create: boolean;
|
|
3719
|
-
update: boolean;
|
|
3720
|
-
delete: boolean;
|
|
3721
|
-
publish: boolean;
|
|
3722
|
-
};
|
|
3723
|
-
/** With `auth.rbac`: the field naming a document's owner (`createdBy` or `admin.ownerField`). */
|
|
3724
|
-
owner?: string;
|
|
3725
|
-
/** Deleting asks to type the title, and says what goes too (`admin.confirmDelete`). */
|
|
3726
|
-
confirmDelete?: {
|
|
3727
|
-
typeTitle?: boolean;
|
|
3728
|
-
impact?: string;
|
|
3729
|
-
};
|
|
3730
|
-
}
|
|
3731
|
-
interface AdminGlobal {
|
|
3732
|
-
slug: string;
|
|
3733
|
-
label?: Label;
|
|
3734
|
-
icon?: string;
|
|
3735
|
-
drafts: boolean;
|
|
3736
|
-
versions: boolean;
|
|
3737
|
-
/** Has live preview (`preview` in the config). */
|
|
3738
|
-
preview: boolean;
|
|
3739
|
-
/** Publishing can be scheduled. */
|
|
3740
|
-
schedule: boolean;
|
|
3741
|
-
fields: AdminField[];
|
|
3742
|
-
/** Panels from admin modules in the edit page's side column. */
|
|
3743
|
-
sidebar?: AdminComponentRef[];
|
|
3744
|
-
/** Tabs, sections and rows of the edit page (`admin.layout`). */
|
|
3745
|
-
layout?: AdminLayoutNode[];
|
|
3746
|
-
permissions: {
|
|
3747
|
-
read: boolean;
|
|
3748
|
-
update: boolean;
|
|
3749
|
-
publish: boolean;
|
|
3750
|
-
};
|
|
3751
|
-
}
|
|
3752
|
-
/** A page of its own in the admin (`admin.pages`) that this user may open. */
|
|
3753
|
-
interface AdminPageRef {
|
|
3754
|
-
path: string;
|
|
3755
|
-
label: Label;
|
|
3756
|
-
icon?: string;
|
|
3757
|
-
component: AdminComponentRef;
|
|
3758
|
-
}
|
|
3759
|
-
/** A dashboard panel (`admin.dashboard`) this user may see. */
|
|
3760
|
-
interface AdminWidgetRef {
|
|
3761
|
-
component: AdminComponentRef;
|
|
3762
|
-
width: 'half' | 'full';
|
|
3763
|
-
}
|
|
3764
|
-
interface AdminSchema {
|
|
3765
|
-
locale: AdminLocale;
|
|
3766
|
-
/** A choice for the whole admin, e.g. the tenant (`admin.switcher`). */
|
|
3767
|
-
switcher: AdminSwitcher | null;
|
|
3768
|
-
/** Admins can email users links to set their password (needs `email` and `serverURL`). */
|
|
3769
|
-
passwordLinks: boolean;
|
|
3770
|
-
/** Menu order of collections (`admin.menu`); unlisted ones follow. */
|
|
3771
|
-
menu: string[];
|
|
3772
|
-
/** The menu: groups and what this user may open in them, in order. */
|
|
3773
|
-
nav: AdminNavNode[];
|
|
3774
|
-
/** More entries of the command palette (`admin.commands`). */
|
|
3775
|
-
commands: {
|
|
3776
|
-
label: Label;
|
|
3777
|
-
to: string;
|
|
3778
|
-
icon?: string;
|
|
3779
|
-
keywords?: string[];
|
|
3780
|
-
}[];
|
|
3781
|
-
/** Content locales, when the config has `localization`. */
|
|
3782
|
-
localization: {
|
|
3783
|
-
locales: string[];
|
|
3784
|
-
defaultLocale: string;
|
|
3785
|
-
} | null;
|
|
3786
|
-
collections: AdminCollection[];
|
|
3787
|
-
globals: AdminGlobal[];
|
|
3788
|
-
/** URLs under the API of the admin modules to load (`admin.modules`). */
|
|
3789
|
-
modules: string[];
|
|
3790
|
-
/** Files can be uploaded from links (`upload.fromURL`). */
|
|
3791
|
-
uploadFromURL: boolean;
|
|
3792
|
-
/** What the media library takes (`upload.maxFileSize`, `upload.mimeTypes` with groups expanded). */
|
|
3793
|
-
upload: {
|
|
3794
|
-
maxFileSize: number;
|
|
3795
|
-
mimeTypes: string[];
|
|
3796
|
-
};
|
|
3797
|
-
/**
|
|
3798
|
-
* The media library has folders (`upload.folders`); `permissions`: this user may choose which
|
|
3799
|
-
* roles use each folder (admins, with `auth.rbac`).
|
|
3800
|
-
*/
|
|
3801
|
-
folders?: {
|
|
3802
|
-
permissions: boolean;
|
|
3803
|
-
private: boolean;
|
|
3804
|
-
};
|
|
3805
|
-
/** Which saved deliveries the admin can show (webhooks, emails), for users who may see them. */
|
|
3806
|
-
deliveries?: {
|
|
3807
|
-
webhook: boolean;
|
|
3808
|
-
email: boolean;
|
|
3809
|
-
};
|
|
3810
|
-
/** Admin pages this user may open: Settings → Backups, Email, Roles; deliveries; the status panel. */
|
|
3811
|
-
views: {
|
|
3812
|
-
status: boolean;
|
|
3813
|
-
deliveries: boolean;
|
|
3814
|
-
backups: boolean;
|
|
3815
|
-
email: boolean;
|
|
3816
|
-
roles: boolean;
|
|
3817
|
-
sso: boolean;
|
|
3818
|
-
audit: boolean;
|
|
3819
|
-
};
|
|
3820
|
-
/** Roles from the admin are on (`auth.rbac`): documents have owners. */
|
|
3821
|
-
rbac: boolean;
|
|
3822
|
-
/** Outside accounts users can sign in with and link (`auth.providers`). */
|
|
3823
|
-
providers: SsoProviderRef[];
|
|
3824
|
-
/** Pages this user may open (`admin.pages`). */
|
|
3825
|
-
pages: AdminPageRef[];
|
|
3826
|
-
/** Dashboard panels this user may see (`admin.dashboard`). */
|
|
3827
|
-
dashboard: AdminWidgetRef[];
|
|
3828
|
-
}
|
|
3829
|
-
|
|
3830
65
|
interface SearchHit {
|
|
3831
66
|
readonly collection: string;
|
|
3832
67
|
readonly id: ID;
|
|
@@ -3904,65 +139,7 @@ interface AdminStatus {
|
|
|
3904
139
|
attention: AdminAttention[];
|
|
3905
140
|
}
|
|
3906
141
|
|
|
3907
|
-
/** The session cookie the REST API sets at login. */
|
|
3908
|
-
declare const SESSION_COOKIE = "ecms-session";
|
|
3909
|
-
|
|
3910
|
-
declare const CSRF_COOKIE = "ecms-csrf";
|
|
3911
|
-
declare const CSRF_HEADER = "x-csrf-token";
|
|
3912
|
-
interface RestHandlerOptions {
|
|
3913
|
-
/** Path the handler is mounted at. Default: `routes.api` from the config (`/api/cms`). */
|
|
3914
|
-
readonly basePath?: string;
|
|
3915
|
-
/** Client IP, used with the email to rate-limit logins. Adapters provide it. */
|
|
3916
|
-
readonly getClientIp?: (request: Request) => string | undefined;
|
|
3917
|
-
}
|
|
3918
|
-
type RestHandler = (request: Request) => Promise<Response>;
|
|
3919
|
-
/** Creates the REST API as a Web-standard `(Request) => Response` handler. */
|
|
3920
|
-
declare function createRestHandler<C extends Config>(instance: EasyCMS<C>, options?: RestHandlerOptions): RestHandler;
|
|
3921
|
-
/**
|
|
3922
|
-
* Serves the endpoints marked `root: true` (e.g. `/robots.txt`), with the same auth, CSRF and
|
|
3923
|
-
* errors as the REST API. Resolves to `undefined` when no root endpoint has the request's path,
|
|
3924
|
-
* so a server can go on to its other routes. Nuxt and Next.js apps own their root and don't
|
|
3925
|
-
* use it; the standalone server does.
|
|
3926
|
-
*/
|
|
3927
|
-
declare function createRootEndpointHandler<C extends Config>(instance: EasyCMS<C>, options?: Pick<RestHandlerOptions, 'getClientIp'>): (request: Request) => Promise<Response | undefined>;
|
|
3928
|
-
declare function readCookie(request: Request, name: string): string | undefined;
|
|
3929
|
-
|
|
3930
|
-
/** A token that proves the form `purpose` names was loaded at `now`: `<time>.<signature>`. */
|
|
3931
|
-
declare function formToken(secret: string, purpose: string, now?: number): string;
|
|
3932
|
-
/**
|
|
3933
|
-
* Checks a token: `ok` when the form was loaded at least `minTime` ms ago (people take a few
|
|
3934
|
-
* seconds; bots don't) and less than a day ago.
|
|
3935
|
-
*/
|
|
3936
|
-
declare function checkFormToken(secret: string, purpose: string, token: unknown, minTime: number, now?: number): 'ok' | 'invalid' | 'too-fast' | 'expired';
|
|
3937
|
-
/**
|
|
3938
|
-
* Rate-limit keys for `subject` (e.g. an IP and a form) in the current and previous window of
|
|
3939
|
-
* `window` seconds. A hash with the window in it: no IP is kept, and keys from different
|
|
3940
|
-
* windows can't be linked.
|
|
3941
|
-
*/
|
|
3942
|
-
declare function rateKeys(secret: string, subject: string, window: number, now?: number): {
|
|
3943
|
-
current: string;
|
|
3944
|
-
previous: string;
|
|
3945
|
-
};
|
|
3946
|
-
/** The hidden field bots fill in: a common name the form doesn't use. */
|
|
3947
|
-
declare function honeypotName(names: readonly string[]): string;
|
|
3948
|
-
/** Checks a Cloudflare Turnstile response with Cloudflare. */
|
|
3949
|
-
declare function verifyTurnstile(secretKey: string, response: unknown, ip: string | undefined, fetcher?: typeof fetch): Promise<boolean>;
|
|
3950
|
-
|
|
3951
|
-
/** Best-effort English singular: "categories" → "category". */
|
|
3952
|
-
declare function singularize(word: string): string;
|
|
3953
|
-
/**
|
|
3954
|
-
* TypeScript declarations for every collection and global, as the REST API returns them.
|
|
3955
|
-
* The output has no imports, so a frontend in another repository can use it as is.
|
|
3956
|
-
*/
|
|
3957
|
-
declare function generateTypes(config: ResolvedConfig): string;
|
|
3958
|
-
|
|
3959
|
-
declare const MIN_SECRET_LENGTH = 32;
|
|
3960
|
-
/** Collections that Easy CMS provides. They can be relationship targets. */
|
|
3961
|
-
declare const BUILTIN_COLLECTIONS: readonly ["users", "media"];
|
|
3962
|
-
/** Checks a config and returns every problem found. An empty array means it is valid. */
|
|
3963
|
-
declare function validateConfig(config: Config): ConfigIssue[];
|
|
3964
|
-
|
|
3965
142
|
/** The version of `@easy-cms/core`, set when the package is built. */
|
|
3966
143
|
declare const VERSION: string;
|
|
3967
144
|
|
|
3968
|
-
export {
|
|
145
|
+
export { type AdminAttention, type AdminDeliveries, type AdminDelivery, type AdminEmail, type AdminEmailDelivery, type AdminStatus, type AdminWebhookDelivery, type DeliveryKind, type DeliveryState, type EmailCheck, EmailSetting, ID, PaginatedDocs, type SearchHit, VERSION };
|