@easy-cms/core 1.12.0 → 1.13.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.
@@ -4637,7 +4637,7 @@ function read(response, maxBytes, deadline) {
4637
4637
  var keys = (list) => new Set(list.split(/\s+/).filter(Boolean));
4638
4638
  var CONFIG = keys(`secret db serverURL cors cronSecret timeZone webhooks events jobs localization routes admin
4639
4639
  upload auth collections globals endpoints cliCommands onRequest fieldTypes apiKeys email backups
4640
- audit logs countCacheSeconds maxPage httpCache outbox plugins`);
4640
+ audit logs countCacheSeconds maxPage httpCache outbox search plugins`);
4641
4641
  var ADMIN = keys("path locale brand siteURL nav commands modules pages dashboard switcher");
4642
4642
  var AUTH = keys(`roles rbac tokenExpiration maxLoginAttempts lockWindow trustedOrigins
4643
4643
  resetPasswordExpiration inviteExpiration providers providerSignUp password setupCode members emails`);
@@ -4647,7 +4647,7 @@ var BACKUPS = keys("frequency at keep dir storage sqlite encryptionKey");
4647
4647
  var AUDIT = keys("keepDays values failedLogins scope");
4648
4648
  var LOGS = keys("format requests");
4649
4649
  var COLLECTION = keys(`slug labels fields useAsTitle drafts versions schedule preview access hooks
4650
- admin httpCache`);
4650
+ admin httpCache search`);
4651
4651
  var GLOBAL = keys(
4652
4652
  "slug label fields drafts versions schedule preview access hooks scope admin httpCache"
4653
4653
  );
@@ -4867,6 +4867,264 @@ function groupOf(group, ids, fallback) {
4867
4867
  return { id: `label:${JSON.stringify(group)}`, label: group };
4868
4868
  }
4869
4869
 
4870
+ // src/search.ts
4871
+ var SEARCHABLE_TYPES = /* @__PURE__ */ new Set([
4872
+ "text",
4873
+ "textarea",
4874
+ "email",
4875
+ "slug",
4876
+ "select",
4877
+ "richText",
4878
+ "array",
4879
+ "group",
4880
+ "blocks"
4881
+ ]);
4882
+ function fieldAt(fields, path) {
4883
+ const [name, ...rest] = path.split(".");
4884
+ const field = fields.find((f) => f.name === name);
4885
+ if (!field || rest.length === 0) return field;
4886
+ return field.type === "group" ? fieldAt(field.fields, rest.join(".")) : void 0;
4887
+ }
4888
+ function textOf(field, value) {
4889
+ if (value === null || value === void 0) return "";
4890
+ switch (field.type) {
4891
+ case "text":
4892
+ case "textarea":
4893
+ case "email":
4894
+ case "slug":
4895
+ return typeof value === "string" ? value : "";
4896
+ case "select":
4897
+ return [value].flat().filter((v) => typeof v === "string").join(" ");
4898
+ case "richText":
4899
+ return richText(value);
4900
+ case "group":
4901
+ return typeof value === "object" ? fieldsText(field.fields, value) : "";
4902
+ case "array":
4903
+ case "blocks":
4904
+ if (!Array.isArray(value) || !hasRows(field)) return "";
4905
+ return value.map((row) => {
4906
+ const fields = rowFields(field, row);
4907
+ return fields && row && typeof row === "object" ? fieldsText(fields, row) : "";
4908
+ }).filter(Boolean).join("\n");
4909
+ default:
4910
+ return "";
4911
+ }
4912
+ }
4913
+ function fieldsText(fields, data) {
4914
+ return fields.filter((f) => SEARCHABLE_TYPES.has(f.type) && !f.hidden).map((f) => textOf(f, data[f.name])).filter(Boolean).join("\n");
4915
+ }
4916
+ function richText(node) {
4917
+ if (!node || typeof node !== "object") return "";
4918
+ const { text: text3, content } = node;
4919
+ if (typeof text3 === "string") return text3;
4920
+ if (!Array.isArray(content)) return "";
4921
+ const inline = content.some((child) => typeof child?.text === "string");
4922
+ return content.map(richText).filter(Boolean).join(inline ? "" : "\n");
4923
+ }
4924
+ function searchText(fields, doc, paths) {
4925
+ const text3 = {};
4926
+ for (const path of paths) {
4927
+ const field = fieldAt(fields, path);
4928
+ let value = doc;
4929
+ for (const name of path.split("."))
4930
+ value = value && typeof value === "object" ? value[name] : void 0;
4931
+ text3[path] = field ? textOf(field, value) : "";
4932
+ }
4933
+ return text3;
4934
+ }
4935
+ var segmenter = new Intl.Segmenter(void 0, { granularity: "word" });
4936
+ function words(text3) {
4937
+ return [...segmenter.segment(text3.normalize("NFC").toLowerCase())].filter((s) => s.isWordLike).map((s) => s.segment);
4938
+ }
4939
+ function memorySearch() {
4940
+ const entries = /* @__PURE__ */ new Map();
4941
+ const key = (collection2, id, locale) => JSON.stringify([collection2, id, locale]);
4942
+ return {
4943
+ name: "memory",
4944
+ apiVersion: 1,
4945
+ ephemeral: true,
4946
+ async index(list) {
4947
+ for (const e of list) {
4948
+ const fields = Object.values(e.text).map(words);
4949
+ entries.set(key(e.collection, e.id, e.locale), {
4950
+ collection: e.collection,
4951
+ id: e.id,
4952
+ locale: e.locale,
4953
+ fields,
4954
+ joined: fields.map((list2) => list2.join(""))
4955
+ });
4956
+ }
4957
+ },
4958
+ async remove(documents) {
4959
+ const gone = new Set(documents.map((d) => JSON.stringify([d.collection, d.id])));
4960
+ for (const [k, e] of entries)
4961
+ if (gone.has(JSON.stringify([e.collection, e.id]))) entries.delete(k);
4962
+ },
4963
+ async clear(collection2) {
4964
+ for (const [k, e] of entries) if (e.collection === collection2) entries.delete(k);
4965
+ },
4966
+ async search(q) {
4967
+ const terms = [...new Set(words(q.query))];
4968
+ if (terms.length === 0) return { hits: [], total: 0 };
4969
+ const collections = new Set(q.collections);
4970
+ const scored = [];
4971
+ for (const e of entries.values()) {
4972
+ if (!collections.has(e.collection) || q.locale !== null && e.locale !== q.locale) continue;
4973
+ let score = 0;
4974
+ for (const term of terms) {
4975
+ let best = 0;
4976
+ e.fields.forEach((list, i) => {
4977
+ const weight = e.fields.length - i;
4978
+ for (const w of list)
4979
+ if (w === term) best = Math.max(best, weight * 4);
4980
+ else if (w.startsWith(term)) best = Math.max(best, weight * 2);
4981
+ if (best === 0 && e.joined[i]?.includes(term)) best = weight;
4982
+ });
4983
+ if (best === 0) {
4984
+ score = 0;
4985
+ break;
4986
+ }
4987
+ score += best;
4988
+ }
4989
+ if (score > 0) scored.push({ e, score });
4990
+ }
4991
+ scored.sort((a, b) => b.score - a.score);
4992
+ return {
4993
+ hits: scored.slice(q.offset, q.offset + q.limit).map(({ e }) => ({ collection: e.collection, id: e.id })),
4994
+ total: scored.length
4995
+ };
4996
+ },
4997
+ describe: () => [
4998
+ { key: "index", value: "memory" },
4999
+ { key: "entries", value: String(entries.size) }
5000
+ ]
5001
+ };
5002
+ }
5003
+ var SearchIndex = class {
5004
+ constructor(adapter, collections, locales, logger, read2, pages2) {
5005
+ this.adapter = adapter;
5006
+ this.collections = collections;
5007
+ this.locales = locales;
5008
+ this.logger = logger;
5009
+ this.read = read2;
5010
+ this.pages = pages2;
5011
+ }
5012
+ adapter;
5013
+ collections;
5014
+ locales;
5015
+ logger;
5016
+ read;
5017
+ pages;
5018
+ /** Indexing started by saves, which searches wait for. */
5019
+ pending = /* @__PURE__ */ new Set();
5020
+ /** The first build of an `ephemeral` index. */
5021
+ building;
5022
+ /** The collection's `search`, or `undefined` when it isn't searchable. */
5023
+ configOf(collection2) {
5024
+ const config = this.collections.find((c) => c.slug === collection2);
5025
+ return config?.search ? config : void 0;
5026
+ }
5027
+ /** Slugs of the searchable collections. */
5028
+ get searchable() {
5029
+ return this.collections.filter((c) => c.search).map((c) => c.slug);
5030
+ }
5031
+ /** Builds an `ephemeral` index in the background; searches wait for it. */
5032
+ start() {
5033
+ if (!this.adapter.ephemeral || this.searchable.length === 0) return;
5034
+ this.building = this.reindex().then(
5035
+ () => void 0,
5036
+ (error) => this.logger.error(`Search: building the index failed: ${error.message}`, {
5037
+ error
5038
+ })
5039
+ );
5040
+ }
5041
+ /**
5042
+ * Indexes a document as it now is, or removes it when it isn't published (or is gone). Reading
5043
+ * it again, rather than taking what a save had, makes running it twice or late harmless.
5044
+ */
5045
+ async sync(collection2, id) {
5046
+ const config = this.configOf(collection2);
5047
+ if (!config) return;
5048
+ const entries = [];
5049
+ for (const locale of this.locales) {
5050
+ const doc = await this.read(collection2, String(id), locale);
5051
+ if (!doc) {
5052
+ await this.adapter.remove([{ collection: collection2, id: String(id) }]);
5053
+ return;
5054
+ }
5055
+ entries.push(this.entry(config, doc, locale));
5056
+ }
5057
+ await this.adapter.index(entries);
5058
+ }
5059
+ /** `sync` after a save, in the background: failures are logged. */
5060
+ syncLater(collection2, id) {
5061
+ const run = this.sync(collection2, id).catch(
5062
+ (error) => this.logger.error(
5063
+ `Search: indexing ${collection2} ${String(id)} failed: ${error.message}; run \`easy-cms search:reindex\` to repair the index`,
5064
+ { error }
5065
+ )
5066
+ ).finally(() => this.pending.delete(run));
5067
+ this.pending.add(run);
5068
+ }
5069
+ /** Waits for indexing started so far. */
5070
+ async flush() {
5071
+ await this.building;
5072
+ while (this.pending.size > 0) await Promise.all(this.pending);
5073
+ }
5074
+ /** Builds the index again from the database: every collection, or one. */
5075
+ async reindex(collection2) {
5076
+ const slugs = collection2 === void 0 ? this.searchable : [collection2];
5077
+ for (const slug2 of slugs)
5078
+ if (!this.configOf(slug2))
5079
+ throw new QueryError(`"${slug2}" is not searchable: it has no \`search\` in the config`);
5080
+ await this.adapter.setup?.(
5081
+ slugs.map((slug2) => ({ slug: slug2, fields: this.configOf(slug2)?.search.fields ?? [] }))
5082
+ );
5083
+ const done = [];
5084
+ for (const slug2 of slugs) {
5085
+ const config = this.configOf(slug2);
5086
+ await this.adapter.clear(slug2);
5087
+ const ids = /* @__PURE__ */ new Set();
5088
+ for (const locale of this.locales)
5089
+ for await (const docs of this.pages(slug2, locale)) {
5090
+ for (const doc of docs) ids.add(String(doc.id));
5091
+ await this.adapter.index(docs.map((doc) => this.entry(config, doc, locale)));
5092
+ }
5093
+ done.push({ collection: slug2, documents: ids.size });
5094
+ }
5095
+ return done;
5096
+ }
5097
+ /** Hits for a query, as documents the reader may read, best match first. */
5098
+ async search(query, readHits) {
5099
+ await this.flush();
5100
+ const found = await this.adapter.search(query);
5101
+ const byCollection = /* @__PURE__ */ new Map();
5102
+ for (const hit of found.hits)
5103
+ byCollection.set(hit.collection, [...byCollection.get(hit.collection) ?? [], hit.id]);
5104
+ const docs = /* @__PURE__ */ new Map();
5105
+ await Promise.all(
5106
+ [...byCollection].map(async ([collection2, ids]) => {
5107
+ for (const doc of await readHits(collection2, ids))
5108
+ docs.set(JSON.stringify([collection2, String(doc.id)]), doc);
5109
+ })
5110
+ );
5111
+ const hits = [];
5112
+ for (const hit of found.hits) {
5113
+ const doc = docs.get(JSON.stringify([hit.collection, hit.id]));
5114
+ if (doc) hits.push({ collection: hit.collection, doc });
5115
+ }
5116
+ return { hits, total: found.total };
5117
+ }
5118
+ entry(config, doc, locale) {
5119
+ return {
5120
+ collection: config.slug,
5121
+ id: String(doc.id),
5122
+ locale,
5123
+ text: searchText(config.fields, doc, config.search.fields)
5124
+ };
5125
+ }
5126
+ };
5127
+
4870
5128
  // src/webhooks.ts
