tempest-express-sdk 0.28.0 → 0.29.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/README.md +1 -1
- package/dist/{chunk-3NS5KVHT.js → chunk-7T4MZJZT.js} +3 -3
- package/dist/{chunk-3NS5KVHT.js.map → chunk-7T4MZJZT.js.map} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +559 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +355 -2
- package/dist/index.d.ts +355 -2
- package/dist/index.js +550 -60
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.cjs
CHANGED
|
@@ -9461,6 +9461,199 @@ function partitionTotal(partition) {
|
|
|
9461
9461
|
return partition.segments.reduce((total, segment) => total + segment.value, 0);
|
|
9462
9462
|
}
|
|
9463
9463
|
|
|
9464
|
+
// src/admin/logs.ts
|
|
9465
|
+
var LIFTED = /* @__PURE__ */ new Set(["level", "logger", "message", "timestamp", "stack"]);
|
|
9466
|
+
function toLogEntry(raw) {
|
|
9467
|
+
const context = {};
|
|
9468
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
9469
|
+
if (LIFTED.has(key) || value === null || value === void 0) continue;
|
|
9470
|
+
context[key] = value;
|
|
9471
|
+
}
|
|
9472
|
+
return {
|
|
9473
|
+
level: typeof raw.level === "string" ? raw.level : "info",
|
|
9474
|
+
logger: typeof raw.logger === "string" ? raw.logger : "",
|
|
9475
|
+
message: typeof raw.message === "string" ? raw.message : "",
|
|
9476
|
+
timestamp: typeof raw.timestamp === "string" ? raw.timestamp : "",
|
|
9477
|
+
stack: typeof raw.stack === "string" ? raw.stack : null,
|
|
9478
|
+
context,
|
|
9479
|
+
raw
|
|
9480
|
+
};
|
|
9481
|
+
}
|
|
9482
|
+
function filterLogEntries(entries, term) {
|
|
9483
|
+
const needle = term.trim().toLowerCase();
|
|
9484
|
+
if (needle === "") return entries;
|
|
9485
|
+
return entries.filter(
|
|
9486
|
+
(entry) => `${entry.message} ${entry.logger} ${entry.stack ?? ""}`.toLowerCase().includes(needle)
|
|
9487
|
+
);
|
|
9488
|
+
}
|
|
9489
|
+
function renderLogEntriesMarkdown(entries, options) {
|
|
9490
|
+
const lines = [
|
|
9491
|
+
"# Application logs",
|
|
9492
|
+
"",
|
|
9493
|
+
`- **Source:** \`${options.source}\``,
|
|
9494
|
+
`- **Search:** ${options.query === "" ? "_none_" : `\`${options.query}\``}`,
|
|
9495
|
+
`- **Exported:** ${entries.length} of ${options.total} matching record(s)`,
|
|
9496
|
+
""
|
|
9497
|
+
];
|
|
9498
|
+
if (entries.length < options.total) {
|
|
9499
|
+
lines.push(
|
|
9500
|
+
`> Truncated: the export is capped, so ${options.total - entries.length} older matching record(s) are not included.`,
|
|
9501
|
+
""
|
|
9502
|
+
);
|
|
9503
|
+
}
|
|
9504
|
+
for (const entry of entries) {
|
|
9505
|
+
lines.push(
|
|
9506
|
+
`## ${entry.level.toUpperCase()} \u2014 ${entry.message || "(no message)"}`,
|
|
9507
|
+
"",
|
|
9508
|
+
`- **When:** ${entry.timestamp || "unknown"}`,
|
|
9509
|
+
`- **Logger:** ${entry.logger || "unknown"}`
|
|
9510
|
+
);
|
|
9511
|
+
for (const [key, value] of Object.entries(entry.context)) {
|
|
9512
|
+
lines.push(
|
|
9513
|
+
`- **${key}:** ${typeof value === "string" ? value : JSON.stringify(value)}`
|
|
9514
|
+
);
|
|
9515
|
+
}
|
|
9516
|
+
lines.push("");
|
|
9517
|
+
if (entry.stack !== null) {
|
|
9518
|
+
lines.push("```text", entry.stack, "```", "");
|
|
9519
|
+
}
|
|
9520
|
+
}
|
|
9521
|
+
return lines.join("\n");
|
|
9522
|
+
}
|
|
9523
|
+
function renderLogEntriesJson(entries) {
|
|
9524
|
+
return JSON.stringify(
|
|
9525
|
+
entries.map((entry) => entry.raw),
|
|
9526
|
+
null,
|
|
9527
|
+
2
|
|
9528
|
+
);
|
|
9529
|
+
}
|
|
9530
|
+
|
|
9531
|
+
// src/admin/sqlConsole.ts
|
|
9532
|
+
var SqlCapability = {
|
|
9533
|
+
/** `SELECT`, `WITH … SELECT`, `EXPLAIN`, `SHOW`. */
|
|
9534
|
+
READ: "read",
|
|
9535
|
+
/** Adds rows. */
|
|
9536
|
+
INSERT: "insert",
|
|
9537
|
+
/** Changes rows. */
|
|
9538
|
+
UPDATE: "update",
|
|
9539
|
+
/** Removes rows. */
|
|
9540
|
+
DELETE: "delete",
|
|
9541
|
+
/** `CREATE` / `ALTER` / `COMMENT`. */
|
|
9542
|
+
DDL: "ddl",
|
|
9543
|
+
/** `DROP` and `TRUNCATE`: irreversible structure loss. */
|
|
9544
|
+
DROP: "drop",
|
|
9545
|
+
/**
|
|
9546
|
+
* `GRANT` / `REVOKE` / `SET`, and anything the analyser cannot classify.
|
|
9547
|
+
* Unknown statements land here on purpose, so a construct nobody anticipated
|
|
9548
|
+
* needs the most privileged capability rather than the least.
|
|
9549
|
+
*/
|
|
9550
|
+
ADMIN: "admin"
|
|
9551
|
+
};
|
|
9552
|
+
var STATEMENT_CAPABILITIES = {
|
|
9553
|
+
select: SqlCapability.READ,
|
|
9554
|
+
with: SqlCapability.READ,
|
|
9555
|
+
explain: SqlCapability.READ,
|
|
9556
|
+
show: SqlCapability.READ,
|
|
9557
|
+
desc: SqlCapability.READ,
|
|
9558
|
+
describe: SqlCapability.READ,
|
|
9559
|
+
insert: SqlCapability.INSERT,
|
|
9560
|
+
replace: SqlCapability.INSERT,
|
|
9561
|
+
update: SqlCapability.UPDATE,
|
|
9562
|
+
delete: SqlCapability.DELETE,
|
|
9563
|
+
create: SqlCapability.DDL,
|
|
9564
|
+
alter: SqlCapability.DDL,
|
|
9565
|
+
comment: SqlCapability.DDL,
|
|
9566
|
+
rename: SqlCapability.DDL,
|
|
9567
|
+
drop: SqlCapability.DROP,
|
|
9568
|
+
truncate: SqlCapability.DROP
|
|
9569
|
+
};
|
|
9570
|
+
var PARSER_HINT = "The admin SQL console needs the optional peer `node-sql-parser`. Install it with: npm install node-sql-parser";
|
|
9571
|
+
async function loadSqlParser() {
|
|
9572
|
+
try {
|
|
9573
|
+
const module = await import('node-sql-parser');
|
|
9574
|
+
const Parser = module.Parser ?? module.default?.Parser;
|
|
9575
|
+
if (Parser === void 0) throw new Error(PARSER_HINT);
|
|
9576
|
+
return new Parser();
|
|
9577
|
+
} catch {
|
|
9578
|
+
throw new Error(PARSER_HINT);
|
|
9579
|
+
}
|
|
9580
|
+
}
|
|
9581
|
+
function analyzeSql(sql5, dialect, parser) {
|
|
9582
|
+
const capabilities = /* @__PURE__ */ new Set();
|
|
9583
|
+
const tables = /* @__PURE__ */ new Set();
|
|
9584
|
+
let statements = 0;
|
|
9585
|
+
let parsed = true;
|
|
9586
|
+
let unscopedWrite = false;
|
|
9587
|
+
try {
|
|
9588
|
+
const ast = parser.astify(sql5, { database: dialect });
|
|
9589
|
+
const list = Array.isArray(ast) ? ast : [ast];
|
|
9590
|
+
statements = list.length;
|
|
9591
|
+
for (const statement of list) {
|
|
9592
|
+
const type = String(statement.type ?? "").toLowerCase();
|
|
9593
|
+
capabilities.add(STATEMENT_CAPABILITIES[type] ?? SqlCapability.ADMIN);
|
|
9594
|
+
if ((type === "update" || type === "delete") && !statement.where) {
|
|
9595
|
+
unscopedWrite = true;
|
|
9596
|
+
}
|
|
9597
|
+
}
|
|
9598
|
+
} catch {
|
|
9599
|
+
parsed = false;
|
|
9600
|
+
statements = 1;
|
|
9601
|
+
capabilities.add(SqlCapability.ADMIN);
|
|
9602
|
+
}
|
|
9603
|
+
try {
|
|
9604
|
+
for (const entry of parser.tableList(sql5, { database: dialect })) {
|
|
9605
|
+
const name = entry.split("::").pop();
|
|
9606
|
+
if (name !== void 0 && name !== "null") tables.add(name.toLowerCase());
|
|
9607
|
+
}
|
|
9608
|
+
} catch {
|
|
9609
|
+
}
|
|
9610
|
+
return {
|
|
9611
|
+
statements,
|
|
9612
|
+
capabilities: [...capabilities],
|
|
9613
|
+
tables: [...tables],
|
|
9614
|
+
parsed,
|
|
9615
|
+
unscopedWrite
|
|
9616
|
+
};
|
|
9617
|
+
}
|
|
9618
|
+
function checkSqlPolicy(analysis, policy) {
|
|
9619
|
+
const granted = new Set(policy.capabilities ?? [SqlCapability.READ]);
|
|
9620
|
+
for (const capability of analysis.capabilities) {
|
|
9621
|
+
if (!granted.has(capability)) {
|
|
9622
|
+
return {
|
|
9623
|
+
allowed: false,
|
|
9624
|
+
reason: analysis.parsed ? `This console may not run ${capability} statements.` : "The statement could not be parsed, so it needs the admin capability."
|
|
9625
|
+
};
|
|
9626
|
+
}
|
|
9627
|
+
}
|
|
9628
|
+
if ((policy.requireWhereOnWrites ?? true) && analysis.unscopedWrite) {
|
|
9629
|
+
return {
|
|
9630
|
+
allowed: false,
|
|
9631
|
+
reason: "An UPDATE or DELETE without a WHERE clause is refused."
|
|
9632
|
+
};
|
|
9633
|
+
}
|
|
9634
|
+
const denied = new Set((policy.denyTables ?? []).map((name) => name.toLowerCase()));
|
|
9635
|
+
for (const table of analysis.tables) {
|
|
9636
|
+
if (denied.has(table)) {
|
|
9637
|
+
return { allowed: false, reason: `Table "${table}" is not available here.` };
|
|
9638
|
+
}
|
|
9639
|
+
}
|
|
9640
|
+
if (policy.allowTables !== void 0) {
|
|
9641
|
+
const allowed = new Set(policy.allowTables.map((name) => name.toLowerCase()));
|
|
9642
|
+
if (analysis.tables.length === 0) {
|
|
9643
|
+
return {
|
|
9644
|
+
allowed: false,
|
|
9645
|
+
reason: "This console only runs statements naming an allowed table."
|
|
9646
|
+
};
|
|
9647
|
+
}
|
|
9648
|
+
for (const table of analysis.tables) {
|
|
9649
|
+
if (!allowed.has(table)) {
|
|
9650
|
+
return { allowed: false, reason: `Table "${table}" is not on the allow list.` };
|
|
9651
|
+
}
|
|
9652
|
+
}
|
|
9653
|
+
}
|
|
9654
|
+
return { allowed: true, reason: null };
|
|
9655
|
+
}
|
|
9656
|
+
|
|
9464
9657
|
// src/admin/inlines.ts
|
|
9465
9658
|
function adminInline(options) {
|
|
9466
9659
|
const slug = options.model.tablename;
|
|
@@ -12112,7 +12305,7 @@ function escapeHtml(value) {
|
|
|
12112
12305
|
return String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
12113
12306
|
}
|
|
12114
12307
|
function renderLayout(context, title, body) {
|
|
12115
|
-
const { site, theme, prefix, session, currentPath, navModels, messages } = context;
|
|
12308
|
+
const { site, theme, prefix, session, currentPath, navModels, navSystem, messages } = context;
|
|
12116
12309
|
const authed = session !== null;
|
|
12117
12310
|
const indexUrl = `${prefix}/`;
|
|
12118
12311
|
const navLink = (url, label, active) => `<a href="${escapeHtml(url)}" class="tempest-admin-sidebar__link${active ? " tempest-admin-sidebar__link--active" : ""}">${escapeHtml(label)}</a>`;
|
|
@@ -12122,6 +12315,9 @@ function renderLayout(context, title, body) {
|
|
|
12122
12315
|
${navLink(indexUrl, "Dashboard", currentPath === indexUrl)}
|
|
12123
12316
|
${navModels.length > 0 ? `<span class="tempest-admin-sidebar__heading">Models</span>${navModels.map(
|
|
12124
12317
|
(entry) => navLink(entry.url, entry.label, currentPath.startsWith(entry.url))
|
|
12318
|
+
).join("")}` : ""}
|
|
12319
|
+
${navSystem.length > 0 ? `<span class="tempest-admin-sidebar__heading">System</span>${navSystem.map(
|
|
12320
|
+
(entry) => navLink(entry.url, entry.label, currentPath.startsWith(entry.url))
|
|
12125
12321
|
).join("")}` : ""}
|
|
12126
12322
|
</nav>
|
|
12127
12323
|
</aside>` : "";
|
|
@@ -12472,11 +12668,11 @@ function renderInline(inline, csrfToken, error) {
|
|
|
12472
12668
|
${more}
|
|
12473
12669
|
</section>`;
|
|
12474
12670
|
}
|
|
12475
|
-
function renderAuditPanel(
|
|
12476
|
-
const rows =
|
|
12671
|
+
function renderAuditPanel(audit2) {
|
|
12672
|
+
const rows = audit2.fields.map(
|
|
12477
12673
|
(field) => `<dt>${escapeHtml(field.label)}</dt><dd>${field.value === "" ? "<em>\u2014</em>" : escapeHtml(field.value)}</dd>`
|
|
12478
12674
|
).join("");
|
|
12479
|
-
const history =
|
|
12675
|
+
const history = audit2.history.length > 0 ? `<ol class="tempest-admin-history">${audit2.history.map((entry) => {
|
|
12480
12676
|
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(
|
|
12481
12677
|
(change) => `<tr><td>${escapeHtml(change.field)}</td><td>${escapeHtml(change.before)}</td><td>${escapeHtml(change.after)}</td></tr>`
|
|
12482
12678
|
).join("")}</tbody></table>` : "<p><em>No field changes recorded.</em></p>";
|
|
@@ -12574,6 +12770,86 @@ function renderFormPage(context, view) {
|
|
|
12574
12770
|
</section>`;
|
|
12575
12771
|
return renderLayout(context, `${heading} \xB7 ${context.site.title}`, body);
|
|
12576
12772
|
}
|
|
12773
|
+
function renderLogsPage(context, view) {
|
|
12774
|
+
const options = view.sources.map(
|
|
12775
|
+
(source) => `<option value="${escapeHtml(source.value)}"${source.selected ? " selected" : ""}>${escapeHtml(source.label)}</option>`
|
|
12776
|
+
).join("");
|
|
12777
|
+
const rows = view.rows.length > 0 ? view.rows.map((row) => {
|
|
12778
|
+
const context_ = row.context.map(
|
|
12779
|
+
(entry) => `<span class="tempest-admin-logs__meta"><b>${escapeHtml(entry.key)}</b>: ${escapeHtml(entry.value)}</span>`
|
|
12780
|
+
).join(" ");
|
|
12781
|
+
const message = row.stack === null ? `${escapeHtml(row.message)}${context_ === "" ? "" : `<br>${context_}`}` : `<details><summary>${escapeHtml(row.message)}</summary>${context_ === "" ? "" : `<p>${context_}</p>`}<pre>${escapeHtml(row.stack)}</pre></details>`;
|
|
12782
|
+
return `<tr>
|
|
12783
|
+
<td data-label="Level"><span class="tempest-log-badge tempest-log-badge--${escapeHtml(row.level)}">${escapeHtml(row.level)}</span></td>
|
|
12784
|
+
<td data-label="When">${escapeHtml(row.timestamp)}</td>
|
|
12785
|
+
<td data-label="Logger">${escapeHtml(row.logger)}</td>
|
|
12786
|
+
<td data-label="Message">${message}</td>
|
|
12787
|
+
</tr>`;
|
|
12788
|
+
}).join("") : `<tr><td colspan="4">No log records yet. Point <code>configureFileLogging</code> at the same directory to populate this page.</td></tr>`;
|
|
12789
|
+
const body = `<section class="tempest-admin-logs">
|
|
12790
|
+
<header class="tempest-admin-list__header">
|
|
12791
|
+
<h1>Logs</h1>
|
|
12792
|
+
<p>${escapeHtml(view.total)} record${view.total === 1 ? "" : "s"}.</p>
|
|
12793
|
+
</header>
|
|
12794
|
+
<div class="tempest-admin-list__toolbar">
|
|
12795
|
+
<form method="get" class="tempest-admin-list__filters">
|
|
12796
|
+
<label><span>Source</span><select name="source">${options}</select></label>
|
|
12797
|
+
<input type="search" name="q" value="${escapeHtml(view.query)}" placeholder="Search\u2026" aria-label="Search">
|
|
12798
|
+
<button type="submit">Apply</button>
|
|
12799
|
+
</form>
|
|
12800
|
+
<div class="tempest-admin-list__actions">
|
|
12801
|
+
<a href="${escapeHtml(view.exportMarkdownUrl)}">Export Markdown</a>
|
|
12802
|
+
<a href="${escapeHtml(view.exportJsonUrl)}">Export JSON</a>
|
|
12803
|
+
</div>
|
|
12804
|
+
</div>
|
|
12805
|
+
<div class="tempest-admin-table-wrap">
|
|
12806
|
+
<table class="tempest-admin-list__table">
|
|
12807
|
+
<thead><tr><th>Level</th><th>When</th><th>Logger</th><th>Message</th></tr></thead>
|
|
12808
|
+
<tbody>${rows}</tbody>
|
|
12809
|
+
</table>
|
|
12810
|
+
</div>
|
|
12811
|
+
${view.pages > 1 ? `<nav class="tempest-admin-list__pagination" aria-label="Pagination">
|
|
12812
|
+
${view.prevUrl !== null ? `<a href="${escapeHtml(view.prevUrl)}">\u2190 Prev</a>` : ""}
|
|
12813
|
+
<span>Page ${escapeHtml(view.page)} of ${escapeHtml(view.pages)}</span>
|
|
12814
|
+
${view.nextUrl !== null ? `<a href="${escapeHtml(view.nextUrl)}">Next \u2192</a>` : ""}
|
|
12815
|
+
</nav>` : ""}
|
|
12816
|
+
<p class="tempest-admin-form__hint">Exports carry at most ${escapeHtml(view.exportMax)} records, newest first, honouring the filters above.</p>
|
|
12817
|
+
</section>`;
|
|
12818
|
+
return renderLayout(context, `Logs \xB7 ${context.site.title}`, body);
|
|
12819
|
+
}
|
|
12820
|
+
function renderSqlPage(context, view) {
|
|
12821
|
+
if (context.session === null) throw new Error("The SQL console requires a session");
|
|
12822
|
+
const result = view.rowCount === null ? "" : `<p class="tempest-admin-form__hint">${escapeHtml(view.rowCount)} row${view.rowCount === 1 ? "" : "s"}${view.durationMs === null ? "" : ` in ${escapeHtml(view.durationMs)} ms`}${view.truncated ? " (truncated by the row cap)" : ""}.</p>
|
|
12823
|
+
<div class="tempest-admin-table-wrap">
|
|
12824
|
+
<table class="tempest-admin-list__table">
|
|
12825
|
+
<thead><tr>${view.columns.map((column6) => `<th>${escapeHtml(column6)}</th>`).join("")}</tr></thead>
|
|
12826
|
+
<tbody>${view.rows.map(
|
|
12827
|
+
(row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`
|
|
12828
|
+
).join("")}</tbody>
|
|
12829
|
+
</table>
|
|
12830
|
+
</div>`;
|
|
12831
|
+
const body = `<section class="tempest-admin-sql">
|
|
12832
|
+
<header class="tempest-admin-list__header">
|
|
12833
|
+
<h1>SQL console</h1>
|
|
12834
|
+
<p>Capabilities: <code>${escapeHtml(view.capabilities.join(", "))}</code></p>
|
|
12835
|
+
</header>
|
|
12836
|
+
${view.error !== null ? `<p class="tempest-admin-form__error">${escapeHtml(view.error)}</p>` : ""}
|
|
12837
|
+
<form method="post" action="${escapeHtml(`${context.prefix}/sql`)}" class="tempest-admin-form__form">
|
|
12838
|
+
<input type="hidden" name="csrf_token" value="${escapeHtml(context.session.csrfToken)}">
|
|
12839
|
+
<div class="tempest-admin-form__field">
|
|
12840
|
+
<label>
|
|
12841
|
+
<span>Statement</span>
|
|
12842
|
+
<textarea name="sql" rows="6" spellcheck="false" required>${escapeHtml(view.sql)}</textarea>
|
|
12843
|
+
</label>
|
|
12844
|
+
</div>
|
|
12845
|
+
<div class="tempest-admin-form__actions">
|
|
12846
|
+
<button type="submit">Run</button>
|
|
12847
|
+
</div>
|
|
12848
|
+
</form>
|
|
12849
|
+
${result}
|
|
12850
|
+
</section>`;
|
|
12851
|
+
return renderLayout(context, `SQL console \xB7 ${context.site.title}`, body);
|
|
12852
|
+
}
|
|
12577
12853
|
function renderImportPage(context, view) {
|
|
12578
12854
|
if (context.session === null) throw new Error("The import page requires a session");
|
|
12579
12855
|
const summary = view.created === null ? "" : `<p class="tempest-admin-import__summary">Created ${escapeHtml(view.created)} record${view.created === 1 ? "" : "s"}.</p>`;
|
|
@@ -12659,7 +12935,66 @@ var AUTOCOMPLETE_SCRIPT = `<script>
|
|
|
12659
12935
|
});
|
|
12660
12936
|
})();
|
|
12661
12937
|
</script>`;
|
|
12938
|
+
function filesFor(dir, source) {
|
|
12939
|
+
if (source === "500") return [path.join(dir, HTTP_500_LOG_FILE)];
|
|
12940
|
+
if (source === "all") return Object.values(LEVEL_LOG_FILES).map((f) => path.join(dir, f));
|
|
12941
|
+
return [path.join(dir, LEVEL_LOG_FILES[source])];
|
|
12942
|
+
}
|
|
12943
|
+
async function readLogEntries(dir, source) {
|
|
12944
|
+
return await readEntries(filesFor(dir, source));
|
|
12945
|
+
}
|
|
12946
|
+
async function readEntries(files) {
|
|
12947
|
+
const entries = [];
|
|
12948
|
+
for (const file of files) {
|
|
12949
|
+
let content;
|
|
12950
|
+
try {
|
|
12951
|
+
content = await promises.readFile(file, "utf8");
|
|
12952
|
+
} catch {
|
|
12953
|
+
continue;
|
|
12954
|
+
}
|
|
12955
|
+
for (const line of content.split("\n")) {
|
|
12956
|
+
if (!line.trim()) continue;
|
|
12957
|
+
try {
|
|
12958
|
+
entries.push(JSON.parse(line));
|
|
12959
|
+
} catch {
|
|
12960
|
+
}
|
|
12961
|
+
}
|
|
12962
|
+
}
|
|
12963
|
+
return entries;
|
|
12964
|
+
}
|
|
12965
|
+
function makeLogsRouter(options) {
|
|
12966
|
+
const router = express3.Router();
|
|
12967
|
+
const path = options.path ?? "/logs";
|
|
12968
|
+
const guards = options.guards ?? [];
|
|
12969
|
+
const handler = (req, res, next) => {
|
|
12970
|
+
const source = req.query.source ?? "all";
|
|
12971
|
+
const validSources = ["all", "debug", "info", "warning", "error", "500"];
|
|
12972
|
+
if (!validSources.includes(source)) {
|
|
12973
|
+
res.status(422).json({ detail: "Invalid log source", code: "VALIDATION_ERROR", details: {} });
|
|
12974
|
+
return;
|
|
12975
|
+
}
|
|
12976
|
+
const page2 = Math.max(1, Number(req.query.page) || 1);
|
|
12977
|
+
const pageSize = Math.min(500, Math.max(1, Number(req.query.pageSize) || 50));
|
|
12978
|
+
readEntries(filesFor(options.dir, source)).then((entries) => {
|
|
12979
|
+
entries.reverse();
|
|
12980
|
+
const total = entries.length;
|
|
12981
|
+
const start = (page2 - 1) * pageSize;
|
|
12982
|
+
res.json({
|
|
12983
|
+
items: entries.slice(start, start + pageSize),
|
|
12984
|
+
total,
|
|
12985
|
+
page: page2,
|
|
12986
|
+
pageSize,
|
|
12987
|
+
pages: Math.ceil(total / pageSize)
|
|
12988
|
+
});
|
|
12989
|
+
}).catch(next);
|
|
12990
|
+
};
|
|
12991
|
+
router.get(path, ...guards, handler);
|
|
12992
|
+
return router;
|
|
12993
|
+
}
|
|
12662
12994
|
var logger2 = new JSONLogger("tempest_express_sdk.admin.router");
|
|
12995
|
+
var LOG_SOURCES = ["all", "debug", "info", "warning", "error", "500"];
|
|
12996
|
+
var LOG_PAGE_SIZE = 50;
|
|
12997
|
+
var LOG_EXPORT_MAX = 500;
|
|
12663
12998
|
var INLINE_ROW_LIMIT = 50;
|
|
12664
12999
|
var AUTOCOMPLETE_LIMIT = 20;
|
|
12665
13000
|
var AUDIT_HISTORY_LIMIT = 50;
|
|
@@ -12691,6 +13026,13 @@ function makeAdminRouter(site, options) {
|
|
|
12691
13026
|
const theme = resolveAdminTheme(site.theme);
|
|
12692
13027
|
const showMetrics = options.showMetrics ?? true;
|
|
12693
13028
|
const exportMaxRows = options.exportMaxRows ?? 5e3;
|
|
13029
|
+
const systemNav = [];
|
|
13030
|
+
if (options.logDir !== void 0) {
|
|
13031
|
+
systemNav.push({ label: "Logs", url: `${prefix}/logs` });
|
|
13032
|
+
}
|
|
13033
|
+
if (options.sqlConsole !== void 0) {
|
|
13034
|
+
systemNav.push({ label: "SQL console", url: `${prefix}/sql` });
|
|
13035
|
+
}
|
|
12694
13036
|
const maxUploadBytes = options.maxUploadBytes ?? 10 * 1024 * 1024;
|
|
12695
13037
|
const sessions = new AdminSessionStore({
|
|
12696
13038
|
secret: options.secretKey,
|
|
@@ -12712,6 +13054,7 @@ function makeAdminRouter(site, options) {
|
|
|
12712
13054
|
label: admin.verboseNamePlural(),
|
|
12713
13055
|
url: `${prefix}/m/${admin.slug()}`
|
|
12714
13056
|
})),
|
|
13057
|
+
navSystem: systemNav,
|
|
12715
13058
|
messages: flashFor(req)
|
|
12716
13059
|
});
|
|
12717
13060
|
const allows = async (principal, admin, action) => {
|
|
@@ -12922,6 +13265,187 @@ function makeAdminRouter(site, options) {
|
|
|
12922
13265
|
);
|
|
12923
13266
|
})
|
|
12924
13267
|
);
|
|
13268
|
+
if (options.logDir !== void 0) {
|
|
13269
|
+
const logDir = options.logDir;
|
|
13270
|
+
router.get(
|
|
13271
|
+
`${prefix}/logs`,
|
|
13272
|
+
guarded(async (req, res) => {
|
|
13273
|
+
const state = await authenticate(req, res);
|
|
13274
|
+
if (state === null) return;
|
|
13275
|
+
const { entries, source, search } = await readLogs(req, logDir);
|
|
13276
|
+
const page2 = Math.max(1, Number.parseInt(queryString(req.query.page), 10) || 1);
|
|
13277
|
+
const start = (page2 - 1) * LOG_PAGE_SIZE;
|
|
13278
|
+
const pages = Math.max(1, Math.ceil(entries.length / LOG_PAGE_SIZE));
|
|
13279
|
+
const exportUrl = (format) => `${prefix}/logs/export?${buildQuery({ source, q: search, format })}`;
|
|
13280
|
+
const pageUrl = (target) => `?${buildQuery({ source, q: search, page: target })}`;
|
|
13281
|
+
html(
|
|
13282
|
+
res,
|
|
13283
|
+
renderLogsPage(context(req, state.session, state.visible), {
|
|
13284
|
+
sources: LOG_SOURCES.map((value) => ({
|
|
13285
|
+
value,
|
|
13286
|
+
label: value === "500" ? "HTTP 500" : humanizeField(value),
|
|
13287
|
+
selected: value === source
|
|
13288
|
+
})),
|
|
13289
|
+
query: search,
|
|
13290
|
+
rows: entries.slice(start, start + LOG_PAGE_SIZE).map((entry) => ({
|
|
13291
|
+
level: entry.level,
|
|
13292
|
+
timestamp: entry.timestamp,
|
|
13293
|
+
logger: entry.logger,
|
|
13294
|
+
message: entry.message,
|
|
13295
|
+
stack: entry.stack,
|
|
13296
|
+
context: Object.entries(entry.context).map(([key, value]) => ({
|
|
13297
|
+
key,
|
|
13298
|
+
value: typeof value === "string" ? value : JSON.stringify(value)
|
|
13299
|
+
}))
|
|
13300
|
+
})),
|
|
13301
|
+
total: entries.length,
|
|
13302
|
+
page: page2,
|
|
13303
|
+
pages,
|
|
13304
|
+
prevUrl: page2 > 1 ? pageUrl(page2 - 1) : null,
|
|
13305
|
+
nextUrl: page2 < pages ? pageUrl(page2 + 1) : null,
|
|
13306
|
+
exportMarkdownUrl: exportUrl("md"),
|
|
13307
|
+
exportJsonUrl: exportUrl("json"),
|
|
13308
|
+
exportMax: LOG_EXPORT_MAX
|
|
13309
|
+
})
|
|
13310
|
+
);
|
|
13311
|
+
})
|
|
13312
|
+
);
|
|
13313
|
+
router.get(
|
|
13314
|
+
`${prefix}/logs/export`,
|
|
13315
|
+
guarded(async (req, res) => {
|
|
13316
|
+
const state = await authenticate(req, res);
|
|
13317
|
+
if (state === null) return;
|
|
13318
|
+
const format = queryString(req.query.format) === "json" ? "json" : "md";
|
|
13319
|
+
const { entries, source, search } = await readLogs(req, logDir);
|
|
13320
|
+
const window = entries.slice(0, LOG_EXPORT_MAX);
|
|
13321
|
+
const payload = format === "json" ? renderLogEntriesJson(window) : renderLogEntriesMarkdown(window, {
|
|
13322
|
+
source,
|
|
13323
|
+
query: search,
|
|
13324
|
+
total: entries.length
|
|
13325
|
+
});
|
|
13326
|
+
res.status(200).type(format === "json" ? "application/json" : "text/markdown; charset=utf-8").set("content-disposition", `attachment; filename="logs.${format}"`).send(payload);
|
|
13327
|
+
})
|
|
13328
|
+
);
|
|
13329
|
+
}
|
|
13330
|
+
if (options.sqlConsole !== void 0) {
|
|
13331
|
+
const console_ = options.sqlConsole;
|
|
13332
|
+
const policy = console_.policy ?? {};
|
|
13333
|
+
const dialect = console_.dialect ?? "postgresql";
|
|
13334
|
+
const capabilities = policy.capabilities ?? [SqlCapability.READ];
|
|
13335
|
+
const maxRows = policy.maxRows ?? 200;
|
|
13336
|
+
router.get(
|
|
13337
|
+
`${prefix}/sql`,
|
|
13338
|
+
guarded(async (req, res) => {
|
|
13339
|
+
const state = await authenticate(req, res);
|
|
13340
|
+
if (state === null) return;
|
|
13341
|
+
html(
|
|
13342
|
+
res,
|
|
13343
|
+
renderSqlPage(context(req, state.session, state.visible), {
|
|
13344
|
+
sql: "",
|
|
13345
|
+
capabilities: [...capabilities],
|
|
13346
|
+
error: null,
|
|
13347
|
+
columns: [],
|
|
13348
|
+
rows: [],
|
|
13349
|
+
rowCount: null,
|
|
13350
|
+
truncated: false,
|
|
13351
|
+
durationMs: null
|
|
13352
|
+
})
|
|
13353
|
+
);
|
|
13354
|
+
})
|
|
13355
|
+
);
|
|
13356
|
+
router.post(
|
|
13357
|
+
`${prefix}/sql`,
|
|
13358
|
+
guarded(async (req, res) => {
|
|
13359
|
+
const state = await authenticate(req, res);
|
|
13360
|
+
if (state === null) return;
|
|
13361
|
+
if (!checkCsrf(req, res, state)) return;
|
|
13362
|
+
const body = req.body;
|
|
13363
|
+
const sql5 = typeof body.sql === "string" ? body.sql : "";
|
|
13364
|
+
const principal = backend.displayName(state.principal);
|
|
13365
|
+
const render = (view) => {
|
|
13366
|
+
html(
|
|
13367
|
+
res,
|
|
13368
|
+
renderSqlPage(context(req, state.session, state.visible), {
|
|
13369
|
+
sql: sql5,
|
|
13370
|
+
capabilities: [...capabilities],
|
|
13371
|
+
...view
|
|
13372
|
+
}),
|
|
13373
|
+
view.error === null ? 200 : 400
|
|
13374
|
+
);
|
|
13375
|
+
};
|
|
13376
|
+
const parser = await loadSqlParser();
|
|
13377
|
+
const analysis = analyzeSql(sql5, dialect, parser);
|
|
13378
|
+
const verdict = checkSqlPolicy(analysis, policy);
|
|
13379
|
+
if (!verdict.allowed) {
|
|
13380
|
+
await audit(console_.onAudit, {
|
|
13381
|
+
sql: sql5,
|
|
13382
|
+
principal,
|
|
13383
|
+
allowed: false,
|
|
13384
|
+
reason: verdict.reason,
|
|
13385
|
+
analysis,
|
|
13386
|
+
durationMs: null,
|
|
13387
|
+
rowCount: null
|
|
13388
|
+
});
|
|
13389
|
+
render({
|
|
13390
|
+
error: verdict.reason,
|
|
13391
|
+
columns: [],
|
|
13392
|
+
rows: [],
|
|
13393
|
+
rowCount: null,
|
|
13394
|
+
truncated: false,
|
|
13395
|
+
durationMs: null
|
|
13396
|
+
});
|
|
13397
|
+
return;
|
|
13398
|
+
}
|
|
13399
|
+
const started = Date.now();
|
|
13400
|
+
let rows;
|
|
13401
|
+
try {
|
|
13402
|
+
rows = console_.run === void 0 ? await state.dbSession.raw(sql5).all() : await console_.run(sql5, state.dbSession);
|
|
13403
|
+
} catch (error) {
|
|
13404
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13405
|
+
await audit(console_.onAudit, {
|
|
13406
|
+
sql: sql5,
|
|
13407
|
+
principal,
|
|
13408
|
+
allowed: true,
|
|
13409
|
+
reason: message,
|
|
13410
|
+
analysis,
|
|
13411
|
+
durationMs: Date.now() - started,
|
|
13412
|
+
rowCount: null
|
|
13413
|
+
});
|
|
13414
|
+
render({
|
|
13415
|
+
error: message,
|
|
13416
|
+
columns: [],
|
|
13417
|
+
rows: [],
|
|
13418
|
+
rowCount: null,
|
|
13419
|
+
truncated: false,
|
|
13420
|
+
durationMs: null
|
|
13421
|
+
});
|
|
13422
|
+
return;
|
|
13423
|
+
}
|
|
13424
|
+
const durationMs = Date.now() - started;
|
|
13425
|
+
await audit(console_.onAudit, {
|
|
13426
|
+
sql: sql5,
|
|
13427
|
+
principal,
|
|
13428
|
+
allowed: true,
|
|
13429
|
+
reason: null,
|
|
13430
|
+
analysis,
|
|
13431
|
+
durationMs,
|
|
13432
|
+
rowCount: rows.length
|
|
13433
|
+
});
|
|
13434
|
+
const window = rows.slice(0, maxRows);
|
|
13435
|
+
const columns = window.length === 0 ? [] : Object.keys(window[0] ?? {});
|
|
13436
|
+
render({
|
|
13437
|
+
error: null,
|
|
13438
|
+
columns,
|
|
13439
|
+
rows: window.map(
|
|
13440
|
+
(row) => columns.map((column6) => formatCellValue(row[column6]))
|
|
13441
|
+
),
|
|
13442
|
+
rowCount: rows.length,
|
|
13443
|
+
truncated: rows.length > window.length,
|
|
13444
|
+
durationMs
|
|
13445
|
+
});
|
|
13446
|
+
})
|
|
13447
|
+
);
|
|
13448
|
+
}
|
|
12925
13449
|
router.get(
|
|
12926
13450
|
`${prefix}/m/:slug`,
|
|
12927
13451
|
guarded(async (req, res) => {
|
|
@@ -13639,6 +14163,15 @@ function makeAdminRouter(site, options) {
|
|
|
13639
14163
|
const row = await admin.repository(dbSession).first({ [admin.identityField]: identity });
|
|
13640
14164
|
return row ?? null;
|
|
13641
14165
|
}
|
|
14166
|
+
async function readLogs(req, dir) {
|
|
14167
|
+
const requested = queryString(req.query.source);
|
|
14168
|
+
const source = LOG_SOURCES.includes(requested) ? requested : "all";
|
|
14169
|
+
const search = queryString(req.query.q);
|
|
14170
|
+
const raw = await readLogEntries(dir, source);
|
|
14171
|
+
const entries = filterLogEntries(raw.map(toLogEntry), search);
|
|
14172
|
+
entries.reverse();
|
|
14173
|
+
return { entries, source, search };
|
|
14174
|
+
}
|
|
13642
14175
|
function renderImport(req, state, admin, error, created, rowErrors) {
|
|
13643
14176
|
return renderImportPage(context(req, state.session, state.visible), {
|
|
13644
14177
|
title: admin.verboseNamePlural(),
|
|
@@ -13927,6 +14460,16 @@ function inlineFieldNames(childAdmin, inline) {
|
|
|
13927
14460
|
function inlineFields(childAdmin, names, key, values, errors) {
|
|
13928
14461
|
return buildFormFields(childAdmin, { values, errors }).filter((field) => names.includes(field.name)).map((field) => ({ ...field, name: `row.${key}.${field.name}` }));
|
|
13929
14462
|
}
|
|
14463
|
+
async function audit(hook, entry) {
|
|
14464
|
+
if (hook === void 0) return;
|
|
14465
|
+
try {
|
|
14466
|
+
await hook(entry);
|
|
14467
|
+
} catch (error) {
|
|
14468
|
+
logger2.error("Admin SQL audit hook failed", {
|
|
14469
|
+
error: error instanceof Error ? error.message : String(error)
|
|
14470
|
+
});
|
|
14471
|
+
}
|
|
14472
|
+
}
|
|
13930
14473
|
function flagAllows(admin, action) {
|
|
13931
14474
|
if (action === AdminPermission.CREATE) return admin.canCreate;
|
|
13932
14475
|
if (action === AdminPermission.EDIT) return admin.canEdit;
|
|
@@ -16013,59 +16556,6 @@ function makeToolSpecRouter(spec, options = {}) {
|
|
|
16013
16556
|
});
|
|
16014
16557
|
return router;
|
|
16015
16558
|
}
|
|
16016
|
-
function filesFor(dir, source) {
|
|
16017
|
-
if (source === "500") return [path.join(dir, HTTP_500_LOG_FILE)];
|
|
16018
|
-
if (source === "all") return Object.values(LEVEL_LOG_FILES).map((f) => path.join(dir, f));
|
|
16019
|
-
return [path.join(dir, LEVEL_LOG_FILES[source])];
|
|
16020
|
-
}
|
|
16021
|
-
async function readEntries(files) {
|
|
16022
|
-
const entries = [];
|
|
16023
|
-
for (const file of files) {
|
|
16024
|
-
let content;
|
|
16025
|
-
try {
|
|
16026
|
-
content = await promises.readFile(file, "utf8");
|
|
16027
|
-
} catch {
|
|
16028
|
-
continue;
|
|
16029
|
-
}
|
|
16030
|
-
for (const line of content.split("\n")) {
|
|
16031
|
-
if (!line.trim()) continue;
|
|
16032
|
-
try {
|
|
16033
|
-
entries.push(JSON.parse(line));
|
|
16034
|
-
} catch {
|
|
16035
|
-
}
|
|
16036
|
-
}
|
|
16037
|
-
}
|
|
16038
|
-
return entries;
|
|
16039
|
-
}
|
|
16040
|
-
function makeLogsRouter(options) {
|
|
16041
|
-
const router = express3.Router();
|
|
16042
|
-
const path = options.path ?? "/logs";
|
|
16043
|
-
const guards = options.guards ?? [];
|
|
16044
|
-
const handler = (req, res, next) => {
|
|
16045
|
-
const source = req.query.source ?? "all";
|
|
16046
|
-
const validSources = ["all", "debug", "info", "warning", "error", "500"];
|
|
16047
|
-
if (!validSources.includes(source)) {
|
|
16048
|
-
res.status(422).json({ detail: "Invalid log source", code: "VALIDATION_ERROR", details: {} });
|
|
16049
|
-
return;
|
|
16050
|
-
}
|
|
16051
|
-
const page2 = Math.max(1, Number(req.query.page) || 1);
|
|
16052
|
-
const pageSize = Math.min(500, Math.max(1, Number(req.query.pageSize) || 50));
|
|
16053
|
-
readEntries(filesFor(options.dir, source)).then((entries) => {
|
|
16054
|
-
entries.reverse();
|
|
16055
|
-
const total = entries.length;
|
|
16056
|
-
const start = (page2 - 1) * pageSize;
|
|
16057
|
-
res.json({
|
|
16058
|
-
items: entries.slice(start, start + pageSize),
|
|
16059
|
-
total,
|
|
16060
|
-
page: page2,
|
|
16061
|
-
pageSize,
|
|
16062
|
-
pages: Math.ceil(total / pageSize)
|
|
16063
|
-
});
|
|
16064
|
-
}).catch(next);
|
|
16065
|
-
};
|
|
16066
|
-
router.get(path, ...guards, handler);
|
|
16067
|
-
return router;
|
|
16068
|
-
}
|
|
16069
16559
|
function createTestDatabase(models) {
|
|
16070
16560
|
const driver = tempestDbJs.NodeSqliteDriver.open(":memory:");
|
|
16071
16561
|
for (const model of models) {
|
|
@@ -16098,7 +16588,7 @@ async function withTestDatabase(models, fn) {
|
|
|
16098
16588
|
}
|
|
16099
16589
|
|
|
16100
16590
|
// src/version.ts
|
|
16101
|
-
var VERSION = "0.
|
|
16591
|
+
var VERSION = "0.29.0";
|
|
16102
16592
|
|
|
16103
16593
|
Object.defineProperty(exports, "OpenAPIRegistry", {
|
|
16104
16594
|
enumerable: true,
|
|
@@ -16335,6 +16825,7 @@ exports.S3UploadStorage = S3UploadStorage;
|
|
|
16335
16825
|
exports.SSEBroker = SSEBroker;
|
|
16336
16826
|
exports.ServerSentEvent = ServerSentEvent;
|
|
16337
16827
|
exports.SessionService = SessionService;
|
|
16828
|
+
exports.SqlCapability = SqlCapability;
|
|
16338
16829
|
exports.TOTPHelper = TOTPHelper;
|
|
16339
16830
|
exports.TaskManager = TaskManager;
|
|
16340
16831
|
exports.TelegramProvider = TelegramProvider;
|
|
@@ -16361,6 +16852,7 @@ exports.adminColumns = adminColumns;
|
|
|
16361
16852
|
exports.adminInline = adminInline;
|
|
16362
16853
|
exports.adminLens = adminLens;
|
|
16363
16854
|
exports.adminThemeCss = adminThemeCss;
|
|
16855
|
+
exports.analyzeSql = analyzeSql;
|
|
16364
16856
|
exports.attachWebSocketHub = attachWebSocketHub;
|
|
16365
16857
|
exports.authResponseSchema = authResponseSchema;
|
|
16366
16858
|
exports.authSettingsShape = authSettingsShape;
|
|
@@ -16377,6 +16869,7 @@ exports.buildPaginationLinkHeader = buildPaginationLinkHeader;
|
|
|
16377
16869
|
exports.cached = cached3;
|
|
16378
16870
|
exports.centsField = centsField;
|
|
16379
16871
|
exports.cepField = cepField;
|
|
16872
|
+
exports.checkSqlPolicy = checkSqlPolicy;
|
|
16380
16873
|
exports.citiesByUf = citiesByUf;
|
|
16381
16874
|
exports.cnpjField = cnpjField;
|
|
16382
16875
|
exports.coerceFlag = coerceFlag;
|
|
@@ -16405,6 +16898,7 @@ exports.envBoolean = looseBoolean;
|
|
|
16405
16898
|
exports.envList = envList;
|
|
16406
16899
|
exports.escapeHtml = escapeHtml;
|
|
16407
16900
|
exports.filterForColumn = filterForColumn;
|
|
16901
|
+
exports.filterLogEntries = filterLogEntries;
|
|
16408
16902
|
exports.foreignKeyFields = foreignKeyFields;
|
|
16409
16903
|
exports.foreignKeyLabel = foreignKeyLabel;
|
|
16410
16904
|
exports.foreignKeyTable = foreignKeyTable;
|
|
@@ -16444,6 +16938,7 @@ exports.keyByJwtSubject = keyByJwtSubject;
|
|
|
16444
16938
|
exports.latitudeField = latitudeField;
|
|
16445
16939
|
exports.listStates = listStates;
|
|
16446
16940
|
exports.loadSettings = loadSettings;
|
|
16941
|
+
exports.loadSqlParser = loadSqlParser;
|
|
16447
16942
|
exports.logEntrySchema = logEntrySchema;
|
|
16448
16943
|
exports.logSettingsShape = logSettingsShape;
|
|
16449
16944
|
exports.loginSchema = loginSchema;
|
|
@@ -16504,6 +16999,7 @@ exports.rabbitmqSettingsShape = rabbitmqSettingsShape;
|
|
|
16504
16999
|
exports.rateLimitMiddleware = rateLimitMiddleware;
|
|
16505
17000
|
exports.ratingField = ratingField;
|
|
16506
17001
|
exports.ratioField = ratioField;
|
|
17002
|
+
exports.readLogEntries = readLogEntries;
|
|
16507
17003
|
exports.redisSettingsShape = redisSettingsShape;
|
|
16508
17004
|
exports.refreshSchema = refreshSchema;
|
|
16509
17005
|
exports.registerExceptionHandlers = registerExceptionHandlers;
|
|
@@ -16514,9 +17010,13 @@ exports.renderFormPage = renderFormPage;
|
|
|
16514
17010
|
exports.renderImportPage = renderImportPage;
|
|
16515
17011
|
exports.renderLayout = renderLayout;
|
|
16516
17012
|
exports.renderListPage = renderListPage;
|
|
17013
|
+
exports.renderLogEntriesJson = renderLogEntriesJson;
|
|
17014
|
+
exports.renderLogEntriesMarkdown = renderLogEntriesMarkdown;
|
|
16517
17015
|
exports.renderLoginPage = renderLoginPage;
|
|
17016
|
+
exports.renderLogsPage = renderLogsPage;
|
|
16518
17017
|
exports.renderMfaPage = renderMfaPage;
|
|
16519
17018
|
exports.renderPasswordResetFormPage = renderPasswordResetFormPage;
|
|
17019
|
+
exports.renderSqlPage = renderSqlPage;
|
|
16520
17020
|
exports.requestIdMiddleware = requestIdMiddleware;
|
|
16521
17021
|
exports.requestTracingMiddleware = requestTracingMiddleware;
|
|
16522
17022
|
exports.requireRoles = requireRoles;
|
|
@@ -16540,6 +17040,7 @@ exports.syncFilterSchema = syncFilterSchema;
|
|
|
16540
17040
|
exports.syncPaginationSchema = syncPaginationSchema;
|
|
16541
17041
|
exports.tableNameFor = tableNameFor;
|
|
16542
17042
|
exports.toDict = toDict;
|
|
17043
|
+
exports.toLogEntry = toLogEntry;
|
|
16543
17044
|
exports.toUtc = toUtc;
|
|
16544
17045
|
exports.tokenFromUrl = tokenFromUrl;
|
|
16545
17046
|
exports.tokenPairSchema = tokenPairSchema;
|