@easy-cms/core 1.14.0 → 1.15.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-Q7MVE3BT.js → chunk-63UEVLN6.js} +495 -185
- package/dist/{config-B-bT0hIn.d.ts → config-BLxN8qcy.d.ts} +23 -3
- package/dist/{handler-COsNEVx0.d.ts → handler-BjlBwaiO.d.ts} +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +1 -1
- package/dist/{plugin-CI6r3SK1.d.ts → plugin-JIb70PKm.d.ts} +1 -1
- package/dist/plugin.d.ts +2 -2
- package/package.json +7 -2
|
@@ -3377,6 +3377,10 @@ function errorFields(error) {
|
|
|
3377
3377
|
return { name: error.name, message: error.message, stack: error.stack };
|
|
3378
3378
|
return { message: String(error) };
|
|
3379
3379
|
}
|
|
3380
|
+
var traceIds;
|
|
3381
|
+
function setTraceIds(read2) {
|
|
3382
|
+
traceIds = read2;
|
|
3383
|
+
}
|
|
3380
3384
|
function jsonLogger(write = (line, level) => (level === "info" ? process.stdout : process.stderr).write(`${line}
|
|
3381
3385
|
`)) {
|
|
3382
3386
|
const log = (level) => (message2, details) => {
|
|
@@ -3389,6 +3393,7 @@ function jsonLogger(write = (line, level) => (level === "info" ? process.stdout
|
|
|
3389
3393
|
message: message2,
|
|
3390
3394
|
...store2?.requestId ? { requestId: store2.requestId } : {},
|
|
3391
3395
|
...store2?.runId ? { runId: store2.runId } : {},
|
|
3396
|
+
...traceIds?.(),
|
|
3392
3397
|
...rest,
|
|
3393
3398
|
...error === void 0 ? {} : { error: errorFields(error) }
|
|
3394
3399
|
};
|
|
@@ -4637,7 +4642,7 @@ function read(response, maxBytes, deadline) {
|
|
|
4637
4642
|
var keys = (list) => new Set(list.split(/\s+/).filter(Boolean));
|
|
4638
4643
|
var CONFIG = keys(`secret db serverURL cors cronSecret timeZone webhooks events jobs localization routes admin
|
|
4639
4644
|
upload auth collections globals endpoints cliCommands onRequest fieldTypes apiKeys email backups
|
|
4640
|
-
audit logs countCacheSeconds maxPage httpCache outbox search plugins`);
|
|
4645
|
+
audit logs countCacheSeconds maxPage httpCache outbox search telemetry plugins`);
|
|
4641
4646
|
var ADMIN = keys("path locale brand siteURL nav commands modules pages dashboard switcher");
|
|
4642
4647
|
var AUTH = keys(`roles rbac tokenExpiration maxLoginAttempts lockWindow trustedOrigins
|
|
4643
4648
|
resetPasswordExpiration inviteExpiration providers providerSignUp password setupCode members emails`);
|
|
@@ -5383,6 +5388,7 @@ function validateConfig(config) {
|
|
|
5383
5388
|
validateCountCache(config, add);
|
|
5384
5389
|
validateOutbox(config, add);
|
|
5385
5390
|
validateSearch(config, add);
|
|
5391
|
+
validateTelemetry(config, add);
|
|
5386
5392
|
validateMaxPage(config, add);
|
|
5387
5393
|
validateHttpCache(config, add);
|
|
5388
5394
|
validateAuth(config, add);
|
|
@@ -6036,6 +6042,18 @@ function validateOutbox(config, add) {
|
|
|
6036
6042
|
if (config.outbox !== void 0 && typeof config.outbox !== "boolean")
|
|
6037
6043
|
add("outbox", "must be true or false");
|
|
6038
6044
|
}
|
|
6045
|
+
function validateTelemetry(config, add) {
|
|
6046
|
+
const value = config.telemetry;
|
|
6047
|
+
if (value === void 0 || typeof value === "boolean") return;
|
|
6048
|
+
if (typeof value !== "object" || value === null) {
|
|
6049
|
+
add("telemetry", "must be true, false or { traces, metrics }");
|
|
6050
|
+
return;
|
|
6051
|
+
}
|
|
6052
|
+
for (const [key, v] of Object.entries(value))
|
|
6053
|
+
if (key !== "traces" && key !== "metrics")
|
|
6054
|
+
add(`telemetry.${key}`, "is not an option", "traces or metrics");
|
|
6055
|
+
else if (typeof v !== "boolean") add(`telemetry.${key}`, "must be true or false");
|
|
6056
|
+
}
|
|
6039
6057
|
function validateSearch(config, add) {
|
|
6040
6058
|
const adapter = config.search;
|
|
6041
6059
|
if (adapter !== void 0) {
|
|
@@ -7011,6 +7029,201 @@ function cacheableResponse(request, body, status, headers, times, tags) {
|
|
|
7011
7029
|
return new Response(request.method.toUpperCase() === "HEAD" ? null : body, { status, headers });
|
|
7012
7030
|
}
|
|
7013
7031
|
|
|
7032
|
+
// src/version.ts
|
|
7033
|
+
var VERSION = true ? "1.15.0" : "0.0.0-dev";
|
|
7034
|
+
|
|
7035
|
+
// src/telemetry.ts
|
|
7036
|
+
var DB_CALLS = /* @__PURE__ */ new Set([
|
|
7037
|
+
"find",
|
|
7038
|
+
"findPage",
|
|
7039
|
+
"findAfter",
|
|
7040
|
+
"findById",
|
|
7041
|
+
"findByIds",
|
|
7042
|
+
"count",
|
|
7043
|
+
"create",
|
|
7044
|
+
"update",
|
|
7045
|
+
"increment",
|
|
7046
|
+
"delete",
|
|
7047
|
+
"findGlobal",
|
|
7048
|
+
"updateGlobal"
|
|
7049
|
+
]);
|
|
7050
|
+
var Telemetry = class _Telemetry {
|
|
7051
|
+
constructor(api, traces, metrics) {
|
|
7052
|
+
this.api = api;
|
|
7053
|
+
this.tracer = traces ? api.trace.getTracer("easy-cms", VERSION) : void 0;
|
|
7054
|
+
if (metrics) {
|
|
7055
|
+
const meter = api.metrics.getMeter("easy-cms", VERSION);
|
|
7056
|
+
this.httpDuration = meter.createHistogram("http.server.request.duration", {
|
|
7057
|
+
unit: "s",
|
|
7058
|
+
description: "Duration of API requests"
|
|
7059
|
+
});
|
|
7060
|
+
this.dbDuration = meter.createHistogram("db.client.operation.duration", {
|
|
7061
|
+
unit: "s",
|
|
7062
|
+
description: "Duration of database calls"
|
|
7063
|
+
});
|
|
7064
|
+
this.jobRuns = meter.createCounter("easy_cms.jobs.runs", {
|
|
7065
|
+
description: "Runs of the config\u2019s jobs, by job and result"
|
|
7066
|
+
});
|
|
7067
|
+
this.jobDuration = meter.createHistogram("easy_cms.jobs.duration", {
|
|
7068
|
+
unit: "s",
|
|
7069
|
+
description: "Duration of the config\u2019s jobs"
|
|
7070
|
+
});
|
|
7071
|
+
meter.createObservableGauge("easy_cms.queue.size", {
|
|
7072
|
+
description: "Background work waiting or failed, as of the last job run"
|
|
7073
|
+
}).addCallback((result) => {
|
|
7074
|
+
for (const [queue, sizes] of Object.entries(this.queues))
|
|
7075
|
+
for (const state of ["pending", "failed"])
|
|
7076
|
+
result.observe(sizes[state], { queue, state });
|
|
7077
|
+
});
|
|
7078
|
+
meter.createObservableGauge("easy_cms.scheduled.late", {
|
|
7079
|
+
description: "Scheduled publishes more than 10 minutes late, as of the last job run"
|
|
7080
|
+
}).addCallback((result) => result.observe(this.late));
|
|
7081
|
+
}
|
|
7082
|
+
if (traces)
|
|
7083
|
+
setTraceIds(() => {
|
|
7084
|
+
const context = api.trace.getActiveSpan()?.spanContext();
|
|
7085
|
+
return context && api.isSpanContextValid(context) ? { traceId: context.traceId, spanId: context.spanId } : void 0;
|
|
7086
|
+
});
|
|
7087
|
+
}
|
|
7088
|
+
api;
|
|
7089
|
+
tracer;
|
|
7090
|
+
httpDuration;
|
|
7091
|
+
dbDuration;
|
|
7092
|
+
jobRuns;
|
|
7093
|
+
jobDuration;
|
|
7094
|
+
queues = {};
|
|
7095
|
+
late = 0;
|
|
7096
|
+
/** `undefined` without `telemetry`; throws when `@opentelemetry/api` isn't installed. */
|
|
7097
|
+
static async load(config) {
|
|
7098
|
+
if (!config) return void 0;
|
|
7099
|
+
const { traces = true, metrics = true } = config === true ? {} : config;
|
|
7100
|
+
if (!traces && !metrics) return void 0;
|
|
7101
|
+
let api;
|
|
7102
|
+
try {
|
|
7103
|
+
api = await import("@opentelemetry/api");
|
|
7104
|
+
} catch {
|
|
7105
|
+
throw new ConfigError([
|
|
7106
|
+
{
|
|
7107
|
+
path: "telemetry",
|
|
7108
|
+
message: "needs @opentelemetry/api",
|
|
7109
|
+
hint: "npm install @opentelemetry/api, and set up the OpenTelemetry SDK in your app"
|
|
7110
|
+
}
|
|
7111
|
+
]);
|
|
7112
|
+
}
|
|
7113
|
+
return new _Telemetry(api, traces, metrics);
|
|
7114
|
+
}
|
|
7115
|
+
/** Runs `fn` in a span; with traces off, just runs it. Errors mark the span and go on. */
|
|
7116
|
+
async span(name, attributes, fn, kind = "internal") {
|
|
7117
|
+
if (!this.tracer) return fn(void 0);
|
|
7118
|
+
const spanKind = { internal: 0, server: 1, client: 2 }[kind];
|
|
7119
|
+
return this.tracer.startActiveSpan(name, { kind: spanKind, attributes }, async (span) => {
|
|
7120
|
+
try {
|
|
7121
|
+
return await fn(span);
|
|
7122
|
+
} catch (error) {
|
|
7123
|
+
span.setStatus({ code: this.api.SpanStatusCode.ERROR });
|
|
7124
|
+
span.setAttribute("error.type", errorType(error));
|
|
7125
|
+
throw error;
|
|
7126
|
+
} finally {
|
|
7127
|
+
span.end();
|
|
7128
|
+
}
|
|
7129
|
+
});
|
|
7130
|
+
}
|
|
7131
|
+
/**
|
|
7132
|
+
* An API request: a server span when nothing traces it yet (`easy-cms serve`), else a span
|
|
7133
|
+
* inside the framework's (Next.js, Nuxt with an SDK).
|
|
7134
|
+
*/
|
|
7135
|
+
async request(method, route2, fn) {
|
|
7136
|
+
const started = performance.now();
|
|
7137
|
+
const kind = this.api.trace.getActiveSpan() ? "internal" : "server";
|
|
7138
|
+
const attributes = { "http.request.method": method, "http.route": route2 };
|
|
7139
|
+
let status = 500;
|
|
7140
|
+
try {
|
|
7141
|
+
return await this.span(
|
|
7142
|
+
`easy-cms ${method} ${route2}`,
|
|
7143
|
+
attributes,
|
|
7144
|
+
async (span) => {
|
|
7145
|
+
const response = await fn();
|
|
7146
|
+
status = response.status;
|
|
7147
|
+
span?.setAttribute("http.response.status_code", status);
|
|
7148
|
+
if (status >= 500) span?.setStatus({ code: this.api.SpanStatusCode.ERROR });
|
|
7149
|
+
return response;
|
|
7150
|
+
},
|
|
7151
|
+
kind
|
|
7152
|
+
);
|
|
7153
|
+
} finally {
|
|
7154
|
+
this.httpDuration?.record((performance.now() - started) / 1e3, {
|
|
7155
|
+
...attributes,
|
|
7156
|
+
"http.response.status_code": status
|
|
7157
|
+
});
|
|
7158
|
+
}
|
|
7159
|
+
}
|
|
7160
|
+
/** The database with a client span and a duration per call (`db <call> <collection>`). */
|
|
7161
|
+
database(db, system2) {
|
|
7162
|
+
return new Proxy(db, {
|
|
7163
|
+
get: (target, key, receiver) => {
|
|
7164
|
+
const value = Reflect.get(target, key, receiver);
|
|
7165
|
+
if (typeof key !== "string" || !DB_CALLS.has(key) || typeof value !== "function")
|
|
7166
|
+
return value;
|
|
7167
|
+
return (args, ...rest) => {
|
|
7168
|
+
const name = args?.collection ?? args?.slug ?? "";
|
|
7169
|
+
const attributes = {
|
|
7170
|
+
"db.system.name": system2,
|
|
7171
|
+
"db.operation.name": key,
|
|
7172
|
+
"db.collection.name": name
|
|
7173
|
+
};
|
|
7174
|
+
const started = performance.now();
|
|
7175
|
+
return this.span(
|
|
7176
|
+
`db ${key} ${name}`,
|
|
7177
|
+
attributes,
|
|
7178
|
+
() => Promise.resolve(value.call(target, args, ...rest)),
|
|
7179
|
+
"client"
|
|
7180
|
+
).finally(() => this.dbDuration?.record((performance.now() - started) / 1e3, attributes));
|
|
7181
|
+
};
|
|
7182
|
+
}
|
|
7183
|
+
});
|
|
7184
|
+
}
|
|
7185
|
+
/** A run of one of the config's jobs, for `easy_cms.jobs.*`. */
|
|
7186
|
+
jobRan(name, ok, ms) {
|
|
7187
|
+
const attributes = { job: name, result: ok ? "ok" : "failed" };
|
|
7188
|
+
this.jobRuns?.add(1, attributes);
|
|
7189
|
+
this.jobDuration?.record(ms / 1e3, { job: name });
|
|
7190
|
+
}
|
|
7191
|
+
/** Whether the queue gauges are reported (and worth counting for). */
|
|
7192
|
+
get measuresQueues() {
|
|
7193
|
+
return this.jobRuns !== void 0;
|
|
7194
|
+
}
|
|
7195
|
+
/** Queue sizes found by the last job run, for the gauges. */
|
|
7196
|
+
queuesCounted(queues, late) {
|
|
7197
|
+
this.queues = queues;
|
|
7198
|
+
this.late = late;
|
|
7199
|
+
}
|
|
7200
|
+
};
|
|
7201
|
+
function errorType(error) {
|
|
7202
|
+
if (error && typeof error === "object") {
|
|
7203
|
+
const code = error.code;
|
|
7204
|
+
if (typeof code === "string" && code) return code;
|
|
7205
|
+
const name = error.name;
|
|
7206
|
+
if (typeof name === "string" && name) return name;
|
|
7207
|
+
}
|
|
7208
|
+
return "Error";
|
|
7209
|
+
}
|
|
7210
|
+
function routeOf(path, root) {
|
|
7211
|
+
if (path === null) return "other";
|
|
7212
|
+
if (root) return path;
|
|
7213
|
+
const parts = path.split("/").filter(Boolean);
|
|
7214
|
+
const [first] = parts;
|
|
7215
|
+
const fixed = /* @__PURE__ */ new Set(["auth", "admin", "jobs", "search", "healthz", "readyz", "api-keys"]);
|
|
7216
|
+
const word = /^[a-z][a-z.-]{0,39}$/;
|
|
7217
|
+
const route2 = parts.map((part, i) => {
|
|
7218
|
+
if (i === 0) return fixed.has(part) || part === "globals" ? part : ":collection";
|
|
7219
|
+
if (first === "globals" && i === 1) return ":global";
|
|
7220
|
+
if (first === "admin" || first === "auth") return word.test(part) ? part : ":param";
|
|
7221
|
+
if (i === 1) return /^\d+$/.test(part) || !word.test(part) ? ":id" : part;
|
|
7222
|
+
return word.test(part) ? part : ":id";
|
|
7223
|
+
});
|
|
7224
|
+
return `/${route2.join("/")}`;
|
|
7225
|
+
}
|
|
7226
|
+
|
|
7014
7227
|
// src/local-api.ts
|
|
7015
7228
|
async function createEasyCMS(config, options = {}) {
|
|
7016
7229
|
const resolved3 = await resolveConfig(config);
|
|
@@ -7023,13 +7236,15 @@ async function createEasyCMS(config, options = {}) {
|
|
|
7023
7236
|
const storage = resolved3.upload.storage ?? diskStorage({ dir: resolved3.upload.dir });
|
|
7024
7237
|
await storage.init?.({ cwd });
|
|
7025
7238
|
if (resolved3.upload.privateStorage) await resolved3.upload.privateStorage.init?.({ cwd });
|
|
7026
|
-
const
|
|
7239
|
+
const telemetry = await Telemetry.load(resolved3.telemetry);
|
|
7240
|
+
const connected = await resolved3.db.init({
|
|
7027
7241
|
config: resolved3,
|
|
7028
7242
|
cwd,
|
|
7029
7243
|
schema: options.schema ?? (process.env.NODE_ENV === "production" ? "verify" : "push"),
|
|
7030
7244
|
logger,
|
|
7031
7245
|
interactive: options.interactive ?? false
|
|
7032
7246
|
});
|
|
7247
|
+
const db = telemetry ? telemetry.database(connected, dbSystem(resolved3.db.name)) : connected;
|
|
7033
7248
|
if (resolved3.outbox && db.outbox !== true) {
|
|
7034
7249
|
await db.destroy();
|
|
7035
7250
|
throw new ConfigError([
|
|
@@ -7041,6 +7256,7 @@ async function createEasyCMS(config, options = {}) {
|
|
|
7041
7256
|
]);
|
|
7042
7257
|
}
|
|
7043
7258
|
const cms = new EasyCMS(resolved3, db, logger, storage, cwd);
|
|
7259
|
+
cms.telemetry = telemetry;
|
|
7044
7260
|
if (resolved3.search?.ephemeral && process.env.NODE_ENV === "production")
|
|
7045
7261
|
logger.warn(
|
|
7046
7262
|
`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.`
|
|
@@ -7054,6 +7270,10 @@ async function createEasyCMS(config, options = {}) {
|
|
|
7054
7270
|
);
|
|
7055
7271
|
return cms;
|
|
7056
7272
|
}
|
|
7273
|
+
function dbSystem(name) {
|
|
7274
|
+
if (name.includes("postgres") || name.includes("pglite")) return "postgresql";
|
|
7275
|
+
return name.includes("sqlite") ? "sqlite" : name;
|
|
7276
|
+
}
|
|
7057
7277
|
var toJob = (row) => ({
|
|
7058
7278
|
id: row.id,
|
|
7059
7279
|
action: row.action,
|
|
@@ -7090,6 +7310,8 @@ var EasyCMS = class {
|
|
|
7090
7310
|
outbox;
|
|
7091
7311
|
/** With `search`: keeps the index in step with published documents. */
|
|
7092
7312
|
searchIndex;
|
|
7313
|
+
/** @internal With `telemetry`: OpenTelemetry spans and metrics (set by `createEasyCMS`). */
|
|
7314
|
+
telemetry;
|
|
7093
7315
|
/** @internal */
|
|
7094
7316
|
constructor(config, db, logger = consoleLogger, storage = diskStorage({ dir: config.upload.dir }), cwd = process.cwd(), privateStorage) {
|
|
7095
7317
|
this.config = config;
|
|
@@ -7117,7 +7339,15 @@ var EasyCMS = class {
|
|
|
7117
7339
|
logger,
|
|
7118
7340
|
config.collections.some((c) => c.slug === EMAIL_DELIVERIES) ? this.emailQueue() : void 0
|
|
7119
7341
|
);
|
|
7120
|
-
this.outbox = config.collections.some((c) => c.slug === OUTBOX) ? new Outbox(
|
|
7342
|
+
this.outbox = config.collections.some((c) => c.slug === OUTBOX) ? new Outbox(
|
|
7343
|
+
db,
|
|
7344
|
+
logger,
|
|
7345
|
+
(entry, done, mark) => this.telemetry ? this.telemetry.span(
|
|
7346
|
+
`easy-cms outbox ${entry.event} ${entry.target}`,
|
|
7347
|
+
{ "easy_cms.event": entry.event, "easy_cms.collection": entry.target },
|
|
7348
|
+
() => this.runOutbox(entry, done, mark)
|
|
7349
|
+
) : this.runOutbox(entry, done, mark)
|
|
7350
|
+
) : void 0;
|
|
7121
7351
|
this.searchIndex = config.search ? new SearchIndex(
|
|
7122
7352
|
config.search,
|
|
7123
7353
|
config.collections,
|
|
@@ -7164,99 +7394,112 @@ var EasyCMS = class {
|
|
|
7164
7394
|
};
|
|
7165
7395
|
}
|
|
7166
7396
|
async find(collection2, options = {}) {
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
requested
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
if (
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
docs:
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7397
|
+
return this.traced("find", String(collection2), async () => {
|
|
7398
|
+
const config = this.collection(collection2);
|
|
7399
|
+
const guard = guardOf(options);
|
|
7400
|
+
const limit = options.limit ?? 10;
|
|
7401
|
+
const page = options.page ?? 1;
|
|
7402
|
+
if (!Number.isInteger(limit) || limit < 0)
|
|
7403
|
+
throw new QueryError("limit must be a non-negative integer");
|
|
7404
|
+
if (!Number.isInteger(page) || page < 1)
|
|
7405
|
+
throw new QueryError("page must be a positive integer");
|
|
7406
|
+
const draft = readsDrafts(guard, options.draft);
|
|
7407
|
+
const where = this.whereFor(
|
|
7408
|
+
config,
|
|
7409
|
+
draftWhere(config, draft, await this.readWhere(config, guard, options.where)),
|
|
7410
|
+
options
|
|
7411
|
+
);
|
|
7412
|
+
const requested = options.sort === void 0 ? ["-createdAt"] : [options.sort].flat();
|
|
7413
|
+
await this.checkQueryFields(config, guard, void 0, requested);
|
|
7414
|
+
const locale = this.localeOf(options);
|
|
7415
|
+
const sort = locale === void 0 ? requested : localizeSort(
|
|
7416
|
+
config.fields,
|
|
7417
|
+
requested,
|
|
7418
|
+
locale === ALL_LOCALES ? this.config.localization?.defaultLocale ?? locale : locale,
|
|
7419
|
+
this.config.localization?.locales ?? []
|
|
7420
|
+
);
|
|
7421
|
+
if (options.cursor !== void 0) {
|
|
7422
|
+
if (options.page !== void 0) throw new QueryError("Use either cursor or page, not both");
|
|
7423
|
+
if (limit === 0) throw new QueryError("A cursor needs a limit above 0");
|
|
7424
|
+
if (!this.db.findAfter)
|
|
7425
|
+
throw new QueryError("This database adapter has no cursor pagination");
|
|
7426
|
+
const query = cursorQuery(collection2, options.where, requested, locale, draft);
|
|
7427
|
+
const after = readCursor(options.cursor, query);
|
|
7428
|
+
const result2 = await this.db.findAfter({ collection: collection2, where, sort, limit, after });
|
|
7429
|
+
const found2 = draft ? await this.withDrafts(config, result2.docs) : result2.docs;
|
|
7430
|
+
const docs2 = await this.output(config, found2, guard, { ...options, draft });
|
|
7431
|
+
return {
|
|
7432
|
+
docs: docs2,
|
|
7433
|
+
limit,
|
|
7434
|
+
nextCursor: result2.hasNextPage && result2.next ? writeCursor(result2.next, query) : null,
|
|
7435
|
+
hasNextPage: result2.hasNextPage,
|
|
7436
|
+
hasPrevPage: after !== null,
|
|
7437
|
+
totalDocs: null,
|
|
7438
|
+
totalPages: null,
|
|
7439
|
+
page: null
|
|
7440
|
+
};
|
|
7441
|
+
}
|
|
7442
|
+
const args = { collection: collection2, where, sort, limit, page };
|
|
7443
|
+
const count = options.count !== false || limit === 0;
|
|
7444
|
+
const result = count ? await this.db.find(args) : this.db.findPage ? await this.db.findPage(args) : { ...await this.db.find(args), totalDocs: null, totalPages: null };
|
|
7445
|
+
const found = draft ? await this.withDrafts(config, result.docs) : result.docs;
|
|
7446
|
+
const docs = await this.output(config, found, guard, { ...options, draft });
|
|
7447
|
+
return { ...result, docs };
|
|
7448
|
+
});
|
|
7215
7449
|
}
|
|
7216
7450
|
async findById(collection2, id, options = {}) {
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7451
|
+
return this.traced("findById", String(collection2), async () => {
|
|
7452
|
+
const config = this.collection(collection2);
|
|
7453
|
+
const guard = guardOf(options);
|
|
7454
|
+
const parsed = parseId(id);
|
|
7455
|
+
if (parsed === void 0) return null;
|
|
7456
|
+
const draft = readsDrafts(guard, options.draft);
|
|
7457
|
+
const where = this.whereFor(
|
|
7458
|
+
config,
|
|
7459
|
+
draftWhere(config, draft, await this.readWhere(config, guard, void 0)),
|
|
7460
|
+
options
|
|
7461
|
+
);
|
|
7462
|
+
const doc = where ? (await this.db.find({
|
|
7463
|
+
collection: collection2,
|
|
7464
|
+
where: andWhere(where, { id: { equals: parsed } }),
|
|
7465
|
+
sort: [],
|
|
7466
|
+
limit: 1,
|
|
7467
|
+
page: 1
|
|
7468
|
+
})).docs[0] : await this.db.findById({ collection: collection2, id: parsed });
|
|
7469
|
+
if (!doc) return null;
|
|
7470
|
+
const [current] = draft ? await this.withDrafts(config, [doc]) : [doc];
|
|
7471
|
+
const [out] = await this.output(config, [current], guard, {
|
|
7472
|
+
...options,
|
|
7473
|
+
draft
|
|
7474
|
+
});
|
|
7475
|
+
return out ?? null;
|
|
7476
|
+
});
|
|
7238
7477
|
}
|
|
7239
7478
|
async count(collection2, options = {}) {
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
draftWhere(
|
|
7479
|
+
return this.traced("count", String(collection2), async () => {
|
|
7480
|
+
const config = this.collection(collection2);
|
|
7481
|
+
const where = this.whereFor(
|
|
7244
7482
|
config,
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7483
|
+
draftWhere(
|
|
7484
|
+
config,
|
|
7485
|
+
readsDrafts(guardOf(options), options.draft),
|
|
7486
|
+
await this.readWhere(config, guardOf(options), options.where)
|
|
7487
|
+
),
|
|
7488
|
+
options
|
|
7489
|
+
);
|
|
7490
|
+
return this.db.count({ collection: collection2, where });
|
|
7491
|
+
});
|
|
7251
7492
|
}
|
|
7252
7493
|
async create(collection2, data, options = {}) {
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7494
|
+
return this.traced("create", String(collection2), async () => {
|
|
7495
|
+
const config = this.collection(collection2);
|
|
7496
|
+
if (config.slug === MEDIA) {
|
|
7497
|
+
throw new ValidationError(MEDIA, [
|
|
7498
|
+
{ field: "file", message: "upload files with cms.upload() or POST multipart" }
|
|
7499
|
+
]);
|
|
7500
|
+
}
|
|
7501
|
+
return await this.createDocument(config, asObject(data, collection2), options);
|
|
7502
|
+
});
|
|
7260
7503
|
}
|
|
7261
7504
|
/**
|
|
7262
7505
|
* Stores a file and creates its `media` document (FR-UPL). The type is detected from
|
|
@@ -7711,7 +7954,14 @@ var EasyCMS = class {
|
|
|
7711
7954
|
}
|
|
7712
7955
|
}
|
|
7713
7956
|
async update(collection2, id, data, options = {}) {
|
|
7714
|
-
return
|
|
7957
|
+
return this.traced("update", String(collection2), async () => {
|
|
7958
|
+
return await this.updateDocument(
|
|
7959
|
+
collection2,
|
|
7960
|
+
id,
|
|
7961
|
+
asObject(data, collection2),
|
|
7962
|
+
options
|
|
7963
|
+
);
|
|
7964
|
+
});
|
|
7715
7965
|
}
|
|
7716
7966
|
/**
|
|
7717
7967
|
* Adds `by` (negative to take away) to a top-level number field in one database statement, so
|
|
@@ -8082,65 +8332,69 @@ var EasyCMS = class {
|
|
|
8082
8332
|
* gives the documents they own to another user; without it they have no owner.
|
|
8083
8333
|
*/
|
|
8084
8334
|
async delete(collection2, id, options = {}) {
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8335
|
+
return this.traced("delete", String(collection2), async () => {
|
|
8336
|
+
const config = this.collection(collection2);
|
|
8337
|
+
const guard = guardOf(options);
|
|
8338
|
+
const parsed = parseId(id);
|
|
8339
|
+
const existing = parsed === void 0 ? null : await this.db.findById({ collection: collection2, id: parsed });
|
|
8340
|
+
if (!existing || parsed === void 0) throw new NotFoundError(collection2, id);
|
|
8341
|
+
await this.checkDocumentAccess(config, "delete", guard, parsed, void 0);
|
|
8342
|
+
if (config.slug === USERS)
|
|
8343
|
+
await this.guardLastAdmin(parsed, existing, { ...existing, active: false });
|
|
8344
|
+
const base = this.hookArgs(config, guard);
|
|
8345
|
+
for (const hook of config.hooks?.beforeDelete ?? []) await hook({ ...base, id: parsed });
|
|
8346
|
+
if (config.slug === USERS) {
|
|
8347
|
+
const to = options.transferTo;
|
|
8348
|
+
if (to !== void 0 && to !== null) {
|
|
8349
|
+
const target = parseId(to);
|
|
8350
|
+
const heir = target === void 0 ? null : await this.db.findById({ collection: USERS, id: target });
|
|
8351
|
+
if (!heir || String(heir.id) === String(parsed))
|
|
8352
|
+
throw new ValidationError(USERS, [
|
|
8353
|
+
{ field: "transferTo", message: "must be another existing user" }
|
|
8354
|
+
]);
|
|
8355
|
+
await this.roles.transfer(parsed, heir.id);
|
|
8356
|
+
} else await this.roles.transfer(parsed, null);
|
|
8357
|
+
await this.auth.revokeSessions(parsed);
|
|
8358
|
+
await this.auth.sso.forget(parsed);
|
|
8359
|
+
}
|
|
8360
|
+
if (collection2 === MEDIA_FOLDERS) {
|
|
8361
|
+
await this.checkMoveLimit(await this.folders.privacyAfter(parsed, { private: false }));
|
|
8362
|
+
await this.folders.release(existing);
|
|
8363
|
+
}
|
|
8364
|
+
const outbox = this.outboxRow(config, "delete", guard, existing, { docId: parsed });
|
|
8365
|
+
await this.db.delete({ collection: collection2, id: parsed, outbox });
|
|
8366
|
+
if (collection2 === MEDIA_FOLDERS) {
|
|
8367
|
+
this.folders.invalidate();
|
|
8368
|
+
const parent = existing.parent ?? null;
|
|
8369
|
+
if (parent !== null) await this.settlePrivacy(parent);
|
|
8370
|
+
else await this.settleTopLevel();
|
|
8371
|
+
}
|
|
8372
|
+
if (versionLimit(config)) await this.versions.deleteAll(collectionParent(collection2), parsed);
|
|
8373
|
+
if (config.schedule) {
|
|
8374
|
+
for (const job of await this.pendingJobs(collection2, parsed))
|
|
8375
|
+
await this.db.delete({ collection: SCHEDULED_JOBS, id: job.id });
|
|
8376
|
+
}
|
|
8377
|
+
if (outbox) this.outbox?.kick();
|
|
8378
|
+
else {
|
|
8379
|
+
await this.notify(config.hooks?.afterDelete, "afterDelete", config.slug, {
|
|
8380
|
+
...base,
|
|
8381
|
+
id: parsed,
|
|
8382
|
+
doc: existing
|
|
8383
|
+
});
|
|
8384
|
+
this.emitChange(config, "delete", existing);
|
|
8385
|
+
}
|
|
8386
|
+
await this.audited(config, "delete", existing, existing, guard);
|
|
8387
|
+
const [out] = await this.output(config, [existing], guard, { depth: 0, draft: true });
|
|
8388
|
+
return out;
|
|
8389
|
+
});
|
|
8138
8390
|
}
|
|
8139
8391
|
async findGlobal(slug2, options = {}) {
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8392
|
+
return this.traced("findGlobal", String(slug2), async () => {
|
|
8393
|
+
const config = this.global(slug2);
|
|
8394
|
+
const guard = guardOf(options);
|
|
8395
|
+
await this.checkGlobalAccess(config, "read", guard);
|
|
8396
|
+
return this.readGlobal(config, this.globalKey(config, guard), guard, options);
|
|
8397
|
+
});
|
|
8144
8398
|
}
|
|
8145
8399
|
/** A global as stored under `key` (see `globalKey`); empty when there is no key. */
|
|
8146
8400
|
async readGlobal(config, key, guard, options) {
|
|
@@ -8180,7 +8434,9 @@ var EasyCMS = class {
|
|
|
8180
8434
|
return key;
|
|
8181
8435
|
}
|
|
8182
8436
|
async updateGlobal(slug2, data, options = {}) {
|
|
8183
|
-
return this.
|
|
8437
|
+
return this.traced("updateGlobal", String(slug2), async () => {
|
|
8438
|
+
return this.saveGlobal(slug2, asObject(data, slug2), options);
|
|
8439
|
+
});
|
|
8184
8440
|
}
|
|
8185
8441
|
/** Takes a global off the site (`status: 'draft'`). See `unpublish`. */
|
|
8186
8442
|
async unpublishGlobal(slug2, options = {}) {
|
|
@@ -8447,36 +8703,39 @@ var EasyCMS = class {
|
|
|
8447
8703
|
* hold fewer documents than `limit`.
|
|
8448
8704
|
*/
|
|
8449
8705
|
async search(query, options = {}) {
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
{
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8706
|
+
return this.traced("search", "", async () => {
|
|
8707
|
+
const index = this.searchIndex;
|
|
8708
|
+
if (!index) throw new QueryError("Search needs a `search` adapter in the config");
|
|
8709
|
+
const limit = options.limit ?? 10;
|
|
8710
|
+
const page = options.page ?? 1;
|
|
8711
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
8712
|
+
throw new QueryError("limit must be an integer from 1 to 100");
|
|
8713
|
+
if (!Number.isInteger(page) || page < 1)
|
|
8714
|
+
throw new QueryError("page must be an integer from 1");
|
|
8715
|
+
const collections = options.collections ?? index.searchable;
|
|
8716
|
+
for (const slug2 of collections)
|
|
8717
|
+
if (!index.configOf(slug2)) throw new QueryError(`"${slug2}" is not searchable`);
|
|
8718
|
+
const localization = this.config.localization;
|
|
8719
|
+
const asked = options.locale && options.locale !== ALL_LOCALES ? options.locale : void 0;
|
|
8720
|
+
if (asked !== void 0 && localization && !localization.locales.includes(asked))
|
|
8721
|
+
throw new QueryError(`Unknown locale "${asked}"`);
|
|
8722
|
+
const locale = localization ? asked ?? localization.defaultLocale ?? null : null;
|
|
8723
|
+
const text3 = query.trim().slice(0, 200);
|
|
8724
|
+
const empty = { hits: [], total: 0, page, limit, hasNextPage: false };
|
|
8725
|
+
if (!text3 || collections.length === 0) return empty;
|
|
8726
|
+
const { collections: _, limit: __, page: ___, ...read2 } = options;
|
|
8727
|
+
const { hits, total } = await index.search(
|
|
8728
|
+
{ query: text3, collections, locale, limit, offset: (page - 1) * limit },
|
|
8729
|
+
async (collection2, ids) => (await this.find(collection2, {
|
|
8730
|
+
...read2,
|
|
8731
|
+
...locale ? { locale } : {},
|
|
8732
|
+
where: { id: { in: ids.map((id) => parseId(id) ?? id) } },
|
|
8733
|
+
limit: ids.length,
|
|
8734
|
+
draft: false
|
|
8735
|
+
})).docs
|
|
8736
|
+
);
|
|
8737
|
+
return { hits, total, page, limit, hasNextPage: page * limit < total };
|
|
8738
|
+
});
|
|
8480
8739
|
}
|
|
8481
8740
|
/**
|
|
8482
8741
|
* Builds the search index again from the database, for every searchable collection or one:
|
|
@@ -8515,6 +8774,18 @@ var EasyCMS = class {
|
|
|
8515
8774
|
if (!found.hasNextPage) return;
|
|
8516
8775
|
}
|
|
8517
8776
|
}
|
|
8777
|
+
/** A Local API call in a span (`easy-cms <operation> <collection>`) with `telemetry`. */
|
|
8778
|
+
traced(operation, target, fn) {
|
|
8779
|
+
if (!this.telemetry) return fn();
|
|
8780
|
+
return this.telemetry.span(
|
|
8781
|
+
`easy-cms ${operation}${target ? ` ${target}` : ""}`,
|
|
8782
|
+
{
|
|
8783
|
+
"easy_cms.operation": operation,
|
|
8784
|
+
...target ? { "easy_cms.collection": target } : {}
|
|
8785
|
+
},
|
|
8786
|
+
fn
|
|
8787
|
+
);
|
|
8788
|
+
}
|
|
8518
8789
|
/** @internal Puts a failed outbox entry back in line (Settings → Jobs). */
|
|
8519
8790
|
retryOutbox(id) {
|
|
8520
8791
|
return this.outbox?.retry(id) ?? Promise.resolve(false);
|
|
@@ -8546,7 +8817,9 @@ var EasyCMS = class {
|
|
|
8546
8817
|
* scheduler and the cron endpoint (`GET <api>/jobs/run`) call.
|
|
8547
8818
|
*/
|
|
8548
8819
|
runJobs(now = /* @__PURE__ */ new Date()) {
|
|
8549
|
-
const run = this.running.then(
|
|
8820
|
+
const run = this.running.then(
|
|
8821
|
+
() => this.telemetry ? this.telemetry.span("easy-cms jobs.run", {}, () => this.runJobsNow(now)) : this.runJobsNow(now)
|
|
8822
|
+
);
|
|
8550
8823
|
this.running = run.catch(() => {
|
|
8551
8824
|
});
|
|
8552
8825
|
return run;
|
|
@@ -8573,6 +8846,10 @@ var EasyCMS = class {
|
|
|
8573
8846
|
jobs: await this.runConfigJobs(now),
|
|
8574
8847
|
outbox: await this.outbox?.run(now) ?? { ran: 0, failed: 0 }
|
|
8575
8848
|
};
|
|
8849
|
+
if (this.telemetry?.measuresQueues)
|
|
8850
|
+
await this.countQueues(now).catch(
|
|
8851
|
+
(error) => this.logger.error(`Telemetry: ${error.message}`, { error })
|
|
8852
|
+
);
|
|
8576
8853
|
const ranAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8577
8854
|
await this.db.updateGlobal({ slug: JOBS_RAN, data: { at: ranAt, updatedAt: ranAt } }).catch((error) => this.logger.error(`Jobs: ${error.message}`, { error }));
|
|
8578
8855
|
return result;
|
|
@@ -8641,19 +8918,47 @@ var EasyCMS = class {
|
|
|
8641
8918
|
const started = Date.now();
|
|
8642
8919
|
let error = null;
|
|
8643
8920
|
try {
|
|
8644
|
-
|
|
8921
|
+
const work = async () => job.run({ cms: this, now });
|
|
8922
|
+
await (this.telemetry ? this.telemetry.span(`easy-cms job ${job.name}`, { "easy_cms.job": job.name }, work) : work());
|
|
8645
8923
|
ran++;
|
|
8646
8924
|
} catch (caught) {
|
|
8647
8925
|
failed++;
|
|
8648
8926
|
error = (caught.message || String(caught)).slice(0, 500);
|
|
8649
8927
|
this.logger.error(`Job ${job.name} failed: ${error}`, { error: caught, job: job.name });
|
|
8650
8928
|
}
|
|
8929
|
+
this.telemetry?.jobRan(job.name, error === null, Date.now() - started);
|
|
8651
8930
|
await this.recordJobRun(job.name, stamp, error, Date.now() - started).catch(
|
|
8652
8931
|
(caught) => this.logger.error(`Job ${job.name}: ${caught.message}`, { error: caught })
|
|
8653
8932
|
);
|
|
8654
8933
|
}
|
|
8655
8934
|
return { ran, failed };
|
|
8656
8935
|
}
|
|
8936
|
+
/** Background work waiting and failed, for `telemetry`'s queue gauges. */
|
|
8937
|
+
async countQueues(now) {
|
|
8938
|
+
const has3 = (slug2) => this.config.collections.some((c) => c.slug === slug2);
|
|
8939
|
+
const sizes = async (collection2, pending) => ({
|
|
8940
|
+
pending: await this.db.count({ collection: collection2, where: { state: { in: pending } } }),
|
|
8941
|
+
failed: await this.db.count({ collection: collection2, where: { state: { equals: "failed" } } })
|
|
8942
|
+
});
|
|
8943
|
+
const queues = {};
|
|
8944
|
+
if (has3(OUTBOX)) queues.outbox = await sizes(OUTBOX, ["pending", "running"]);
|
|
8945
|
+
if (has3(WEBHOOK_DELIVERIES)) queues.webhooks = await sizes(WEBHOOK_DELIVERIES, ["pending"]);
|
|
8946
|
+
if (has3(EMAIL_DELIVERIES)) queues.emails = await sizes(EMAIL_DELIVERIES, ["pending"]);
|
|
8947
|
+
let late = 0;
|
|
8948
|
+
if (has3(SCHEDULED_JOBS)) {
|
|
8949
|
+
queues.scheduled = await sizes(SCHEDULED_JOBS, ["pending", "running"]);
|
|
8950
|
+
late = await this.db.count({
|
|
8951
|
+
collection: SCHEDULED_JOBS,
|
|
8952
|
+
where: {
|
|
8953
|
+
and: [
|
|
8954
|
+
{ state: { equals: "pending" } },
|
|
8955
|
+
{ runAt: { lt: new Date(now.getTime() - 10 * 6e4).toISOString() } }
|
|
8956
|
+
]
|
|
8957
|
+
}
|
|
8958
|
+
});
|
|
8959
|
+
}
|
|
8960
|
+
this.telemetry?.queuesCounted(queues, late);
|
|
8961
|
+
}
|
|
8657
8962
|
async recordJobRun(name, at, error, ms) {
|
|
8658
8963
|
const { docs } = await this.db.find({
|
|
8659
8964
|
collection: JOB_RUNS,
|
|
@@ -10750,9 +11055,6 @@ async function readAdminModule(cms, index) {
|
|
|
10750
11055
|
return { body, etag: `"${createHash6("sha1").update(body).digest("base64url")}"` };
|
|
10751
11056
|
}
|
|
10752
11057
|
|
|
10753
|
-
// src/version.ts
|
|
10754
|
-
var VERSION = true ? "1.14.0" : "0.0.0-dev";
|
|
10755
|
-
|
|
10756
11058
|
// src/rest/handler.ts
|
|
10757
11059
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
10758
11060
|
|
|
@@ -11528,6 +11830,7 @@ async function adminStatus(cms) {
|
|
|
11528
11830
|
storage: config.upload.storage?.name ?? "local",
|
|
11529
11831
|
email: config.email ? config.email.name ?? "custom" : null,
|
|
11530
11832
|
search: config.search ? config.search.name ?? "custom" : null,
|
|
11833
|
+
telemetry: !!config.telemetry,
|
|
11531
11834
|
plugins: config.installedPlugins.map((p) => ({ ...p })),
|
|
11532
11835
|
fieldTypes: config.fieldTypes.map((t) => t.name)
|
|
11533
11836
|
},
|
|
@@ -11726,7 +12029,14 @@ var HEALTH_CHECKS = /* @__PURE__ */ new Map([
|
|
|
11726
12029
|
["readyz", "readyz"]
|
|
11727
12030
|
]);
|
|
11728
12031
|
var REQUEST_ID = /^[\w.-]{1,128}$/;
|
|
11729
|
-
|
|
12032
|
+
function respond(cms, request, url, path, root, options, production) {
|
|
12033
|
+
const run = () => respondLogged(cms, request, url, path, root, options, production);
|
|
12034
|
+
const probe = !root && (path === "/healthz" || path === "/readyz");
|
|
12035
|
+
if (!cms.telemetry || probe) return run();
|
|
12036
|
+
const route2 = root || path === null ? routeOf(path, root) : `${cms.config.routes.api}${routeOf(path, root)}`;
|
|
12037
|
+
return cms.telemetry.request(request.method, route2, run);
|
|
12038
|
+
}
|
|
12039
|
+
async function respondLogged(cms, request, url, path, root, options, production) {
|
|
11730
12040
|
const started = performance.now();
|
|
11731
12041
|
const incoming = request.headers.get("x-request-id");
|
|
11732
12042
|
const requestId = incoming && REQUEST_ID.test(incoming) ? incoming : randomUUID4();
|
|
@@ -13227,10 +13537,10 @@ export {
|
|
|
13227
13537
|
applyPlugins,
|
|
13228
13538
|
resolveConfig,
|
|
13229
13539
|
cacheTags,
|
|
13540
|
+
VERSION,
|
|
13230
13541
|
createEasyCMS,
|
|
13231
13542
|
EasyCMS,
|
|
13232
13543
|
resolveAdminModule,
|
|
13233
|
-
VERSION,
|
|
13234
13544
|
CSRF_COOKIE,
|
|
13235
13545
|
CSRF_HEADER,
|
|
13236
13546
|
createRestHandler,
|
|
@@ -119,7 +119,8 @@ declare function logFormat(logs: LogsConfig | undefined): 'pretty' | 'json';
|
|
|
119
119
|
declare const isJsonLogger: (logger: Logger) => boolean;
|
|
120
120
|
/**
|
|
121
121
|
* One JSON object per line: `time`, `level`, `name`, `message`, the request's `requestId` (or
|
|
122
|
-
* the job run's `runId`), `
|
|
122
|
+
* the job run's `runId`), with `telemetry` the active span's `traceId` and `spanId`, `error`
|
|
123
|
+
* with its stack, and any other details.
|
|
123
124
|
*/
|
|
124
125
|
declare function jsonLogger(write?: (line: string, level: 'info' | 'warn' | 'error') => void): Logger;
|
|
125
126
|
|
|
@@ -2107,6 +2108,8 @@ declare class EasyCMS<C extends Config = Config> {
|
|
|
2107
2108
|
}[]>;
|
|
2108
2109
|
/** Published documents of a collection in a locale, a page at a time, for the search index. */
|
|
2109
2110
|
private publishedPages;
|
|
2111
|
+
/** A Local API call in a span (`easy-cms <operation> <collection>`) with `telemetry`. */
|
|
2112
|
+
private traced;
|
|
2110
2113
|
destroy(): Promise<void>;
|
|
2111
2114
|
private schedulerTimer;
|
|
2112
2115
|
private running;
|
|
@@ -2147,6 +2150,8 @@ declare class EasyCMS<C extends Config = Config> {
|
|
|
2147
2150
|
* many instances run jobs.
|
|
2148
2151
|
*/
|
|
2149
2152
|
private runConfigJobs;
|
|
2153
|
+
/** Background work waiting and failed, for `telemetry`'s queue gauges. */
|
|
2154
|
+
private countQueues;
|
|
2150
2155
|
private recordJobRun;
|
|
2151
2156
|
/**
|
|
2152
2157
|
* Sends an event of the app or a plugin (`events` in the config, e.g. `order.paid`) to the
|
|
@@ -2732,6 +2737,14 @@ interface CollectionSearch {
|
|
|
2732
2737
|
*/
|
|
2733
2738
|
declare function memorySearch(): SearchAdapter;
|
|
2734
2739
|
|
|
2740
|
+
/** `telemetry` in the config: OpenTelemetry traces and metrics, both on with `true`. */
|
|
2741
|
+
interface TelemetryConfig {
|
|
2742
|
+
/** Spans for API requests, Local API calls, database calls and background jobs. Default true. */
|
|
2743
|
+
readonly traces?: boolean;
|
|
2744
|
+
/** Request and database durations, job runs and queue sizes. Default true. */
|
|
2745
|
+
readonly metrics?: boolean;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2735
2748
|
declare const WEBHOOK_EVENTS: readonly ["create", "update", "delete", "publish", "unpublish", "draft"];
|
|
2736
2749
|
/**
|
|
2737
2750
|
* `create`, `update`, `delete`: the stored document changed. `publish` / `unpublish`: its status
|
|
@@ -3616,6 +3629,13 @@ interface Config {
|
|
|
3616
3629
|
* (with `index: 'trigram'` on Postgres) still finds text.
|
|
3617
3630
|
*/
|
|
3618
3631
|
readonly search?: SearchAdapter;
|
|
3632
|
+
/**
|
|
3633
|
+
* OpenTelemetry traces and metrics through `@opentelemetry/api` (install it, and set up the SDK
|
|
3634
|
+
* in your app): spans for API requests, Local API calls, database calls and jobs, and metrics
|
|
3635
|
+
* for request and database durations, job runs and queue sizes. `true` turns both on. Spans
|
|
3636
|
+
* carry names of collections, routes and operations, never query values or documents.
|
|
3637
|
+
*/
|
|
3638
|
+
readonly telemetry?: boolean | TelemetryConfig;
|
|
3619
3639
|
readonly plugins?: readonly Plugin[];
|
|
3620
3640
|
}
|
|
3621
3641
|
/** The config after plugins ran, validation passed and defaults were applied. */
|
|
@@ -3647,5 +3667,5 @@ interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes'
|
|
|
3647
3667
|
*/
|
|
3648
3668
|
declare function defineConfig<const TConfig extends Config>(config: TConfig): TConfig;
|
|
3649
3669
|
|
|
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,
|
|
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
|
|
3670
|
+
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_GLOBAL_OPERATIONS as c$, WEBHOOK_EVENTS as cA, anyone as cG, consoleEmail as cH, consoleLogger as cI, createEasyCMS as cJ, defineConfig as cK, defineFieldType as cL, definePlugin as cM, diskStorage as cN, hasRows as cO, isAdmin as cP, isHasMany as cQ, isSignedIn as cR, isStaff as cS, isSystemAdmin as cT, jsonLogger as cU, memorySearch as cV, mimeAllowedBy as cW, rowFields as cX, silentLogger as cY, uniqueWithinOf as cZ, API_KEYS as c_, SingleSignOn as cc, TooManyRequestsError as cm, UnauthorizedError as co, ValidationError as cw, API_KEY_OPERATIONS as d0, DEFAULT_MAX_VERSIONS as d1, DEFAULT_PASSWORD_EMAILS as d2, FIELD_TYPE_BASES as d3, FOLDER_LEVELS as d4, MediaFolders as d5, SSO_COOKIE as d6, codeOfStatus as d7, isJsonLogger as d8, keyAllows as d9, logFormat as da };
|
|
3671
|
+
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 cB, WebhookEvent as cC, WebhookPayload as cD, Where as cE, WhereOperators as cF, SignupArgs as ca, SignupResult as cb, SlugField as cd, SlugOf as ce, SsoOutcome as cf, SsoProviderRef as cg, StorageAdapter as ch, StoredFile as ci, TelemetryConfig as cj, TextField as ck, TextareaField as cl, TypedPlugin as cn, UncountedDocs as cp, UpdateInput as cq, UpdateOptions as cr, UploadConfig as cs, UploadField as ct, UploadFromURLConfig as cu, UserIdentity as cv, Version as cx, VersionSummary as cy, VersionsConfig as cz, 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, 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-
|
|
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-BLxN8qcy.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 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
|
|
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-BLxN8qcy.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 TelemetryConfig, ck as TextField, cl as TextareaField, cm as TooManyRequestsError, cn as TypedPlugin, co as UnauthorizedError, cp as UncountedDocs, cq as UpdateInput, cr as UpdateOptions, cs as UploadConfig, ct as UploadField, cu as UploadFromURLConfig, cv as UserIdentity, cw as ValidationError, cx as Version, cy as VersionSummary, cz as VersionsConfig, cA as WEBHOOK_EVENTS, cB as WebhookConfig, cC as WebhookEvent, cD as WebhookPayload, cE as Where, cF as WhereOperators, cG as anyone, cH as consoleEmail, cI as consoleLogger, cJ as createEasyCMS, cK as defineConfig, cL as defineFieldType, cM as definePlugin, cN as diskStorage, cO as hasRows, cP as isAdmin, cQ as isHasMany, cR as isSignedIn, cS as isStaff, cT as isSystemAdmin, cU as jsonLogger, cV as memorySearch, cW as mimeAllowedBy, cX as rowFields, cY as silentLogger, cZ as uniqueWithinOf } from './config-BLxN8qcy.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-JIb70PKm.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-BjlBwaiO.js';
|
|
5
5
|
export { FieldCondition, matchesCondition } from './conditions.js';
|
|
6
6
|
|
|
7
7
|
type DeliveryKind = 'webhook' | 'email';
|
|
@@ -232,6 +232,8 @@ interface AdminStatus {
|
|
|
232
232
|
email: string | null;
|
|
233
233
|
/** The search adapter's name, `custom` when it has none, or `null` without `search`. */
|
|
234
234
|
search: string | null;
|
|
235
|
+
/** `telemetry` is on: OpenTelemetry traces or metrics. */
|
|
236
|
+
telemetry: boolean;
|
|
235
237
|
/** The config's plugins, with their name and version when they give them. */
|
|
236
238
|
plugins: {
|
|
237
239
|
name?: string;
|
package/dist/index.js
CHANGED
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 { c_ as API_KEYS, c$ as API_KEY_GLOBAL_OPERATIONS, d0 as API_KEY_OPERATIONS, d1 as DEFAULT_MAX_VERSIONS, d2 as DEFAULT_PASSWORD_EMAILS, d3 as FIELD_TYPE_BASES, d4 as FOLDER_LEVELS, d5 as MediaFolders, d6 as SSO_COOKIE, d7 as codeOfStatus, cO as hasRows, cQ as isHasMany, d8 as isJsonLogger, d9 as keyAllows, da as logFormat, cW as mimeAllowedBy, cX as rowFields, cZ as uniqueWithinOf } from './config-BLxN8qcy.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-BjlBwaiO.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 { be as Label, aF as Field, I as ID, k as AdminLocale, t as AdminSwitcher, cg as SsoProviderRef } from './config-
|
|
1
|
+
import { be as Label, aF as Field, I as ID, k as AdminLocale, t as AdminSwitcher, cg as SsoProviderRef } from './config-BLxN8qcy.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, 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,
|
|
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, cm as TooManyRequestsError, cn as TypedPlugin, co as UnauthorizedError, cw as ValidationError, cE as Where, cL as defineFieldType, cM as definePlugin } from './config-BLxN8qcy.js';
|
|
2
|
+
export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-JIb70PKm.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.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "Embedded, code-first headless CMS for Nuxt and Next.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -54,14 +54,19 @@
|
|
|
54
54
|
"react"
|
|
55
55
|
],
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"sharp": ">=0.33.0"
|
|
57
|
+
"sharp": ">=0.33.0",
|
|
58
|
+
"@opentelemetry/api": ">=1.9.0 <2"
|
|
58
59
|
},
|
|
59
60
|
"peerDependenciesMeta": {
|
|
60
61
|
"sharp": {
|
|
61
62
|
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@opentelemetry/api": {
|
|
65
|
+
"optional": true
|
|
62
66
|
}
|
|
63
67
|
},
|
|
64
68
|
"devDependencies": {
|
|
69
|
+
"@opentelemetry/api": "~1.9.1",
|
|
65
70
|
"sharp": "^0.35.5"
|
|
66
71
|
},
|
|
67
72
|
"scripts": {
|