@easy-cms/core 0.62.0 → 0.64.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.
@@ -33,8 +33,9 @@ import {
33
33
  typeFromName,
34
34
  uniqueWithinOf,
35
35
  warnDeprecated,
36
+ withCharset,
36
37
  withPrivacy
37
- } from "./chunk-FPFWLQB5.js";
38
+ } from "./chunk-RTKWMVAC.js";
38
39
 
39
40
  // src/access.ts
40
41
  var isSystemAdmin = (user) => user?.role === "admin" && !user.apiKey && user.scoped !== true;
@@ -1015,6 +1016,21 @@ async function verifyTurnstile(secretKey, response, ip, fetcher = fetch) {
1015
1016
 
1016
1017
  // src/auth/cookie.ts
1017
1018
  var SESSION_COOKIE = "ecms-session";
1019
+ var hostCookie = (name) => `__Host-${name}`;
1020
+ function cookieValue(header, name) {
1021
+ if (!header) return void 0;
1022
+ let plain;
1023
+ for (const part of header.split(";")) {
1024
+ const eq = part.indexOf("=");
1025
+ if (eq <= 0) continue;
1026
+ const key = part.slice(0, eq).trim();
1027
+ if (key !== name && key !== hostCookie(name)) continue;
1028
+ const value = decodeURIComponent(part.slice(eq + 1).trim());
1029
+ if (key !== name) return value;
1030
+ plain ??= value;
1031
+ }
1032
+ return plain;
1033
+ }
1018
1034
 
1019
1035
  // src/auth/emails.ts
1020
1036
  var esc = (value) => value.replace(
@@ -1169,11 +1185,13 @@ function safeEqual(a, b) {
1169
1185
  const right = Buffer.from(b);
1170
1186
  return left.length === right.length && timingSafeEqual4(left, right);
1171
1187
  }
1172
- function signPreviewToken(secret, target, expiresAt) {
1188
+ function signPreviewToken(secret, target, expiresAt, user) {
1173
1189
  const payload = Buffer.from(
1174
- JSON.stringify(
1175
- "global" in target ? { g: target.global, e: expiresAt } : { c: target.collection, i: target.id, e: expiresAt }
1176
- )
1190
+ JSON.stringify({
1191
+ ..."global" in target ? { g: target.global } : { c: target.collection, i: target.id },
1192
+ e: expiresAt,
1193
+ ...user !== void 0 ? { u: user } : {}
1194
+ })
1177
1195
  ).toString("base64url");
1178
1196
  return `${payload}.${hmac(secret, `preview:${payload}`)}`;
1179
1197
  }
@@ -1185,9 +1203,10 @@ function verifyPreviewToken(secret, token, now = Date.now()) {
1185
1203
  try {
1186
1204
  const data = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
1187
1205
  if (typeof data.e !== "number" || data.e <= now) return null;
1188
- if (typeof data.g === "string") return { global: data.g, expiresAt: data.e };
1206
+ const user = typeof data.u === "string" ? { user: data.u } : {};
1207
+ if (typeof data.g === "string") return { global: data.g, expiresAt: data.e, ...user };
1189
1208
  if (typeof data.c === "string" && typeof data.i === "string")
1190
- return { collection: data.c, id: data.i, expiresAt: data.e };
1209
+ return { collection: data.c, id: data.i, expiresAt: data.e, ...user };
1191
1210
  return null;
1192
1211
  } catch {
1193
1212
  return null;
@@ -1908,12 +1927,8 @@ var Auth = class {
1908
1927
  async userFromHeaders(headers) {
1909
1928
  const authorization = headers.get("authorization");
1910
1929
  if (authorization?.startsWith("Bearer ")) return this.verify(authorization.slice(7).trim());
1911
- for (const part of headers.get("cookie")?.split(";") ?? []) {
1912
- const eq = part.indexOf("=");
1913
- if (eq > 0 && part.slice(0, eq).trim() === SESSION_COOKIE)
1914
- return this.verify(decodeURIComponent(part.slice(eq + 1).trim()));
1915
- }
1916
- return null;
1930
+ const cookie2 = cookieValue(headers.get("cookie"), SESSION_COOKIE);
1931
+ return cookie2 ? this.verify(cookie2) : null;
1917
1932
  }
1918
1933
  /**
1919
1934
  * Returns the user for a signed session token, or for an API key (`ecms_…`, with `apiKey` set
@@ -1934,6 +1949,12 @@ var Auth = class {
1934
1949
  if (!user || user.active === false) return null;
1935
1950
  return this.toAuthUser(user);
1936
1951
  }
1952
+ /** An active user by id, as requests see them; `null` when deleted or deactivated. @internal */
1953
+ async activeUser(id) {
1954
+ const user = await this.db.findById({ collection: USERS, id });
1955
+ if (!user || user.active === false) return null;
1956
+ return this.toAuthUser(user);
1957
+ }
1937
1958
  /** The CSRF token clients must echo for a signed session token. @internal */
1938
1959
  csrfFor(signedToken) {
1939
1960
  const token = unsignToken(this.config.secret, signedToken);
@@ -6818,7 +6839,7 @@ var EasyCMS = class {
6818
6839
  const filename = withPrivacy(storageKey(file.name, mimeType, random2), isPrivate);
6819
6840
  const stored = [];
6820
6841
  try {
6821
- await storage.put(filename, file.data, { contentType: mimeType });
6842
+ await storage.put(filename, file.data, { contentType: withCharset(mimeType) });
6822
6843
  stored.push(filename);
6823
6844
  const dimensions = imageDimensions(file.data, mimeType);
6824
6845
  const sizes = await this.resizeImage(
@@ -7119,18 +7140,23 @@ var EasyCMS = class {
7119
7140
  }
7120
7141
  /**
7121
7142
  * How many documents use any of these files in a top-level upload field (rich text and fields
7122
- * inside groups, arrays and blocks are not counted). Drafts count too.
7143
+ * inside groups, arrays and blocks are not counted). Drafts count too. With
7144
+ * `overrideAccess: false`, only documents the user may read.
7123
7145
  */
7124
- async mediaUsage(ids) {
7146
+ async mediaUsage(ids, options = {}) {
7125
7147
  if (ids.length === 0) return 0;
7126
7148
  let count = 0;
7127
7149
  for (const config of this.config.collections) {
7128
7150
  if (INTERNAL_COLLECTIONS.has(config.slug)) continue;
7129
7151
  for (const field of config.fields) {
7130
7152
  if (field.type !== "upload") continue;
7131
- count += await this.db.count({
7132
- collection: config.slug,
7133
- where: { [field.name]: { in: [...ids] } }
7153
+ count += await this.count(config.slug, {
7154
+ ...options,
7155
+ where: { [field.name]: { in: [...ids] } },
7156
+ draft: true
7157
+ }).catch((error) => {
7158
+ if (error instanceof ForbiddenError || error instanceof UnauthorizedError) return 0;
7159
+ throw error;
7134
7160
  });
7135
7161
  }
7136
7162
  }
@@ -7159,7 +7185,7 @@ var EasyCMS = class {
7159
7185
  const old = [String(doc.filename)];
7160
7186
  const copy = async (source, target) => {
7161
7187
  const file = await from.get(source);
7162
- if (file) await to.put(target, file.body, { contentType });
7188
+ if (file) await to.put(target, file.body, { contentType: withCharset(contentType) });
7163
7189
  };
7164
7190
  await copy(String(doc.filename), filename);
7165
7191
  const sizes = {};
@@ -7381,12 +7407,15 @@ var EasyCMS = class {
7381
7407
  /**
7382
7408
  * A token that lets a page read one document's current draft for a while (default one hour),
7383
7409
  * without a login: `GET /api/cms/:collection/:id?preview=<token>`. The admin adds one to the
7384
- * preview URL as `easy-cms-preview`, for sites on another origin.
7410
+ * preview URL as `easy-cms-preview`, for sites on another origin. With `user`, the page reads
7411
+ * it with that user's access (fields they can't read are left out; a deactivated user's token
7412
+ * stops working); without, with full access.
7385
7413
  */
7386
7414
  createPreviewToken(target, options = {}) {
7387
7415
  const seconds = options.expiresIn ?? 60 * 60;
7388
7416
  const normalized = "global" in target ? target : { collection: target.collection, id: String(target.id) };
7389
- return signPreviewToken(this.config.secret, normalized, Date.now() + seconds * 1e3);
7417
+ const user = options.user ? String(options.user.id) : void 0;
7418
+ return signPreviewToken(this.config.secret, normalized, Date.now() + seconds * 1e3, user);
7390
7419
  }
7391
7420
  /** What a preview token opens, or `null` when it is invalid or expired. */
7392
7421
  verifyPreviewToken(token) {
@@ -7846,7 +7875,8 @@ var EasyCMS = class {
7846
7875
  const check3 = (operation) => this.checkDocumentAccess(config, operation, guard, parsed, void 0).then(
7847
7876
  () => true,
7848
7877
  (error) => {
7849
- if (error instanceof ForbiddenError || error instanceof UnauthorizedError) return false;
7878
+ if (error instanceof ForbiddenError || error instanceof UnauthorizedError || error instanceof NotFoundError)
7879
+ return false;
7850
7880
  throw error;
7851
7881
  }
7852
7882
  );
@@ -8315,7 +8345,38 @@ var EasyCMS = class {
8315
8345
  await this.checkQueryFields(config, guard, where);
8316
8346
  return andWhere(andWhere(where, access), own);
8317
8347
  }
8348
+ /**
8349
+ * Throws unless the caller may update or delete this document. A user who may not even read
8350
+ * it gets `NotFoundError`, as for an id that doesn't exist: a 403 would tell it does.
8351
+ */
8318
8352
  async checkDocumentAccess(config, operation, guard, id, data) {
8353
+ await this.hidingDenied(
8354
+ config.slug,
8355
+ guard,
8356
+ id,
8357
+ () => this.documentAccess(config, operation, guard, id, data)
8358
+ );
8359
+ }
8360
+ /** Runs a check; a `ForbiddenError` for a document the user can't read becomes a 404. */
8361
+ async hidingDenied(collection2, guard, id, check3) {
8362
+ try {
8363
+ await check3();
8364
+ } catch (error) {
8365
+ if (error instanceof ForbiddenError && !await this.mayRead(collection2, guard, id))
8366
+ throw new NotFoundError(collection2, String(id));
8367
+ throw error;
8368
+ }
8369
+ }
8370
+ async mayRead(collection2, guard, id) {
8371
+ const config = this.collection(collection2);
8372
+ try {
8373
+ const where = await this.readWhere(config, guard, { id: { equals: id } });
8374
+ return await this.db.count({ collection: collection2, where }) > 0;
8375
+ } catch {
8376
+ return false;
8377
+ }
8378
+ }
8379
+ async documentAccess(config, operation, guard, id, data) {
8319
8380
  if (!guard.enforce) return;
8320
8381
  const own = await this.checkGrant(guard, { collection: config.slug }, operation);
8321
8382
  const allowed2 = await evaluateAccess(config.access?.[operation], {
@@ -8382,13 +8443,15 @@ var EasyCMS = class {
8382
8443
  }
8383
8444
  /** `checkGrant` for one document: a role limited to its own documents must own it. */
8384
8445
  async checkDocumentGrant(guard, collection2, operation, id) {
8385
- const own = await this.checkGrant(guard, { collection: collection2 }, operation);
8386
- if (own === true) return;
8387
- const matches2 = await this.db.count({
8388
- collection: collection2,
8389
- where: andWhere(own, { id: { equals: id } })
8446
+ await this.hidingDenied(collection2, guard, id, async () => {
8447
+ const own = await this.checkGrant(guard, { collection: collection2 }, operation);
8448
+ if (own === true) return;
8449
+ const matches2 = await this.db.count({
8450
+ collection: collection2,
8451
+ where: andWhere(own, { id: { equals: id } })
8452
+ });
8453
+ if (matches2 === 0) throw deny(guard.user);
8390
8454
  });
8391
- if (matches2 === 0) throw deny(guard.user);
8392
8455
  }
8393
8456
  /** Runs afterRead hooks, populates relationships and removes what the caller may not see. */
8394
8457
  async output(config, docs, guard, options) {
@@ -9042,7 +9105,7 @@ async function readAdminModule(cms, index) {
9042
9105
  }
9043
9106
 
9044
9107
  // src/version.ts
9045
- var VERSION = true ? "0.62.0" : "0.0.0-dev";
9108
+ var VERSION = true ? "0.64.0" : "0.0.0-dev";
9046
9109
 
9047
9110
  // src/rest/admin-deliveries.ts
9048
9111
  var PER_PAGE = 50;
@@ -9978,7 +10041,8 @@ async function respond(cms, request, url, path, root, options, production) {
9978
10041
  const headers = new Headers({
9979
10042
  "content-type": "application/json; charset=utf-8",
9980
10043
  "cache-control": "no-store",
9981
- "x-content-type-options": "nosniff"
10044
+ "x-content-type-options": "nosniff",
10045
+ ...production ? { "strict-transport-security": "max-age=31536000" } : {}
9982
10046
  });
9983
10047
  try {
9984
10048
  if (path === null) throw new HttpError("Not found", 404);
@@ -9995,7 +10059,8 @@ async function respond(cms, request, url, path, root, options, production) {
9995
10059
  context,
9996
10060
  via: signedIn ? via : null,
9997
10061
  token,
9998
- headers
10062
+ headers,
10063
+ trustProxy: options.trustProxy === true
9999
10064
  };
10000
10065
  const method = request.method.toUpperCase();
10001
10066
  if (method !== "GET" && method !== "HEAD") checkCsrf(cms, ctx);
@@ -10171,6 +10236,7 @@ async function route(cms, ctx, method, path, options) {
10171
10236
  case "POST logout": {
10172
10237
  if (ctx.token) await cms.auth.logout(ctx.token);
10173
10238
  clearSessionCookies(ctx);
10239
+ ctx.headers.set("clear-site-data", '"cache"');
10174
10240
  return { body: { message: "Logged out" } };
10175
10241
  }
10176
10242
  case "GET me": {
@@ -10406,7 +10472,12 @@ async function route(cms, ctx, method, path, options) {
10406
10472
  if (!ctx.user || ctx.user.apiKey) throw new UnauthorizedError();
10407
10473
  if (method !== "GET") throw methodNotAllowed(ctx, "GET");
10408
10474
  const ids = (ctx.url.searchParams.get("ids") ?? "").split(",").map((id2) => parseId(id2)).filter((id2) => id2 !== void 0);
10409
- return { body: { count: await cms.mediaUsage(ids) } };
10475
+ const count = await cms.mediaUsage(ids, {
10476
+ overrideAccess: false,
10477
+ user: ctx.user,
10478
+ context: ctx.context
10479
+ });
10480
+ return { body: { count } };
10410
10481
  }
10411
10482
  if (first === "admin" && second === "roles") {
10412
10483
  if (!ctx.user) throw new UnauthorizedError();
@@ -10551,9 +10622,10 @@ async function route(cms, ctx, method, path, options) {
10551
10622
  if (third !== void 0) return globalAction(cms, ctx, method, second, segments.slice(2));
10552
10623
  if (method === "GET") {
10553
10624
  if (ctx.url.searchParams.has("preview")) {
10554
- requirePreview(cms, ctx, { global: second });
10625
+ const as = await requirePreview(cms, ctx, { global: second });
10555
10626
  return {
10556
10627
  body: await cms.findGlobal(second, {
10628
+ ...as,
10557
10629
  ...parseDepth(ctx.url),
10558
10630
  ...parseLocale(ctx.url),
10559
10631
  draft: true
@@ -10662,8 +10734,9 @@ async function route(cms, ctx, method, path, options) {
10662
10734
  }
10663
10735
  const id = second;
10664
10736
  if (method === "GET" && ctx.url.searchParams.has("preview")) {
10665
- requirePreview(cms, ctx, { collection: collection2, id });
10737
+ const as = await requirePreview(cms, ctx, { collection: collection2, id });
10666
10738
  const doc = await cms.findById(collection2, id, {
10739
+ ...as,
10667
10740
  ...parseDepth(ctx.url),
10668
10741
  ...parseLocale(ctx.url),
10669
10742
  draft: true
@@ -10742,7 +10815,7 @@ async function documentAction(cms, ctx, method, collection2, id, path) {
10742
10815
  if (method !== "POST") throw methodNotAllowed(ctx, "POST");
10743
10816
  const body = await readJson(ctx.request);
10744
10817
  const result = await cms.preview(collection2, id, body, { ...access, ...depth });
10745
- const token = cms.createPreviewToken({ collection: collection2, id });
10818
+ const token = cms.createPreviewToken({ collection: collection2, id }, { user: ctx.user });
10746
10819
  return { body: { ...result, url: result.url && withPreviewToken(result.url, token) } };
10747
10820
  }
10748
10821
  if (path.length === 1 && (action === "unpublish" || action === "discard-draft")) {
@@ -10792,7 +10865,7 @@ async function globalAction(cms, ctx, method, slug2, path) {
10792
10865
  if (method !== "POST") throw methodNotAllowed(ctx, "POST");
10793
10866
  const body = await readJson(ctx.request);
10794
10867
  const result = await cms.previewGlobal(slug2, body, { ...access, ...depth });
10795
- const token = cms.createPreviewToken({ global: slug2 });
10868
+ const token = cms.createPreviewToken({ global: slug2 }, { user: ctx.user });
10796
10869
  return { body: { ...result, url: result.url && withPreviewToken(result.url, token) } };
10797
10870
  }
10798
10871
  if (path.length === 1 && (action === "unpublish" || action === "discard-draft")) {
@@ -10831,10 +10904,16 @@ function withPreviewToken(url, token) {
10831
10904
  const separator = base.includes("?") ? "&" : "?";
10832
10905
  return `${base}${separator}${PREVIEW_PARAM}=${encodeURIComponent(token)}${fragment}`;
10833
10906
  }
10834
- function requirePreview(cms, ctx, target) {
10907
+ async function requirePreview(cms, ctx, target) {
10835
10908
  const opened = cms.verifyPreviewToken(ctx.url.searchParams.get("preview"));
10836
10909
  const matches2 = opened !== null && ("global" in target ? "global" in opened && opened.global === target.global : "collection" in opened && opened.collection === target.collection && opened.id === target.id);
10837
10910
  if (!matches2) throw new UnauthorizedError("Invalid or expired preview token");
10911
+ if (opened.user === void 0) return {};
10912
+ const id = parseId(opened.user);
10913
+ const issuer = id === void 0 ? null : await cms.auth.activeUser(id);
10914
+ if (!issuer) throw new UnauthorizedError("Invalid or expired preview token");
10915
+ const { user, context } = await cms.applyOnRequest(ctx.request.headers, ctx.url, issuer);
10916
+ return { overrideAccess: false, user, context };
10838
10917
  }
10839
10918
  function parsePage(url) {
10840
10919
  const { page, limit } = parseListQuery(url);
@@ -10852,23 +10931,17 @@ function readToken(request) {
10852
10931
  return { token, via: token ? "cookie" : null };
10853
10932
  }
10854
10933
  function readCookie(request, name) {
10855
- const header = request.headers.get("cookie");
10856
- if (!header) return void 0;
10857
- for (const part of header.split(";")) {
10858
- const eq = part.indexOf("=");
10859
- if (eq > 0 && part.slice(0, eq).trim() === name)
10860
- return decodeURIComponent(part.slice(eq + 1).trim());
10861
- }
10862
- return void 0;
10934
+ return cookieValue(request.headers.get("cookie"), name);
10863
10935
  }
10864
- function sameHost(origin, request) {
10936
+ function sameHost(origin, request, trustProxy) {
10865
10937
  let host;
10866
10938
  try {
10867
10939
  host = new URL(origin).host;
10868
10940
  } catch {
10869
10941
  return false;
10870
10942
  }
10871
- const forwarded = request.headers.get("x-forwarded-host")?.split(",")[0]?.trim();
10943
+ const trusted = trustProxy || !!process.env.VERCEL || !!process.env.NETLIFY;
10944
+ const forwarded = trusted ? request.headers.get("x-forwarded-host")?.split(",")[0]?.trim() : void 0;
10872
10945
  return host === (forwarded || request.headers.get("host"));
10873
10946
  }
10874
10947
  function checkCsrf(cms, ctx) {
@@ -10876,7 +10949,7 @@ function checkCsrf(cms, ctx) {
10876
10949
  const fetchSite = ctx.request.headers.get("sec-fetch-site");
10877
10950
  const cookie2 = ctx.via === "cookie" && ctx.token !== void 0;
10878
10951
  const { cors } = cms.config;
10879
- const trusted = origin !== null && (origin === ctx.url.origin || sameHost(origin, ctx.request) || cms.config.auth.trustedOrigins.includes(origin) || !cookie2 && (cors === "*" || cors.includes(origin)));
10952
+ const trusted = origin !== null && (origin === ctx.url.origin || sameHost(origin, ctx.request, ctx.trustProxy) || cms.config.auth.trustedOrigins.includes(origin) || !cookie2 && (cors === "*" || cors.includes(origin)));
10880
10953
  if (origin !== null && !trusted) throw new ForbiddenError("CSRF check failed: untrusted origin");
10881
10954
  if (origin === null && fetchSite === "cross-site")
10882
10955
  throw new ForbiddenError("CSRF check failed: cross-site request");
@@ -10898,7 +10971,7 @@ async function serveFile(cms, key, head) {
10898
10971
  const file = await cms.storage.get(key);
10899
10972
  if (!file) throw new HttpError("Not found", 404);
10900
10973
  const extension = key.slice(key.lastIndexOf(".") + 1);
10901
- const type = TYPE_BY_EXTENSION[extension] ?? "application/octet-stream";
10974
+ const type = withCharset(TYPE_BY_EXTENSION[extension] ?? "application/octet-stream");
10902
10975
  return new Response(
10903
10976
  head ? null : file.body,
10904
10977
  {
@@ -10941,7 +11014,9 @@ async function servePrivateFile(cms, ctx, key, head) {
10941
11014
  }
10942
11015
  const file = await cms.privateStorage.get(key);
10943
11016
  if (!file) throw new HttpError("Not found", 404);
10944
- const type = TYPE_BY_EXTENSION[key.slice(key.lastIndexOf(".") + 1)] ?? "application/octet-stream";
11017
+ const type = withCharset(
11018
+ TYPE_BY_EXTENSION[key.slice(key.lastIndexOf(".") + 1)] ?? "application/octet-stream"
11019
+ );
10945
11020
  return new Response(
10946
11021
  head ? null : file.body,
10947
11022
  {
@@ -11052,9 +11127,14 @@ function isSecure(ctx) {
11052
11127
  return ctx.url.protocol === "https:" || process.env.NODE_ENV === "production";
11053
11128
  }
11054
11129
  function cookie(ctx, name, value, opts) {
11055
- const parts = [`${name}=${encodeURIComponent(value)}`, "Path=/", "SameSite=Lax"];
11130
+ const secure = isSecure(ctx);
11131
+ const parts = [
11132
+ `${secure ? hostCookie(name) : name}=${encodeURIComponent(value)}`,
11133
+ "Path=/",
11134
+ "SameSite=Lax"
11135
+ ];
11056
11136
  if (opts.httpOnly) parts.push("HttpOnly");
11057
- if (isSecure(ctx)) parts.push("Secure");
11137
+ if (secure) parts.push("Secure");
11058
11138
  if (opts.maxAge !== void 0) parts.push(`Max-Age=${opts.maxAge}`);
11059
11139
  return parts.join("; ");
11060
11140
  }
@@ -11133,6 +11213,9 @@ function setSessionCookies(cms, ctx, session) {
11133
11213
  function clearSessionCookies(ctx) {
11134
11214
  ctx.headers.append("set-cookie", cookie(ctx, SESSION_COOKIE, "", { httpOnly: true, maxAge: 0 }));
11135
11215
  ctx.headers.append("set-cookie", cookie(ctx, CSRF_COOKIE, "", { httpOnly: false, maxAge: 0 }));
11216
+ if (isSecure(ctx))
11217
+ for (const name of [SESSION_COOKIE, CSRF_COOKIE])
11218
+ ctx.headers.append("set-cookie", `${name}=; Path=/; Max-Age=0; Secure; SameSite=Lax`);
11136
11219
  }
11137
11220
  function errorResponse(cms, error, headers, production) {
11138
11221
  if (error instanceof EasyCMSError) {
@@ -11198,6 +11281,7 @@ function createApiHandler(config, options = {}) {
11198
11281
  if (!handler) {
11199
11282
  handler = createRestHandler(cms, {
11200
11283
  getClientIp,
11284
+ trustProxy: options.trustProxy === true,
11201
11285
  ...options.basePath ? { basePath: options.basePath } : {}
11202
11286
  });
11203
11287
  handlers.set(cms, handler);
@@ -271,6 +271,12 @@ function sizeKey(filename, size) {
271
271
  return filename.replace(/\.([^.]+)$/, `-${size}.$1`);
272
272
  }
273
273
  var PRIVATE_KEY = /^[\p{L}\p{M}\p{N}-]+\.private(-[\p{L}\p{N}_-]+)?\.[a-z0-9]+$/u;
274
+ function withCharset(type) {
275
+ if (/;\s*charset=/i.test(type)) return type;
276
+ const base = type.split(";")[0]?.trim().toLowerCase() ?? "";
277
+ const text = base.startsWith("text/") || /^application\/(json|xml|javascript|ecmascript)$/.test(base) || /\+(json|xml)$/.test(base);
278
+ return text ? `${type}; charset=utf-8` : type;
279
+ }
274
280
 
275
281
  // src/fields.ts
276
282
  var uniqueWithinOf = (field) => field.uniqueWithin === void 0 ? [] : [field.uniqueWithin].flat();
@@ -534,6 +540,7 @@ export {
534
540
  withPrivacy,
535
541
  sizeKey,
536
542
  PRIVATE_KEY,
543
+ withCharset,
537
544
  uniqueWithinOf,
538
545
  isHasMany,
539
546
  mimeAllowedBy,
@@ -1746,9 +1746,10 @@ declare class EasyCMS<C extends Config = Config> {
1746
1746
  private mediaSignature;
1747
1747
  /**
1748
1748
  * How many documents use any of these files in a top-level upload field (rich text and fields
1749
- * inside groups, arrays and blocks are not counted). Drafts count too.
1749
+ * inside groups, arrays and blocks are not counted). Drafts count too. With
1750
+ * `overrideAccess: false`, only documents the user may read.
1750
1751
  */
1751
- mediaUsage(ids: readonly ID[]): Promise<number>;
1752
+ mediaUsage(ids: readonly ID[], options?: AccessOptions): Promise<number>;
1752
1753
  /** The storage for public files, or for private ones. */
1753
1754
  storageFor(isPrivate: boolean): StorageAdapter;
1754
1755
  /**
@@ -1811,7 +1812,9 @@ declare class EasyCMS<C extends Config = Config> {
1811
1812
  /**
1812
1813
  * A token that lets a page read one document's current draft for a while (default one hour),
1813
1814
  * without a login: `GET /api/cms/:collection/:id?preview=<token>`. The admin adds one to the
1814
- * preview URL as `easy-cms-preview`, for sites on another origin.
1815
+ * preview URL as `easy-cms-preview`, for sites on another origin. With `user`, the page reads
1816
+ * it with that user's access (fields they can't read are left out; a deactivated user's token
1817
+ * stops working); without, with full access.
1815
1818
  */
1816
1819
  createPreviewToken(target: {
1817
1820
  collection: string;
@@ -1820,10 +1823,12 @@ declare class EasyCMS<C extends Config = Config> {
1820
1823
  global: string;
1821
1824
  }, options?: {
1822
1825
  expiresIn?: number;
1826
+ user?: AuthUser | null;
1823
1827
  }): string;
1824
1828
  /** What a preview token opens, or `null` when it is invalid or expired. */
1825
1829
  verifyPreviewToken(token: string | null | undefined): (PreviewTarget & {
1826
1830
  expiresAt: number;
1831
+ user?: string;
1827
1832
  }) | null;
1828
1833
  /** Live preview of a global. See `preview`. */
1829
1834
  previewGlobal<S extends GSlug<C>>(slug: S, data: Record<string, unknown>, options?: DepthOptions): Promise<LivePreview<GDoc<C, S>>>;
@@ -1992,7 +1997,15 @@ declare class EasyCMS<C extends Config = Config> {
1992
1997
  private checkGrant;
1993
1998
  /** The query constraint read access adds, or `undefined` when there is none. Throws when denied. */
1994
1999
  private readWhere;
2000
+ /**
2001
+ * Throws unless the caller may update or delete this document. A user who may not even read
2002
+ * it gets `NotFoundError`, as for an id that doesn't exist: a 403 would tell it does.
2003
+ */
1995
2004
  private checkDocumentAccess;
2005
+ /** Runs a check; a `ForbiddenError` for a document the user can't read becomes a 404. */
2006
+ private hidingDenied;
2007
+ private mayRead;
2008
+ private documentAccess;
1996
2009
  private checkGlobalAccess;
1997
2010
  private fieldChecker;
1998
2011
  /**
@@ -1,4 +1,4 @@
1
- import { I as ID, ab as Config, au as EasyCMS, bd as LocalizationConfig, aD as Field, aH as FieldError, aS as FilterOptions, af as CreateEasyCMSOptions, bI as ResolvedConfig } from './config-jzFGsNL6.js';
1
+ import { I as ID, ab as Config, au as EasyCMS, bd as LocalizationConfig, aD as Field, aH as FieldError, aS as FilterOptions, af as CreateEasyCMSOptions, bI as ResolvedConfig } from './config-Dgs6FE0S.js';
2
2
 
3
3
  type BackupState = 'pending' | 'running' | 'done' | 'failed';
4
4
  /** One backup as the admin shows it (`GET <api>/admin/backups`). */
@@ -172,6 +172,11 @@ interface RestHandlerOptions {
172
172
  readonly basePath?: string;
173
173
  /** Client IP, used with the email to rate-limit logins. Adapters provide it. */
174
174
  readonly getClientIp?: (request: Request) => string | undefined;
175
+ /**
176
+ * Behind a reverse proxy you control: its `X-Forwarded-Host` names the public host (for the
177
+ * CSRF check of a request's Origin). Vercel and Netlify are recognized without it.
178
+ */
179
+ readonly trustProxy?: boolean;
175
180
  }
176
181
  type RestHandler = (request: Request) => Promise<Response>;
177
182
  /** Creates the REST API as a Web-standard `(Request) => Response` handler. */
@@ -183,6 +188,7 @@ declare function createRestHandler<C extends Config>(instance: EasyCMS<C>, optio
183
188
  * use it; the standalone server does.
184
189
  */
185
190
  declare function createRootEndpointHandler<C extends Config>(instance: EasyCMS<C>, options?: Pick<RestHandlerOptions, 'getClientIp'>): (request: Request) => Promise<Response | undefined>;
191
+ /** A cookie of the request; the `__Host-` one (set over HTTPS) first. */
186
192
  declare function readCookie(request: Request, name: string): string | undefined;
187
193
 
188
194
  export { type AdminBackup as A, type BackupState as B, CONFIG_FILE_NAMES as C, DEFAULT_ADMIN_PATH as D, validateFields as E, writeBackupFile as F, type LoadConfigOptions as L, type RestHandler as R, type AdminBackups as a, type ApiHandlerOptions as b, type RestHandlerOptions as c, createApiHandler as d, createRestHandler as e, slugify as f, CSRF_COOKIE as g, CSRF_HEADER as h, DEFAULT_API_PATH as i, DEFAULT_MAX_FILE_SIZE as j, DEFAULT_TOKEN_EXPIRATION as k, loadConfig as l, applyDefaults as m, applyPlugins as n, createRootEndpointHandler as o, fillMissing as p, findConfigFile as q, resolveConfig as r, sharedEasyCMS as s, forwardedClientIp as t, generateSlugs as u, importConfig as v, mergeForUpdate as w, parseId as x, platformClientIp as y, readCookie as z };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
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, w as warnDeprecated } from './plugin-C19Bt8ux.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';
1
+ import { P as PaginatedDocs, I as ID, E as EmailSetting } from './config-Dgs6FE0S.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-Dgs6FE0S.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, w as warnDeprecated } from './plugin-8hrkYMuR.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-CcmsrLs8.js';
5
5
  export { FieldCondition, matchesCondition } from './conditions.js';
6
6
 
7
7
  type DeliveryKind = 'webhook' | 'email';
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  sharedEasyCMS,
24
24
  silentLogger,
25
25
  slugify
26
- } from "./chunk-CX7R6QBJ.js";
26
+ } from "./chunk-JZW6G6XI.js";
27
27
  import {
28
28
  matchesCondition
29
29
  } from "./chunk-U266LV4M.js";
@@ -48,7 +48,7 @@ import {
48
48
  defineFieldType,
49
49
  definePlugin,
50
50
  warnDeprecated
51
- } from "./chunk-FPFWLQB5.js";
51
+ } from "./chunk-RTKWMVAC.js";
52
52
  export {
53
53
  ADAPTER_API_VERSION,
54
54
  ADMIN_API_VERSION,
@@ -1,6 +1,6 @@
1
- import { ab as Config, al as Database, bl as NavGroup, a6 as CollectionConfig, I as ID, bE as RawDocument, aD as Field, bI as ResolvedConfig, ad as ConfigIssue } from './config-jzFGsNL6.js';
2
- export { cH as API_KEYS, cI as API_KEY_GLOBAL_OPERATIONS, cJ as API_KEY_OPERATIONS, cK as DEFAULT_MAX_VERSIONS, cL as DEFAULT_PASSWORD_EMAILS, cM as FIELD_TYPE_BASES, cN as FOLDER_LEVELS, cO as MediaFolders, cP as SSO_COOKIE, cQ as codeOfStatus, cx as hasRows, cz as isHasMany, cR as keyAllows, cD as mimeAllowedBy, cE as rowFields, cG as uniqueWithinOf } from './config-jzFGsNL6.js';
3
- export { C as CONFIG_FILE_NAMES, g as CSRF_COOKIE, h as CSRF_HEADER, D as DEFAULT_ADMIN_PATH, i as DEFAULT_API_PATH, j as DEFAULT_MAX_FILE_SIZE, k as DEFAULT_TOKEN_EXPIRATION, m as applyDefaults, n as applyPlugins, o as createRootEndpointHandler, p as fillMissing, q as findConfigFile, t as forwardedClientIp, u as generateSlugs, v as importConfig, w as mergeForUpdate, x as parseId, y as platformClientIp, z as readCookie, E as validateFields, F as writeBackupFile } from './handler-CMA1LIq7.js';
1
+ import { ab as Config, al as Database, bl as NavGroup, a6 as CollectionConfig, I as ID, bE as RawDocument, aD as Field, bI as ResolvedConfig, ad as ConfigIssue } from './config-Dgs6FE0S.js';
2
+ export { cH as API_KEYS, cI as API_KEY_GLOBAL_OPERATIONS, cJ as API_KEY_OPERATIONS, cK as DEFAULT_MAX_VERSIONS, cL as DEFAULT_PASSWORD_EMAILS, cM as FIELD_TYPE_BASES, cN as FOLDER_LEVELS, cO as MediaFolders, cP as SSO_COOKIE, cQ as codeOfStatus, cx as hasRows, cz as isHasMany, cR as keyAllows, cD as mimeAllowedBy, cE as rowFields, cG as uniqueWithinOf } from './config-Dgs6FE0S.js';
3
+ export { C as CONFIG_FILE_NAMES, g as CSRF_COOKIE, h as CSRF_HEADER, D as DEFAULT_ADMIN_PATH, i as DEFAULT_API_PATH, j as DEFAULT_MAX_FILE_SIZE, k as DEFAULT_TOKEN_EXPIRATION, m as applyDefaults, n as applyPlugins, o as createRootEndpointHandler, p as fillMissing, q as findConfigFile, t as forwardedClientIp, u as generateSlugs, v as importConfig, w as mergeForUpdate, x as parseId, y as platformClientIp, z as readCookie, E as validateFields, F as writeBackupFile } from './handler-CcmsrLs8.js';
4
4
  export { conditionIssues } from './conditions.js';
5
5
 
6
6
  /** The session cookie the REST API sets at login. */
package/dist/internal.js CHANGED
@@ -52,7 +52,7 @@ import {
52
52
  verifyPassword,
53
53
  verifyTurnstile,
54
54
  writeBackupFile
55
- } from "./chunk-CX7R6QBJ.js";
55
+ } from "./chunk-JZW6G6XI.js";
56
56
  import {
57
57
  conditionIssues
58
58
  } from "./chunk-U266LV4M.js";
@@ -70,7 +70,7 @@ import {
70
70
  rowFields,
71
71
  sniffMimeType,
72
72
  uniqueWithinOf
73
- } from "./chunk-FPFWLQB5.js";
73
+ } from "./chunk-RTKWMVAC.js";
74
74
 
75
75
  // src/typegen.ts
76
76
  var pascal = (name) => name.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -1,4 +1,4 @@
1
- import { ba as Label, aD as Field, I as ID, k as AdminLocale, t as AdminSwitcher, c1 as SsoProviderRef } from './config-jzFGsNL6.js';
1
+ import { ba as Label, aD as Field, I as ID, k as AdminLocale, t as AdminSwitcher, c1 as SsoProviderRef } from './config-Dgs6FE0S.js';
2
2
  import { FieldCondition } from './conditions.js';
3
3
 
4
4
  /** A Web Component from an admin module, with its `props` (plain JSON). */
package/dist/plugin.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { a as ADMIN_ICONS, g as AdminComponent, j as AdminIcon, S as AuthUser, a3 as CliCommand, a4 as CollectionAccess, a6 as CollectionConfig, ab as Config, ac as ConfigError, ak as DashboardWidget, av as EasyCMSError, az as Endpoint, aA as EndpointRequest, aB as ErrorCode, aD as Field, aE as FieldAccess, aH as FieldError, aK as FieldTypeBase, aL as FieldTypeDefinition, aM as FieldTypeValidateContext, aY as ForbiddenError, a_ as GlobalConfig, I as ID, b8 as JobConfig, ba as Label, bl as NavGroup, bm as NotFoundError, bo as OnRequest, bp as OnRequestArgs, br as PLUGIN_API_VERSION, bw as PayloadTooLargeError, bx as Plugin, by as PluginInfo, bz as PluginTypes, bC as QueryError, bH as RequestContext, c6 as TooManyRequestsError, c7 as TypedPlugin, c8 as UnauthorizedError, cf as ValidationError, cn as Where, cu as defineFieldType, cv as definePlugin } from './config-jzFGsNL6.js';
2
- export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-C19Bt8ux.js';
1
+ export { a as ADMIN_ICONS, g as AdminComponent, j as AdminIcon, S as AuthUser, a3 as CliCommand, a4 as CollectionAccess, a6 as CollectionConfig, ab as Config, ac as ConfigError, ak as DashboardWidget, av as EasyCMSError, az as Endpoint, aA as EndpointRequest, aB as ErrorCode, aD as Field, aE as FieldAccess, aH as FieldError, aK as FieldTypeBase, aL as FieldTypeDefinition, aM as FieldTypeValidateContext, aY as ForbiddenError, a_ as GlobalConfig, I as ID, b8 as JobConfig, ba as Label, bl as NavGroup, bm as NotFoundError, bo as OnRequest, bp as OnRequestArgs, br as PLUGIN_API_VERSION, bw as PayloadTooLargeError, bx as Plugin, by as PluginInfo, bz as PluginTypes, bC as QueryError, bH as RequestContext, c6 as TooManyRequestsError, c7 as TypedPlugin, c8 as UnauthorizedError, cf as ValidationError, cn as Where, cu as defineFieldType, cv as definePlugin } from './config-Dgs6FE0S.js';
2
+ export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-8hrkYMuR.js';
3
3
  import './conditions.js';
package/dist/plugin.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  defineFieldType,
17
17
  definePlugin,
18
18
  warnDeprecated
19
- } from "./chunk-FPFWLQB5.js";
19
+ } from "./chunk-RTKWMVAC.js";
20
20
  export {
21
21
  ADMIN_API_VERSION,
22
22
  ADMIN_ICONS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easy-cms/core",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "description": "Embedded, code-first headless CMS for Nuxt and Next.js",
5
5
  "license": "MIT",
6
6
  "type": "module",