tempest-express-sdk 0.25.0 → 0.26.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/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { z, looseBoolean, toDict, PasswordUtils } from './chunk-TIW2KPT2.js';
2
- export { PasswordUtils, VERSION, baseAppSettingsSchema, baseAppSettingsShape, baseResponseSchema, centsField, corsSettingsShape, databaseSettingsShape, looseBoolean as envBoolean, hexColorField, latitudeField, loadSettings, longitudeField, looseBoolean, nonEmptyStrField, nonNegativeFloatField, nonNegativeIntField, percentField, portField, positiveFloatField, positiveIntField, priceField, ratingField, ratioField, serverSettingsShape, slugField, toDict, z } from './chunk-TIW2KPT2.js';
1
+ import { z, looseBoolean, toDict, PasswordUtils } from './chunk-GLZYNX63.js';
2
+ export { PasswordUtils, VERSION, baseAppSettingsSchema, baseAppSettingsShape, baseResponseSchema, centsField, corsSettingsShape, databaseSettingsShape, looseBoolean as envBoolean, hexColorField, latitudeField, loadSettings, longitudeField, looseBoolean, nonEmptyStrField, nonNegativeFloatField, nonNegativeIntField, percentField, portField, positiveFloatField, positiveIntField, priceField, ratingField, ratioField, serverSettingsShape, slugField, toDict, z } from './chunk-GLZYNX63.js';
3
3
  import { AsyncLocalStorage } from 'async_hooks';
4
4
  import { Model, column, sql, BaseRepository, RecordNotFound, detectDialect, columnsOf, NodeSqliteDriver, AsyncEngine, or, and } from 'tempest-db-js';
5
5
  export { AsyncEngine, AsyncResult, AsyncSession, BaseRepository, Column, DeleteBuilder, InsertBuilder, Model, NoResultError, NodeSqliteDriver, PostgresDialect, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncSession, UpdateBuilder, and, belongsTo, column, columnsOf, createEngine, createSyncEngine, del, detectDialect, getDialect, hasMany, insert, join, loadRelations, not, or, parseDatabaseUrl, select, sql, update } from 'tempest-db-js';
@@ -9317,12 +9317,51 @@ var MessagingHub = class {
9317
9317
  }
9318
9318
  };
9319
9319
 
9320
+ // src/admin/dashboard.ts
9321
+ function metricCard(label, compute, helpText) {
9322
+ return helpText === void 0 ? { label, compute } : { label, compute, helpText };
9323
+ }
9324
+ function trendPercent(trend) {
9325
+ if (trend.previous === 0) return null;
9326
+ return (trend.value - trend.previous) / trend.previous * 100;
9327
+ }
9328
+ function trendDirection(trend) {
9329
+ if (trend.value > trend.previous) return "up";
9330
+ if (trend.value < trend.previous) return "down";
9331
+ return "flat";
9332
+ }
9333
+ function partitionTotal(partition) {
9334
+ return partition.segments.reduce((total, segment) => total + segment.value, 0);
9335
+ }
9336
+
9337
+ // src/admin/lenses.ts
9338
+ function slugify(name) {
9339
+ return name.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "lens";
9340
+ }
9341
+ function adminLens(options) {
9342
+ return {
9343
+ name: options.name,
9344
+ slug: slugify(options.name),
9345
+ label: options.label ?? options.name,
9346
+ filters: options.filters ?? {},
9347
+ orderBy: options.orderBy ?? null
9348
+ };
9349
+ }
9350
+
9351
+ // src/admin/permissions.ts
9352
+ var AdminPermission = {
9353
+ VIEW: "view",
9354
+ CREATE: "create",
9355
+ EDIT: "edit",
9356
+ DELETE: "delete"
9357
+ };
9358
+
9320
9359
  // src/admin/actions.ts
