@easy-cms/core 1.9.0 → 1.11.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.
@@ -3874,7 +3874,7 @@ var VersionStore = class {
3874
3874
  };
3875
3875
 
3876
3876
  // src/local-api.ts
3877
- import { createHash as createHash4, createHmac as createHmac6, randomBytes as randomBytes6, randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual5 } from "crypto";
3877
+ import { createHash as createHash5, createHmac as createHmac6, randomBytes as randomBytes6, randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual5 } from "crypto";
3878
3878
 
3879
3879
  // src/auth/common-passwords.ts
3880
3880
  var LIST = `
@@ -4426,7 +4426,7 @@ function read(response, maxBytes, deadline) {
4426
4426
  var keys = (list) => new Set(list.split(/\s+/).filter(Boolean));
4427
4427
  var CONFIG = keys(`secret db serverURL cors cronSecret timeZone webhooks events jobs localization routes admin
4428
4428
  upload auth collections globals endpoints cliCommands onRequest fieldTypes apiKeys email backups
4429
- audit logs countCacheSeconds maxPage plugins`);
4429
+ audit logs countCacheSeconds maxPage httpCache plugins`);
4430
4430
  var ADMIN = keys("path locale brand siteURL nav commands modules pages dashboard switcher");
4431
4431
  var AUTH = keys(`roles rbac tokenExpiration maxLoginAttempts lockWindow trustedOrigins
4432
4432
  resetPasswordExpiration inviteExpiration providers providerSignUp password setupCode members emails`);
@@ -4436,8 +4436,10 @@ var BACKUPS = keys("frequency at keep dir storage sqlite encryptionKey");
4436
4436
  var AUDIT = keys("keepDays values failedLogins scope");
4437
4437
  var LOGS = keys("format requests");
