@easy-cms/core 1.10.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"
@@ -4891,6 +4893,7 @@ function validateConfig(config) {
4891
4893
  validateLogs(config, add);
4892
4894
  validateCountCache(config, add);
4893
4895
  validateMaxPage(config, add);
4896
+ validateHttpCache(config, add);
4894
4897
  validateAuth(config, add);
4895
4898
  validateCors(config.cors, add);
4896
4899
  validateLocalization(config, add);
@@ -5513,6 +5516,26 @@ function validateUpload(config, add) {
5513
5516
  var FOLDER_KEY = /^[a-z0-9][a-z0-9_-]{0,59}$/;
5514
5517
  var MIME_PATTERN = /^([\w.+-]+\/(\*|[\w.+-]+)|documents|office|archives)$/i;
5515
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
+ }
5516
5539
  function validateMaxPage(config, add) {
5517
5540
  const max = config.maxPage;
5518
5541
  if (max !== void 0 && !(Number.isInteger(max) && max >= 1))
@@ -6413,6 +6436,41 @@ function resolveAudit(audit) {
6413
6436
  };
6414
6437
  }
6415
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
+
6416
6474
  // src/local-api.ts
6417
6475
  async function createEasyCMS(config, options = {}) {
6418
6476
  const resolved3 = await resolveConfig(config);
@@ -8391,11 +8449,11 @@ var EasyCMS = class {
8391
8449
  const access = allowed2 === false ? false : andWhere(own === true ? void 0 : own, allowed2) ?? true;
8392
8450
  if (access === true) return;
8393
8451
  if (access !== false) {
8394
- const matches2 = await this.db.count({
8452
+ const matches3 = await this.db.count({
8395
8453
  collection: config.slug,
8396
8454
  where: andWhere(access, { id: { equals: id } })
8397
8455
  });
8398
- if (matches2 > 0) return;
8456
+ if (matches3 > 0) return;
8399
8457
  }
8400
8458
  throw deny(guard.user);
8401
8459
  }
@@ -8449,11 +8507,11 @@ var EasyCMS = class {
8449
8507
  await this.hidingDenied(collection2, guard, id, async () => {
8450
8508
  const own = await this.checkGrant(guard, { collection: collection2 }, operation);
8451
8509
  if (own === true) return;
8452
- const matches2 = await this.db.count({
8510
+ const matches3 = await this.db.count({
8453
8511
  collection: collection2,
8454
8512
  where: andWhere(own, { id: { equals: id } })
8455
8513
  });
8456
- if (matches2 === 0) throw deny(guard.user);
8514
+ if (matches3 === 0) throw deny(guard.user);
8457
8515
  });
8458
8516
  }
8459
8517
  /** Runs afterRead hooks, populates relationships and removes what the caller may not see. */
@@ -8615,6 +8673,20 @@ var EasyCMS = class {
8615
8673
  return version && doc.status === "published" && version.status === "draft" ? draftOf(doc, version) : doc;
8616
8674
  });
8617
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
+ }
8618
8690
  /** Sends webhook events for a change; `before` is the stored status before it, if any. */
8619
8691
  emitChange(config, event, doc, before) {
8620
8692
  const isGlobal = !this.config.collections.includes(config);
@@ -8627,6 +8699,7 @@ var EasyCMS = class {
8627
8699
  };
8628
8700
  const target = isGlobal ? { global: config.slug } : { collection: config.slug, id: doc.id };
8629
8701
  this.webhooks.emit(event, target, body);
8702
+ this.purgePublic(config, isGlobal, event, doc, before);
8630
8703
  if (event === "draft" || event === "delete" || !config.drafts) return;
8631
8704
  const after = doc.status;
8632
8705
  if (after === "published" && before !== "published") this.webhooks.emit("publish", target, body);
@@ -9076,7 +9149,7 @@ function durationSeconds(value) {
9076
9149
  }
9077
9150
  function cursorQuery(collection2, where, sort, locale, draft) {
9078
9151
  const key = JSON.stringify([collection2, where ?? null, sort, locale ?? null, draft]);
9079
- return createHash4("sha256").update(key).digest("base64url").slice(0, 16);
9152
+ return createHash5("sha256").update(key).digest("base64url").slice(0, 16);
9080
9153
  }
9081
9154
  function writeCursor(keys2, query) {
9082
9155
  return Buffer.from(JSON.stringify({ k: keys2, q: query })).toString("base64url");
@@ -9827,7 +9900,7 @@ function rank(role, config) {
9827
9900
  }
9828
9901
 
9829
9902
  // src/rest/admin-modules.ts
9830
- import { createHash as createHash5 } from "crypto";
9903
+ import { createHash as createHash6 } from "crypto";
9831
9904
  import { readFile as readFile3 } from "fs/promises";
9832
9905
  import { createRequire } from "module";
9833
9906
  import { join as join3 } from "path";
@@ -9868,11 +9941,11 @@ async function readAdminModule(cms, index) {
9868
9941
  if (!file) return void 0;
9869
9942
  const body = await readFile3(file, "utf8").catch(() => void 0);
9870
9943
  if (body === void 0) return void 0;
9871
- return { body, etag: `"${createHash5("sha1").update(body).digest("base64url")}"` };
9944
+ return { body, etag: `"${createHash6("sha1").update(body).digest("base64url")}"` };
9872
9945
  }
9873
9946
 
9874
9947
  // src/version.ts
9875
- var VERSION = true ? "1.10.0" : "0.0.0-dev";
9948
+ var VERSION = true ? "1.11.0" : "0.0.0-dev";
9876
9949
 
9877
9950
  // src/rest/handler.ts
9878
9951
  import { randomUUID as randomUUID4 } from "crypto";
@@ -10917,7 +10990,18 @@ async function respondNow(cms, request, url, path, root, options, production, re
10917
10990
  );
10918
10991
  if (!result) throw new HttpError("Not found", 404);
10919
10992
  if (result.body instanceof Response) return result.body;
10920
- 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 });
10921
11005
  } catch (error) {
10922
11006
  return errorResponse(cms, error, headers, production);
10923
11007
  }
@@ -11501,7 +11585,11 @@ async function route(cms, ctx, method, path, options) {
11501
11585
  })
11502
11586
  };
11503
11587
  }
