@easy-cms/core 1.1.0 → 1.3.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.
@@ -55,6 +55,7 @@ var SCHEDULED_JOBS = "scheduled-jobs";
55
55
  var WEBHOOK_DELIVERIES = "webhook-deliveries";
56
56
  var EMAIL_DELIVERIES = "email-deliveries";
57
57
  var DATABASE_BACKUPS = "database-backups";
58
+ var JOB_RUNS = "job-runs";
58
59
  var ROLES = "user-roles";
59
60
  var USER_IDENTITIES = "user-identities";
60
61
  var AUDIT_LOGS = "audit-logs";
@@ -66,6 +67,7 @@ var INTERNAL_COLLECTIONS = /* @__PURE__ */ new Set([
66
67
  WEBHOOK_DELIVERIES,
67
68
  EMAIL_DELIVERIES,
68
69
  DATABASE_BACKUPS,
70
+ JOB_RUNS,
69
71
  ROLES,
70
72
  USER_IDENTITIES,
71
73
  AUDIT_LOGS
@@ -497,6 +499,26 @@ var auditLogsCollection = {
497
499
  { name: "scope", type: "text", index: true }
498
500
  ]
499
501
  };
502
+ var jobRunsCollection = {
503
+ slug: JOB_RUNS,
504
+ access: { read: nobody, create: nobody, update: nobody, delete: nobody },
505
+ fields: [
506
+ { name: "name", type: "text", required: true, unique: true },
507
+ { name: "lastRunAt", type: "text", required: true },
508
+ // running | ok | failed; empty until it first runs here.
509
+ { name: "lastStatus", type: "text" },
510
+ { name: "lastError", type: "text" },
511
+ { name: "lastDurationMs", type: "number" },
512
+ // Set from Settings → Jobs by an admin: paused, or a schedule in place of the code's (only
513
+ // for jobs with `adminSchedule`). Empty: as the code says.
514
+ { name: "pausedAt", type: "text" },
515
+ { name: "pausedBy", type: "text" },
516
+ { name: "everyOverride", type: "number" },
517
+ { name: "atOverride", type: "text" },
518
+ { name: "changedBy", type: "text" },
519
+ { name: "changedAt", type: "text" }
520
+ ]
521
+ };
500
522
  var emailDeliveriesCollection = {
501
523
  slug: EMAIL_DELIVERIES,
502
524
  access: { read: nobody, create: nobody, update: nobody, delete: nobody },
@@ -2671,7 +2693,12 @@ var Mailer = class {
2671
2693
  let failed = 0;
2672
2694
  for (const { id, ...queued } of await this.queue.due(now, 50)) {
2673
2695
  const attempts = queued.attempts + 1;
2674
- await this.queue.update(id, { ...queued, attempts, nextAttemptAt: later(now, CLAIM) });
2696
+ const claimed = await this.queue.claim(id, queued, {
2697
+ ...queued,
2698
+ attempts,
2699
+ nextAttemptAt: later(now, CLAIM)
2700
+ });
2701
+ if (!claimed) continue;
2675
2702
  const error = await this.attempt(JSON.parse(queued.message));
2676
2703
  if (!error) {
2677
2704
  await this.queue.remove(id);
@@ -3015,7 +3042,16 @@ async function startBackup(cms, trigger, author) {
3015
3042
  }
3016
3043
  async function runBackup(cms, record) {
3017
3044
  const started = /* @__PURE__ */ new Date();
3018
- let row = await update(cms, record, { state: "running", startedAt: started.toISOString() });
3045
+ const { id, ...rest } = record;
3046
+ const changes = { state: "running", startedAt: started.toISOString() };
3047
+ const claimed = await cms.db.update({
3048
+ collection: DATABASE_BACKUPS,
3049
+ id,
3050
+ data: { ...rest, ...changes, updatedAt: started.toISOString() },
3051
+ where: { state: { equals: "pending" } }
3052
+ });
3053
+ if (!claimed) return;
3054
+ let row = { ...record, ...changes };
3019
3055
  const dir = await mkdtemp(join2(tmpdir(), "easy-cms-backup-"));
3020
3056
  try {
3021
3057
  const file = join2(dir, "backup.db");
@@ -4539,7 +4575,12 @@ var Webhooks = class {
4539
4575
  continue;
4540
4576
  }
4541
4577
  const attempts = queued.attempts + 1;
4542
- await this.queue.update(id, { ...queued, attempts, nextAttemptAt: later2(now, CLAIM2) });
4578
+ const claimed = await this.queue.claim(id, queued, {
4579
+ ...queued,
4580
+ attempts,
4581
+ nextAttemptAt: later2(now, CLAIM2)
4582
+ });
4583
+ if (!claimed) continue;
4543
4584
  const result = await this.attempt(hook, queued.event, queued.body, queued.delivery);
4544
4585
  if (result.ok) {
4545
4586
  await this.queue.remove(id);
@@ -6137,6 +6178,7 @@ async function resolveConfig(input) {
6137
6178
  ...(config.webhooks?.length ?? 0) > 0 ? [webhookDeliveriesCollection] : [],
6138
6179
  ...config.apiKeys ? [apiKeysCollection] : [],
6139
6180
  ...config.email ? [emailDeliveriesCollection] : [],
6181
+ ...(config.jobs?.length ?? 0) > 0 ? [jobRunsCollection] : [],
6140
6182
  databaseBackupsCollection,
6141
6183
  ...config.auth?.rbac ? [rolesCollection] : [],
6142
6184
  ...(config.auth?.providers?.length ?? 0) > 0 ? [userIdentitiesCollection] : [],
@@ -8022,6 +8064,16 @@ var EasyCMS = class {
8022
8064
  data: toRow(email2, current?.createdAt)
8023
8065
  });
8024
8066
  },
8067
+ claim: async (id, current, next) => {
8068
+ const row = await this.db.findById({ collection: collection2, id });
8069
+ if (!row) return false;
8070
+ return this.claim(collection2, row, toRow(next, row.createdAt), {
8071
+ and: [
8072
+ { state: { equals: current.state } },
8073
+ { nextAttemptAt: { equals: current.nextAttemptAt } }
8074
+ ]
8075
+ });
8076
+ },
8025
8077
  remove: async (id) => {
8026
8078
  await this.db.delete({ collection: collection2, id });
8027
8079
  },
@@ -8053,10 +8105,8 @@ var EasyCMS = class {
8053
8105
  startScheduler(interval = 6e4) {
8054
8106
  if (this.schedulerTimer) return;
8055
8107
  this.schedulerTimer = setInterval(() => {
8056
- this.running = this.running.then(
8057
- () => this.runJobs().catch(
8058
- (error) => this.logger.error(`Scheduled jobs failed: ${error.message}`)
8059
- )
8108
+ void this.runJobs().catch(
8109
+ (error) => this.logger.error(`Scheduled jobs failed: ${error.message}`)
8060
8110
  );
8061
8111
  }, interval);
8062
8112
  this.schedulerTimer.unref?.();
@@ -8065,7 +8115,13 @@ var EasyCMS = class {
8065
8115
  * Runs due scheduled publishes and unpublishes, and retries failed webhook deliveries. What the
8066
8116
  * scheduler and the cron endpoint (`GET <api>/jobs/run`) call.
8067
8117
  */
8068
- async runJobs(now = /* @__PURE__ */ new Date()) {
8118
+ runJobs(now = /* @__PURE__ */ new Date()) {
8119
+ const run = this.running.then(() => this.runJobsNow(now));
8120
+ this.running = run.catch(() => {
8121
+ });
8122
+ return run;
8123
+ }
8124
+ runJobsNow(now) {
8069
8125
  return auditContext.run({ via: "scheduler", user: null }, async () => {
8070
8126
  const scheduled = await this.runScheduled(now);
8071
8127
  await this.pruneFailedDeliveries(now);
@@ -8081,31 +8137,91 @@ var EasyCMS = class {
8081
8137
  };
8082
8138
  });
8083
8139
  }
8084
- /** `jobs` of the config that are due; when each last ran is kept in the database. */
8140
+ /**
8141
+ * `jobs` of the config that are due. When each last ran is a row of `job-runs`; an instance
8142
+ * claims a run by moving `lastRunAt` from the value it read, so each run happens once however
8143
+ * many instances run jobs.
8144
+ */
8085
8145
  async runConfigJobs(now) {
8086
8146
  const { jobs } = this.config;
8087
8147
  if (jobs.length === 0) return { ran: 0, failed: 0 };
8088
- const last = await this.db.findGlobal({ slug: JOB_RUNS }) ?? {};
8089
- const runs = {};
8090
- for (const [name, at] of Object.entries(last)) if (typeof at === "string") runs[name] = at;
8148
+ const { docs } = await this.db.find({ collection: JOB_RUNS, sort: ["name"], limit: 0, page: 1 });
8149
+ const rows = new Map(docs.map((row) => [String(row.name), row]));
8150
+ let legacy;
8151
+ const stamp = now.toISOString();
8091
8152
  let ran = 0;
8092
8153
  let failed = 0;
8093
8154
  for (const job of jobs) {
8094
- const previous = runs[job.name];
8095
- if (job.every !== void 0 && previous !== void 0 && now.getTime() - Date.parse(previous) < job.every * 1e3)
8096
- continue;
8097
- runs[job.name] = now.toISOString();
8155
+ const due = (previous) => job.every === void 0 || typeof previous !== "string" || now.getTime() - Date.parse(previous) >= job.every * 1e3;
8156
+ const row = rows.get(job.name);
8157
+ let claimed;
8158
+ if (row) {
8159
+ if (!due(row.lastRunAt)) continue;
8160
+ claimed = await this.claim(
8161
+ JOB_RUNS,
8162
+ row,
8163
+ { lastRunAt: stamp, lastStatus: "running", updatedAt: stamp },
8164
+ { lastRunAt: { equals: String(row.lastRunAt) } }
8165
+ );
8166
+ } else {
8167
+ legacy ??= await this.db.findGlobal({ slug: LEGACY_JOB_RUNS }) ?? {};
8168
+ const previous = legacy[job.name];
8169
+ const run = due(previous);
8170
+ claimed = await this.db.create({
8171
+ collection: JOB_RUNS,
8172
+ data: {
8173
+ name: job.name,
8174
+ lastRunAt: run ? stamp : String(previous),
8175
+ lastStatus: run ? "running" : null,
8176
+ lastError: null,
8177
+ lastDurationMs: null,
8178
+ createdAt: stamp,
8179
+ updatedAt: stamp
8180
+ }
8181
+ }).then(
8182
+ () => run,
8183
+ () => false
8184
+ );
8185
+ }
8186
+ if (!claimed) continue;
8187
+ const started = Date.now();
8188
+ let error = null;
8098
8189
  try {
8099
8190
  await job.run({ cms: this, now });
8100
8191
  ran++;
8101
- } catch (error) {
8192
+ } catch (caught) {
8102
8193
  failed++;
8103
- this.logger.error(`Job ${job.name} failed: ${error.message}`);
8194
+ error = (caught.message || String(caught)).slice(0, 500);
8195
+ this.logger.error(`Job ${job.name} failed: ${error}`);
8104
8196
  }
8197
+ await this.recordJobRun(job.name, stamp, error, Date.now() - started).catch(
8198
+ (caught) => this.logger.error(`Job ${job.name}: ${caught.message}`)
8199
+ );
8105
8200
  }
8106
- await this.db.updateGlobal({ slug: JOB_RUNS, data: { ...runs, updatedAt: now.toISOString() } });
8107
8201
  return { ran, failed };
8108
8202
  }
8203
+ async recordJobRun(name, at, error, ms) {
8204
+ const { docs } = await this.db.find({
8205
+ collection: JOB_RUNS,
8206
+ where: { name: { equals: name } },
8207
+ sort: ["name"],
8208
+ limit: 1,
8209
+ page: 1
8210
+ });
8211
+ const row = docs[0];
8212
+ if (!row || row.lastRunAt !== at) return;
8213
+ await this.claim(
8214
+ JOB_RUNS,
8215
+ row,
8216
+ {
8217
+ lastStatus: error ? "failed" : "ok",
8218
+ lastError: error,
8219
+ lastDurationMs: ms,
8220
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
8221
+ },
8222
+ { lastRunAt: { equals: at } }
8223
+ );
8224
+ }
8109
8225
  /**
8110
8226
  * Sends an event of the app or a plugin (`events` in the config, e.g. `order.paid`) to the
8111
8227
  * webhooks that list it. Returns at once; deliveries are queued and retried like content changes.
@@ -8139,6 +8255,16 @@ var EasyCMS = class {
8139
8255
  data: toRow(delivery, current?.createdAt)
8140
8256
  });
8141
8257
  },
8258
+ claim: async (id, current, next) => {
8259
+ const row = await this.db.findById({ collection: collection2, id });
8260
+ if (!row) return false;
8261
+ return this.claim(collection2, row, toRow(next, row.createdAt), {
8262
+ and: [
8263
+ { state: { equals: current.state } },
8264
+ { nextAttemptAt: { equals: current.nextAttemptAt } }
8265
+ ]
8266
+ });
8267
+ },
8142
8268
  remove: async (id) => {
8143
8269
  await this.db.delete({ collection: collection2, id });
8144
8270
  },
@@ -8156,13 +8282,37 @@ var EasyCMS = class {
8156
8282
  }
8157
8283
  };
8158
8284
  }
8285
+ /**
8286
+ * Takes a queued row for this process by writing `changes`, only if it still matches `expect`
8287
+ * (compare and set): `false` when another process changed it first.
8288
+ */
8289
+ async claim(collection2, row, changes, expect) {
8290
+ const { id, ...data } = row;
8291
+ return await this.db.update({ collection: collection2, id, data: { ...data, ...changes }, where: expect }) !== null;
8292
+ }
8159
8293
  /**
8160
8294
  * Publishes or unpublishes documents whose scheduled time has come. Safe to call often, e.g.
8161
- * from a cron job (`GET <api>/jobs/run`) where no process keeps running.
8295
+ * from a cron job (`GET <api>/jobs/run`) where no process keeps running, and from several
8296
+ * processes at once: each job is claimed before it runs.
8162
8297
  */
8163
8298
  async runScheduled(now = /* @__PURE__ */ new Date()) {
8164
8299
  if (!this.config.collections.some((c) => c.slug === SCHEDULED_JOBS))
8165
8300
  return { ran: 0, failed: 0 };
8301
+ const expired = new Date(now.getTime() - JOB_LEASE).toISOString();
8302
+ const stuck = await this.db.find({
8303
+ collection: SCHEDULED_JOBS,
8304
+ where: { and: [{ state: { equals: "running" } }, { updatedAt: { lt: expired } }] },
8305
+ sort: ["updatedAt"],
8306
+ limit: 100,
8307
+ page: 1
8308
+ });
8309
+ for (const row of stuck.docs)
8310
+ await this.claim(
8311
+ SCHEDULED_JOBS,
8312
+ row,
8313
+ { state: "pending", updatedAt: now.toISOString() },
8314
+ { and: [{ state: { equals: "running" } }, { updatedAt: { lt: expired } }] }
8315
+ );
8166
8316
  const due = await this.db.find({
8167
8317
  collection: SCHEDULED_JOBS,
8168
8318
  where: {
@@ -8172,8 +8322,16 @@ var EasyCMS = class {
8172
8322
  limit: 100,
8173
8323
  page: 1
8174
8324
  });
8325
+ let ran = 0;
8175
8326
  let failed = 0;
8176
8327
  for (const row of due.docs) {
8328
+ const claimed = await this.claim(
8329
+ SCHEDULED_JOBS,
8330
+ row,
8331
+ { state: "running", updatedAt: now.toISOString() },
8332
+ { state: { equals: "pending" } }
8333
+ );
8334
+ if (!claimed) continue;
8177
8335
  const parent = String(row.parent);
8178
8336
  const action = row.action;
8179
8337
  try {
@@ -8196,8 +8354,9 @@ var EasyCMS = class {
8196
8354
  await this.db.update({
8197
8355
  collection: SCHEDULED_JOBS,
8198
8356
  id: row.id,
8199
- data: { ...row, state: "done" }
8357
+ data: { ...row, state: "done", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
8200
8358
  });
8359
+ ran++;
8201
8360
  } catch (error) {
8202
8361
  failed++;
8203
8362
  const message2 = error.message;
@@ -8205,11 +8364,16 @@ var EasyCMS = class {
8205
8364
  await this.db.update({
8206
8365
  collection: SCHEDULED_JOBS,
8207
8366
  id: row.id,
8208
- data: { ...row, state: "failed", error: message2.slice(0, 500) }
8367
+ data: {
8368
+ ...row,
8369
+ state: "failed",
8370
+ error: message2.slice(0, 500),
8371
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
8372
+ }
8209
8373
  });
8210
8374
  }
8211
8375
  }
8212
- return { ran: due.docs.length - failed, failed };
8376
+ return { ran, failed };
8213
8377
  }
8214
8378
  /** Schedules a publish or unpublish, replacing a pending job with the same action. */
8215
8379
  async schedule(collection2, id, job, options = {}) {
@@ -8369,6 +8533,20 @@ var EasyCMS = class {
8369
8533
  const collection2 = kind === "webhook" ? WEBHOOK_DELIVERIES : EMAIL_DELIVERIES;
8370
8534
  const row = await this.db.findById({ collection: collection2, id });
8371
8535
  if (!row) throw new NotFoundError(collection2, id);
8536
+ const busy = { ok: false, error: "it is being sent right now; try again in a minute" };
8537
+ if (row.state !== "pending" && row.state !== "failed") return busy;
8538
+ const claimed = await this.claim(
8539
+ collection2,
8540
+ row,
8541
+ { state: "sending", updatedAt: (/* @__PURE__ */ new Date()).toISOString() },
8542
+ {
8543
+ and: [
8544
+ { state: { equals: row.state } },
8545
+ { nextAttemptAt: { equals: String(row.nextAttemptAt) } }
8546
+ ]
8547
+ }
8548
+ );
8549
+ if (!claimed) return busy;
8372
8550
  const error = kind === "webhook" ? await this.webhooks.sendNow(row).then((result) => result.ok ? void 0 : result.error) : await this.mailer.sendNow(String(row.message));
8373
8551
  if (error === void 0) {
8374
8552
  await this.db.delete({ collection: collection2, id });
@@ -8388,11 +8566,33 @@ var EasyCMS = class {
8388
8566
  });
8389
8567
  return { ok: false, error };
8390
8568
  }
8391
- /** Removes webhook deliveries and emails that failed more than 30 days ago. */
8569
+ /**
8570
+ * Removes webhook deliveries and emails that failed more than 30 days ago, and marks as failed
8571
+ * the ones an admin's Retry left `sending` because its instance stopped.
8572
+ */
8392
8573
  async pruneFailedDeliveries(now) {
8393
8574
  const before = new Date(now.getTime() - 30 * 864e5).toISOString();
8575
+ const stuck = new Date(now.getTime() - 5 * 6e4).toISOString();
8394
8576
  for (const collection2 of [WEBHOOK_DELIVERIES, EMAIL_DELIVERIES]) {
8395
8577
  if (!this.config.collections.some((c) => c.slug === collection2)) continue;
8578
+ const sending = await this.db.find({
8579
+ collection: collection2,
8580
+ where: { and: [{ state: { equals: "sending" } }, { updatedAt: { lt: stuck } }] },
8581
+ sort: ["updatedAt"],
8582
+ limit: 100,
8583
+ page: 1
8584
+ });
8585
+ for (const row of sending.docs)
8586
+ await this.claim(
8587
+ collection2,
8588
+ row,
8589
+ {
8590
+ state: "failed",
8591
+ error: "Interrupted: the server stopped while sending",
8592
+ updatedAt: now.toISOString()
8593
+ },
8594
+ { and: [{ state: { equals: "sending" } }, { updatedAt: { lt: stuck } }] }
8595
+ );
8396
8596
  for (; ; ) {
8397
8597
  const { docs } = await this.db.find({
8398
8598
  collection: collection2,
@@ -9152,7 +9352,8 @@ var UPLOAD_URL_SECONDS = 15 * 60;
9152
9352
  var UPLOAD_TICKET_SECONDS = 24 * 60 * 60;
9153
9353
  var SNIFF_BYTES = 64 * 1024;
9154
9354
  var MAX_FILES_TO_MOVE = 200;
9155
- var JOB_RUNS = "easy-cms:jobs";
9355
+ var LEGACY_JOB_RUNS = "easy-cms:jobs";
9356
+ var JOB_LEASE = 10 * 6e4;
9156
9357
  function durationSeconds(value) {
9157
9358
  const match = typeof value === "string" ? /^(\d+)\s*([smhd])$/.exec(value.trim()) : null;
9158
9359
  const seconds = typeof value === "number" ? value : match ? Number(match[1]) * { s: 1, m: 60, h: 3600, d: 86400 }[match[2]] : Number.NaN;
@@ -9208,7 +9409,7 @@ async function readAdminModule(cms, index) {
9208
9409
  }
9209
9410
 
9210
9411
  // src/version.ts
9211
- var VERSION = true ? "1.1.0" : "0.0.0-dev";
9412
+ var VERSION = true ? "1.3.0" : "0.0.0-dev";
9212
9413
 
9213
9414
  // src/rest/admin-deliveries.ts
9214
9415
  var PER_PAGE = 50;
@@ -1934,8 +1934,14 @@ declare class EasyCMS<C extends Config = Config> {
1934
1934
  failed: number;
1935
1935
  };
1936
1936
  }>;
1937
- /** `jobs` of the config that are due; when each last ran is kept in the database. */
1937
+ private runJobsNow;
1938
+ /**
1939
+ * `jobs` of the config that are due. When each last ran is a row of `job-runs`; an instance
1940
+ * claims a run by moving `lastRunAt` from the value it read, so each run happens once however
1941
+ * many instances run jobs.
1942
+ */
1938
1943
  private runConfigJobs;
1944
+ private recordJobRun;
1939
1945
  /**
1940
1946
  * Sends an event of the app or a plugin (`events` in the config, e.g. `order.paid`) to the
1941
1947
  * webhooks that list it. Returns at once; deliveries are queued and retried like content changes.
@@ -1954,9 +1960,15 @@ declare class EasyCMS<C extends Config = Config> {
1954
1960
  }>;
1955
1961
  /** Webhook deliveries until they succeed, stored in `webhook-deliveries`. */
1956
1962
  private webhookQueue;
1963
+ /**
1964
+ * Takes a queued row for this process by writing `changes`, only if it still matches `expect`
1965
+ * (compare and set): `false` when another process changed it first.
1966
+ */
1967
+ private claim;
1957
1968
  /**
1958
1969
  * Publishes or unpublishes documents whose scheduled time has come. Safe to call often, e.g.
1959
- * from a cron job (`GET <api>/jobs/run`) where no process keeps running.
1970
+ * from a cron job (`GET <api>/jobs/run`) where no process keeps running, and from several
1971
+ * processes at once: each job is claimed before it runs.
1960
1972
  */
1961
1973
  runScheduled(now?: Date): Promise<{
1962
1974
  ran: number;
@@ -1992,7 +2004,10 @@ declare class EasyCMS<C extends Config = Config> {
1992
2004
  })[]>;
1993
2005
  private pendingJobs;
1994
2006
  private removeJob;
1995
- /** Removes webhook deliveries and emails that failed more than 30 days ago. */
2007
+ /**
2008
+ * Removes webhook deliveries and emails that failed more than 30 days ago, and marks as failed
2009
+ * the ones an admin's Retry left `sending` because its instance stopped.
2010
+ */
1996
2011
  private pruneFailedDeliveries;
1997
2012
  private global;
1998
2013
  /**
@@ -1,4 +1,4 @@
1
- import { I as ID, ab as Config, au as EasyCMS, bd as LocalizationConfig, aD as Field, aH as FieldError, aS as FilterOptions, af as CreateEasyCMSOptions, bI as ResolvedConfig } from './config-BK8lrNcJ.js';
1
+ import { I as ID, ab as Config, au as EasyCMS, bd as LocalizationConfig, aD as Field, aH as FieldError, aS as FilterOptions, af as CreateEasyCMSOptions, bI as ResolvedConfig } from './config-BhTVeJg4.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-BK8lrNcJ.js';
2
- export { A as ADAPTER_API_VERSION, a as ADMIN_ICONS, b as Access, c as AccessArgs, d as AccessOptions, e as AdminBrand, f as AdminCommand, g as AdminComponent, h as AdminConfig, i as AdminFieldArgs, j as AdminIcon, k as AdminLocale, l as AdminPage, m as AdminRole, n as AdminRoleChange, o as AdminRoleField, p as AdminRoleTarget, q as AdminRoleView, r as AdminRoles, s as AdminSso, t as AdminSwitcher, u as AdminViewAccess, v as AfterChangeHook, w as AfterDeleteHook, x as AfterReadHook, y as ApiKeyContext, z as ApiKeyGlobalOperation, B as ApiKeyOperation, C as ApiKeyPermissions, D as ArrayField, F as AuditChange, G as AuditConfig, H as AuditEntry, J as AuditLog, K as AuditPage, L as AuditVerification, M as Auth, N as AuthConfig, O as AuthProvider, Q as AuthProviderProfile, R as AuthProviderRequest, S as AuthUser, T as BackupsConfig, U as BeforeChangeHook, V as BeforeDeleteHook, W as BeforeValidateHook, X as Block, Y as BlocksField, Z as BooleanField, _ as BuiltinApiKeysCollection, $ as BuiltinMediaCollection, a0 as BuiltinMediaFoldersCollection, a1 as BuiltinUsersCollection, a2 as BundleHints, a3 as CliCommand, a4 as CollectionAccess, a5 as CollectionAdmin, a6 as CollectionConfig, a7 as CollectionDocument, a8 as CollectionHooks, a9 as CollectionListAdmin, aa as CollectionSlug, ab as Config, ac as ConfigError, ad as ConfigIssue, ae as ContainerAdmin, af as CreateEasyCMSOptions, ag as CreateInput, ah as CreateMigrationResult, ai as CustomField, aj as CustomFieldTypes, ak as DashboardWidget, al as Database, am as DatabaseAdapter, an as DatabaseInitArgs, ao as DatabaseTransfer, ap as DateField, aq as DepthOptions, ar as DirectUpload, as as DiskStorageOptions, at as DocumentOf, au as EasyCMS, av as EasyCMSError, aw as EmailAdapter, ax as EmailField, ay as EmailMessage, az as Endpoint, aA as EndpointRequest, aB as ErrorCode, aC as FIELD_TYPES, aD as Field, aE as FieldAccess, aF as FieldAccessFn, aG as FieldAdmin, aH as FieldError, aI as FieldRule, aJ as FieldType, aK as FieldTypeBase, aL as FieldTypeDefinition, aM as FieldTypeValidateContext, aN as FieldValidate, aO as FieldValidateContext, aP as FieldValue, aQ as FieldsInput, aR as FieldsValue, aS as FilterOptions, aT as FilterOptionsArgs, aU as FindArgs, aV as FindOptions, aW as FolderLevel, aX as FolderPermissions, aY as ForbiddenError, aZ as GlobalAccess, a_ as GlobalConfig, a$ as GlobalDocument, b0 as GlobalHooks, b1 as GlobalInput, b2 as GlobalSlug, b3 as GroupField, b4 as ImageSize, b5 as IncrementOptions, b6 as InferCollection, b7 as InferGlobal, b8 as JobConfig, b9 as JsonField, ba as Label, bb as LayoutNode, bc as LivePreview, bd as LocalizationConfig, be as Logger, bf as LoginArgs, bg as MediaDocument, bh as MediaSize, bi as MembersConfig, bj as MembersSignup, bk as MigrationInfo, bl as NavGroup, bm as NotFoundError, bn as NumberField, bo as OnRequest, bp as OnRequestArgs, bq as Operation, br as PLUGIN_API_VERSION, bs as PasswordEmail, bt as PasswordEmailArgs, bu as PasswordEmailFn, bv as PasswordLinkOptions, bw as PayloadTooLargeError, bx as Plugin, by as PluginInfo, bz as PluginTypes, bA as PreviewTarget, bB as PreviewURL, bC as QueryError, bD as ROLE_OPERATIONS, bE as RawDocument, bF as ReadOptions, bG as RelationshipField, bH as RequestContext, bI as ResolvedConfig, bJ as RichTextDocument, bK as RichTextField, bL as Role, bM as RoleOperation, bN as RolePermissions, bO as Roles, bP as RoutesConfig, bQ as ScheduledPublish, bR as SchemaError, bS as SchemaMode, bT as SelectField, bU as SelectOption, bV as Session, bW as SidebarPanel, bX as SignupArgs, bY as SignupResult, bZ as SingleSignOn, b_ as SlugField, b$ as SlugOf, c0 as SsoOutcome, c1 as SsoProviderRef, c2 as StorageAdapter, c3 as StoredFile, c4 as TextField, c5 as TextareaField, c6 as TooManyRequestsError, c7 as TypedPlugin, c8 as UnauthorizedError, c9 as UpdateInput, ca as UpdateOptions, cb as UploadConfig, cc as UploadField, cd as UploadFromURLConfig, ce as UserIdentity, cf as ValidationError, cg as Version, ch as VersionSummary, ci as VersionsConfig, cj as WEBHOOK_EVENTS, ck as WebhookConfig, cl as WebhookEvent, cm as WebhookPayload, cn as Where, co as WhereOperators, cp as anyone, cq as consoleEmail, cr as consoleLogger, cs as createEasyCMS, ct as defineConfig, cu as defineFieldType, cv as definePlugin, cw as diskStorage, cx as hasRows, cy as isAdmin, cz as isHasMany, cA as isSignedIn, cB as isStaff, cC as isSystemAdmin, cD as mimeAllowedBy, cE as rowFields, cF as silentLogger, cG as uniqueWithinOf } from './config-BK8lrNcJ.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-aVtmn8t4.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-Bx_3ilqI.js';
1
+ import { P as PaginatedDocs, I as ID, E as EmailSetting } from './config-BhTVeJg4.js';
2
+ export { A as ADAPTER_API_VERSION, a as ADMIN_ICONS, b as Access, c as AccessArgs, d as AccessOptions, e as AdminBrand, f as AdminCommand, g as AdminComponent, h as AdminConfig, i as AdminFieldArgs, j as AdminIcon, k as AdminLocale, l as AdminPage, m as AdminRole, n as AdminRoleChange, o as AdminRoleField, p as AdminRoleTarget, q as AdminRoleView, r as AdminRoles, s as AdminSso, t as AdminSwitcher, u as AdminViewAccess, v as AfterChangeHook, w as AfterDeleteHook, x as AfterReadHook, y as ApiKeyContext, z as ApiKeyGlobalOperation, B as ApiKeyOperation, C as ApiKeyPermissions, D as ArrayField, F as AuditChange, G as AuditConfig, H as AuditEntry, J as AuditLog, K as AuditPage, L as AuditVerification, M as Auth, N as AuthConfig, O as AuthProvider, Q as AuthProviderProfile, R as AuthProviderRequest, S as AuthUser, T as BackupsConfig, U as BeforeChangeHook, V as BeforeDeleteHook, W as BeforeValidateHook, X as Block, Y as BlocksField, Z as BooleanField, _ as BuiltinApiKeysCollection, $ as BuiltinMediaCollection, a0 as BuiltinMediaFoldersCollection, a1 as BuiltinUsersCollection, a2 as BundleHints, a3 as CliCommand, a4 as CollectionAccess, a5 as CollectionAdmin, a6 as CollectionConfig, a7 as CollectionDocument, a8 as CollectionHooks, a9 as CollectionListAdmin, aa as CollectionSlug, ab as Config, ac as ConfigError, ad as ConfigIssue, ae as ContainerAdmin, af as CreateEasyCMSOptions, ag as CreateInput, ah as CreateMigrationResult, ai as CustomField, aj as CustomFieldTypes, ak as DashboardWidget, al as Database, am as DatabaseAdapter, an as DatabaseInitArgs, ao as DatabaseTransfer, ap as DateField, aq as DepthOptions, ar as DirectUpload, as as DiskStorageOptions, at as DocumentOf, au as EasyCMS, av as EasyCMSError, aw as EmailAdapter, ax as EmailField, ay as EmailMessage, az as Endpoint, aA as EndpointRequest, aB as ErrorCode, aC as FIELD_TYPES, aD as Field, aE as FieldAccess, aF as FieldAccessFn, aG as FieldAdmin, aH as FieldError, aI as FieldRule, aJ as FieldType, aK as FieldTypeBase, aL as FieldTypeDefinition, aM as FieldTypeValidateContext, aN as FieldValidate, aO as FieldValidateContext, aP as FieldValue, aQ as FieldsInput, aR as FieldsValue, aS as FilterOptions, aT as FilterOptionsArgs, aU as FindArgs, aV as FindOptions, aW as FolderLevel, aX as FolderPermissions, aY as ForbiddenError, aZ as GlobalAccess, a_ as GlobalConfig, a$ as GlobalDocument, b0 as GlobalHooks, b1 as GlobalInput, b2 as GlobalSlug, b3 as GroupField, b4 as ImageSize, b5 as IncrementOptions, b6 as InferCollection, b7 as InferGlobal, b8 as JobConfig, b9 as JsonField, ba as Label, bb as LayoutNode, bc as LivePreview, bd as LocalizationConfig, be as Logger, bf as LoginArgs, bg as MediaDocument, bh as MediaSize, bi as MembersConfig, bj as MembersSignup, bk as MigrationInfo, bl as NavGroup, bm as NotFoundError, bn as NumberField, bo as OnRequest, bp as OnRequestArgs, bq as Operation, br as PLUGIN_API_VERSION, bs as PasswordEmail, bt as PasswordEmailArgs, bu as PasswordEmailFn, bv as PasswordLinkOptions, bw as PayloadTooLargeError, bx as Plugin, by as PluginInfo, bz as PluginTypes, bA as PreviewTarget, bB as PreviewURL, bC as QueryError, bD as ROLE_OPERATIONS, bE as RawDocument, bF as ReadOptions, bG as RelationshipField, bH as RequestContext, bI as ResolvedConfig, bJ as RichTextDocument, bK as RichTextField, bL as Role, bM as RoleOperation, bN as RolePermissions, bO as Roles, bP as RoutesConfig, bQ as ScheduledPublish, bR as SchemaError, bS as SchemaMode, bT as SelectField, bU as SelectOption, bV as Session, bW as SidebarPanel, bX as SignupArgs, bY as SignupResult, bZ as SingleSignOn, b_ as SlugField, b$ as SlugOf, c0 as SsoOutcome, c1 as SsoProviderRef, c2 as StorageAdapter, c3 as StoredFile, c4 as TextField, c5 as TextareaField, c6 as TooManyRequestsError, c7 as TypedPlugin, c8 as UnauthorizedError, c9 as UpdateInput, ca as UpdateOptions, cb as UploadConfig, cc as UploadField, cd as UploadFromURLConfig, ce as UserIdentity, cf as ValidationError, cg as Version, ch as VersionSummary, ci as VersionsConfig, cj as WEBHOOK_EVENTS, ck as WebhookConfig, cl as WebhookEvent, cm as WebhookPayload, cn as Where, co as WhereOperators, cp as anyone, cq as consoleEmail, cr as consoleLogger, cs as createEasyCMS, ct as defineConfig, cu as defineFieldType, cv as definePlugin, cw as diskStorage, cx as hasRows, cy as isAdmin, cz as isHasMany, cA as isSignedIn, cB as isStaff, cC as isSystemAdmin, cD as mimeAllowedBy, cE as rowFields, cF as silentLogger, cG as uniqueWithinOf } from './config-BhTVeJg4.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-dt85uDgT.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-D7FaZRW-.js';
5
5
  export { FieldCondition, matchesCondition } from './conditions.js';
6
6
 
7
7
  type DeliveryKind = 'webhook' | 'email';
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  sharedEasyCMS,
24
24
  silentLogger,
25
25
  slugify
26
- } from "./chunk-SGAAJMGL.js";
26
+ } from "./chunk-4NRYIE5Z.js";
27
27
  import {
28
28
  matchesCondition
29
29
  } from "./chunk-U266LV4M.js";
@@ -1,6 +1,6 @@
1
- import { ab as Config, al as Database, bl as NavGroup, a6 as CollectionConfig, I as ID, bE as RawDocument, aD as Field, bI as ResolvedConfig, ad as ConfigIssue } from './config-BK8lrNcJ.js';
2
- export { cH as API_KEYS, cI as API_KEY_GLOBAL_OPERATIONS, cJ as API_KEY_OPERATIONS, cK as DEFAULT_MAX_VERSIONS, cL as DEFAULT_PASSWORD_EMAILS, cM as FIELD_TYPE_BASES, cN as FOLDER_LEVELS, cO as MediaFolders, cP as SSO_COOKIE, cQ as codeOfStatus, cx as hasRows, cz as isHasMany, cR as keyAllows, cD as mimeAllowedBy, cE as rowFields, cG as uniqueWithinOf } from './config-BK8lrNcJ.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-Bx_3ilqI.js';
1
+ import { ab as Config, al as Database, bl as NavGroup, a6 as CollectionConfig, I as ID, bE as RawDocument, aD as Field, bI as ResolvedConfig, ad as ConfigIssue } from './config-BhTVeJg4.js';
2
+ export { cH as API_KEYS, cI as API_KEY_GLOBAL_OPERATIONS, cJ as API_KEY_OPERATIONS, cK as DEFAULT_MAX_VERSIONS, cL as DEFAULT_PASSWORD_EMAILS, cM as FIELD_TYPE_BASES, cN as FOLDER_LEVELS, cO as MediaFolders, cP as SSO_COOKIE, cQ as codeOfStatus, cx as hasRows, cz as isHasMany, cR as keyAllows, cD as mimeAllowedBy, cE as rowFields, cG as uniqueWithinOf } from './config-BhTVeJg4.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-D7FaZRW-.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
@@ -53,7 +53,7 @@ import {
53
53
  verifyPassword,
54
54
  verifyTurnstile,
55
55
  writeBackupFile
56
- } from "./chunk-SGAAJMGL.js";
56
+ } from "./chunk-4NRYIE5Z.js";
57
57
  import {
58
58
  conditionIssues
59
59
  } from "./chunk-U266LV4M.js";
@@ -1,4 +1,4 @@
1
- import { ba as Label, aD as Field, I as ID, k as AdminLocale, t as AdminSwitcher, c1 as SsoProviderRef } from './config-BK8lrNcJ.js';
1
+ import { ba as Label, aD as Field, I as ID, k as AdminLocale, t as AdminSwitcher, c1 as SsoProviderRef } from './config-BhTVeJg4.js';
2
2
  import { FieldCondition } from './conditions.js';
3
3
 
4
4
  /** A Web Component from an admin module, with its `props` (plain JSON). */
package/dist/plugin.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { a as ADMIN_ICONS, g as AdminComponent, j as AdminIcon, S as AuthUser, a3 as CliCommand, a4 as CollectionAccess, a6 as CollectionConfig, ab as Config, ac as ConfigError, ak as DashboardWidget, av as EasyCMSError, az as Endpoint, aA as EndpointRequest, aB as ErrorCode, aD as Field, aE as FieldAccess, aH as FieldError, aK as FieldTypeBase, aL as FieldTypeDefinition, aM as FieldTypeValidateContext, aY as ForbiddenError, a_ as GlobalConfig, I as ID, b8 as JobConfig, ba as Label, bl as NavGroup, bm as NotFoundError, bo as OnRequest, bp as OnRequestArgs, br as PLUGIN_API_VERSION, bw as PayloadTooLargeError, bx as Plugin, by as PluginInfo, bz as PluginTypes, bC as QueryError, bH as RequestContext, c6 as TooManyRequestsError, c7 as TypedPlugin, c8 as UnauthorizedError, cf as ValidationError, cn as Where, cu as defineFieldType, cv as definePlugin } from './config-BK8lrNcJ.js';
2
- export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-aVtmn8t4.js';
1
+ export { a as ADMIN_ICONS, g as AdminComponent, j as AdminIcon, S as AuthUser, a3 as CliCommand, a4 as CollectionAccess, a6 as CollectionConfig, ab as Config, ac as ConfigError, ak as DashboardWidget, av as EasyCMSError, az as Endpoint, aA as EndpointRequest, aB as ErrorCode, aD as Field, aE as FieldAccess, aH as FieldError, aK as FieldTypeBase, aL as FieldTypeDefinition, aM as FieldTypeValidateContext, aY as ForbiddenError, a_ as GlobalConfig, I as ID, b8 as JobConfig, ba as Label, bl as NavGroup, bm as NotFoundError, bo as OnRequest, bp as OnRequestArgs, br as PLUGIN_API_VERSION, bw as PayloadTooLargeError, bx as Plugin, by as PluginInfo, bz as PluginTypes, bC as QueryError, bH as RequestContext, c6 as TooManyRequestsError, c7 as TypedPlugin, c8 as UnauthorizedError, cf as ValidationError, cn as Where, cu as defineFieldType, cv as definePlugin } from './config-BhTVeJg4.js';
2
+ export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-dt85uDgT.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.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Embedded, code-first headless CMS for Nuxt and Next.js",
5
5
  "license": "MIT",
6
6
  "type": "module",