@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.
- package/dist/{chunk-244WCY6S.js → chunk-MHXQUX74.js} +441 -9
- package/dist/{config-CHEEPk1d.d.ts → config-B-bT0hIn.d.ts} +145 -3
- package/dist/{handler-adKUq8-U.d.ts → handler-COsNEVx0.d.ts} +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +3 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +1 -1
- package/dist/{plugin-B8xypoWd.d.ts → plugin-CI6r3SK1.d.ts} +1 -1
- package/dist/plugin.d.ts +2 -2
- package/package.json +1 -1
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 ?
|
|
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,
|
|
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 _,
|
|
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,
|
|
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-
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Config, Database, EasyCMS, NavGroup, CollectionConfig, ID, RawDocument, Field, ResolvedConfig, ConfigIssue } from './index.js';
|
|
2
|
-
export {
|
|
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-
|
|
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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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,
|
|
2
|
-
export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-
|
|
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';
|