4438
4438
  var COLLECTION = keys(`slug labels fields useAsTitle drafts versions schedule preview access hooks
4439
- admin`);
4440
- var GLOBAL = keys("slug label fields drafts versions schedule preview access hooks scope admin");
4439
+ admin httpCache`);
4440
+ var GLOBAL = keys(
4441
+ "slug label fields drafts versions schedule preview access hooks scope admin httpCache"
4442
+ );
4441
4443
  var CONTAINER_ADMIN = keys("icon order sidebar layout group");
4442
4444
  var COLLECTION_ADMIN = keys(
4443
4445
  "icon order sidebar layout group editIn badge count empty list ownerField confirmDelete"
@@ -4866,6 +4868,7 @@ var RESERVED_SLUGS = /* @__PURE__ */ new Set([
4866
4868
  "readyz"
4867
4869
  ]);
4868
4870
  var SYSTEM_FIELD_NAMES = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt"]);
4871
+ var TRIGRAM_TYPES = /* @__PURE__ */ new Set(["text", "textarea", "email"]);
4869
4872
  var SLUG_PATTERN = /^[a-z][a-z0-9_-]*$/;
4870
4873
  var FIELD_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
4871
4874
  function validateConfig(config) {
@@ -4890,6 +4893,7 @@ function validateConfig(config) {
4890
4893
  validateLogs(config, add);
4891
4894
  validateCountCache(config, add);
4892
4895
  validateMaxPage(config, add);
4896
+ validateHttpCache(config, add);
4893
4897
  validateAuth(config, add);
4894
4898
  validateCors(config.cors, add);
4895
4899
  validateLocalization(config, add);
@@ -5512,6 +5516,26 @@ function validateUpload(config, add) {
5512
5516
  var FOLDER_KEY = /^[a-z0-9][a-z0-9_-]{0,59}$/;
5513
5517
  var MIME_PATTERN = /^([\w.+-]+\/(\*|[\w.+-]+)|documents|office|archives)$/i;
5514
5518
  var HOST_PATTERN = /^(\*|(\*\.)?[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*)$/i;
5519
+ function validateHttpCache(config, add) {
5520
+ const times = (value, path, purge) => {
5521
+ if (value === void 0) return;
5522
+ if (value === false && !purge) return;
5523
+ if (typeof value !== "object" || value === null) {
5524
+ add(path, purge ? "must be an object" : "must be false or an object");
5525
+ return;
5526
+ }
5527
+ const v = value;
5528
+ for (const key of ["sMaxAge", "staleWhileRevalidate"])
5529
+ if (v[key] !== void 0 && !(Number.isInteger(v[key]) && v[key] >= 0))
5530
+ add(`${path}.${key}`, "must be a whole number of seconds, 0 or more");
5531
+ if (purge && v.purge !== void 0 && typeof v.purge !== "function")
5532
+ add(`${path}.purge`, "must be a function");
5533
+ };
5534
+ times(config.httpCache, "httpCache", true);
5535
+ for (const c of config.collections ?? [])
5536
+ times(c?.httpCache, `collections.${c?.slug}.httpCache`, false);
5537
+ for (const g of config.globals ?? []) times(g?.httpCache, `globals.${g?.slug}.httpCache`, false);
5538
+ }
5515
5539
  function validateMaxPage(config, add) {
5516
5540
  const max = config.maxPage;
5517
5541
  if (max !== void 0 && !(Number.isInteger(max) && max >= 1))
@@ -5831,6 +5855,15 @@ function validateFields2(fields, path, reserved, collectionSlugs, add) {
5831
5855
  }
5832
5856
  if (names.has(name)) add(`${path}.${name}`, `duplicate field name "${name}"`);
5833
5857
  names.add(name);
5858
+ const index = field.index;
5859
+ if (index !== void 0 && typeof index !== "boolean" && index !== "trigram")
5860
+ add(`${path}.${name}.index`, "must be true, false or 'trigram'");
5861
+ else if (index === "trigram" && !TRIGRAM_TYPES.has(field.type))
5862
+ add(
5863
+ `${path}.${name}.index`,
5864
+ `'trigram' is for text, textarea and email fields, not ${field.type}`,
5865
+ "use index: true"
5866
+ );
5834
5867
  byName.set(name, field);
5835
5868
  });
5836
5869
  for (const field of fields) {
@@ -6403,6 +6436,41 @@ function resolveAudit(audit) {
6403
6436
  };
6404
6437
  }
6405
6438
 
6439
+ // src/rest/http-cache.ts
6440
+ import { createHash as createHash4 } from "crypto";
6441
+ var cacheTags = {
6442
+ collection: (slug2) => ["ecms", `ecms:${slug2}`],
6443
+ document: (slug2, id) => ["ecms", `ecms:${slug2}`, `ecms:${slug2}:${id}`],
6444
+ global: (slug2) => ["ecms", `ecms:global:${slug2}`]
6445
+ };
6446
+ function cacheTimes(config, container) {
6447
+ const own = container.httpCache;
6448
+ if (own === false || own === void 0 && config.httpCache === void 0) return null;
6449
+ return {
6450
+ sMaxAge: own?.sMaxAge ?? config.httpCache?.sMaxAge ?? 60,
6451
+ staleWhileRevalidate: own?.staleWhileRevalidate ?? config.httpCache?.staleWhileRevalidate ?? 300
6452
+ };
6453
+ }
6454
+ function matches2(header, etag) {
6455
+ if (!header) return false;
6456
+ if (header.trim() === "*") return true;
6457
+ const bare = (tag) => tag.trim().replace(/^W\//, "");
6458
+ return header.split(",").some((tag) => bare(tag) === bare(etag));
6459
+ }
6460
+ function cacheableResponse(request, body, status, headers, times, tags) {
6461
+ headers.set(
6462
+ "cache-control",
6463
+ `public, max-age=0, s-maxage=${times.sMaxAge}, stale-while-revalidate=${times.staleWhileRevalidate}`
6464
+ );
6465
+ headers.set("cache-tag", tags.join(","));
6466
+ headers.set("surrogate-key", tags.join(" "));
6467
+ const etag = `W/"${createHash4("sha1").update(body).digest("base64url")}"`;
6468
+ headers.set("etag", etag);
6469
+ if (matches2(request.headers.get("if-none-match"), etag))
6470
+ return new Response(null, { status: 304, headers });
6471
+ return new Response(request.method.toUpperCase() === "HEAD" ? null : body, { status, headers });
6472
+ }
6473
+
6406
6474
  // src/local-api.ts
6407
6475
  async function createEasyCMS(config, options = {}) {
6408
6476
  const resolved3 = await resolveConfig(config);
@@ -8381,11 +8449,11 @@ var EasyCMS = class {
8381
8449
  const access = allowed2 === false ? false : andWhere(own === true ? void 0 : own, allowed2) ?? true;
8382
8450
  if (access === true) return;
8383
8451
  if (access !== false) {
8384
- const matches2 = await this.db.count({
8452
+ const matches3 = await this.db.count({
8385
8453
  collection: config.slug,
8386
8454
  where: andWhere(access, { id: { equals: id } })
8387
8455
  });
8388
- if (matches2 > 0) return;
8456
+ if (matches3 > 0) return;
8389
8457
  }
8390
8458
  throw deny(guard.user);
8391
8459
  }
@@ -8439,11 +8507,11 @@ var EasyCMS = class {
8439
8507
  await this.hidingDenied(collection2, guard, id, async () => {
8440
8508
  const own = await this.checkGrant(guard, { collection: collection2 }, operation);
8441
8509
  if (own === true) return;
8442
- const matches2 = await this.db.count({
8510
+ const matches3 = await this.db.count({
8443
8511
  collection: collection2,
8444
8512
  where: andWhere(own, { id: { equals: id } })
8445
8513
  });
8446
- if (matches2 === 0) throw deny(guard.user);
8514
+ if (matches3 === 0) throw deny(guard.user);
8447
8515
  });
8448
8516
  }
8449
8517
  /** Runs afterRead hooks, populates relationships and removes what the caller may not see. */
@@ -8605,6 +8673,20 @@ var EasyCMS = class {
8605
8673
  return version && doc.status === "published" && version.status === "draft" ? draftOf(doc, version) : doc;
8606
8674
  });
8607
8675
  }
8676
+ /**
8677
+ * `httpCache.purge`: what readers see changed (anything, without drafts; with drafts, a
8678
+ * published document saved, published, unpublished or deleted). Not awaited; failures are
8679
+ * logged.
8680
+ */
8681
+ purgePublic(config, isGlobal, event, doc, before) {
8682
+ const purge = this.config.httpCache?.purge;
8683
+ if (!purge || config.httpCache === false || event === "draft") return;
8684
+ if (config.drafts && doc.status !== "published" && before !== "published") return;
8685
+ const tags = isGlobal ? cacheTags.global(config.slug) : cacheTags.document(config.slug, doc.id);
8686
+ void Promise.resolve().then(() => purge(tags)).catch(
8687
+ (error) => this.logger.error(`httpCache.purge failed: ${error.message}`, { error })
8688
+ );
8689
+ }
8608
8690
  /** Sends webhook events for a change; `before` is the stored status before it, if any. */
8609
8691
  emitChange(config, event, doc, before) {
8610
8692
  const isGlobal = !this.config.collections.includes(config);
@@ -8617,6 +8699,7 @@ var EasyCMS = class {
8617
8699
  };
8618
8700
  const target = isGlobal ? { global: config.slug } : { collection: config.slug, id: doc.id };
8619
8701
  this.webhooks.emit(event, target, body);
8702
+ this.purgePublic(config, isGlobal, event, doc, before);
8620
8703
  if (event === "draft" || event === "delete" || !config.drafts) return;
8621
8704
  const after = doc.status;
8622
8705
  if (after === "published" && before !== "published") this.webhooks.emit("publish", target, body);
@@ -9066,7 +9149,7 @@ function durationSeconds(value) {
9066
9149
  }
9067
9150
  function cursorQuery(collection2, where, sort, locale, draft) {
9068
9151
  const key = JSON.stringify([collection2, where ?? null, sort, locale ?? null, draft]);
9069
- return createHash4("sha256").update(key).digest("base64url").slice(0, 16);
9152
+ return createHash5("sha256").update(key).digest("base64url").slice(0, 16);
9070
9153
  }
9071
9154
  function writeCursor(keys2, query) {
9072
9155
  return Buffer.from(JSON.stringify({ k: keys2, q: query })).toString("base64url");
@@ -9817,7 +9900,7 @@ function rank(role, config) {
9817
9900
  }
9818
9901
 
9819
9902
  // src/rest/admin-modules.ts
9820
- import { createHash as createHash5 } from "crypto";
9903
+ import { createHash as createHash6 } from "crypto";
9821
9904
  import { readFile as readFile3 } from "fs/promises";
9822
9905
  import { createRequire } from "module";
9823
9906
  import { join as join3 } from "path";
@@ -9858,11 +9941,11 @@ async function readAdminModule(cms, index) {
9858
9941
  if (!file) return void 0;
9859
9942
  const body = await readFile3(file, "utf8").catch(() => void 0);
9860
9943
  if (body === void 0) return void 0;
9861
- return { body, etag: `"${createHash5("sha1").update(body).digest("base64url")}"` };
9944
+ return { body, etag: `"${createHash6("sha1").update(body).digest("base64url")}"` };
9862
9945
  }
9863
9946
 
9864
9947
  // src/version.ts
9865
- var VERSION = true ? "1.9.0" : "0.0.0-dev";
9948
+ var VERSION = true ? "1.11.0" : "0.0.0-dev";
9866
9949
 
9867
9950
  // src/rest/handler.ts
9868
9951
  import { randomUUID as randomUUID4 } from "crypto";
@@ -10907,7 +10990,18 @@ async function respondNow(cms, request, url, path, root, options, production, re
10907
10990
  );
10908
10991
  if (!result) throw new HttpError("Not found", 404);
10909
10992
  if (result.body instanceof Response) return result.body;
10910
- return new Response(JSON.stringify(result.body), { status: result.status ?? 200, headers });
10993
+ const body = JSON.stringify(result.body);
10994
+ const times = result.cache && !user && !ctx.via && !ctx.token && !url.searchParams.has("draft") ? cacheTimes(cms.config, result.cache.container) : null;
10995
+ if (times && result.cache)
10996
+ return cacheableResponse(
10997
+ request,
10998
+ body,
10999
+ result.status ?? 200,
11000
+ headers,
11001
+ times,
11002
+ result.cache.tags
11003
+ );
11004
+ return new Response(body, { status: result.status ?? 200, headers });
10911
11005
  } catch (error) {
10912
11006
  return errorResponse(cms, error, headers, production);
10913
11007
  }
@@ -11491,7 +11585,11 @@ async function route(cms, ctx, method, path, options) {
11491
11585
  })
11492
11586
  };
11493
11587
  }
11494
- return { body: await cms.findGlobal(second, { ...access, ...parseDepth(ctx.url), draft }) };
11588
+ const global2 = cms.config.globals.find((g) => g.slug === second);
11589
+ return {
11590
+ body: await cms.findGlobal(second, { ...access, ...parseDepth(ctx.url), draft }),
11591
+ cache: { container: global2, tags: cacheTags.global(second) }
11592
+ };
11495
11593
  }
11496
11594
  if (method === "PATCH" || method === "POST") {
11497
11595
  if (method === "POST")
@@ -11588,7 +11686,8 @@ async function route(cms, ctx, method, path, options) {
11588
11686
  body: await cms.find(
11589
11687
  collection2,
11590
11688
  query.cursor === void 0 ? { ...options2, page } : options2
11591
- )
11689
+ ),
11690
+ cache: { container: cms.collection(collection2), tags: cacheTags.collection(collection2) }
11592
11691
  };
11593
11692
  }
11594
11693
  if (method === "POST") {
@@ -11615,7 +11714,13 @@ async function route(cms, ctx, method, path, options) {
11615
11714
  if (method === "GET") {
11616
11715
  const doc = await cms.findById(collection2, id, { ...access, ...parseDepth(ctx.url), draft });
11617
11716
  if (!doc) throw new NotFoundError(collection2, id);
11618
- return { body: doc };
11717
+ return {
11718
+ body: doc,
11719
+ cache: {
11720
+ container: cms.collection(collection2),
11721
+ tags: cacheTags.document(collection2, doc.id)
11722
+ }
11723
+ };
11619
11724
  }
11620
11725
  if (method === "PATCH") {
11621
11726
  const body = await readJson(ctx.request);
@@ -11774,8 +11879,8 @@ function withPreviewToken(url, token) {
11774
11879
  }
11775
11880
  async function requirePreview(cms, ctx, target) {
11776
11881
  const opened = cms.verifyPreviewToken(ctx.url.searchParams.get("preview"));
11777
- 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);
11778
- if (!matches2) throw new UnauthorizedError("Invalid or expired preview token");
11882
+ const matches3 = opened !== null && ("global" in target ? "global" in opened && opened.global === target.global : "collection" in opened && opened.collection === target.collection && opened.id === target.id);
11883
+ if (!matches3) throw new UnauthorizedError("Invalid or expired preview token");
11779
11884
  if (opened.user === void 0) return {};
11780
11885
  const id = parseId(opened.user);
11781
11886
  const issuer = id === void 0 ? null : await cms.auth.activeUser(id);
@@ -12283,6 +12388,7 @@ export {
12283
12388
  DEFAULT_TOKEN_EXPIRATION,
12284
12389
  applyPlugins,
12285
12390
  resolveConfig,
12391
+ cacheTags,
12286
12392
  createEasyCMS,
12287
12393
  EasyCMS,
12288
12394
  resolveAdminModule,
@@ -788,7 +788,12 @@ interface BaseField<TType extends string, TValue> {
788
788
  * can both be `team`. Single, unlocalized relationship, select, text or number fields.
789
789
  */
790
790
  readonly uniqueWithin?: string | readonly string[];
791
- readonly index?: boolean;
791
+ /**
792
+ * `true` for a database index (filters and sorts on this field). `'trigram'`, on text, textarea
793
+ * and email fields: an index for `like`, which finds any part of the text, in Thai as in other
794
+ * languages. Postgres only (pg_trgm); nothing on SQLite.
795
+ */
796
+ readonly index?: boolean | 'trigram';
792
797
  readonly defaultValue?: TValue;
793
798
  readonly validate?: FieldValidate<TValue>;
794
799
  readonly access?: FieldAccess;
@@ -2178,6 +2183,12 @@ declare class EasyCMS<C extends Config = Config> {
2178
2183
  private separateDrafts;
2179
2184
  /** Replaces published documents that have a newer draft with that draft. */
2180
2185
  private withDrafts;
2186
+ /**
2187
+ * `httpCache.purge`: what readers see changed (anything, without drafts; with drafts, a
2188
+ * published document saved, published, unpublished or deleted). Not awaited; failures are
2189
+ * logged.
2190
+ */
2191
+ private purgePublic;
2181
2192
  /** Sends webhook events for a change; `before` is the stored status before it, if any. */
2182
2193
  private emitChange;
2183
2194
  /**
@@ -2781,6 +2792,11 @@ interface CollectionConfig {
2781
2792
  readonly versions?: boolean | VersionsConfig;
2782
2793
  /** Allow scheduling publish/unpublish for a later time (needs `drafts`). */
2783
2794
  readonly schedule?: boolean;
2795
+ /**
2796
+ * HTTP caching of public reads of this content (no login, published only), for a CDN in front.
2797
+ * Overrides the config's `httpCache` for this one; `false` keeps every read `no-store`.
2798
+ */
2799
+ readonly httpCache?: false | HttpCacheTimes;
2784
2800
  /** Live preview: the page that shows a document, e.g. `({ doc }) => \`/posts/${doc.slug}\``. */