9321
- function slugify(label) {
9360
+ function slugify2(label) {
9322
9361
  return label.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "action";
9323
9362
  }
9324
9363
  function adminAction(options, handler) {
9325
- const name = options.name ?? slugify(options.label);
9364
+ const name = options.name ?? slugify2(options.label);
9326
9365
  if (name === "") throw new Error("adminAction requires a non-empty name or label");
9327
9366
  return {
9328
9367
  name,
@@ -9428,6 +9467,12 @@ var NEVER_EDITABLE = [
9428
9467
  "hashedPassword"
9429
9468
  ];
9430
9469
  var NEVER_LISTED = ["hashedPassword"];
9470
+ var AUDIT_FIELDS = [
9471
+ "createdAt",
9472
+ "updatedAt",
9473
+ "createdBy",
9474
+ "updatedBy"
9475
+ ];
9431
9476
  var AdminModel = class {
9432
9477
  /** The managed model class. */
9433
9478
  model;
@@ -9451,6 +9496,10 @@ var AdminModel = class {
9451
9496
  canEdit;
9452
9497
  /** Whether the delete action is exposed. */
9453
9498
  canDelete;
9499
+ /** Audit-log model backing the detail timeline, or `null`. */
9500
+ auditModel;
9501
+ /** Saved list-view presets, in declaration order. */
9502
+ lenses;
9454
9503
  actions = /* @__PURE__ */ new Map();
9455
9504
  slugOverride;
9456
9505
  listDisplayOverride;
@@ -9476,6 +9525,8 @@ var AdminModel = class {
9476
9525
  this.canCreate = options.canCreate ?? true;
9477
9526
  this.canEdit = options.canEdit ?? true;
9478
9527
  this.canDelete = options.canDelete ?? true;
9528
+ this.auditModel = options.auditModel ?? null;
9529
+ this.lenses = [...options.lenses ?? []];
9479
9530
  for (const action of options.actions ?? []) {
9480
9531
  if (this.actions.has(action.name)) {
9481
9532
  throw new Error(
@@ -9560,6 +9611,25 @@ var AdminModel = class {
9560
9611
  if (this.listDisplayOverride !== null) return [...this.listDisplayOverride];
9561
9612
  return this.columnNames().filter((name) => !NEVER_LISTED.includes(name));
9562
9613
  }
9614
+ /**
9615
+ * Look a lens up by its slug.
9616
+ *
9617
+ * @param slug - The `?lens=` value.
9618
+ * @returns The lens, or `null` when nothing matches.
9619
+ */
9620
+ getLens(slug) {
9621
+ return this.lenses.find((lens) => lens.slug === slug) ?? null;
9622
+ }
9623
+ /**
9624
+ * Return the audit/timestamp columns the model actually declares.
9625
+ *
9626
+ * @returns The subset of `createdAt` / `updatedAt` / `createdBy` /
9627
+ * `updatedBy` present on the model, in that order.
9628
+ */
9629
+ auditFieldNames() {
9630
+ const known = new Set(this.columnNames());
9631
+ return AUDIT_FIELDS.filter((name) => known.has(name));
9632
+ }
9563
9633
  /**
9564
9634
  * Return the columns the detail view renders.
9565
9635
  *
@@ -9568,10 +9638,16 @@ var AdminModel = class {
9568
9638
  * where an operator goes to see the whole record, so trimming it there would
9569
9639
  * hide data with nowhere else to read it.
9570
9640
  *
9571
- * @returns Every column but the password hash, in declaration order.
9641
+ * The audit/timestamp columns are held back too — they render in the detail
9642
+ * view's own audit panel, next to the change history, rather than scattered
9643
+ * among the domain fields.
9644
+ *
9645
+ * @returns Every domain column, in declaration order.
9572
9646
  */
9573
9647
  detailFieldNames() {
9574
- return this.columnNames().filter((name) => !NEVER_LISTED.includes(name));
9648
+ return this.columnNames().filter(
9649
+ (name) => !NEVER_LISTED.includes(name) && !AUDIT_FIELDS.includes(name)
9650
+ );
9575
9651
  }
9576
9652
  /**
9577
9653
  * Return the columns a create/edit form exposes.
@@ -10028,6 +10104,8 @@ var AdminSite = class {
10028
10104
  siteUrl;
10029
10105
  /** Typed appearance overrides. */
10030
10106
  theme;
10107
+ /** Business-metric cards rendered at the top of the dashboard. */
10108
+ dashboardCards;
10031
10109
  registry = /* @__PURE__ */ new Map();
10032
10110
  /**
10033
10111
  * Initialize the site.
@@ -10040,6 +10118,7 @@ var AdminSite = class {
10040
10118
  this.indexSubtitle = options.indexSubtitle ?? "Site administration";
10041
10119
  this.siteUrl = options.siteUrl ?? null;
10042
10120
  this.theme = options.theme ?? {};
10121
+ this.dashboardCards = [...options.dashboardCards ?? []];
10043
10122
  }
10044
10123
  /**
10045
10124
  * Return the centered header brand text.
@@ -11859,7 +11938,7 @@ function renderMfaPage(context, error) {
11859
11938
  </section>`;
11860
11939
  return renderLayout(context, `Two-factor \xB7 ${context.site.title}`, body);
11861
11940
  }
11862
- function renderDashboardPage(context, cards, metrics) {
11941
+ function renderDashboardPage(context, cards, metrics, businessCards = []) {
11863
11942
  const metricsPanel = metrics === null ? "" : `<div class="tempest-admin-stats" aria-label="System metrics">
11864
11943
  <div class="tempest-admin-stat">
11865
11944
  <span class="tempest-admin-stat__label">CPU</span>
@@ -11871,6 +11950,7 @@ function renderDashboardPage(context, cards, metrics) {
11871
11950
  <span class="tempest-admin-stat__sub">${escapeHtml(metrics.memoryUsedGb)} / ${escapeHtml(metrics.memoryTotalGb)} GB</span>
11872
11951
  </div>
11873
11952
  </div>`;
11953
+ const business = businessCards.length > 0 ? `<div class="tempest-admin-cards" aria-label="Business metrics">${businessCards.map(renderBusinessCard).join("")}</div>` : "";
11874
11954
  const models = cards.length > 0 ? `<div class="tempest-admin-models">${cards.map(
11875
11955
  (card) => `<article class="tempest-admin-model-card">
11876
11956
  <header class="tempest-admin-model-card__head">
@@ -11887,10 +11967,53 @@ function renderDashboardPage(context, cards, metrics) {
11887
11967
  <h1>${escapeHtml(context.site.title)}</h1>
11888
11968
  <p>${escapeHtml(context.site.indexSubtitle)}</p>
11889
11969
  ${metricsPanel}
11970
+ ${business}
11890
11971
  ${models}
11891
11972
  </section>`;
11892
11973
  return renderLayout(context, context.site.title, body);
11893
11974
  }
11975
+ function renderBusinessCard(card) {
11976
+ const help = card.helpText !== null ? `<span class="tempest-admin-card__help">${escapeHtml(card.helpText)}</span>` : "";
11977
+ if (card.error !== null) {
11978
+ return `<article class="tempest-admin-card tempest-admin-card--value">
11979
+ <span class="tempest-admin-card__label">${escapeHtml(card.label)}</span>
11980
+ <span class="tempest-admin-card__value">\u2014</span>
11981
+ <span class="tempest-admin-card__help">${escapeHtml(card.error)}</span>
11982
+ </article>`;
11983
+ }
11984
+ const unit = card.unit !== null ? ` <small>${escapeHtml(card.unit)}</small>` : "";
11985
+ if (card.kind === "partition") {
11986
+ const parts = card.segments.map(
11987
+ (segment) => `<li>
11988
+ <span class="tempest-admin-card__part-label">${escapeHtml(segment.label)}</span>
11989
+ <span class="tempest-admin-card__part-bar"><span style="width: ${Math.round(segment.percent)}%"></span></span>
11990
+ <span class="tempest-admin-card__part-value">${escapeHtml(segment.value)}</span>
11991
+ </li>`
11992
+ ).join("");
11993
+ return `<article class="tempest-admin-card tempest-admin-card--partition">
11994
+ <span class="tempest-admin-card__label">${escapeHtml(card.label)}</span>
11995
+ <ul class="tempest-admin-card__parts">${parts}</ul>
11996
+ ${help}
11997
+ </article>`;
11998
+ }
11999
+ if (card.kind === "trend") {
12000
+ const arrow = card.direction === "up" ? "\u25B2" : card.direction === "down" ? "\u25BC" : "\u25AC";
12001
+ return `<article class="tempest-admin-card tempest-admin-card--trend">
12002
+ <span class="tempest-admin-card__label">${escapeHtml(card.label)}</span>
12003
+ <span class="tempest-admin-card__value">${escapeHtml(card.value)}${unit}</span>
12004
+ <span class="tempest-admin-card__trend tempest-admin-card__trend--${escapeHtml(card.direction)}">
12005
+ ${arrow} ${card.percent === null ? "\u2014" : escapeHtml(card.percent)}
12006
+ <small>vs prev ${escapeHtml(card.previous)}</small>
12007
+ </span>
12008
+ ${help}
12009
+ </article>`;
12010
+ }
12011
+ return `<article class="tempest-admin-card tempest-admin-card--value">
12012
+ <span class="tempest-admin-card__label">${escapeHtml(card.label)}</span>
12013
+ <span class="tempest-admin-card__value">${escapeHtml(card.value)}${unit}</span>
12014
+ ${help}
12015
+ </article>`;
12016
+ }
11894
12017
  function renderFilter(filter) {
11895
12018
  const name = `filter_${filter.field}`;
11896
12019
  if (filter.kind === "select") {
@@ -11960,6 +12083,9 @@ function renderListPage(context, view) {
11960
12083
  <a href="${escapeHtml(view.exportJsonUrl)}">Export JSON</a>
11961
12084
  </div>
11962
12085
  </div>
12086
+ ${view.lenses.length > 0 ? `<nav class="tempest-admin-lenses" aria-label="Lenses">${view.lenses.map(
12087
+ (lens) => `<a class="tempest-admin-lens${lens.active ? " tempest-admin-lens--active" : ""}" href="${escapeHtml(lens.url)}">${escapeHtml(lens.label)}</a>`
12088
+ ).join("")}</nav>` : ""}
11963
12089
  ${bulkBar}
11964
12090
  <div class="tempest-admin-table-wrap">
11965
12091
  <table class="tempest-admin-list__table">
@@ -11983,6 +12109,7 @@ function renderDetailPage(context, view) {
11983
12109
  const fields = view.fields.map(
11984
12110
  (field) => `<dt>${escapeHtml(field.label)}</dt><dd>${field.value === "" ? "<em>\u2014</em>" : escapeHtml(field.value)}</dd>`
11985
12111
  ).join("");
12112
+ const auditPanel = view.audit === null ? "" : renderAuditPanel(view.audit);
11986
12113
  const body = `<section class="tempest-admin-detail">
11987
12114
  <header class="tempest-admin-detail__header">
11988
12115
  <h1>${escapeHtml(view.title)} \xB7 ${escapeHtml(view.identity)}</h1>
@@ -11996,9 +12123,37 @@ function renderDetailPage(context, view) {
11996
12123
  </div>
11997
12124
  </header>
11998
12125
  <dl class="tempest-admin-detail__fields">${fields}</dl>
12126
+ ${auditPanel}
11999
12127
  </section>`;
12000
12128
  return renderLayout(context, `${view.title} \xB7 ${view.identity}`, body);
12001
12129
  }
12130
+ function renderAuditPanel(audit) {
12131
+ const rows = audit.fields.map(
12132
+ (field) => `<dt>${escapeHtml(field.label)}</dt><dd>${field.value === "" ? "<em>\u2014</em>" : escapeHtml(field.value)}</dd>`
12133
+ ).join("");
12134
+ const history = audit.history.length > 0 ? `<ol class="tempest-admin-history">${audit.history.map((entry) => {
12135
+ const changes = entry.changes.length > 0 ? `<table class="tempest-admin-history__changes"><thead><tr><th>Field</th><th>Before</th><th>After</th></tr></thead><tbody>${entry.changes.map(
12136
+ (change) => `<tr><td>${escapeHtml(change.field)}</td><td>${escapeHtml(change.before)}</td><td>${escapeHtml(change.after)}</td></tr>`
12137
+ ).join("")}</tbody></table>` : "<p><em>No field changes recorded.</em></p>";
12138
+ const context = entry.context !== null ? `<pre class="tempest-admin-detail__json">${escapeHtml(entry.context)}</pre>` : "";
12139
+ return `<li class="tempest-admin-history__item">
12140
+ <details>
12141
+ <summary>
12142
+ <span class="tempest-admin-history__action">${escapeHtml(entry.action)}</span>
12143
+ <span class="tempest-admin-history__actor">${escapeHtml(entry.actor)}</span>
12144
+ <span class="tempest-admin-history__at">${escapeHtml(entry.at)}</span>
12145
+ </summary>
12146
+ ${changes}
12147
+ ${context}
12148
+ </details>
12149
+ </li>`;
12150
+ }).join("")}</ol>` : "";
12151
+ return `<section class="tempest-admin-audit">
12152
+ <h2>Audit</h2>
12153
+ <dl class="tempest-admin-detail__fields">${rows}</dl>
12154
+ ${history}
12155
+ </section>`;
12156
+ }
12002
12157
  function renderFormField(field) {
12003
12158
  const required = field.required ? " required" : "";
12004
12159
  const name = escapeHtml(field.name);
@@ -12062,6 +12217,7 @@ function renderFormPage(context, view) {
12062
12217
  return renderLayout(context, `${heading} \xB7 ${context.site.title}`, body);
12063
12218
  }
12064
12219
  var logger2 = new JSONLogger("tempest_express_sdk.admin.router");
12220
+ var AUDIT_HISTORY_LIMIT = 50;
12065
12221
  var FK_OPTION_CAP = 1e3;
12066
12222
  var FLASH_MAX_LENGTH = 300;
12067
12223
  var FLASH_MESSAGES = {
@@ -12100,18 +12256,23 @@ function makeAdminRouter(site, options) {
12100
12256
  const backend = options.authBackend;
12101
12257
  const router = express3.Router();
12102
12258
  router.use(prefix, express3.urlencoded({ extended: false }));
12103
- const context = (req, session) => ({
12259
+ const context = (req, session, models = site.list()) => ({
12104
12260
  site,
12105
12261
  theme,
12106
12262
  prefix,
12107
12263
  session,
12108
12264
  currentPath: req.originalUrl.split("?")[0] ?? req.path,
12109
- navModels: site.list().map((admin) => ({
12265
+ navModels: models.map((admin) => ({
12110
12266
  label: admin.verboseNamePlural(),
12111
12267
  url: `${prefix}/m/${admin.slug()}`
12112
12268
  })),
12113
12269
  messages: flashFor(req)
12114
12270
  });
12271
+ const allows = async (principal, admin, action) => {
12272
+ if (!flagAllows(admin, action)) return false;
12273
+ if (options.accessPolicy === void 0) return true;
12274
+ return Boolean(await options.accessPolicy(principal, admin, action));
12275
+ };
12115
12276
  const flashFor = (req) => {
12116
12277
  const fixed = FLASH_MESSAGES[queryString(req.query.ok)];
12117
12278
  if (fixed !== void 0) return [fixed];
@@ -12159,14 +12320,28 @@ function makeAdminRouter(site, options) {
12159
12320
  res.redirect(`${prefix}/mfa`);
12160
12321
  return null;
12161
12322
  }
12162
- return { session, dbSession, principal };
12323
+ const visible = [];
12324
+ for (const admin of site.list()) {
12325
+ if (await allows(principal, admin, AdminPermission.VIEW)) visible.push(admin);
12326
+ }
12327
+ return { session, dbSession, principal, visible };
12163
12328
  };
12164
- const resolveAdmin = (req, res, state) => {
12329
+ const resolveAdmin = async (req, res, state, action = AdminPermission.VIEW) => {
12165
12330
  const admin = site.get(String(req.params.slug));
12166
- if (admin === null) {
12167
- html(res, renderNotFound(context(req, state.session)), 404);
12331
+ if (admin === null || !await allows(state.principal, admin, AdminPermission.VIEW)) {
12332
+ html(res, renderNotFound(context(req, state.session, state.visible)), 404);
12168
12333
  return null;
12169
12334
  }
12335
+ if (action !== AdminPermission.VIEW) {
12336
+ if (!flagAllows(admin, action)) {
12337
+ html(res, renderNotFound(context(req, state.session, state.visible)), 404);
12338
+ return null;
12339
+ }
12340
+ if (!await allows(state.principal, admin, action)) {
12341
+ html(res, renderNotFound(context(req, state.session, state.visible)), 403);
12342
+ return null;
12343
+ }
12344
+ }
12170
12345
  return admin;
12171
12346
  };
12172
12347
  const renderNotFound = (ctx) => renderDashboardPage(
@@ -12177,7 +12352,7 @@ function makeAdminRouter(site, options) {
12177
12352
  const checkCsrf = (req, res, state) => {
12178
12353
  const body = req.body;
12179
12354
  if (csrfTokenMatches(state.session, body?.csrf_token)) return true;
12180
- html(res, renderNotFound(context(req, state.session)), 403);
12355
+ html(res, renderNotFound(context(req, state.session, state.visible)), 403);
12181
12356
  return false;
12182
12357
  };
12183
12358
  router.get(`${prefix}/static/admin.css`, (_req, res) => {
@@ -12259,7 +12434,7 @@ function makeAdminRouter(site, options) {
12259
12434
  const state = await authenticate(req, res);
12260
12435
  if (state === null) return;
12261
12436
  const cards = [];
12262
- for (const admin of site.list()) {
12437
+ for (const admin of state.visible) {
12263
12438
  let count = null;
12264
12439
  try {
12265
12440
  count = await admin.repository(state.dbSession).count();
@@ -12273,9 +12448,13 @@ function makeAdminRouter(site, options) {
12273
12448
  label: admin.verboseNamePlural(),
12274
12449
  count,
12275
12450
  url: `${prefix}/m/${admin.slug()}`,
12276
- newUrl: admin.canCreate ? `${prefix}/m/${admin.slug()}/new` : null
12451
+ newUrl: await allows(state.principal, admin, AdminPermission.CREATE) ? `${prefix}/m/${admin.slug()}/new` : null
12277
12452
  });
12278
12453
  }
12454
+ const businessCards = [];
12455
+ for (const card of site.dashboardCards) {
12456
+ businessCards.push(await computeBusinessCard(card, state.dbSession));
12457
+ }
12279
12458
  let metrics = null;
12280
12459
  if (showMetrics) {
12281
12460
  const snapshot2 = MetricsUtils.system();
@@ -12286,7 +12465,15 @@ function makeAdminRouter(site, options) {
12286
12465
  memoryTotalGb: (snapshot2.memory.total / 1024 ** 3).toFixed(1)
12287
12466
  };
12288
12467
  }
12289
- html(res, renderDashboardPage(context(req, state.session), cards, metrics));
12468
+ html(
12469
+ res,
12470
+ renderDashboardPage(
12471
+ context(req, state.session, state.visible),
12472
+ cards,
12473
+ metrics,
12474
+ businessCards
12475
+ )
12476
+ );
12290
12477
  })
12291
12478
  );
12292
12479
  router.get(
@@ -12294,7 +12481,7 @@ function makeAdminRouter(site, options) {
12294
12481
  guarded(async (req, res) => {
12295
12482
  const state = await authenticate(req, res);
12296
12483
  if (state === null) return;
12297
- const admin = resolveAdmin(req, res, state);
12484
+ const admin = await resolveAdmin(req, res, state);
12298
12485
  if (admin === null) return;
12299
12486
  html(res, await renderList(req, admin, state));
12300
12487
  })
@@ -12304,15 +12491,15 @@ function makeAdminRouter(site, options) {
12304
12491
  guarded(async (req, res) => {
12305
12492
  const state = await authenticate(req, res);
12306
12493
  if (state === null) return;
12307
- const admin = resolveAdmin(req, res, state);
12494
+ const admin = await resolveAdmin(req, res, state, AdminPermission.CREATE);
12308
12495
  if (admin === null) return;
12309
12496
  if (!admin.canCreate) {
12310
- html(res, renderNotFound(context(req, state.session)), 404);
12497
+ html(res, renderNotFound(context(req, state.session, state.visible)), 404);
12311
12498
  return;
12312
12499
  }
12313
12500
  html(
12314
12501
  res,
12315
- renderFormPage(context(req, state.session), {
12502
+ renderFormPage(context(req, state.session, state.visible), {
12316
12503
  mode: "create",
12317
12504
  title: admin.verboseName(),
12318
12505
  fields: buildFormFields(admin, {
@@ -12330,10 +12517,10 @@ function makeAdminRouter(site, options) {
12330
12517
  guarded(async (req, res) => {
12331
12518
  const state = await authenticate(req, res);
12332
12519
  if (state === null) return;
12333
- const admin = resolveAdmin(req, res, state);
12520
+ const admin = await resolveAdmin(req, res, state, AdminPermission.CREATE);
12334
12521
  if (admin === null) return;
12335
12522
  if (!admin.canCreate) {
12336
- html(res, renderNotFound(context(req, state.session)), 404);
12523
+ html(res, renderNotFound(context(req, state.session, state.visible)), 404);
12337
12524
  return;
12338
12525
  }
12339
12526
  if (!checkCsrf(req, res, state)) return;
@@ -12343,7 +12530,7 @@ function makeAdminRouter(site, options) {
12343
12530
  const rerender = (error, status) => {
12344
12531
  html(
12345
12532
  res,
12346
- renderFormPage(context(req, state.session), {
12533
+ renderFormPage(context(req, state.session, state.visible), {
12347
12534
  mode: "create",
12348
12535
  title: admin.verboseName(),
12349
12536
  fields: buildFormFields(admin, {
@@ -12362,6 +12549,7 @@ function makeAdminRouter(site, options) {
12362
12549
  rerender("Please fix the highlighted fields.", 400);
12363
12550
  return;
12364
12551
  }
12552
+ stampActor(admin, parsed.data, backend.principalId(state.principal), true);
12365
12553
  try {
12366
12554
  await admin.repository(state.dbSession).create(parsed.data);
12367
12555
  } catch (error) {
@@ -12376,11 +12564,11 @@ function makeAdminRouter(site, options) {
12376
12564
  guarded(async (req, res) => {
12377
12565
  const state = await authenticate(req, res);
12378
12566
  if (state === null) return;
12379
- const admin = resolveAdmin(req, res, state);
12567
+ const admin = await resolveAdmin(req, res, state);
12380
12568
  if (admin === null) return;
12381
12569
  const format = String(req.params.format);
12382
12570
  if (format !== "csv" && format !== "json") {
12383
- html(res, renderNotFound(context(req, state.session)), 404);
12571
+ html(res, renderNotFound(context(req, state.session, state.visible)), 404);
12384
12572
  return;
12385
12573
  }
12386
12574
  const query = await resolveListQuery(req, admin, state.dbSession);
@@ -12402,7 +12590,7 @@ function makeAdminRouter(site, options) {
12402
12590
  guarded(async (req, res) => {
12403
12591
  const state = await authenticate(req, res);
12404
12592
  if (state === null) return;
12405
- const admin = resolveAdmin(req, res, state);
12593
+ const admin = await resolveAdmin(req, res, state);
12406
12594
  if (admin === null) return;
12407
12595
  if (!checkCsrf(req, res, state)) return;
12408
12596
  const body = req.body;
@@ -12418,7 +12606,12 @@ function makeAdminRouter(site, options) {
12418
12606
  return;
12419
12607
  }
12420
12608
  if (!bulkActionsFor(admin).some((option) => option.value === action)) {
12421
- html(res, renderNotFound(context(req, state.session)), 400);
12609
+ html(res, renderNotFound(context(req, state.session, state.visible)), 400);
12610
+ return;
12611
+ }
12612
+ const needed = action === "delete" ? AdminPermission.DELETE : AdminPermission.EDIT;
12613
+ if (!await allows(state.principal, admin, needed)) {
12614
+ html(res, renderNotFound(context(req, state.session, state.visible)), 403);
12422
12615
  return;
12423
12616
  }
12424
12617
  const repository = admin.repository(state.dbSession);
@@ -12426,7 +12619,7 @@ function makeAdminRouter(site, options) {
12426
12619
  if (action.startsWith("custom:")) {
12427
12620
  const custom = admin.getAction(action.slice("custom:".length));
12428
12621
  if (custom === null) {
12429
- html(res, renderNotFound(context(req, state.session)), 400);
12622
+ html(res, renderNotFound(context(req, state.session, state.visible)), 400);
12430
12623
  return;
12431
12624
  }
12432
12625
  let result;
@@ -12476,23 +12669,24 @@ function makeAdminRouter(site, options) {
12476
12669
  guarded(async (req, res) => {
12477
12670
  const state = await authenticate(req, res);
12478
12671
  if (state === null) return;
12479
- const admin = resolveAdmin(req, res, state);
12672
+ const admin = await resolveAdmin(req, res, state);
12480
12673
  if (admin === null) return;
12481
12674
  const row = await findRow(admin, state.dbSession, String(req.params.identity));
12482
12675
  if (row === null) {
12483
- html(res, renderNotFound(context(req, state.session)), 404);
12676
+ html(res, renderNotFound(context(req, state.session, state.visible)), 404);
12484
12677
  return;
12485
12678
  }
12486
12679
  const identity = String(row[admin.identityField]);
12487
12680
  html(
12488
12681
  res,
12489
- renderDetailPage(context(req, state.session), {
12682
+ renderDetailPage(context(req, state.session, state.visible), {
12490
12683
  title: admin.verboseName(),
12491
12684
  identity,
12685
+ audit: await buildAuditView(admin, row, state.dbSession),
12492
12686
  fields: admin.detailFieldNames().map((name) => ({ label: name, value: formatCellValue(row[name]) })),
12493
12687
  backUrl: `${prefix}/m/${admin.slug()}`,
12494
- editUrl: admin.canEdit ? `${prefix}/m/${admin.slug()}/${identity}/edit` : null,
12495
- deleteUrl: admin.canDelete ? `${prefix}/m/${admin.slug()}/${identity}/delete` : null
12688
+ editUrl: await allows(state.principal, admin, AdminPermission.EDIT) ? `${prefix}/m/${admin.slug()}/${identity}/edit` : null,
12689
+ deleteUrl: await allows(state.principal, admin, AdminPermission.DELETE) ? `${prefix}/m/${admin.slug()}/${identity}/delete` : null
12496
12690
  })
12497
12691
  );
12498
12692
  })
@@ -12502,17 +12696,17 @@ function makeAdminRouter(site, options) {
12502
12696
  guarded(async (req, res) => {
12503
12697
  const state = await authenticate(req, res);
12504
12698
  if (state === null) return;
12505
- const admin = resolveAdmin(req, res, state);
12699
+ const admin = await resolveAdmin(req, res, state, AdminPermission.EDIT);
12506
12700
  if (admin === null) return;
12507
12701
  const identity = String(req.params.identity);
12508
12702
  const row = admin.canEdit ? await findRow(admin, state.dbSession, identity) : null;
12509
12703
  if (row === null) {
12510
- html(res, renderNotFound(context(req, state.session)), 404);
12704
+ html(res, renderNotFound(context(req, state.session, state.visible)), 404);
12511
12705
  return;
12512
12706
  }
12513
12707
  html(
12514
12708
  res,
12515
- renderFormPage(context(req, state.session), {
12709
+ renderFormPage(context(req, state.session, state.visible), {
12516
12710
  mode: "edit",
12517
12711
  title: admin.verboseName(),
12518
12712
  fields: buildFormFields(admin, {
@@ -12531,11 +12725,11 @@ function makeAdminRouter(site, options) {
12531
12725
  guarded(async (req, res) => {
12532
12726
  const state = await authenticate(req, res);
12533
12727
  if (state === null) return;
12534
- const admin = resolveAdmin(req, res, state);
12728
+ const admin = await resolveAdmin(req, res, state, AdminPermission.EDIT);
12535
12729
  if (admin === null) return;
12536
12730
  const identity = String(req.params.identity);
12537
12731
  if (!admin.canEdit) {
12538
- html(res, renderNotFound(context(req, state.session)), 404);
12732
+ html(res, renderNotFound(context(req, state.session, state.visible)), 404);
12539
12733
  return;
12540
12734
  }
12541
12735
  if (!checkCsrf(req, res, state)) return;
@@ -12545,7 +12739,7 @@ function makeAdminRouter(site, options) {
12545
12739
  const rerender = (error, status) => {
12546
12740
  html(
12547
12741
  res,
12548
- renderFormPage(context(req, state.session), {
12742
+ renderFormPage(context(req, state.session, state.visible), {
12549
12743
  mode: "edit",
12550
12744
  title: admin.verboseName(),
12551
12745
  fields: buildFormFields(admin, {
@@ -12564,10 +12758,16 @@ function makeAdminRouter(site, options) {
12564
12758
  rerender("Please fix the highlighted fields.", 400);
12565
12759
  return;
12566
12760
  }
12761
+ stampActor(
12762
+ admin,
12763
+ parsed.data,
12764
+ backend.principalId(state.principal),
12765
+ false
12766
+ );
12567
12767
  try {
12568
12768
  const changed = await admin.repository(state.dbSession).update({ [admin.identityField]: identity }, parsed.data);
12569
12769
  if (changed === 0) {
12570
- html(res, renderNotFound(context(req, state.session)), 404);
12770
+ html(res, renderNotFound(context(req, state.session, state.visible)), 404);
12571
12771
  return;
12572
12772
  }
12573
12773
  } catch (error) {
@@ -12582,10 +12782,10 @@ function makeAdminRouter(site, options) {
12582
12782
  guarded(async (req, res) => {
12583
12783
  const state = await authenticate(req, res);
12584
12784
  if (state === null) return;
12585
- const admin = resolveAdmin(req, res, state);
12785
+ const admin = await resolveAdmin(req, res, state, AdminPermission.DELETE);
12586
12786
  if (admin === null) return;
12587
12787
  if (!admin.canDelete) {
12588
- html(res, renderNotFound(context(req, state.session)), 404);
12788
+ html(res, renderNotFound(context(req, state.session, state.visible)), 404);
12589
12789
  return;
12590
12790
  }
12591
12791
  if (!checkCsrf(req, res, state)) return;
@@ -12593,10 +12793,58 @@ function makeAdminRouter(site, options) {
12593
12793
  res.redirect(`${prefix}/m/${admin.slug()}?ok=deleted`);
12594
12794
  })
12595
12795
  );
12796
+ async function actorLabel(actor, dbSession) {
12797
+ if (actor === null || actor === void 0 || actor === "") return "";
12798
+ const principal = await backend.loadPrincipal(dbSession, String(actor));
12799
+ return principal === null ? String(actor) : backend.displayName(principal);
12800
+ }
12801
+ async function buildAuditView(admin, row, dbSession) {
12802
+ const fields = [];
12803
+ for (const name of admin.auditFieldNames()) {
12804
+ const value = name === "createdBy" || name === "updatedBy" ? await actorLabel(row[name], dbSession) : formatCellValue(row[name]);
12805
+ fields.push({ label: humanizeField(name), value });
12806
+ }
12807
+ const history = [];
12808
+ if (admin.auditModel !== null) {
12809
+ const entity = admin.model.name;
12810
+ const entityId = String(row[admin.identityField]);
12811
+ const page2 = await new BaseRepository(admin.auditModel, dbSession).paginate({
12812
+ page: 1,
12813
+ pageSize: AUDIT_HISTORY_LIMIT,
12814
+ orderBy: "createdAt",
12815
+ ascending: false,
12816
+ filters: { entity, entityId }
12817
+ });
12818
+ for (const entry of page2.items) {
12819
+ const changes = entry.changes ?? {};
12820
+ history.push({
12821
+ action: String(entry.action ?? ""),
12822
+ at: formatCellValue(entry.createdAt),
12823
+ actor: await actorLabel(entry.actor, dbSession) || "\u2014",
12824
+ changes: Object.entries(changes).map(([field, change]) => ({
12825
+ field,
12826
+ before: formatCellValue(change?.before),
12827
+ after: formatCellValue(change?.after)
12828
+ })),
12829
+ context: entry.context === null || entry.context === void 0 ? null : JSON.stringify(entry.context, null, 2)
12830
+ });
12831
+ }
12832
+ }
12833
+ if (fields.length === 0 && history.length === 0) return null;
12834
+ return { fields, history };
12835
+ }
12596
12836
  async function findRow(admin, dbSession, identity) {
12597
12837
  const row = await admin.repository(dbSession).first({ [admin.identityField]: identity });
12598
12838
  return row ?? null;
12599
12839
  }
12840
+ async function permittedBulkActions(admin, principal) {
12841
+ const permitted = [];
12842
+ for (const option of bulkActionsFor(admin)) {
12843
+ const needed = option.value === "delete" ? AdminPermission.DELETE : AdminPermission.EDIT;
12844
+ if (await allows(principal, admin, needed)) permitted.push(option);
12845
+ }
12846
+ return permitted;
12847
+ }
12600
12848
  async function renderList(req, admin, state) {
12601
12849
  const columns = adminColumns(admin.model);
12602
12850
  const query = await resolveListQuery(req, admin, state.dbSession);
@@ -12651,21 +12899,36 @@ function makeAdminRouter(site, options) {
12651
12899
  searchValue: query.search,
12652
12900
  filters: query.filterViews,
12653
12901
  sort,
12654
- newUrl: admin.canCreate ? `${prefix}/m/${admin.slug()}/new` : null,
12655
- bulkActions: bulkActionsFor(admin),
12902
+ newUrl: await allows(state.principal, admin, AdminPermission.CREATE) ? `${prefix}/m/${admin.slug()}/new` : null,
12903
+ bulkActions: await permittedBulkActions(admin, state.principal),
12656
12904
  bulkUrl: `${prefix}/m/${admin.slug()}/bulk`,
12657
12905
  exportCsvUrl: exportUrl("csv"),
12658
- exportJsonUrl: exportUrl("json")
12906
+ exportJsonUrl: exportUrl("json"),
12907
+ lenses: admin.lenses.length === 0 ? [] : [
12908
+ {
12909
+ label: "All",
12910
+ url: `?${buildQuery({ ...query.baseQuery, lens: void 0 })}`,
12911
+ active: query.lens === ""
12912
+ },
12913
+ ...admin.lenses.map((entry) => ({
12914
+ label: entry.label,
12915
+ url: `?${buildQuery({ ...query.baseQuery, lens: entry.slug })}`,
12916
+ active: query.lens === entry.slug
12917
+ }))
12918
+ ]
12659
12919
  };
12660
- return renderListPage(context(req, state.session), view);
12920
+ return renderListPage(context(req, state.session, state.visible), view);
12661
12921
  }
12662
12922
  async function resolveListQuery(req, admin, dbSession) {
12663
12923
  const columns = adminColumns(admin.model);
12664
12924
  const search = queryString(req.query.q);
12665
12925
  const sortField = queryString(req.query.sort);
12666
12926
  const sortColumn = sortField in columns ? sortField : null;
12667
- const ascending = sortColumn === null ? admin.orderAscending : queryString(req.query.dir) !== "desc";
12927
+ const lens = admin.getLens(queryString(req.query.lens));
12668
12928
  const conditions = [];
12929
+ if (lens !== null && Object.keys(lens.filters).length > 0) {
12930
+ conditions.push(lens.filters);
12931
+ }
12669
12932
  const filterViews = [];
12670
12933
  for (const field of admin.listFilter) {
12671
12934
  const column6 = columns[field];
@@ -12729,15 +12992,21 @@ function makeAdminRouter(site, options) {
12729
12992
  baseQuery[`filter_${view.field}`] = view.value;
12730
12993
  }
12731
12994
  }
12995
+ const lensOrder = lens?.orderBy ?? null;
12996
+ const lensDescending = lensOrder?.startsWith("-");
12997
+ const lensColumn = lensOrder === null ? null : lensOrder.replace(/^-/, "");
12998
+ const ascending = sortColumn !== null ? queryString(req.query.dir) !== "desc" : lensColumn !== null ? !lensDescending : admin.orderAscending;
12999
+ if (lens !== null) baseQuery.lens = lens.slug;
12732
13000
  return {
12733
13001
  search,
12734
13002
  searchable,
12735
13003
  where: conditions.length === 0 ? void 0 : and(...conditions),
12736
- orderBy: sortColumn ?? admin.orderKey ?? void 0,
13004
+ orderBy: sortColumn ?? lensColumn ?? admin.orderKey ?? void 0,
12737
13005
  ascending,
12738
13006
  sortColumn,
12739
13007
  filterViews,
12740
- baseQuery
13008
+ baseQuery,
13009
+ lens: lens?.slug ?? ""
12741
13010
  };
12742
13011
  }
12743
13012
  async function relatedOptions(column6, dbSession) {
@@ -12764,6 +13033,76 @@ function makeAdminRouter(site, options) {
12764
13033
  }
12765
13034
  return router;
12766
13035
  }
13036
+ function flagAllows(admin, action) {
13037
+ if (action === AdminPermission.CREATE) return admin.canCreate;
13038
+ if (action === AdminPermission.EDIT) return admin.canEdit;
13039
+ if (action === AdminPermission.DELETE) return admin.canDelete;
13040
+ return true;
13041
+ }
13042
+ function stampActor(admin, data, actorId, creating) {
13043
+ const columns = adminColumns(admin.model);
13044
+ if (creating && "createdBy" in columns) data.createdBy = actorId;
13045
+ if ("updatedBy" in columns) data.updatedBy = actorId;
13046
+ }
13047
+ function formatMetric(value) {
13048
+ if (typeof value === "string") return value;
13049
+ return Number.isInteger(value) ? String(value) : value.toFixed(2);
13050
+ }
13051
+ async function computeBusinessCard(card, dbSession) {
13052
+ const base = {
13053
+ label: card.label,
13054
+ unit: null,
13055
+ direction: "flat",
13056
+ percent: null,
13057
+ previous: "",
13058
+ segments: [],
13059
+ helpText: card.helpText ?? null
13060
+ };
13061
+ let data;
13062
+ try {
13063
+ data = await card.compute(dbSession);
13064
+ } catch (error) {
13065
+ logger2.warning("Admin dashboard card failed", {
13066
+ card: card.label,
13067
+ error: error instanceof Error ? error.message : String(error)
13068
+ });
13069
+ return { ...base, kind: "value", value: "", error: "Could not compute this metric." };
13070
+ }
13071
+ if (data.kind === "partition") {
13072
+ const total = partitionTotal(data);
13073
+ return {
13074
+ ...base,
13075
+ kind: "partition",
13076
+ value: formatMetric(total),
13077
+ segments: data.segments.map((segment) => ({
13078
+ label: segment.label,
13079
+ value: formatMetric(segment.value),
13080
+ percent: total === 0 ? 0 : segment.value / total * 100
13081
+ })),
13082
+ error: null
13083
+ };
13084
+ }
13085
+ if (data.kind === "trend") {
13086
+ const percent = trendPercent(data);
13087
+ return {
13088
+ ...base,
13089
+ kind: "trend",
13090
+ value: formatMetric(data.value),
13091
+ unit: data.unit ?? null,
13092
+ direction: trendDirection(data),
13093
+ percent: percent === null ? null : `${percent >= 0 ? "+" : ""}${percent.toFixed(1)}%`,
13094
+ previous: formatMetric(data.previous),
13095
+ error: null
13096
+ };
13097
+ }
13098
+ return {
13099
+ ...base,
13100
+ kind: "value",
13101
+ value: formatMetric(data.value),
13102
+ unit: data.unit ?? null,
13103
+ error: null
13104
+ };
13105
+ }
12767
13106
  function bulkActionsFor(admin) {
12768
13107
  const actions = [];
12769
13108
  const hasActiveFlag = "isActive" in adminColumns(admin.model);
@@ -14815,6 +15154,6 @@ async function withTestDatabase(models, fn) {
14815
15154
  }
14816
15155
  }
14817
15156
 
14818
- export { ADMIN_CSS, ActivationService, AdminJsonSite, AdminModel, AdminSessionStore, AdminSite, AppException, AttemptThrottle, AuditAction, BaseAuditLogModel, BaseController, BaseModel, BaseOAuthClient, BaseOutboxModel, BaseService, BaseUserModel, BaseUserRefreshTokenModel, BaseUserTokenModel, CEP_PATTERN, CNPJ_PATTERN, CPF_PATTERN, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, CircuitOpenError, CompositeFeatureFlagBackend, ConflictException, DEFAULT_DOCS_FAVICON, DEFAULT_LOCALE, EmailProvider, EmailUtils, EnvFeatureFlagBackend, EventStream, ExpiredTokenException, FeatureFlags, ForbiddenException, GitHubOAuthClient, GoogleOAuthClient, GracefulShutdown, HTTPClient, HTTP_500_LOG_FILE, HTTP_500_MARKER, HttpMetrics, IDEMPOTENCY_HEADER, InvalidTokenException, JSONLogger, JWTUtils, LEVEL_LOG_FILES, LocalUploadStorage, MemoryBroker, MemoryCacheManager, MemoryFeatureFlagBackend, MemoryIdempotencyStore, MemoryRateLimitStore, MemorySessionStore, MemoryThrottleBackend, MessageCatalog, MessagingHub, MetricsUtils, MfaService, NotFoundException, OAuthError, OIDCProvider, OutboxRelay, OutboxStatus, PHONE_BR_PATTERN, PasswordResetService, REDOC_CDN_URL, REQUEST_ID_HEADER, RabbitBroker, RedisCacheManager, RedisIdempotencyStore, RedisRateLimitStore, RedisSSEBroker, RedisSessionStore, Region, RetryPolicy, S3UploadStorage, SSEBroker, ServerSentEvent, SessionService, TOTPHelper, TaskManager, TelegramProvider, TenantScopedRepository, TooManyRequestsException, TwilioSmsProvider, UF, UnauthorizedException, UserAuthService, UserModelAuthBackend, UserTokenPurpose, ValidationException, WebPushDispatcher, WebPushError, WebPushGoneError, WebSocketHub, WebhookSignatureVerifier, WhatsAppProvider, activationSchema, addLogSink, adminAction, adminColumns, adminThemeCss, attachWebSocketHub, authResponseSchema, authSettingsShape, backupDatabase, bearerToken, bodySizeLimitMiddleware, broadcastText, buildContentDisposition, buildFormFields, buildPaginationLinkHeader, cached2 as cached, cepField, citiesByUf, cnpjField, coerceFlag, configureFileLogging, configureLogging, cpfField, cpfOrCnpjField, createApp, createOpenApiRegistry, createTestDatabase, createdByColumn, csrfMiddleware, csrfTokenMatches, cursorPaginationFilterSchema, cursorPaginationSchema, decodeCursor, defaultMessageCatalog, defineEnum, deletedAtColumn, diffSnapshots, emailSettingsShape, encodeCursor, envList, escapeHtml, filterForColumn, foreignKeyFields, foreignKeyLabel, foreignKeyTable, formatCellValue, formatFieldValue, generateCsrfToken, generateOAuthState, generateOpaqueToken, generateOpenApiDocument, getAuth, getClientIp, getConditions, getPaginationConditions, getRequestId, getState, hashOpaqueToken, humanizeField, idempotencyMiddleware, inboundMessageSchema, isColumnOptional, isSearchableColumn, isValidCep, isValidCity, isValidCnpj, isValidCpf, isValidCpfCnpj, isValidPhoneBr, isValidUf, jwtSettingsShape, keyByHeader, keyByIp, keyByJwtClaim, keyByJwtSubject, listStates, logEntrySchema, logSettingsShape, loginSchema, makeAdminJsonRouter, makeAdminRouter, makeAppExceptionHandler, makeAuthRouter, makeFlagGuard, makeHealthRouter, makeJwtAuthMiddleware, makeLogsRouter, makeMetricsRouter, makeSessionMiddleware, makeToolSpecRouter, makeTwilioWebhookRouter, makeUnhandledExceptionHandler, makeWhatsAppWebhookRouter, mfaChallengeSchema, mfaCodeSchema, mfaEnrollResponseSchema, minioSettingsShape, modifyDict, mountOpenApiJson, mountRedoc, mountSwaggerUi, normalizeCep, normalizeCnpj, normalizeCpf, normalizeCpfCnpj, normalizePhoneBr, normalizeUf, notFoundHandler, onlyDigits, paginationFilterSchema, paginationSchema, parseAcceptLanguage, parseCookies, parseFormBody, passwordResetConfirmSchema, passwordResetRequestSchema, phoneBrField, prometheusMiddleware, rabbitmqSettingsShape, rateLimitMiddleware, redisSettingsShape, refreshSchema, registerExceptionHandlers, renderAuthResultPage, renderDashboardPage, renderDetailPage, renderFormPage, renderLayout, renderListPage, renderLoginPage, renderMfaPage, renderPasswordResetFormPage, requestIdMiddleware, requestTracingMiddleware, requireRoles, resolveAdminTheme, resolveDownloadPath, resolveRedocBundle, runServer, runWithRequestContext, sendBytesDownload, sendFileDownload, sessionCookie, sessionSettingsShape, setRequestId, signupSchema, snapshot, sseResponse, statesByRegion, syncFilterSchema, syncPaginationSchema, tableNameFor, toUtc, tokenFromUrl, tokenPairSchema, tokenSettingsShape, ufField, updatedByColumn, uploadSettingsShape, userPublicSchema, utcnow, validateTwilioSignature, verifyOpaqueToken, webPushKeysSchema, webPushPayloadSchema, webPushSettingsShape, webPushSubscriptionSchema, webSocketSettingsShape, widgetForColumn, withTestDatabase, wrapWithSlowQueryLog, wsEnvelopeSchema };
15157
+ export { ADMIN_CSS, ActivationService, AdminJsonSite, AdminModel, AdminPermission, AdminSessionStore, AdminSite, AppException, AttemptThrottle, AuditAction, BaseAuditLogModel, BaseController, BaseModel, BaseOAuthClient, BaseOutboxModel, BaseService, BaseUserModel, BaseUserRefreshTokenModel, BaseUserTokenModel, CEP_PATTERN, CNPJ_PATTERN, CPF_PATTERN, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, CircuitOpenError, CompositeFeatureFlagBackend, ConflictException, DEFAULT_DOCS_FAVICON, DEFAULT_LOCALE, EmailProvider, EmailUtils, EnvFeatureFlagBackend, EventStream, ExpiredTokenException, FeatureFlags, ForbiddenException, GitHubOAuthClient, GoogleOAuthClient, GracefulShutdown, HTTPClient, HTTP_500_LOG_FILE, HTTP_500_MARKER, HttpMetrics, IDEMPOTENCY_HEADER, InvalidTokenException, JSONLogger, JWTUtils, LEVEL_LOG_FILES, LocalUploadStorage, MemoryBroker, MemoryCacheManager, MemoryFeatureFlagBackend, MemoryIdempotencyStore, MemoryRateLimitStore, MemorySessionStore, MemoryThrottleBackend, MessageCatalog, MessagingHub, MetricsUtils, MfaService, NotFoundException, OAuthError, OIDCProvider, OutboxRelay, OutboxStatus, PHONE_BR_PATTERN, PasswordResetService, REDOC_CDN_URL, REQUEST_ID_HEADER, RabbitBroker, RedisCacheManager, RedisIdempotencyStore, RedisRateLimitStore, RedisSSEBroker, RedisSessionStore, Region, RetryPolicy, S3UploadStorage, SSEBroker, ServerSentEvent, SessionService, TOTPHelper, TaskManager, TelegramProvider, TenantScopedRepository, TooManyRequestsException, TwilioSmsProvider, UF, UnauthorizedException, UserAuthService, UserModelAuthBackend, UserTokenPurpose, ValidationException, WebPushDispatcher, WebPushError, WebPushGoneError, WebSocketHub, WebhookSignatureVerifier, WhatsAppProvider, activationSchema, addLogSink, adminAction, adminColumns, adminLens, adminThemeCss, attachWebSocketHub, authResponseSchema, authSettingsShape, backupDatabase, bearerToken, bodySizeLimitMiddleware, broadcastText, buildContentDisposition, buildFormFields, buildPaginationLinkHeader, cached2 as cached, cepField, citiesByUf, cnpjField, coerceFlag, configureFileLogging, configureLogging, cpfField, cpfOrCnpjField, createApp, createOpenApiRegistry, createTestDatabase, createdByColumn, csrfMiddleware, csrfTokenMatches, cursorPaginationFilterSchema, cursorPaginationSchema, decodeCursor, defaultMessageCatalog, defineEnum, deletedAtColumn, diffSnapshots, emailSettingsShape, encodeCursor, envList, escapeHtml, filterForColumn, foreignKeyFields, foreignKeyLabel, foreignKeyTable, formatCellValue, formatFieldValue, generateCsrfToken, generateOAuthState, generateOpaqueToken, generateOpenApiDocument, getAuth, getClientIp, getConditions, getPaginationConditions, getRequestId, getState, hashOpaqueToken, humanizeField, idempotencyMiddleware, inboundMessageSchema, isColumnOptional, isSearchableColumn, isValidCep, isValidCity, isValidCnpj, isValidCpf, isValidCpfCnpj, isValidPhoneBr, isValidUf, jwtSettingsShape, keyByHeader, keyByIp, keyByJwtClaim, keyByJwtSubject, listStates, logEntrySchema, logSettingsShape, loginSchema, makeAdminJsonRouter, makeAdminRouter, makeAppExceptionHandler, makeAuthRouter, makeFlagGuard, makeHealthRouter, makeJwtAuthMiddleware, makeLogsRouter, makeMetricsRouter, makeSessionMiddleware, makeToolSpecRouter, makeTwilioWebhookRouter, makeUnhandledExceptionHandler, makeWhatsAppWebhookRouter, metricCard, mfaChallengeSchema, mfaCodeSchema, mfaEnrollResponseSchema, minioSettingsShape, modifyDict, mountOpenApiJson, mountRedoc, mountSwaggerUi, normalizeCep, normalizeCnpj, normalizeCpf, normalizeCpfCnpj, normalizePhoneBr, normalizeUf, notFoundHandler, onlyDigits, paginationFilterSchema, paginationSchema, parseAcceptLanguage, parseCookies, parseFormBody, partitionTotal, passwordResetConfirmSchema, passwordResetRequestSchema, phoneBrField, prometheusMiddleware, rabbitmqSettingsShape, rateLimitMiddleware, redisSettingsShape, refreshSchema, registerExceptionHandlers, renderAuthResultPage, renderDashboardPage, renderDetailPage, renderFormPage, renderLayout, renderListPage, renderLoginPage, renderMfaPage, renderPasswordResetFormPage, requestIdMiddleware, requestTracingMiddleware, requireRoles, resolveAdminTheme, resolveDownloadPath, resolveRedocBundle, runServer, runWithRequestContext, sendBytesDownload, sendFileDownload, sessionCookie, sessionSettingsShape, setRequestId, signupSchema, snapshot, sseResponse, statesByRegion, syncFilterSchema, syncPaginationSchema, tableNameFor, toUtc, tokenFromUrl, tokenPairSchema, tokenSettingsShape, trendDirection, trendPercent, ufField, updatedByColumn, uploadSettingsShape, userPublicSchema, utcnow, validateTwilioSignature, verifyOpaqueToken, webPushKeysSchema, webPushPayloadSchema, webPushSettingsShape, webPushSubscriptionSchema, webSocketSettingsShape, widgetForColumn, withTestDatabase, wrapWithSlowQueryLog, wsEnvelopeSchema };
14819
15158
  //# sourceMappingURL=index.js.map
14820
15159
  //# sourceMappingURL=index.js.map