11504
- 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
+ };
11505
11593
  }
11506
11594
  if (method === "PATCH" || method === "POST") {
11507
11595
  if (method === "POST")
@@ -11598,7 +11686,8 @@ async function route(cms, ctx, method, path, options) {
11598
11686
  body: await cms.find(
11599
11687
  collection2,
11600
11688
  query.cursor === void 0 ? { ...options2, page } : options2
11601
- )
11689
+ ),
11690
+ cache: { container: cms.collection(collection2), tags: cacheTags.collection(collection2) }
11602
11691
  };
11603
11692
  }
11604
11693
  if (method === "POST") {
@@ -11625,7 +11714,13 @@ async function route(cms, ctx, method, path, options) {
11625
11714
  if (method === "GET") {
11626
11715
  const doc = await cms.findById(collection2, id, { ...access, ...parseDepth(ctx.url), draft });
11627
11716
  if (!doc) throw new NotFoundError(collection2, id);
11628
- return { body: doc };
11717
+ return {
11718
+ body: doc,
11719
+ cache: {
11720
+ container: cms.collection(collection2),
11721
+ tags: cacheTags.document(collection2, doc.id)
11722
+ }
11723
+ };
11629
11724
  }
11630
11725
  if (method === "PATCH") {
11631
11726
  const body = await readJson(ctx.request);
@@ -11784,8 +11879,8 @@ function withPreviewToken(url, token) {
11784
11879
  }
11785
11880
  async function requirePreview(cms, ctx, target) {
11786
11881
  const opened = cms.verifyPreviewToken(ctx.url.searchParams.get("preview"));
11787
- 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);
11788
- 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");
11789
11884
  if (opened.user === void 0) return {};
11790
11885
  const id = parseId(opened.user);
11791
11886
  const issuer = id === void 0 ? null : await cms.auth.activeUser(id);
@@ -12293,6 +12388,7 @@ export {
12293
12388
  DEFAULT_TOKEN_EXPIRATION,
12294
12389
  applyPlugins,
12295
12390
  resolveConfig,
12391
+ cacheTags,
12296
12392
  createEasyCMS,
12297
12393
  EasyCMS,
12298
12394
  resolveAdminModule,
@@ -2183,6 +2183,12 @@ declare class EasyCMS<C extends Config = Config> {
2183
2183
  private separateDrafts;
2184
2184
  /** Replaces published documents that have a newer draft with that draft. */
2185
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;
2186
2192
  /** Sends webhook events for a change; `before` is the stored status before it, if any. */
2187
2193
  private emitChange;
2188
2194
  /**
@@ -2786,6 +2792,11 @@ interface CollectionConfig {
2786
2792
  readonly versions?: boolean | VersionsConfig;
2787
2793
  /** Allow scheduling publish/unpublish for a later time (needs `drafts`). */
2788
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;
2789
2800
  /** Live preview: the page that shows a document, e.g. `({ doc }) => \`/posts/${doc.slug}\``. */
2790
2801
  readonly preview?: PreviewURL;
2791
2802
  readonly access?: CollectionAccess;
@@ -2793,6 +2804,20 @@ interface CollectionConfig {
2793
2804
  /** How the collection looks in the admin: its menu item, list, edit page and components. */
2794
2805
  readonly admin?: CollectionAdmin;
2795
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
+ }
2796
2821
  interface GlobalConfig {
2797
2822
  readonly slug: string;
2798
2823
  readonly label?: Label;
@@ -2802,6 +2827,11 @@ interface GlobalConfig {
2802
2827
  readonly versions?: boolean | VersionsConfig;
2803
2828
  /** Allow scheduling publish/unpublish for a later time (needs `drafts`). */
2804
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;
2805
2835
  /** Live preview: the page that shows the global. See `CollectionConfig.preview`. */
2806
2836
  readonly preview?: PreviewURL;
2807
2837
  readonly access?: GlobalAccess;
@@ -3379,6 +3409,12 @@ interface Config {
3379
3409
  * Default 5.
3380
3410
  */
3381
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;
3382
3418
  /**
3383
3419
  * The deepest `page` the REST API serves to API clients (not to the signed-in admin); deeper
3384
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-DL4beesW.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-DL4beesW.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-DL4beesW.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-BXHlV5IZ.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--JrVIdYW.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-54UFTH6S.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-DL4beesW.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--JrVIdYW.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-54UFTH6S.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-DL4beesW.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-DL4beesW.js';
2
- export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-BXHlV5IZ.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.10.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",