4871
5129
  import { createHmac as createHmac5, randomUUID as randomUUID2 } from "crypto";
4872
5130
  var WEBHOOK_EVENTS = [
@@ -5124,6 +5382,7 @@ function validateConfig(config) {
5124
5382
  validateLogs(config, add);
5125
5383
  validateCountCache(config, add);
5126
5384
  validateOutbox(config, add);
5385
+ validateSearch(config, add);
5127
5386
  validateMaxPage(config, add);
5128
5387
  validateHttpCache(config, add);
5129
5388
  validateAuth(config, add);
@@ -5777,6 +6036,49 @@ function validateOutbox(config, add) {
5777
6036
  if (config.outbox !== void 0 && typeof config.outbox !== "boolean")
5778
6037
  add("outbox", "must be true or false");
5779
6038
  }
6039
+ function validateSearch(config, add) {
6040
+ const adapter = config.search;
6041
+ if (adapter !== void 0) {
6042
+ const a = adapter;
6043
+ if (typeof a !== "object" || a === null || !["index", "remove", "clear", "search"].every((m) => typeof a[m] === "function"))
6044
+ add("search", "must be a search adapter", "e.g. search: memorySearch()");
6045
+ for (const c of config.collections ?? [])
6046
+ if (c?.slug === "search")
6047
+ add(
6048
+ "collections.search.slug",
6049
+ '"search" is the search route when `search` is set',
6050
+ "choose another slug"
6051
+ );
6052
+ }
6053
+ for (const c of config.collections ?? []) {
6054
+ const search = c?.search;
6055
+ if (search === void 0) continue;
6056
+ const path = `collections.${c.slug}.search`;
6057
+ if (adapter === void 0) {
6058
+ add(path, "needs a search adapter in the config", "e.g. search: memorySearch()");
6059
+ continue;
6060
+ }
6061
+ const fields = search?.fields;
6062
+ if (!Array.isArray(fields) || fields.length === 0) {
6063
+ add(
6064
+ `${path}.fields`,
6065
+ "must list the fields to index",
6066
+ "e.g. search: { fields: ['title', 'body'] }"
6067
+ );
6068
+ continue;
6069
+ }
6070
+ for (const name of fields) {
6071
+ const field = typeof name === "string" ? fieldAt(c.fields ?? [], name) : void 0;
6072
+ if (!field) add(`${path}.fields`, `has no field "${String(name)}"`);
6073
+ else if (!SEARCHABLE_TYPES.has(field.type) || field.hidden)
6074
+ add(
6075
+ `${path}.fields`,
6076
+ `can't index "${name}" (${field.hidden ? "hidden" : field.type})`,
6077
+ "text, textarea, email, slug, select, rich text, array, group and blocks fields hold text"
6078
+ );
6079
+ }
6080
+ }
6081
+ }
5780
6082
  function validateCountCache(config, add) {
5781
6083
  const seconds = config.countCacheSeconds;
5782
6084
  if (seconds !== void 0 && !(typeof seconds === "number" && Number.isFinite(seconds) && seconds >= 0))
@@ -6519,6 +6821,7 @@ function validateAdapterVersions(config, add) {
6519
6821
  ["upload.privateStorage", config.upload?.privateStorage],
6520
6822
  ["backups.storage", config.backups?.storage],
6521
6823
  ["email", config.email],
6824
+ ["search", config.search],
6522
6825
  ...(config.auth?.providers ?? []).map(
6523
6826
  (p, i) => [`auth.providers[${i}]`, p]
6524
6827
  )
@@ -6738,6 +7041,11 @@ async function createEasyCMS(config, options = {}) {
6738
7041
  ]);
6739
7042
  }
6740
7043
  const cms = new EasyCMS(resolved3, db, logger, storage, cwd);
7044
+ if (resolved3.search?.ephemeral && process.env.NODE_ENV === "production")
7045
+ logger.warn(
7046
+ `Search: the "${resolved3.search.name ?? "custom"}" index lives in this process's memory: it is built again at each start, and each instance has its own. Use a search engine adapter for large sites or several instances.`
7047
+ );
7048
+ if (options.scheduler !== false) cms.startSearch();
6741
7049
  const scheduling = [...resolved3.collections, ...resolved3.globals].some((c) => c.schedule) || (resolved3.webhooks?.length ?? 0) > 0 || resolved3.email !== void 0 || resolved3.jobs.length > 0 || resolved3.outbox === true;
6742
7050
  if (scheduling && options.scheduler !== false) cms.startScheduler();
6743
7051
  if (process.env.NODE_ENV === "production" && !resolved3.auth.setupCode && await cms.auth.hasUsers().catch(() => true) === false)
@@ -6780,6 +7088,8 @@ var EasyCMS = class {
6780
7088
  mailer;
6781
7089
  /** With `outbox: true`: runs after hooks and events from rows saves write. */
6782
7090
  outbox;
7091
+ /** With `search`: keeps the index in step with published documents. */
7092
+ searchIndex;
6783
7093
  /** @internal */
6784
7094
  constructor(config, db, logger = consoleLogger, storage = diskStorage({ dir: config.upload.dir }), cwd = process.cwd(), privateStorage) {
6785
7095
  this.config = config;
@@ -6808,6 +7118,18 @@ var EasyCMS = class {
6808
7118
  config.collections.some((c) => c.slug === EMAIL_DELIVERIES) ? this.emailQueue() : void 0
6809
7119
  );
6810
7120
  this.outbox = config.collections.some((c) => c.slug === OUTBOX) ? new Outbox(db, logger, (entry, done, mark) => this.runOutbox(entry, done, mark)) : void 0;
7121
+ this.searchIndex = config.search ? new SearchIndex(
7122
+ config.search,
7123
+ config.collections,
7124
+ config.localization ? config.localization.locales : [null],
7125
+ logger,
7126
+ (collection2, id, locale) => this.findById(collection2, id, {
7127
+ draft: false,
7128
+ depth: 0,
7129
+ ...locale ? { locale } : {}
7130
+ }),
7131
+ (collection2, locale) => this.publishedPages(collection2, locale)
7132
+ ) : void 0;
6811
7133
  this.auth = new Auth(this);
6812
7134
  }
6813
7135
  /**
@@ -8118,6 +8440,81 @@ var EasyCMS = class {
8118
8440
  flushOutbox() {
8119
8441
  return this.outbox?.flush() ?? Promise.resolve();
8120
8442
  }
8443
+ /**
8444
+ * Searches the collections that have `search` with the config's `search` adapter: published
8445
+ * documents, best match first, read with the access options given (pass `overrideAccess: false`
8446
+ * and `user` to apply access rules). `total` counts matches before access rules, so a page can
8447
+ * hold fewer documents than `limit`.
8448
+ */
8449
+ async search(query, options = {}) {
8450
+ const index = this.searchIndex;
8451
+ if (!index) throw new QueryError("Search needs a `search` adapter in the config");
8452
+ const limit = options.limit ?? 10;
8453
+ const page = options.page ?? 1;
8454
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100)
8455
+ throw new QueryError("limit must be an integer from 1 to 100");
8456
+ if (!Number.isInteger(page) || page < 1) throw new QueryError("page must be an integer from 1");
8457
+ const collections = options.collections ?? index.searchable;
8458
+ for (const slug2 of collections)
8459
+ if (!index.configOf(slug2)) throw new QueryError(`"${slug2}" is not searchable`);
8460
+ const localization = this.config.localization;
8461
+ const asked = options.locale && options.locale !== ALL_LOCALES ? options.locale : void 0;
8462
+ if (asked !== void 0 && localization && !localization.locales.includes(asked))
8463
+ throw new QueryError(`Unknown locale "${asked}"`);
8464
+ const locale = localization ? asked ?? localization.defaultLocale ?? null : null;
8465
+ const text3 = query.trim().slice(0, 200);
8466
+ const empty = { hits: [], total: 0, page, limit, hasNextPage: false };
8467
+ if (!text3 || collections.length === 0) return empty;
8468
+ const { collections: _, limit: __, page: ___, ...read2 } = options;
8469
+ const { hits, total } = await index.search(
8470
+ { query: text3, collections, locale, limit, offset: (page - 1) * limit },
8471
+ async (collection2, ids) => (await this.find(collection2, {
8472
+ ...read2,
8473
+ ...locale ? { locale } : {},
8474
+ where: { id: { in: ids.map((id) => parseId(id) ?? id) } },
8475
+ limit: ids.length,
8476
+ draft: false
8477
+ })).docs
8478
+ );
8479
+ return { hits, total, page, limit, hasNextPage: page * limit < total };
8480
+ }
8481
+ /**
8482
+ * Builds the search index again from the database, for every searchable collection or one:
8483
+ * after adding `search` to a collection, changing its fields, or to repair the index
8484
+ * (`easy-cms search:reindex`).
8485
+ */
8486
+ async reindexSearch(collection2) {
8487
+ if (!this.searchIndex) throw new QueryError("Search needs a `search` adapter in the config");
8488
+ return this.searchIndex.reindex(collection2);
8489
+ }
8490
+ /** @internal Builds an in-memory search index (`ephemeral`) when the CMS starts. */
8491
+ startSearch() {
8492
+ this.searchIndex?.start();
8493
+ }
8494
+ /** Published documents of a collection in a locale, a page at a time, for the search index. */
8495
+ async *publishedPages(collection2, locale) {
8496
+ const options = {
8497
+ draft: false,
8498
+ depth: 0,
8499
+ limit: 100,
8500
+ sort: "id",
8501
+ ...locale ? { locale } : {}
8502
+ };
8503
+ if (this.db.findAfter) {
8504
+ let cursor = "";
8505
+ for (; ; ) {
8506
+ const found = await this.find(collection2, { ...options, cursor });
8507
+ yield found.docs;
8508
+ if (!found.nextCursor) return;
8509
+ cursor = found.nextCursor;
8510
+ }
8511
+ }
8512
+ for (let page = 1; ; page++) {
8513
+ const found = await this.find(collection2, { ...options, page, count: false });
8514
+ yield found.docs;
8515
+ if (!found.hasNextPage) return;
8516
+ }
8517
+ }
8121
8518
  /** @internal Puts a failed outbox entry back in line (Settings → Jobs). */
8122
8519
  retryOutbox(id) {
8123
8520
  return this.outbox?.retry(id) ?? Promise.resolve(false);
@@ -8126,6 +8523,7 @@ var EasyCMS = class {
8126
8523
  if (this.schedulerTimer) clearInterval(this.schedulerTimer);
8127
8524
  await this.running;
8128
8525
  await this.outbox?.flush();
8526
+ await this.searchIndex?.flush();
8129
8527
  await this.webhooks.flush();
8130
8528
  await this.mailer.flush();
8131
8529
  await this.db.destroy();
@@ -8984,6 +9382,10 @@ var EasyCMS = class {
8984
9382
  if (config.drafts && doc.status !== "published" && before !== "published") return;
8985
9383
  return this.isGlobal(config) ? cacheTags.global(config.slug) : cacheTags.document(config.slug, doc.id);
8986
9384
  }
9385
+ /** Whether a change may change what the search index holds: a save of the document itself. */
9386
+ searches(config, event) {
9387
+ return event !== "draft" && !this.isGlobal(config) && !!this.searchIndex?.configOf(config.slug);
9388
+ }
8987
9389
  isGlobal(config) {
8988
9390
  return !this.config.collections.includes(config);
8989
9391
  }
@@ -9014,6 +9416,7 @@ var EasyCMS = class {
9014
9416
  emitChange(config, event, doc, before) {
9015
9417
  for (const change of this.changeEvents(config, event, doc, before))
9016
9418
  this.webhooks.emit(change.event, change.target, change.body);
9419
+ if (this.searches(config, event)) this.searchIndex?.syncLater(config.slug, doc.id);
9017
9420
  const purge = this.config.httpCache?.purge;
9018
9421
  const tags = this.purgeTags(config, event, doc, before);
9019
9422
  if (!purge || !tags) return;
@@ -9030,7 +9433,7 @@ var EasyCMS = class {
9030
9433
  const hooks = event === "delete" ? config.hooks?.afterDelete : config.hooks?.afterChange;
9031
9434
  const isGlobal = this.isGlobal(config);
9032
9435
  const effects = (isGlobal || !INTERNAL_COLLECTIONS.has(config.slug)) && ((this.config.webhooks?.length ?? 0) > 0 || this.config.httpCache?.purge !== void 0);
9033
- if (!hooks?.length && !effects) return void 0;
9436
+ if (!hooks?.length && !effects && !this.searches(config, event)) return void 0;
9034
9437
  return this.outbox.row({
9035
9438
  event,
9036
9439
  target: config.slug,
@@ -9066,6 +9469,10 @@ var EasyCMS = class {
9066
9469
  await this.webhooks.emitSaved(change.event, change.target, change.body);
9067
9470
  if (changes.length > 0 && (this.config.webhooks?.length ?? 0) > 0) await mark("webhooks");
9068
9471
  }
9472
+ if (!done.has("search") && this.searchIndex && this.searches(config, event)) {
9473
+ await this.searchIndex.sync(config.slug, parseId(entry.docId ?? void 0));
9474
+ await mark("search");
9475
+ }
9069
9476
  const purge = this.config.httpCache?.purge;
9070
9477
  const tags = this.purgeTags(config, event, doc, entry.before);
9071
9478
  if (purge && tags) await purge(tags);
@@ -10344,7 +10751,7 @@ async function readAdminModule(cms, index) {
10344
10751
  }
10345
10752
 
10346
10753
  // src/version.ts
10347
- var VERSION = true ? "1.12.0" : "0.0.0-dev";
10754
+ var VERSION = true ? "1.13.0" : "0.0.0-dev";
10348
10755
 
10349
10756
  // src/rest/handler.ts
10350
10757
  import { randomUUID as randomUUID4 } from "crypto";
@@ -11010,9 +11417,17 @@ async function adminSearch(cms, who, q, perCollection = 5) {
11010
11417
  const conditions = [];
11011
11418
  if (title) conditions.push({ [title]: { like: query } });
11012
11419
  if (/^\d+$/.test(query)) conditions.push({ id: { equals: Number(query) } });
11013
- if (conditions.length === 0 || !await readable(cms, c, who)) return [];
11420
+ const indexed = cms.config.search !== void 0 && c.search !== void 0;
11421
+ if (conditions.length === 0 && !indexed || !await readable(cms, c, who)) return [];
11014
11422
  try {
11015
- const found = await cms.find(c.slug, {
11423
+ const ranked = indexed ? (await cms.search(query, {
11424
+ ...who,
11425
+ overrideAccess: false,
11426
+ depth: 0,
11427
+ limit: perCollection,
11428
+ collections: [c.slug]
11429
+ })).hits.map((hit) => hit.doc) : [];
11430
+ const found = conditions.length === 0 ? { docs: [] } : await cms.find(c.slug, {
11016
11431
  ...who,
11017
11432
  overrideAccess: false,
11018
11433
  draft: true,
@@ -11021,7 +11436,9 @@ async function adminSearch(cms, who, q, perCollection = 5) {
11021
11436
  where: conditions.length === 1 ? conditions[0] : { or: conditions },
11022
11437
  sort: "-updatedAt"
11023
11438
  });
11024
- return found.docs.map((doc) => {
11439
+ const seen = /* @__PURE__ */ new Set();
11440
+ const docs = [...ranked, ...found.docs].filter((doc) => !seen.has(doc.id) && seen.add(doc.id)).slice(0, perCollection);
11441
+ return docs.map((doc) => {
11025
11442
  const d = doc;
11026
11443
  const value = title ? d[title] : void 0;
11027
11444
  return {
@@ -11110,6 +11527,7 @@ async function adminStatus(cms) {
11110
11527
  database: config.db.name,
11111
11528
  storage: config.upload.storage?.name ?? "local",
11112
11529
  email: config.email ? config.email.name ?? "custom" : null,
11530
+ search: config.search ? config.search.name ?? "custom" : null,
11113
11531
  plugins: config.installedPlugins.map((p) => ({ ...p })),
11114
11532
  fieldTypes: config.fieldTypes.map((t) => t.name)
11115
11533
  },
@@ -11118,7 +11536,7 @@ async function adminStatus(cms) {
11118
11536
  }
11119
11537
 
11120
11538
  // src/rest/filter-options.ts
11121
- function fieldAt(fields, path) {
11539
+ function fieldAt2(fields, path) {
11122
11540
  let current = fields;
11123
11541
  let found;
11124
11542
  for (const part of path.split(".")) {
@@ -11137,7 +11555,7 @@ async function pickerFilter(cms, url, target, user, context = {}) {
11137
11555
  const dot = spec.indexOf(".");
11138
11556
  const slug2 = dot > 0 ? spec.slice(0, dot) : "";
11139
11557
  const owner = forCollection !== null ? cms.config.collections.find((c) => c.slug === slug2) : cms.config.globals.find((g) => g.slug === slug2);
11140
- const field = owner ? fieldAt(owner.fields, spec.slice(dot + 1)) : void 0;
11558
+ const field = owner ? fieldAt2(owner.fields, spec.slice(dot + 1)) : void 0;
11141
11559
  const to = field?.type === "upload" ? "media" : field?.type === "relationship" ? field.to : null;
11142
11560
  if (!field || field.type !== "relationship" && field.type !== "upload" || to !== target)
11143
11561
  throw new QueryError(`filterFor: no relationship to "${target}" at "${spec}"`);
@@ -12065,6 +12483,19 @@ async function route(cms, ctx, method, path, options) {
12065
12483
  );
12066
12484
  return { status: 201, body: { ...doc, key } };
12067
12485
  }
12486
+ if (first === "search" && second === void 0 && cms.config.search) {
12487
+ if (method !== "GET") throw methodNotAllowed(ctx, "GET");
12488
+ const q = ctx.url.searchParams;
12489
+ const collections = q.get("collections");
12490
+ return {
12491
+ body: await cms.search(q.get("q") ?? "", {
12492
+ ...access,
12493
+ ...parseDepth(ctx.url),
12494
+ ...pageQuery(q, 10),
12495
+ ...collections ? { collections: collections.split(",").filter(Boolean) } : {}
12496
+ })
12497
+ };
12498
+ }
12068
12499
  const collection2 = first;
12069
12500
  if (INTERNAL_COLLECTIONS.has(collection2) || !cms.config.collections.some((c) => c.slug === collection2)) {
12070
12501
  throw new HttpError(`Unknown collection "${collection2}"`, 404);
@@ -12784,6 +13215,7 @@ export {
12784
13215
  Roles,
12785
13216
  checkRenamedOptions,
12786
13217
  BUILTIN_NAV,
13218
+ memorySearch,
12787
13219
  WEBHOOK_EVENTS,
12788
13220
  MIN_SECRET_LENGTH,
12789
13221
  BUILTIN_COLLECTIONS,
@@ -1748,13 +1748,34 @@ interface FindOptions extends ReadOptions {
1748
1748
  */
1749
1749
  readonly cursor?: string;
1750
1750
  }
1751
+ interface SearchOptions extends DepthOptions {
1752
+ /** Searchable collections to look in. Default: every collection with `search`. */
1753
+ readonly collections?: readonly string[];
1754
+ /** Default 10, at most 100. */
1755
+ readonly limit?: number;
1756
+ /** 1-based. Default 1. */
1757
+ readonly page?: number;
1758
+ }
1759
+ /** What `cms.search()` finds: documents, best match first. */
1760
+ interface SearchResults {
1761
+ readonly hits: readonly {
1762
+ readonly collection: string;
1763
+ readonly doc: Record<string, unknown>;
1764
+ }[];
1765
+ /** Matches before access rules: a page can hold fewer than `limit`. */
1766
+ readonly total: number;
1767
+ readonly page: number;
1768
+ readonly limit: number;
1769
+ readonly hasNextPage: boolean;
1770
+ }
1751
1771
  interface CreateEasyCMSOptions {
1752
1772
  /** Project root. Default `process.cwd()`. */
1753
1773
  readonly cwd?: string;
1754
1774
  /**
1755
1775
  * Run due scheduled jobs and webhook retries every minute in this process. Default: on when
1756
1776
  * a collection or global has `schedule`, or `webhooks`, `email` or `jobs` is set. Turn it off
1757
- * where a cron calls `runJobs` (or `GET <api>/jobs/run`) instead.
1777
+ * where a cron calls `runJobs` (or `GET <api>/jobs/run`) instead. Off, an in-memory `search`
1778
+ * index (`memorySearch()`) isn't built at start either: call `reindexSearch()`.
1758
1779
  */
1759
1780
  readonly scheduler?: boolean;
1760
1781
  /** Default `verify` when `NODE_ENV=production`, otherwise `push`. */
@@ -1801,6 +1822,8 @@ declare class EasyCMS<C extends Config = Config> {
1801
1822
  private readonly mailer;
1802
1823
  /** With `outbox: true`: runs after hooks and events from rows saves write. */
1803
1824
  private readonly outbox;
1825
+ /** With `search`: keeps the index in step with published documents. */
1826
+ private readonly searchIndex;
1804
1827
  /**
1805
1828
  * The user and context of a request, as the REST API works them out: its session cookie or
1806
1829
  * Bearer token, then `onRequest`. For framework routes and server code that act for a visitor;
@@ -2066,6 +2089,24 @@ declare class EasyCMS<C extends Config = Config> {
2066
2089
  * function returns. What fails stays queued for its retry.
2067
2090
  */
2068
2091
  flushOutbox(): Promise<void>;
2092
+ /**
2093
+ * Searches the collections that have `search` with the config's `search` adapter: published
2094
+ * documents, best match first, read with the access options given (pass `overrideAccess: false`
2095
+ * and `user` to apply access rules). `total` counts matches before access rules, so a page can
2096
+ * hold fewer documents than `limit`.
2097
+ */
2098
+ search(query: string, options?: SearchOptions): Promise<SearchResults>;
2099
+ /**
2100
+ * Builds the search index again from the database, for every searchable collection or one:
2101
+ * after adding `search` to a collection, changing its fields, or to repair the index
2102
+ * (`easy-cms search:reindex`).
2103
+ */
2104
+ reindexSearch(collection?: string): Promise<{
2105
+ collection: string;
2106
+ documents: number;
2107
+ }[]>;
2108
+ /** Published documents of a collection in a locale, a page at a time, for the search index. */
2109
+ private publishedPages;
2069
2110
  destroy(): Promise<void>;
2070
2111
  private schedulerTimer;
2071
2112
  private running;
@@ -2221,6 +2262,8 @@ declare class EasyCMS<C extends Config = Config> {
2221
2262
  * drafts; with drafts, a published document saved, published, unpublished or deleted).
2222
2263
  */
2223
2264
  private purgeTags;
2265
+ /** Whether a change may change what the search index holds: a save of the document itself. */
2266
+ private searches;
2224
2267
  private isGlobal;
2225
2268
  /** The webhook events of a change; `before` is the stored status before it, if any. */
2226
2269
  private changeEvents;
@@ -2602,6 +2645,93 @@ interface FieldTypeDefinition {
2602
2645
  /** Declares a field type; returns it unchanged. */
2603
2646
  declare function defineFieldType(definition: FieldTypeDefinition): FieldTypeDefinition;
2604
2647
 
2648
+ /** One document in one locale, as a search engine indexes it: plain text per field. */
2649
+ interface SearchEntry {
2650
+ readonly collection: string;
2651
+ readonly id: string;
2652
+ /** `null` without `localization`. */
2653
+ readonly locale: string | null;
2654
+ /** The collection's `search.fields`, in their order (most important first), as plain text. */
2655
+ readonly text: Readonly<Record<string, string>>;
2656
+ }
2657
+ interface SearchQuery {
2658
+ /** What the person typed. */
2659
+ readonly query: string;
2660
+ /** Searchable collections to look in; never empty. */
2661
+ readonly collections: readonly string[];
2662
+ /** The locale to search; `null` without `localization`. */
2663
+ readonly locale: string | null;
2664
+ readonly limit: number;
2665
+ readonly offset: number;
2666
+ }
2667
+ interface SearchHits {
2668
+ /** Best match first. */
2669
+ readonly hits: readonly {
2670
+ readonly collection: string;
2671
+ readonly id: string;
2672
+ }[];
2673
+ /** Matches in all, for paging. */
2674
+ readonly total: number;
2675
+ }
2676
+ /** One setting a search adapter shows to admins (`describe()`). */
2677
+ interface SearchSetting {
2678
+ readonly key: string;
2679
+ /** `null`: not set. */
2680
+ readonly value: string | null;
2681
+ }
2682
+ /**
2683
+ * Indexes published documents for relevance-ranked full-text search (`search` in the config):
2684
+ * `memorySearch()`, or an adapter for a search engine. Easy CMS turns documents into plain text,
2685
+ * calls `index` and `remove` as they change, and reads the hits back with the user's access.
2686
+ */
2687
+ interface SearchAdapter {
2688
+ /** E.g. `memory`; shown to admins on the dashboard (System). */
2689
+ readonly name?: string;
2690
+ /**
2691
+ * The adapter interface it is written for (`ADAPTER_API_VERSION`); Easy CMS refuses another.
2692
+ * Leave it out to skip the check.
2693
+ */
2694
+ readonly apiVersion?: number;
2695
+ /**
2696
+ * The index starts empty in each process (it lives in memory): Easy CMS builds it when it
2697
+ * starts, and searches wait until it is built.
2698
+ */
2699
+ readonly ephemeral?: boolean;
2700
+ /** Creates or updates the indexes for these collections; runs before a reindex. */
2701
+ setup?(collections: readonly {
2702
+ slug: string;
2703
+ fields: readonly string[];
2704
+ }[]): Promise<void>;
2705
+ /** Adds or replaces entries (same collection, id and locale). */
2706
+ index(entries: readonly SearchEntry[]): Promise<void>;
2707
+ /** Removes documents, in every locale. */
2708
+ remove(documents: readonly {
2709
+ collection: string;
2710
+ id: string;
2711
+ }[]): Promise<void>;
2712
+ /** Removes every document of a collection, before a reindex. */
2713
+ clear(collection: string): Promise<void>;
2714
+ search(query: SearchQuery): Promise<SearchHits>;
2715
+ /** The settings in use, for admins. Never return secrets. */
2716
+ describe?(): readonly SearchSetting[];
2717
+ }
2718
+ /** What `search` of a collection says: the fields to index, most important first. */
2719
+ interface CollectionSearch {
2720
+ /**
2721
+ * Field names, or paths through groups such as `seo.description`: text, textarea, email, slug,
2722
+ * select and rich text fields, or arrays, groups and blocks (all the text inside them).
2723
+ */
2724
+ readonly fields: readonly string[];
2725
+ }
2726
+ /**
2727
+ * A search index in memory, for development, tests and small sites with one process: built when
2728
+ * the server starts, and lost when it stops. Words are split with `Intl.Segmenter`, so Thai text
2729
+ * is found by its words. Every word searched must be in the document, at the start of one of its
2730
+ * words or across words; documents whose first fields (`search.fields`) match, and match whole
2731
+ * words, rank first.
2732
+ */
2733
+ declare function memorySearch(): SearchAdapter;
2734
+
2605
2735
  declare const WEBHOOK_EVENTS: readonly ["create", "update", "delete", "publish", "unpublish", "draft"];
2606
2736
  /**
2607
2737
  * `create`, `update`, `delete`: the stored document changed. `publish` / `unpublish`: its status
@@ -2842,6 +2972,11 @@ interface CollectionConfig {
2842
2972
  * Overrides the config's `httpCache` for this one; `false` keeps every read `no-store`.
2843
2973
  */
2844
2974
  readonly httpCache?: false | HttpCacheTimes;
2975
+ /**
2976
+ * Relevance-ranked search of this collection's published documents with the config's `search`
2977
+ * adapter: `cms.search()` and `GET <api>/search`. The fields to index, most important first.
2978
+ */
2979
+ readonly search?: CollectionSearch;
2845
2980
  /** Live preview: the page that shows a document, e.g. `({ doc }) => \`/posts/${doc.slug}\``. */
2846
2981
  readonly preview?: PreviewURL;
2847
2982
  readonly access?: CollectionAccess;
@@ -3474,6 +3609,13 @@ interface Config {
3474
3609
  * Default `false`: hooks run in the request, after the save.
3475
3610
  */
3476
3611
  readonly outbox?: boolean;
3612
+ /**
3613
+ * Relevance-ranked search of the collections that have `search`, e.g. `memorySearch()` for
3614
+ * development and small sites, or an adapter for a search engine. Published documents are
3615
+ * indexed as they change; `easy-cms search:reindex` builds the index again. Without it, `like`
3616
+ * (with `index: 'trigram'` on Postgres) still finds text.
3617
+ */
3618
+ readonly search?: SearchAdapter;
3477
3619
  readonly plugins?: readonly Plugin[];
3478
3620
  }
3479
3621
  /** The config after plugins ran, validation passed and defaults were applied. */
@@ -3505,5 +3647,5 @@ interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes'
3505
3647
  */
3506
3648
  declare function defineConfig<const TConfig extends Config>(config: TConfig): TConfig;
3507
3649
 
3508
- export { ADAPTER_API_VERSION as A, AuditLog as J, Auth as M, ADMIN_ICONS as a, FIELD_TYPES as aD, ForbiddenError as a_, ConfigError as ac, EasyCMS as av, EasyCMSError as aw, PayloadTooLargeError as bD, QueryError as bJ, ROLE_OPERATIONS as bK, Roles as bV, SchemaError as bY, NotFoundError as bs, PLUGIN_API_VERSION as by, keyAllows as c$, SingleSignOn as c4, createEasyCMS as cA, defineConfig as cB, defineFieldType as cC, definePlugin as cD, diskStorage as cE, hasRows as cF, isAdmin as cG, isHasMany as cH, isSignedIn as cI, isStaff as cJ, isSystemAdmin as cK, jsonLogger as cL, mimeAllowedBy as cM, rowFields as cN, silentLogger as cO, uniqueWithinOf as cP, API_KEYS as cQ, API_KEY_GLOBAL_OPERATIONS as cR, API_KEY_OPERATIONS as cS, DEFAULT_MAX_VERSIONS as cT, DEFAULT_PASSWORD_EMAILS as cU, FIELD_TYPE_BASES as cV, FOLDER_LEVELS as cW, MediaFolders as cX, SSO_COOKIE as cY, codeOfStatus as cZ, isJsonLogger as c_, TooManyRequestsError as cd, UnauthorizedError as cf, ValidationError as cn, WEBHOOK_EVENTS as cr, anyone as cx, consoleEmail as cy, consoleLogger as cz, logFormat as d0 };
3509
- export type { BuiltinMediaCollection as $, ApiKeyOperation as B, ApiKeyPermissions as C, ArrayField as D, EmailSetting as E, AuditChange as F, AuditConfig as G, AuditEntry as H, ID as I, AuditPage as K, AuditVerification as L, AuthConfig as N, AuthProvider as O, PaginatedDocs as P, AuthProviderProfile as Q, AuthProviderRequest as R, AuthUser as S, BackupsConfig as T, BeforeChangeHook as U, BeforeDeleteHook as V, BeforeValidateHook as W, Block as X, BlocksField as Y, BooleanField as Z, BuiltinApiKeysCollection as _, GlobalAccess as a$, BuiltinMediaFoldersCollection as a0, BuiltinUsersCollection as a1, BundleHints as a2, CliCommand as a3, CollectionAccess as a4, CollectionAdmin as a5, CollectionConfig as a6, CollectionDocument as a7, CollectionHooks as a8, CollectionListAdmin as a9, Endpoint as aA, EndpointRequest as aB, ErrorCode as aC, Field as aE, FieldAccess as aF, FieldAccessFn as aG, FieldAdmin as aH, FieldError as aI, FieldRule as aJ, FieldType as aK, FieldTypeBase as aL, FieldTypeDefinition as aM, FieldTypeValidateContext as aN, FieldValidate as aO, FieldValidateContext as aP, FieldValue as aQ, FieldsInput as aR, FieldsValue as aS, FilterOptions as aT, FilterOptionsArgs as aU, FindAfterArgs as aV, FindArgs as aW, FindOptions as aX, FolderLevel as aY, FolderPermissions as aZ, CollectionSlug as aa, Config as ab, ConfigIssue as ad, ContainerAdmin as ae, CreateEasyCMSOptions as af, CreateInput as ag, CreateMigrationResult as ah, CursorDocs as ai, CustomField as aj, CustomFieldTypes as ak, DashboardWidget as al, Database as am, DatabaseAdapter as an, DatabaseInitArgs as ao, DatabaseTransfer as ap, DateField as aq, DepthOptions as ar, DirectUpload as as, DiskStorageOptions as at, DocumentOf as au, EmailAdapter as ax, EmailField as ay, EmailMessage as az, Access as b, SelectOption as b$, GlobalConfig as b0, GlobalDocument as b1, GlobalHooks as b2, GlobalInput as b3, GlobalSlug as b4, GroupField as b5, ImageSize as b6, IncrementOptions as b7, InferCollection as b8, InferGlobal as b9, PasswordEmailArgs as bA, PasswordEmailFn as bB, PasswordLinkOptions as bC, Plugin as bE, PluginInfo as bF, PluginTypes as bG, PreviewTarget as bH, PreviewURL as bI, RawDocument as bL, ReadOptions as bM, RelationshipField as bN, RequestContext as bO, ResolvedConfig as bP, RichTextDocument as bQ, RichTextField as bR, Role as bS, RoleOperation as bT, RolePermissions as bU, RoutesConfig as bW, ScheduledPublish as bX, SchemaMode as bZ, SelectField as b_, JobConfig as ba, JsonField as bb, KeysetPage as bc, Label as bd, LayoutNode as be, LivePreview as bf, LocaleOf as bg, LocalizationConfig as bh, LogDetails as bi, Logger as bj, LoginArgs as bk, LogsConfig as bl, MediaDocument as bm, MediaSize as bn, MembersConfig as bo, MembersSignup as bp, MigrationInfo as bq, NavGroup as br, NumberField as bt, OnRequest as bu, OnRequestArgs as bv, Operation as bw, OutboxRow as bx, PasswordEmail as bz, AccessArgs as c, Session as c0, SidebarPanel as c1, SignupArgs as c2, SignupResult as c3, SlugField as c5, SlugOf as c6, SsoOutcome as c7, SsoProviderRef as c8, StorageAdapter as c9, StoredFile as ca, TextField as cb, TextareaField as cc, TypedPlugin as ce, UncountedDocs as cg, UpdateInput as ch, UpdateOptions as ci, UploadConfig as cj, UploadField as ck, UploadFromURLConfig as cl, UserIdentity as cm, Version as co, VersionSummary as cp, VersionsConfig as cq, WebhookConfig as cs, WebhookEvent as ct, WebhookPayload as cu, Where as cv, WhereOperators as cw, AccessOptions as d, AdminBrand as e, AdminCommand as f, AdminComponent as g, AdminConfig as h, AdminFieldArgs as i, AdminIcon as j, AdminLocale as k, AdminPage as l, AdminRole as m, AdminRoleChange as n, AdminRoleField as o, AdminRoleTarget as p, AdminRoleView as q, AdminRoles as r, AdminSso as s, AdminSwitcher as t, AdminViewAccess as u, AfterChangeHook as v, AfterDeleteHook as w, AfterReadHook as x, ApiKeyContext as y, ApiKeyGlobalOperation as z };
3650
+ export { ADAPTER_API_VERSION as A, AuditLog as J, Auth as M, ADMIN_ICONS as a, ForbiddenError as a$, FIELD_TYPES as aE, ConfigError as ad, EasyCMS as aw, EasyCMSError as ax, PayloadTooLargeError as bE, QueryError as bK, ROLE_OPERATIONS as bL, Roles as bW, SchemaError as bZ, NotFoundError as bt, PLUGIN_API_VERSION as bz, API_KEY_OPERATIONS as c$, anyone as cF, consoleEmail as cG, consoleLogger as cH, createEasyCMS as cI, defineConfig as cJ, defineFieldType as cK, definePlugin as cL, diskStorage as cM, hasRows as cN, isAdmin as cO, isHasMany as cP, isSignedIn as cQ, isStaff as cR, isSystemAdmin as cS, jsonLogger as cT, memorySearch as cU, mimeAllowedBy as cV, rowFields as cW, silentLogger as cX, uniqueWithinOf as cY, API_KEYS as cZ, API_KEY_GLOBAL_OPERATIONS as c_, SingleSignOn as cc, TooManyRequestsError as cl, UnauthorizedError as cn, ValidationError as cv, WEBHOOK_EVENTS as cz, DEFAULT_MAX_VERSIONS as d0, DEFAULT_PASSWORD_EMAILS as d1, FIELD_TYPE_BASES as d2, FOLDER_LEVELS as d3, MediaFolders as d4, SSO_COOKIE as d5, codeOfStatus as d6, isJsonLogger as d7, keyAllows as d8, logFormat as d9 };
3651
+ export type { BuiltinMediaCollection as $, ApiKeyOperation as B, ApiKeyPermissions as C, ArrayField as D, EmailSetting as E, AuditChange as F, AuditConfig as G, AuditEntry as H, ID as I, AuditPage as K, AuditVerification as L, AuthConfig as N, AuthProvider as O, PaginatedDocs as P, AuthProviderProfile as Q, AuthProviderRequest as R, AuthUser as S, BackupsConfig as T, BeforeChangeHook as U, BeforeDeleteHook as V, BeforeValidateHook as W, Block as X, BlocksField as Y, BooleanField as Z, BuiltinApiKeysCollection as _, BuiltinMediaFoldersCollection as a0, BuiltinUsersCollection as a1, BundleHints as a2, CliCommand as a3, CollectionAccess as a4, CollectionAdmin as a5, CollectionConfig as a6, CollectionDocument as a7, CollectionHooks as a8, CollectionListAdmin as a9, EmailMessage as aA, Endpoint as aB, EndpointRequest as aC, ErrorCode as aD, Field as aF, FieldAccess as aG, FieldAccessFn as aH, FieldAdmin as aI, FieldError as aJ, FieldRule as aK, FieldType as aL, FieldTypeBase as aM, FieldTypeDefinition as aN, FieldTypeValidateContext as aO, FieldValidate as aP, FieldValidateContext as aQ, FieldValue as aR, FieldsInput as aS, FieldsValue as aT, FilterOptions as aU, FilterOptionsArgs as aV, FindAfterArgs as aW, FindArgs as aX, FindOptions as aY, FolderLevel as aZ, FolderPermissions as a_, CollectionSearch as aa, CollectionSlug as ab, Config as ac, ConfigIssue as ae, ContainerAdmin as af, CreateEasyCMSOptions as ag, CreateInput as ah, CreateMigrationResult as ai, CursorDocs as aj, CustomField as ak, CustomFieldTypes as al, DashboardWidget as am, Database as an, DatabaseAdapter as ao, DatabaseInitArgs as ap, DatabaseTransfer as aq, DateField as ar, DepthOptions as as, DirectUpload as at, DiskStorageOptions as au, DocumentOf as av, EmailAdapter as ay, EmailField as az, Access as b, SearchAdapter as b$, GlobalAccess as b0, GlobalConfig as b1, GlobalDocument as b2, GlobalHooks as b3, GlobalInput as b4, GlobalSlug as b5, GroupField as b6, ImageSize as b7, IncrementOptions as b8, InferCollection as b9, PasswordEmail as bA, PasswordEmailArgs as bB, PasswordEmailFn as bC, PasswordLinkOptions as bD, Plugin as bF, PluginInfo as bG, PluginTypes as bH, PreviewTarget as bI, PreviewURL as bJ, RawDocument as bM, ReadOptions as bN, RelationshipField as bO, RequestContext as bP, ResolvedConfig as bQ, RichTextDocument as bR, RichTextField as bS, Role as bT, RoleOperation as bU, RolePermissions as bV, RoutesConfig as bX, ScheduledPublish as bY, SchemaMode as b_, InferGlobal as ba, JobConfig as bb, JsonField as bc, KeysetPage as bd, Label as be, LayoutNode as bf, LivePreview as bg, LocaleOf as bh, LocalizationConfig as bi, LogDetails as bj, Logger as bk, LoginArgs as bl, LogsConfig as bm, MediaDocument as bn, MediaSize as bo, MembersConfig as bp, MembersSignup as bq, MigrationInfo as br, NavGroup as bs, NumberField as bu, OnRequest as bv, OnRequestArgs as bw, Operation as bx, OutboxRow as by, AccessArgs as c, SearchEntry as c0, SearchHits as c1, SearchOptions as c2, SearchQuery as c3, SearchResults as c4, SearchSetting as c5, SelectField as c6, SelectOption as c7, Session as c8, SidebarPanel as c9, WebhookConfig as cA, WebhookEvent as cB, WebhookPayload as cC, Where as cD, WhereOperators as cE, SignupArgs as ca, SignupResult as cb, SlugField as cd, SlugOf as ce, SsoOutcome as cf, SsoProviderRef as cg, StorageAdapter as ch, StoredFile as ci, TextField as cj, TextareaField as ck, TypedPlugin as cm, UncountedDocs as co, UpdateInput as cp, UpdateOptions as cq, UploadConfig as cr, UploadField as cs, UploadFromURLConfig as ct, UserIdentity as cu, Version as cw, VersionSummary as cx, VersionsConfig as cy, AccessOptions as d, AdminBrand as e, AdminCommand as f, AdminComponent as g, AdminConfig as h, AdminFieldArgs as i, AdminIcon as j, AdminLocale as k, AdminPage as l, AdminRole as m, AdminRoleChange as n, AdminRoleField as o, AdminRoleTarget as p, AdminRoleView as q, AdminRoles as r, AdminSso as s, AdminSwitcher as t, AdminViewAccess as u, AfterChangeHook as v, AfterDeleteHook as w, AfterReadHook as x, ApiKeyContext as y, ApiKeyGlobalOperation as z };
@@ -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, bP as ResolvedConfig } from './config-CHEEPk1d.js';
1
+ import { I as ID, ac as Config, aw as EasyCMS, bi as LocalizationConfig, aF as Field, aJ as FieldError, aU as FilterOptions, ag as CreateEasyCMSOptions, bQ as ResolvedConfig } from './config-B-bT0hIn.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-CHEEPk1d.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 OutboxRow, by as PLUGIN_API_VERSION, bz as PasswordEmail, bA as PasswordEmailArgs, bB as PasswordEmailFn, bC as PasswordLinkOptions, bD as PayloadTooLargeError, bE as Plugin, bF as PluginInfo, bG as PluginTypes, bH as PreviewTarget, bI as PreviewURL, bJ as QueryError, bK as ROLE_OPERATIONS, bL as RawDocument, bM as ReadOptions, bN as RelationshipField, bO as RequestContext, bP as ResolvedConfig, bQ as RichTextDocument, bR as RichTextField, bS as Role, bT as RoleOperation, bU as RolePermissions, bV as Roles, bW as RoutesConfig, bX as ScheduledPublish, bY as SchemaError, bZ as SchemaMode, b_ as SelectField, b$ as SelectOption, c0 as Session, c1 as SidebarPanel, c2 as SignupArgs, c3 as SignupResult, c4 as SingleSignOn, c5 as SlugField, c6 as SlugOf, c7 as SsoOutcome, c8 as SsoProviderRef, c9 as StorageAdapter, ca as StoredFile, cb as TextField, cc as TextareaField, cd as TooManyRequestsError, ce as TypedPlugin, cf as UnauthorizedError, cg as UncountedDocs, ch as UpdateInput, ci as UpdateOptions, cj as UploadConfig, ck as UploadField, cl as UploadFromURLConfig, cm as UserIdentity, cn as ValidationError, co as Version, cp as VersionSummary, cq as VersionsConfig, cr as WEBHOOK_EVENTS, cs as WebhookConfig, ct as WebhookEvent, cu as WebhookPayload, cv as Where, cw as WhereOperators, cx as anyone, cy as consoleEmail, cz as consoleLogger, cA as createEasyCMS, cB as defineConfig, cC as defineFieldType, cD as definePlugin, cE as diskStorage, cF as hasRows, cG as isAdmin, cH as isHasMany, cI as isSignedIn, cJ as isStaff, cK as isSystemAdmin, cL as jsonLogger, cM as mimeAllowedBy, cN as rowFields, cO as silentLogger, cP as uniqueWithinOf } from './config-CHEEPk1d.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-B8xypoWd.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-adKUq8-U.js';
1
+ import { P as PaginatedDocs, I as ID, E as EmailSetting } from './config-B-bT0hIn.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 CollectionSearch, ab as CollectionSlug, ac as Config, ad as ConfigError, ae as ConfigIssue, af as ContainerAdmin, ag as CreateEasyCMSOptions, ah as CreateInput, ai as CreateMigrationResult, aj as CursorDocs, ak as CustomField, al as CustomFieldTypes, am as DashboardWidget, an as Database, ao as DatabaseAdapter, ap as DatabaseInitArgs, aq as DatabaseTransfer, ar as DateField, as as DepthOptions, at as DirectUpload, au as DiskStorageOptions, av as DocumentOf, aw as EasyCMS, ax as EasyCMSError, ay as EmailAdapter, az as EmailField, aA as EmailMessage, aB as Endpoint, aC as EndpointRequest, aD as ErrorCode, aE as FIELD_TYPES, aF as Field, aG as FieldAccess, aH as FieldAccessFn, aI as FieldAdmin, aJ as FieldError, aK as FieldRule, aL as FieldType, aM as FieldTypeBase, aN as FieldTypeDefinition, aO as FieldTypeValidateContext, aP as FieldValidate, aQ as FieldValidateContext, aR as FieldValue, aS as FieldsInput, aT as FieldsValue, aU as FilterOptions, aV as FilterOptionsArgs, aW as FindAfterArgs, aX as FindArgs, aY as FindOptions, aZ as FolderLevel, a_ as FolderPermissions, a$ as ForbiddenError, b0 as GlobalAccess, b1 as GlobalConfig, b2 as GlobalDocument, b3 as GlobalHooks, b4 as GlobalInput, b5 as GlobalSlug, b6 as GroupField, b7 as ImageSize, b8 as IncrementOptions, b9 as InferCollection, ba as InferGlobal, bb as JobConfig, bc as JsonField, bd as KeysetPage, be as Label, bf as LayoutNode, bg as LivePreview, bh as LocaleOf, bi as LocalizationConfig, bj as LogDetails, bk as Logger, bl as LoginArgs, bm as LogsConfig, bn as MediaDocument, bo as MediaSize, bp as MembersConfig, bq as MembersSignup, br as MigrationInfo, bs as NavGroup, bt as NotFoundError, bu as NumberField, bv as OnRequest, bw as OnRequestArgs, bx as Operation, by as OutboxRow, bz as PLUGIN_API_VERSION, bA as PasswordEmail, bB as PasswordEmailArgs, bC as PasswordEmailFn, bD as PasswordLinkOptions, bE as PayloadTooLargeError, bF as Plugin, bG as PluginInfo, bH as PluginTypes, bI as PreviewTarget, bJ as PreviewURL, bK as QueryError, bL as ROLE_OPERATIONS, bM as RawDocument, bN as ReadOptions, bO as RelationshipField, bP as RequestContext, bQ as ResolvedConfig, bR as RichTextDocument, bS as RichTextField, bT as Role, bU as RoleOperation, bV as RolePermissions, bW as Roles, bX as RoutesConfig, bY as ScheduledPublish, bZ as SchemaError, b_ as SchemaMode, b$ as SearchAdapter, c0 as SearchEntry, c1 as SearchHits, c2 as SearchOptions, c3 as SearchQuery, c4 as SearchResults, c5 as SearchSetting, c6 as SelectField, c7 as SelectOption, c8 as Session, c9 as SidebarPanel, ca as SignupArgs, cb as SignupResult, cc as SingleSignOn, cd as SlugField, ce as SlugOf, cf as SsoOutcome, cg as SsoProviderRef, ch as StorageAdapter, ci as StoredFile, cj as TextField, ck as TextareaField, cl as TooManyRequestsError, cm as TypedPlugin, cn as UnauthorizedError, co as UncountedDocs, cp as UpdateInput, cq as UpdateOptions, cr as UploadConfig, cs as UploadField, ct as UploadFromURLConfig, cu as UserIdentity, cv as ValidationError, cw as Version, cx as VersionSummary, cy as VersionsConfig, cz as WEBHOOK_EVENTS, cA as WebhookConfig, cB as WebhookEvent, cC as WebhookPayload, cD as Where, cE as WhereOperators, cF as anyone, cG as consoleEmail, cH as consoleLogger, cI as createEasyCMS, cJ as defineConfig, cK as defineFieldType, cL as definePlugin, cM as diskStorage, cN as hasRows, cO as isAdmin, cP as isHasMany, cQ as isSignedIn, cR as isStaff, cS as isSystemAdmin, cT as jsonLogger, cU as memorySearch, cV as mimeAllowedBy, cW as rowFields, cX as silentLogger, cY as uniqueWithinOf } from './config-B-bT0hIn.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-CI6r3SK1.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-COsNEVx0.js';
5
5
  export { FieldCondition, matchesCondition } from './conditions.js';
6
6
 
7
7
  type DeliveryKind = 'webhook' | 'email';
@@ -230,6 +230,8 @@ interface AdminStatus {
230
230
  storage: string;
231
231
  /** The email adapter's name, `custom` when it has none, or `null` without email. */
232
232
  email: string | null;
233
+ /** The search adapter's name, `custom` when it has none, or `null` without `search`. */
234
+ search: string | null;
233
235
  /** The config's plugins, with their name and version when they give them. */
234
236
  plugins: {
235
237
  name?: string;
package/dist/index.js CHANGED
@@ -21,11 +21,12 @@ import {
21
21
  isSystemAdmin,
22
22
  jsonLogger,
23
23
  loadConfig,
24
+ memorySearch,
24
25
  resolveConfig,
25
26
  sharedEasyCMS,
26
27
  silentLogger,
27
28
  slugify
28
- } from "./chunk-244WCY6S.js";
29
+ } from "./chunk-MHXQUX74.js";
29
30
  import {
30
31
  matchesCondition
31
32
  } from "./chunk-U266LV4M.js";
@@ -92,6 +93,7 @@ export {
92
93
  jsonLogger,
93
94
  loadConfig,
94
95
  matchesCondition,
96
+ memorySearch,
95
97
  resolveConfig,
96
98
  sharedEasyCMS,
97
99
  silentLogger,
@@ -1,6 +1,6 @@
1
1
  import { Config, Database, EasyCMS, NavGroup, CollectionConfig, ID, RawDocument, Field, ResolvedConfig, ConfigIssue } from './index.js';
2
- export { cQ as API_KEYS, cR as API_KEY_GLOBAL_OPERATIONS, cS as API_KEY_OPERATIONS, cT as DEFAULT_MAX_VERSIONS, cU as DEFAULT_PASSWORD_EMAILS, cV as FIELD_TYPE_BASES, cW as FOLDER_LEVELS, cX as MediaFolders, cY as SSO_COOKIE, cZ as codeOfStatus, cF as hasRows, cH as isHasMany, c_ as isJsonLogger, c$ as keyAllows, d0 as logFormat, cM as mimeAllowedBy, cN as rowFields, cP as uniqueWithinOf } from './config-CHEEPk1d.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-adKUq8-U.js';
2
+ export { cZ as API_KEYS, c_ as API_KEY_GLOBAL_OPERATIONS, c$ as API_KEY_OPERATIONS, d0 as DEFAULT_MAX_VERSIONS, d1 as DEFAULT_PASSWORD_EMAILS, d2 as FIELD_TYPE_BASES, d3 as FOLDER_LEVELS, d4 as MediaFolders, d5 as SSO_COOKIE, d6 as codeOfStatus, cN as hasRows, cP as isHasMany, d7 as isJsonLogger, d8 as keyAllows, d9 as logFormat, cV as mimeAllowedBy, cW as rowFields, cY as uniqueWithinOf } from './config-B-bT0hIn.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-COsNEVx0.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-244WCY6S.js";
59
+ } from "./chunk-MHXQUX74.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, c8 as SsoProviderRef } from './config-CHEEPk1d.js';
1
+ import { be as Label, aF as Field, I as ID, k as AdminLocale, t as AdminSwitcher, cg as SsoProviderRef } from './config-B-bT0hIn.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, by as PLUGIN_API_VERSION, bD as PayloadTooLargeError, bE as Plugin, bF as PluginInfo, bG as PluginTypes, bJ as QueryError, bO as RequestContext, cd as TooManyRequestsError, ce as TypedPlugin, cf as UnauthorizedError, cn as ValidationError, cv as Where, cC as defineFieldType, cD as definePlugin } from './config-CHEEPk1d.js';
2
- export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-B8xypoWd.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, ac as Config, ad as ConfigError, am as DashboardWidget, ax as EasyCMSError, aB as Endpoint, aC as EndpointRequest, aD as ErrorCode, aF as Field, aG as FieldAccess, aJ as FieldError, aM as FieldTypeBase, aN as FieldTypeDefinition, aO as FieldTypeValidateContext, a$ as ForbiddenError, b1 as GlobalConfig, I as ID, bb as JobConfig, be as Label, bs as NavGroup, bt as NotFoundError, bv as OnRequest, bw as OnRequestArgs, bz as PLUGIN_API_VERSION, bE as PayloadTooLargeError, bF as Plugin, bG as PluginInfo, bH as PluginTypes, bK as QueryError, bP as RequestContext, cl as TooManyRequestsError, cm as TypedPlugin, cn as UnauthorizedError, cv as ValidationError, cD as Where, cK as defineFieldType, cL as definePlugin } from './config-B-bT0hIn.js';
2
+ export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-CI6r3SK1.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.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Embedded, code-first headless CMS for Nuxt and Next.js",
5
5
  "license": "MIT",
6
6
  "type": "module",