2785
2801
  readonly preview?: PreviewURL;
2786
2802
  readonly access?: CollectionAccess;
@@ -2788,6 +2804,20 @@ interface CollectionConfig {
2788
2804
  /** How the collection looks in the admin: its menu item, list, edit page and components. */
2789
2805
  readonly admin?: CollectionAdmin;
2790
2806
  }
2807
+ /** How long a CDN may keep a public read. */
2808
+ interface HttpCacheTimes {
2809
+ /** Seconds a CDN serves it without asking again (`s-maxage`). Default 60. */
2810
+ readonly sMaxAge?: number;
2811
+ /** Seconds after that it may still serve it while it fetches a fresh one. Default 300. */
2812
+ readonly staleWhileRevalidate?: number;
2813
+ }
2814
+ interface HttpCacheConfig extends HttpCacheTimes {
2815
+ /**
2816
+ * Called after public content changes, with the cache tags of what changed: purge them from
2817
+ * your CDN, or call Next's `revalidateTag` for each. Not awaited; a failure is logged.
2818
+ */
2819
+ readonly purge?: (tags: string[]) => void | Promise<void>;
2820
+ }
2791
2821
  interface GlobalConfig {
2792
2822
  readonly slug: string;
2793
2823
  readonly label?: Label;
@@ -2797,6 +2827,11 @@ interface GlobalConfig {
2797
2827
  readonly versions?: boolean | VersionsConfig;
2798
2828
  /** Allow scheduling publish/unpublish for a later time (needs `drafts`). */
2799
2829
  readonly schedule?: boolean;
2830
+ /**
2831
+ * HTTP caching of public reads of this content (no login, published only), for a CDN in front.
2832
+ * Overrides the config's `httpCache` for this one; `false` keeps every read `no-store`.
2833
+ */
2834
+ readonly httpCache?: false | HttpCacheTimes;
2800
2835
  /** Live preview: the page that shows the global. See `CollectionConfig.preview`. */
2801
2836
  readonly preview?: PreviewURL;
2802
2837
  readonly access?: GlobalAccess;
@@ -3374,6 +3409,12 @@ interface Config {
3374
3409
  * Default 5.
3375
3410
  */
3376
3411
  readonly countCacheSeconds?: number;
3412
+ /**
3413
+ * HTTP caching of public reads (no login, no API key, published content) by a CDN or proxy in
3414
+ * front: `Cache-Control` with `s-maxage`, an `ETag`, and cache tags to purge by. Off by default:
3415
+ * every response is `no-store`. Collections and globals can override it.
3416
+ */
3417
+ readonly httpCache?: HttpCacheConfig;
3377
3418
  /**
3378
3419
  * The deepest `page` the REST API serves to API clients (not to the signed-in admin); deeper
3379
3420
  * requests get `400`, pointing to `cursor`. Deep pages are read row by row. Default: no limit.
@@ -1,4 +1,4 @@
1
- import { I as ID, ab as Config, av as EasyCMS, bh as LocalizationConfig, aE as Field, aI as FieldError, aT as FilterOptions, af as CreateEasyCMSOptions, bO as ResolvedConfig } from './config-BZgVbC3f.js';
1
+ import { I as ID, ab as Config, av as EasyCMS, bh as LocalizationConfig, aE as Field, aI as FieldError, aT as FilterOptions, af as CreateEasyCMSOptions, bO as ResolvedConfig } from './config-n7PtGl4V.js';
2
2
 
3
3
  /** Decrypts a file from `encryptBackup`; throws for another key or a changed file. */
4
4
  declare function decryptBackup(data: Uint8Array, encryptionKey: string): Uint8Array;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { P as PaginatedDocs, I as ID, E as EmailSetting } from './config-BZgVbC3f.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 CursorDocs, aj as CustomField, ak as CustomFieldTypes, al as DashboardWidget, am as Database, an as DatabaseAdapter, ao as DatabaseInitArgs, ap as DatabaseTransfer, aq as DateField, ar as DepthOptions, as as DirectUpload, at as DiskStorageOptions, au as DocumentOf, av as EasyCMS, aw as EasyCMSError, ax as EmailAdapter, ay as EmailField, az as EmailMessage, aA as Endpoint, aB as EndpointRequest, aC as ErrorCode, aD as FIELD_TYPES, aE as Field, aF as FieldAccess, aG as FieldAccessFn, aH as FieldAdmin, aI as FieldError, aJ as FieldRule, aK as FieldType, aL as FieldTypeBase, aM as FieldTypeDefinition, aN as FieldTypeValidateContext, aO as FieldValidate, aP as FieldValidateContext, aQ as FieldValue, aR as FieldsInput, aS as FieldsValue, aT as FilterOptions, aU as FilterOptionsArgs, aV as FindAfterArgs, aW as FindArgs, aX as FindOptions, aY as FolderLevel, aZ as FolderPermissions, a_ as ForbiddenError, a$ as GlobalAccess, b0 as GlobalConfig, b1 as GlobalDocument, b2 as GlobalHooks, b3 as GlobalInput, b4 as GlobalSlug, b5 as GroupField, b6 as ImageSize, b7 as IncrementOptions, b8 as InferCollection, b9 as InferGlobal, ba as JobConfig, bb as JsonField, bc as KeysetPage, bd as Label, be as LayoutNode, bf as LivePreview, bg as LocaleOf, bh as LocalizationConfig, bi as LogDetails, bj as Logger, bk as LoginArgs, bl as LogsConfig, bm as MediaDocument, bn as MediaSize, bo as MembersConfig, bp as MembersSignup, bq as MigrationInfo, br as NavGroup, bs as NotFoundError, bt as NumberField, bu as OnRequest, bv as OnRequestArgs, bw as Operation, bx as PLUGIN_API_VERSION, by as PasswordEmail, bz as PasswordEmailArgs, bA as PasswordEmailFn, bB as PasswordLinkOptions, bC as PayloadTooLargeError, bD as Plugin, bE as PluginInfo, bF as PluginTypes, bG as PreviewTarget, bH as PreviewURL, bI as QueryError, bJ as ROLE_OPERATIONS, bK as RawDocument, bL as ReadOptions, bM as RelationshipField, bN as RequestContext, bO as ResolvedConfig, bP as RichTextDocument, bQ as RichTextField, bR as Role, bS as RoleOperation, bT as RolePermissions, bU as Roles, bV as RoutesConfig, bW as ScheduledPublish, bX as SchemaError, bY as SchemaMode, bZ as SelectField, b_ as SelectOption, b$ as Session, c0 as SidebarPanel, c1 as SignupArgs, c2 as SignupResult, c3 as SingleSignOn, c4 as SlugField, c5 as SlugOf, c6 as SsoOutcome, c7 as SsoProviderRef, c8 as StorageAdapter, c9 as StoredFile, ca as TextField, cb as TextareaField, cc as TooManyRequestsError, cd as TypedPlugin, ce as UnauthorizedError, cf as UncountedDocs, cg as UpdateInput, ch as UpdateOptions, ci as UploadConfig, cj as UploadField, ck as UploadFromURLConfig, cl as UserIdentity, cm as ValidationError, cn as Version, co as VersionSummary, cp as VersionsConfig, cq as WEBHOOK_EVENTS, cr as WebhookConfig, cs as WebhookEvent, ct as WebhookPayload, cu as Where, cv as WhereOperators, cw as anyone, cx as consoleEmail, cy as consoleLogger, cz as createEasyCMS, cA as defineConfig, cB as defineFieldType, cC as definePlugin, cD as diskStorage, cE as hasRows, cF as isAdmin, cG as isHasMany, cH as isSignedIn, cI as isStaff, cJ as isSystemAdmin, cK as jsonLogger, cL as mimeAllowedBy, cM as rowFields, cN as silentLogger, cO as uniqueWithinOf } from './config-BZgVbC3f.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-DlSta1rd.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-j6s6J723.js';
1
+ import { P as PaginatedDocs, I as ID, E as EmailSetting } from './config-n7PtGl4V.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 CursorDocs, aj as CustomField, ak as CustomFieldTypes, al as DashboardWidget, am as Database, an as DatabaseAdapter, ao as DatabaseInitArgs, ap as DatabaseTransfer, aq as DateField, ar as DepthOptions, as as DirectUpload, at as DiskStorageOptions, au as DocumentOf, av as EasyCMS, aw as EasyCMSError, ax as EmailAdapter, ay as EmailField, az as EmailMessage, aA as Endpoint, aB as EndpointRequest, aC as ErrorCode, aD as FIELD_TYPES, aE as Field, aF as FieldAccess, aG as FieldAccessFn, aH as FieldAdmin, aI as FieldError, aJ as FieldRule, aK as FieldType, aL as FieldTypeBase, aM as FieldTypeDefinition, aN as FieldTypeValidateContext, aO as FieldValidate, aP as FieldValidateContext, aQ as FieldValue, aR as FieldsInput, aS as FieldsValue, aT as FilterOptions, aU as FilterOptionsArgs, aV as FindAfterArgs, aW as FindArgs, aX as FindOptions, aY as FolderLevel, aZ as FolderPermissions, a_ as ForbiddenError, a$ as GlobalAccess, b0 as GlobalConfig, b1 as GlobalDocument, b2 as GlobalHooks, b3 as GlobalInput, b4 as GlobalSlug, b5 as GroupField, b6 as ImageSize, b7 as IncrementOptions, b8 as InferCollection, b9 as InferGlobal, ba as JobConfig, bb as JsonField, bc as KeysetPage, bd as Label, be as LayoutNode, bf as LivePreview, bg as LocaleOf, bh as LocalizationConfig, bi as LogDetails, bj as Logger, bk as LoginArgs, bl as LogsConfig, bm as MediaDocument, bn as MediaSize, bo as MembersConfig, bp as MembersSignup, bq as MigrationInfo, br as NavGroup, bs as NotFoundError, bt as NumberField, bu as OnRequest, bv as OnRequestArgs, bw as Operation, bx as PLUGIN_API_VERSION, by as PasswordEmail, bz as PasswordEmailArgs, bA as PasswordEmailFn, bB as PasswordLinkOptions, bC as PayloadTooLargeError, bD as Plugin, bE as PluginInfo, bF as PluginTypes, bG as PreviewTarget, bH as PreviewURL, bI as QueryError, bJ as ROLE_OPERATIONS, bK as RawDocument, bL as ReadOptions, bM as RelationshipField, bN as RequestContext, bO as ResolvedConfig, bP as RichTextDocument, bQ as RichTextField, bR as Role, bS as RoleOperation, bT as RolePermissions, bU as Roles, bV as RoutesConfig, bW as ScheduledPublish, bX as SchemaError, bY as SchemaMode, bZ as SelectField, b_ as SelectOption, b$ as Session, c0 as SidebarPanel, c1 as SignupArgs, c2 as SignupResult, c3 as SingleSignOn, c4 as SlugField, c5 as SlugOf, c6 as SsoOutcome, c7 as SsoProviderRef, c8 as StorageAdapter, c9 as StoredFile, ca as TextField, cb as TextareaField, cc as TooManyRequestsError, cd as TypedPlugin, ce as UnauthorizedError, cf as UncountedDocs, cg as UpdateInput, ch as UpdateOptions, ci as UploadConfig, cj as UploadField, ck as UploadFromURLConfig, cl as UserIdentity, cm as ValidationError, cn as Version, co as VersionSummary, cp as VersionsConfig, cq as WEBHOOK_EVENTS, cr as WebhookConfig, cs as WebhookEvent, ct as WebhookPayload, cu as Where, cv as WhereOperators, cw as anyone, cx as consoleEmail, cy as consoleLogger, cz as createEasyCMS, cA as defineConfig, cB as defineFieldType, cC as definePlugin, cD as diskStorage, cE as hasRows, cF as isAdmin, cG as isHasMany, cH as isSignedIn, cI as isStaff, cJ as isSystemAdmin, cK as jsonLogger, cL as mimeAllowedBy, cM as rowFields, cN as silentLogger, cO as uniqueWithinOf } from './config-n7PtGl4V.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-C_vknKa2.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-DV-1ICVz.js';
5
5
  export { FieldCondition, matchesCondition } from './conditions.js';
6
6
 
7
7
  type DeliveryKind = 'webhook' | 'email';
@@ -218,8 +218,19 @@ interface AdminStatus {
218
218
  attention: AdminAttention[];
219
219
  }
220
220
 
221
+ /**
222
+ * The cache tags public reads carry and `httpCache.purge` receives: `ecms` for all content,
223
+ * `ecms:posts` for a collection's lists, `ecms:posts:42` for a document, `ecms:global:site` for
224
+ * a global. Use them to tag your own fetches, e.g. Next's `fetch(url, { next: { tags } })`.
225
+ */
226
+ declare const cacheTags: {
227
+ collection: (slug: string) => string[];
228
+ document: (slug: string, id: unknown) => string[];
229
+ global: (slug: string) => string[];
230
+ };
231
+
221
232
  /** The version of `@easy-cms/core`, set when the package is built. */
222
233
  declare const VERSION: string;
223
234
 
224
- export { EmailSetting, ID, PaginatedDocs, VERSION };
235
+ export { EmailSetting, ID, PaginatedDocs, VERSION, cacheTags };
225
236
  export type { AdminAttention, AdminDeliveries, AdminDelivery, AdminEmail, AdminEmailDelivery, AdminJob, AdminJobs, AdminScheduledJob, AdminStatus, AdminWebhookDelivery, BuiltInJob, DeliveryKind, DeliveryState, EmailCheck, SearchHit };
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  VERSION,
9
9
  WEBHOOK_EVENTS,
10
10
  anyone,
11
+ cacheTags,
11
12
  consoleEmail,
12
13
  consoleLogger,
13
14
  createApiHandler,
@@ -24,7 +25,7 @@ import {
24
25
  sharedEasyCMS,
25
26
  silentLogger,
26
27
  slugify
27
- } from "./chunk-HAHGAHFD.js";
28
+ } from "./chunk-TMO4JTGJ.js";
28
29
  import {
29
30
  matchesCondition
30
31
  } from "./chunk-U266LV4M.js";
@@ -74,6 +75,7 @@ export {
74
75
  ValidationError,
75
76
  WEBHOOK_EVENTS,
76
77
  anyone,
78
+ cacheTags,
77
79
  consoleEmail,
78
80
  consoleLogger,
79
81
  createApiHandler,
@@ -1,6 +1,6 @@
1
1
  import { Config, Database, EasyCMS, NavGroup, CollectionConfig, ID, RawDocument, Field, ResolvedConfig, ConfigIssue } from './index.js';
2
- export { cP as API_KEYS, cQ as API_KEY_GLOBAL_OPERATIONS, cR as API_KEY_OPERATIONS, cS as DEFAULT_MAX_VERSIONS, cT as DEFAULT_PASSWORD_EMAILS, cU as FIELD_TYPE_BASES, cV as FOLDER_LEVELS, cW as MediaFolders, cX as SSO_COOKIE, cY as codeOfStatus, cE as hasRows, cG as isHasMany, cZ as isJsonLogger, c_ as keyAllows, c$ as logFormat, cL as mimeAllowedBy, cM as rowFields, cO as uniqueWithinOf } from './config-BZgVbC3f.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 decryptBackup, q as fillMissing, t as findConfigFile, u as forwardedClientIp, v as generateSlugs, w as importConfig, x as mergeForUpdate, y as parseId, z as platformClientIp, E as readCookie, F as validateFields, G as writeBackupFile } from './handler-j6s6J723.js';
2
+ export { cP as API_KEYS, cQ as API_KEY_GLOBAL_OPERATIONS, cR as API_KEY_OPERATIONS, cS as DEFAULT_MAX_VERSIONS, cT as DEFAULT_PASSWORD_EMAILS, cU as FIELD_TYPE_BASES, cV as FOLDER_LEVELS, cW as MediaFolders, cX as SSO_COOKIE, cY as codeOfStatus, cE as hasRows, cG as isHasMany, cZ as isJsonLogger, c_ as keyAllows, c$ as logFormat, cL as mimeAllowedBy, cM as rowFields, cO as uniqueWithinOf } from './config-n7PtGl4V.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 decryptBackup, q as fillMissing, t as findConfigFile, u as forwardedClientIp, v as generateSlugs, w as importConfig, x as mergeForUpdate, y as parseId, z as platformClientIp, E as readCookie, F as validateFields, G as writeBackupFile } from './handler-DV-1ICVz.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
@@ -56,7 +56,7 @@ import {
56
56
  verifyPassword,
57
57
  verifyTurnstile,
58
58
  writeBackupFile
59
- } from "./chunk-HAHGAHFD.js";
59
+ } from "./chunk-TMO4JTGJ.js";
60
60
  import {
61
61
  conditionIssues
62
62
  } from "./chunk-U266LV4M.js";
@@ -1,4 +1,4 @@
1
- import { bd as Label, aE as Field, I as ID, k as AdminLocale, t as AdminSwitcher, c7 as SsoProviderRef } from './config-BZgVbC3f.js';
1
+ import { bd as Label, aE as Field, I as ID, k as AdminLocale, t as AdminSwitcher, c7 as SsoProviderRef } from './config-n7PtGl4V.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, al as DashboardWidget, aw as EasyCMSError, aA as Endpoint, aB as EndpointRequest, aC as ErrorCode, aE as Field, aF as FieldAccess, aI as FieldError, aL as FieldTypeBase, aM as FieldTypeDefinition, aN as FieldTypeValidateContext, a_ as ForbiddenError, b0 as GlobalConfig, I as ID, ba as JobConfig, bd as Label, br as NavGroup, bs as NotFoundError, bu as OnRequest, bv as OnRequestArgs, bx as PLUGIN_API_VERSION, bC as PayloadTooLargeError, bD as Plugin, bE as PluginInfo, bF as PluginTypes, bI as QueryError, bN as RequestContext, cc as TooManyRequestsError, cd as TypedPlugin, ce as UnauthorizedError, cm as ValidationError, cu as Where, cB as defineFieldType, cC as definePlugin } from './config-BZgVbC3f.js';
2
- export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-DlSta1rd.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, al as DashboardWidget, aw as EasyCMSError, aA as Endpoint, aB as EndpointRequest, aC as ErrorCode, aE as Field, aF as FieldAccess, aI as FieldError, aL as FieldTypeBase, aM as FieldTypeDefinition, aN as FieldTypeValidateContext, a_ as ForbiddenError, b0 as GlobalConfig, I as ID, ba as JobConfig, bd as Label, br as NavGroup, bs as NotFoundError, bu as OnRequest, bv as OnRequestArgs, bx as PLUGIN_API_VERSION, bC as PayloadTooLargeError, bD as Plugin, bE as PluginInfo, bF as PluginTypes, bI as QueryError, bN as RequestContext, cc as TooManyRequestsError, cd as TypedPlugin, ce as UnauthorizedError, cm as ValidationError, cu as Where, cB as defineFieldType, cC as definePlugin } from './config-n7PtGl4V.js';
2
+ export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-C_vknKa2.js';
3
3
  import './conditions.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easy-cms/core",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "Embedded, code-first headless CMS for Nuxt and Next.js",
5
5
  "license": "MIT",
6
6
  "type": "module",