@getstrata/core 2.2.16 → 2.2.17
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/CHANGELOG.md +4 -0
- package/dist/core/database/migrations/runner.d.ts +1 -0
- package/dist/core/database/migrations/types.d.ts +6 -2
- package/dist/entries/auth/oauth/samlProvider.js +2 -2
- package/dist/entries/auth/saml/samlServiceProvider.js +2 -2
- package/dist/entries/database/errors.js +2 -2
- package/dist/entries/database/migrations.js +30 -2
- package/dist/entries/http/memoryThrottleMiddleware.js +14 -1
- package/dist/entries/http/response.js +16 -3
- package/dist/entries/http/scimThrottleMiddleware.js +14 -1
- package/dist/entries/http/throttleMiddleware.js +14 -1
- package/dist/index.js +31 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @getstrata/core changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.17
|
|
4
|
+
|
|
5
|
+
Add explicit development-only migration reset context while preserving guarded rollback and encryption archival; align generated fresh entries and reject production/staging or unsupported reset arguments before provisioning. Replace CSP-blocked error-page JavaScript navigation with safe same-origin links, and correct ambiguous Postgres/SQLite foreign-key messages without changing their HTTP status.
|
|
6
|
+
|
|
3
7
|
## 2.2.16
|
|
4
8
|
|
|
5
9
|
Allow managed outbox workers to reuse a configured instance, preserving application tenant resolution, listener registration and delivery settings. Keep generated listeners-only defaults compatible, reject ambiguous worker sources, and cover configured SQL delivery, cancellation, cleanup and TypeScript contracts.
|
|
@@ -20,6 +20,7 @@ declare function rollbackDatabase(db: MigrationDatabase, migrations: Migration[]
|
|
|
20
20
|
declare function freshDatabase(db: MigrationDatabase, migrations: Migration[], options?: {
|
|
21
21
|
advisoryLock?: boolean;
|
|
22
22
|
onMigration?: (name: string) => void;
|
|
23
|
+
destructive?: boolean;
|
|
23
24
|
}): Promise<void>;
|
|
24
25
|
export { withMigrationLock } from "./advisoryLock.ts";
|
|
25
26
|
export { ensureMigrationsTable, freshDatabase, getAppliedMigrations, getMigrationStatus, loadMigrationsFromDirectory, migrateDatabase, rollbackDatabase, runPendingMigrations, };
|
|
@@ -2,14 +2,18 @@ interface MigrationDatabase {
|
|
|
2
2
|
unsafe<T = unknown>(query: string, params?: readonly unknown[]): Promise<T[]>;
|
|
3
3
|
schema?: import("../schema/schema.ts").SchemaBuilder;
|
|
4
4
|
}
|
|
5
|
+
/** Explicit destructive reset is distinct from an ordinary rollback. */
|
|
6
|
+
interface MigrationDownContext {
|
|
7
|
+
operation: "fresh";
|
|
8
|
+
}
|
|
5
9
|
interface Migration {
|
|
6
10
|
name: string;
|
|
7
11
|
up(db: MigrationDatabase): Promise<void>;
|
|
8
|
-
down(db: MigrationDatabase): Promise<void>;
|
|
12
|
+
down(db: MigrationDatabase, context?: MigrationDownContext): Promise<void>;
|
|
9
13
|
}
|
|
10
14
|
type MigrationStatus = {
|
|
11
15
|
name: string;
|
|
12
16
|
status: "up" | "pending";
|
|
13
17
|
batch: number | null;
|
|
14
18
|
};
|
|
15
|
-
export type { Migration, MigrationDatabase, MigrationStatus };
|
|
19
|
+
export type { Migration, MigrationDatabase, MigrationDownContext, MigrationStatus };
|
|
@@ -362,7 +362,7 @@ function mapSqliteError(error) {
|
|
|
362
362
|
return new ConflictError("A record with these values already exists.");
|
|
363
363
|
}
|
|
364
364
|
if (code === "SQLITE_CONSTRAINT_FOREIGNKEY") {
|
|
365
|
-
return new UnprocessableEntityError("
|
|
365
|
+
return new UnprocessableEntityError("Operation conflicts with related records.");
|
|
366
366
|
}
|
|
367
367
|
if (code === "SQLITE_CONSTRAINT_NOTNULL") {
|
|
368
368
|
return new BadRequestError("Required field is missing.");
|
|
@@ -407,7 +407,7 @@ function mapDatabaseError(error) {
|
|
|
407
407
|
if (error.detail) {
|
|
408
408
|
console.error("[database] foreign-key violation", error.detail);
|
|
409
409
|
}
|
|
410
|
-
return new UnprocessableEntityError("
|
|
410
|
+
return new UnprocessableEntityError("Operation conflicts with related records.");
|
|
411
411
|
case "23502":
|
|
412
412
|
if (error.detail) {
|
|
413
413
|
console.error("[database] not-null violation", error.detail);
|
|
@@ -362,7 +362,7 @@ function mapSqliteError(error) {
|
|
|
362
362
|
return new ConflictError("A record with these values already exists.");
|
|
363
363
|
}
|
|
364
364
|
if (code === "SQLITE_CONSTRAINT_FOREIGNKEY") {
|
|
365
|
-
return new UnprocessableEntityError("
|
|
365
|
+
return new UnprocessableEntityError("Operation conflicts with related records.");
|
|
366
366
|
}
|
|
367
367
|
if (code === "SQLITE_CONSTRAINT_NOTNULL") {
|
|
368
368
|
return new BadRequestError("Required field is missing.");
|
|
@@ -407,7 +407,7 @@ function mapDatabaseError(error) {
|
|
|
407
407
|
if (error.detail) {
|
|
408
408
|
console.error("[database] foreign-key violation", error.detail);
|
|
409
409
|
}
|
|
410
|
-
return new UnprocessableEntityError("
|
|
410
|
+
return new UnprocessableEntityError("Operation conflicts with related records.");
|
|
411
411
|
case "23502":
|
|
412
412
|
if (error.detail) {
|
|
413
413
|
console.error("[database] not-null violation", error.detail);
|
|
@@ -38,7 +38,7 @@ function mapSqliteError(error) {
|
|
|
38
38
|
return new ConflictError("A record with these values already exists.");
|
|
39
39
|
}
|
|
40
40
|
if (code === "SQLITE_CONSTRAINT_FOREIGNKEY") {
|
|
41
|
-
return new UnprocessableEntityError("
|
|
41
|
+
return new UnprocessableEntityError("Operation conflicts with related records.");
|
|
42
42
|
}
|
|
43
43
|
if (code === "SQLITE_CONSTRAINT_NOTNULL") {
|
|
44
44
|
return new BadRequestError("Required field is missing.");
|
|
@@ -83,7 +83,7 @@ function mapDatabaseError(error) {
|
|
|
83
83
|
if (error.detail) {
|
|
84
84
|
console.error("[database] foreign-key violation", error.detail);
|
|
85
85
|
}
|
|
86
|
-
return new UnprocessableEntityError("
|
|
86
|
+
return new UnprocessableEntityError("Operation conflicts with related records.");
|
|
87
87
|
case "23502":
|
|
88
88
|
if (error.detail) {
|
|
89
89
|
console.error("[database] not-null violation", error.detail);
|
|
@@ -228,11 +228,33 @@ var init_dialect = __esm(() => {
|
|
|
228
228
|
});
|
|
229
229
|
|
|
230
230
|
// ../../src/core/database/migrations/runner.ts
|
|
231
|
-
init_dialect();
|
|
232
231
|
import { readdir } from "fs/promises";
|
|
233
232
|
import { join } from "path";
|
|
234
233
|
import { pathToFileURL } from "url";
|
|
235
234
|
|
|
235
|
+
// ../../src/core/runtime/appEnv.ts
|
|
236
|
+
var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
|
|
237
|
+
function normalizeEnvValue(value) {
|
|
238
|
+
return (value ?? "").trim().toLowerCase();
|
|
239
|
+
}
|
|
240
|
+
function isProductionEnv(env = process.env) {
|
|
241
|
+
const appEnv = normalizeEnvValue(env.APP_ENV);
|
|
242
|
+
const nodeEnv = normalizeEnvValue(env.NODE_ENV);
|
|
243
|
+
if (appEnv === "production" || nodeEnv === "production") {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
if (appEnv === "") {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
return !NON_PRODUCTION_APP_ENVS.has(appEnv);
|
|
250
|
+
}
|
|
251
|
+
function envFlagEnabled(value) {
|
|
252
|
+
return value === "true";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ../../src/core/database/migrations/runner.ts
|
|
256
|
+
init_dialect();
|
|
257
|
+
|
|
236
258
|
// ../../src/core/database/migrations/advisoryLock.ts
|
|
237
259
|
var MIGRATION_LOCK_KEY = 42424242;
|
|
238
260
|
async function withMigrationLock(db, callback, lockKey = MIGRATION_LOCK_KEY) {
|
|
@@ -346,13 +368,19 @@ async function rollbackDatabase(db, migrations, options = {}) {
|
|
|
346
368
|
return rolledBack;
|
|
347
369
|
}
|
|
348
370
|
async function freshDatabase(db, migrations, options = {}) {
|
|
371
|
+
if (options.destructive && isProductionEnv()) {
|
|
372
|
+
throw new Error("Destructive migration reset is only available in development/test environments.");
|
|
373
|
+
}
|
|
349
374
|
const runFresh = async () => {
|
|
350
375
|
const applied = await getAppliedMigrations(db);
|
|
351
376
|
const appliedNames = new Set(applied.map(({ name }) => name));
|
|
352
377
|
const appliedMigrations = migrations.filter(({ name }) => appliedNames.has(name));
|
|
353
378
|
for (const migration of [...appliedMigrations].reverse()) {
|
|
354
379
|
options.onMigration?.(migration.name);
|
|
355
|
-
|
|
380
|
+
if (options.destructive)
|
|
381
|
+
await migration.down(db, { operation: "fresh" });
|
|
382
|
+
else
|
|
383
|
+
await migration.down(db);
|
|
356
384
|
}
|
|
357
385
|
if (appliedMigrations.length > 0) {
|
|
358
386
|
await db.unsafe(`DELETE FROM ${MIGRATIONS_TABLE}`);
|
|
@@ -345,12 +345,25 @@ var configuredErrorView = {};
|
|
|
345
345
|
function escapeHtml(value) {
|
|
346
346
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
347
347
|
}
|
|
348
|
+
function errorReturnPath(request) {
|
|
349
|
+
if (!request)
|
|
350
|
+
return "/";
|
|
351
|
+
try {
|
|
352
|
+
const current = new URL(request.url);
|
|
353
|
+
const previous = new URL(request.headers.get("referer") ?? "");
|
|
354
|
+
if (previous.origin !== current.origin || (request.method === "GET" || request.method === "HEAD") && previous.pathname === current.pathname)
|
|
355
|
+
return "/";
|
|
356
|
+
return sanitizeInternalPath(previous.pathname, "/");
|
|
357
|
+
} catch {
|
|
358
|
+
return "/";
|
|
359
|
+
}
|
|
360
|
+
}
|
|
348
361
|
function renderKernelErrorChrome(input) {
|
|
349
362
|
const title = escapeHtml(input.title);
|
|
350
363
|
const message = escapeHtml(input.message);
|
|
351
364
|
const errorLines = Object.entries(input.errors ?? {}).flatMap(([field, messages]) => messages.map((item) => `${field}: ${item}`)).map((line) => `<li>${escapeHtml(line)}</li>`).join("");
|
|
352
365
|
const details = errorLines ? `<ul class="error-list">${errorLines}</ul>` : "";
|
|
353
|
-
const goBack = input.status === 422 ? `<p><a href="
|
|
366
|
+
const goBack = input.status === 422 ? `<p><a href="${escapeHtml(errorReturnPath(input.request ?? currentRequestMeta3().request))}">Go back</a></p>` : "";
|
|
354
367
|
return `<!doctype html>
|
|
355
368
|
<html lang="en">
|
|
356
369
|
<head>
|
|
@@ -162,7 +162,7 @@ function mapSqliteError(error) {
|
|
|
162
162
|
return new ConflictError("A record with these values already exists.");
|
|
163
163
|
}
|
|
164
164
|
if (code === "SQLITE_CONSTRAINT_FOREIGNKEY") {
|
|
165
|
-
return new UnprocessableEntityError("
|
|
165
|
+
return new UnprocessableEntityError("Operation conflicts with related records.");
|
|
166
166
|
}
|
|
167
167
|
if (code === "SQLITE_CONSTRAINT_NOTNULL") {
|
|
168
168
|
return new BadRequestError("Required field is missing.");
|
|
@@ -207,7 +207,7 @@ function mapDatabaseError(error) {
|
|
|
207
207
|
if (error.detail) {
|
|
208
208
|
console.error("[database] foreign-key violation", error.detail);
|
|
209
209
|
}
|
|
210
|
-
return new UnprocessableEntityError("
|
|
210
|
+
return new UnprocessableEntityError("Operation conflicts with related records.");
|
|
211
211
|
case "23502":
|
|
212
212
|
if (error.detail) {
|
|
213
213
|
console.error("[database] not-null violation", error.detail);
|
|
@@ -403,12 +403,25 @@ var configuredErrorView = {};
|
|
|
403
403
|
function escapeHtml(value) {
|
|
404
404
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
405
405
|
}
|
|
406
|
+
function errorReturnPath(request) {
|
|
407
|
+
if (!request)
|
|
408
|
+
return "/";
|
|
409
|
+
try {
|
|
410
|
+
const current = new URL(request.url);
|
|
411
|
+
const previous = new URL(request.headers.get("referer") ?? "");
|
|
412
|
+
if (previous.origin !== current.origin || (request.method === "GET" || request.method === "HEAD") && previous.pathname === current.pathname)
|
|
413
|
+
return "/";
|
|
414
|
+
return sanitizeInternalPath(previous.pathname, "/");
|
|
415
|
+
} catch {
|
|
416
|
+
return "/";
|
|
417
|
+
}
|
|
418
|
+
}
|
|
406
419
|
function renderKernelErrorChrome(input) {
|
|
407
420
|
const title = escapeHtml(input.title);
|
|
408
421
|
const message = escapeHtml(input.message);
|
|
409
422
|
const errorLines = Object.entries(input.errors ?? {}).flatMap(([field, messages]) => messages.map((item) => `${field}: ${item}`)).map((line) => `<li>${escapeHtml(line)}</li>`).join("");
|
|
410
423
|
const details = errorLines ? `<ul class="error-list">${errorLines}</ul>` : "";
|
|
411
|
-
const goBack = input.status === 422 ? `<p><a href="
|
|
424
|
+
const goBack = input.status === 422 ? `<p><a href="${escapeHtml(errorReturnPath(input.request ?? currentRequestMeta2().request))}">Go back</a></p>` : "";
|
|
412
425
|
return `<!doctype html>
|
|
413
426
|
<html lang="en">
|
|
414
427
|
<head>
|
|
@@ -399,12 +399,25 @@ var configuredErrorView = {};
|
|
|
399
399
|
function escapeHtml(value) {
|
|
400
400
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
401
401
|
}
|
|
402
|
+
function errorReturnPath(request) {
|
|
403
|
+
if (!request)
|
|
404
|
+
return "/";
|
|
405
|
+
try {
|
|
406
|
+
const current = new URL(request.url);
|
|
407
|
+
const previous = new URL(request.headers.get("referer") ?? "");
|
|
408
|
+
if (previous.origin !== current.origin || (request.method === "GET" || request.method === "HEAD") && previous.pathname === current.pathname)
|
|
409
|
+
return "/";
|
|
410
|
+
return sanitizeInternalPath(previous.pathname, "/");
|
|
411
|
+
} catch {
|
|
412
|
+
return "/";
|
|
413
|
+
}
|
|
414
|
+
}
|
|
402
415
|
function renderKernelErrorChrome(input) {
|
|
403
416
|
const title = escapeHtml(input.title);
|
|
404
417
|
const message = escapeHtml(input.message);
|
|
405
418
|
const errorLines = Object.entries(input.errors ?? {}).flatMap(([field, messages]) => messages.map((item) => `${field}: ${item}`)).map((line) => `<li>${escapeHtml(line)}</li>`).join("");
|
|
406
419
|
const details = errorLines ? `<ul class="error-list">${errorLines}</ul>` : "";
|
|
407
|
-
const goBack = input.status === 422 ? `<p><a href="
|
|
420
|
+
const goBack = input.status === 422 ? `<p><a href="${escapeHtml(errorReturnPath(input.request ?? currentRequestMeta3().request))}">Go back</a></p>` : "";
|
|
408
421
|
return `<!doctype html>
|
|
409
422
|
<html lang="en">
|
|
410
423
|
<head>
|
|
@@ -284,12 +284,25 @@ var configuredErrorView = {};
|
|
|
284
284
|
function escapeHtml(value) {
|
|
285
285
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
286
286
|
}
|
|
287
|
+
function errorReturnPath(request) {
|
|
288
|
+
if (!request)
|
|
289
|
+
return "/";
|
|
290
|
+
try {
|
|
291
|
+
const current = new URL(request.url);
|
|
292
|
+
const previous = new URL(request.headers.get("referer") ?? "");
|
|
293
|
+
if (previous.origin !== current.origin || (request.method === "GET" || request.method === "HEAD") && previous.pathname === current.pathname)
|
|
294
|
+
return "/";
|
|
295
|
+
return sanitizeInternalPath(previous.pathname, "/");
|
|
296
|
+
} catch {
|
|
297
|
+
return "/";
|
|
298
|
+
}
|
|
299
|
+
}
|
|
287
300
|
function renderKernelErrorChrome(input) {
|
|
288
301
|
const title = escapeHtml(input.title);
|
|
289
302
|
const message = escapeHtml(input.message);
|
|
290
303
|
const errorLines = Object.entries(input.errors ?? {}).flatMap(([field, messages]) => messages.map((item) => `${field}: ${item}`)).map((line) => `<li>${escapeHtml(line)}</li>`).join("");
|
|
291
304
|
const details = errorLines ? `<ul class="error-list">${errorLines}</ul>` : "";
|
|
292
|
-
const goBack = input.status === 422 ? `<p><a href="
|
|
305
|
+
const goBack = input.status === 422 ? `<p><a href="${escapeHtml(errorReturnPath(input.request ?? currentRequestMeta3().request))}">Go back</a></p>` : "";
|
|
293
306
|
return `<!doctype html>
|
|
294
307
|
<html lang="en">
|
|
295
308
|
<head>
|
package/dist/index.js
CHANGED
|
@@ -3215,7 +3215,7 @@ function mapSqliteError(error) {
|
|
|
3215
3215
|
return new ConflictError("A record with these values already exists.");
|
|
3216
3216
|
}
|
|
3217
3217
|
if (code === "SQLITE_CONSTRAINT_FOREIGNKEY") {
|
|
3218
|
-
return new UnprocessableEntityError("
|
|
3218
|
+
return new UnprocessableEntityError("Operation conflicts with related records.");
|
|
3219
3219
|
}
|
|
3220
3220
|
if (code === "SQLITE_CONSTRAINT_NOTNULL") {
|
|
3221
3221
|
return new BadRequestError("Required field is missing.");
|
|
@@ -3260,7 +3260,7 @@ function mapDatabaseError(error) {
|
|
|
3260
3260
|
if (error.detail) {
|
|
3261
3261
|
console.error("[database] foreign-key violation", error.detail);
|
|
3262
3262
|
}
|
|
3263
|
-
return new UnprocessableEntityError("
|
|
3263
|
+
return new UnprocessableEntityError("Operation conflicts with related records.");
|
|
3264
3264
|
case "23502":
|
|
3265
3265
|
if (error.detail) {
|
|
3266
3266
|
console.error("[database] not-null violation", error.detail);
|
|
@@ -7164,7 +7164,13 @@ function createFieldEncryptionRotationMigration(name, driver, options = {}) {
|
|
|
7164
7164
|
if (options.rls)
|
|
7165
7165
|
await db.unsafe(enableTenantRlsSql(checkpointTable.name));
|
|
7166
7166
|
},
|
|
7167
|
-
async down() {
|
|
7167
|
+
async down(db, context) {
|
|
7168
|
+
if (context?.operation === "fresh") {
|
|
7169
|
+
await Schema.run(db, driver, (schema) => {
|
|
7170
|
+
schema.drop(checkpointTable.name);
|
|
7171
|
+
});
|
|
7172
|
+
return;
|
|
7173
|
+
}
|
|
7168
7174
|
throw new Error("Encryption rotation checkpoints require explicit administrative archival.");
|
|
7169
7175
|
}
|
|
7170
7176
|
};
|
|
@@ -7520,10 +7526,10 @@ async function withMigrationLock(db, callback, lockKey = MIGRATION_LOCK_KEY) {
|
|
|
7520
7526
|
}
|
|
7521
7527
|
}
|
|
7522
7528
|
// ../../src/core/database/migrations/runner.ts
|
|
7523
|
-
init_dialect();
|
|
7524
7529
|
import { readdir } from "fs/promises";
|
|
7525
7530
|
import { join } from "path";
|
|
7526
7531
|
import { pathToFileURL } from "url";
|
|
7532
|
+
init_dialect();
|
|
7527
7533
|
var MIGRATIONS_TABLE = "framework_migrations";
|
|
7528
7534
|
function migrationsNameColumn() {
|
|
7529
7535
|
return currentSqlDialect().driver === "mysql" ? "VARCHAR(255) PRIMARY KEY" : "TEXT PRIMARY KEY";
|
|
@@ -7625,13 +7631,19 @@ async function rollbackDatabase(db, migrations, options = {}) {
|
|
|
7625
7631
|
return rolledBack;
|
|
7626
7632
|
}
|
|
7627
7633
|
async function freshDatabase(db, migrations, options = {}) {
|
|
7634
|
+
if (options.destructive && isProductionEnv()) {
|
|
7635
|
+
throw new Error("Destructive migration reset is only available in development/test environments.");
|
|
7636
|
+
}
|
|
7628
7637
|
const runFresh = async () => {
|
|
7629
7638
|
const applied = await getAppliedMigrations(db);
|
|
7630
7639
|
const appliedNames = new Set(applied.map(({ name }) => name));
|
|
7631
7640
|
const appliedMigrations = migrations.filter(({ name }) => appliedNames.has(name));
|
|
7632
7641
|
for (const migration of [...appliedMigrations].reverse()) {
|
|
7633
7642
|
options.onMigration?.(migration.name);
|
|
7634
|
-
|
|
7643
|
+
if (options.destructive)
|
|
7644
|
+
await migration.down(db, { operation: "fresh" });
|
|
7645
|
+
else
|
|
7646
|
+
await migration.down(db);
|
|
7635
7647
|
}
|
|
7636
7648
|
if (appliedMigrations.length > 0) {
|
|
7637
7649
|
await db.unsafe(`DELETE FROM ${MIGRATIONS_TABLE}`);
|
|
@@ -12339,12 +12351,25 @@ function configureWebErrorView(options) {
|
|
|
12339
12351
|
function escapeHtml(value) {
|
|
12340
12352
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
12341
12353
|
}
|
|
12354
|
+
function errorReturnPath(request) {
|
|
12355
|
+
if (!request)
|
|
12356
|
+
return "/";
|
|
12357
|
+
try {
|
|
12358
|
+
const current = new URL(request.url);
|
|
12359
|
+
const previous = new URL(request.headers.get("referer") ?? "");
|
|
12360
|
+
if (previous.origin !== current.origin || (request.method === "GET" || request.method === "HEAD") && previous.pathname === current.pathname)
|
|
12361
|
+
return "/";
|
|
12362
|
+
return sanitizeInternalPath(previous.pathname, "/");
|
|
12363
|
+
} catch {
|
|
12364
|
+
return "/";
|
|
12365
|
+
}
|
|
12366
|
+
}
|
|
12342
12367
|
function renderKernelErrorChrome(input) {
|
|
12343
12368
|
const title = escapeHtml(input.title);
|
|
12344
12369
|
const message = escapeHtml(input.message);
|
|
12345
12370
|
const errorLines = Object.entries(input.errors ?? {}).flatMap(([field, messages]) => messages.map((item) => `${field}: ${item}`)).map((line) => `<li>${escapeHtml(line)}</li>`).join("");
|
|
12346
12371
|
const details = errorLines ? `<ul class="error-list">${errorLines}</ul>` : "";
|
|
12347
|
-
const goBack = input.status === 422 ? `<p><a href="
|
|
12372
|
+
const goBack = input.status === 422 ? `<p><a href="${escapeHtml(errorReturnPath(input.request ?? currentRequestMeta().request))}">Go back</a></p>` : "";
|
|
12348
12373
|
return `<!doctype html>
|
|
12349
12374
|
<html lang="en">
|
|
12350
12375
|
<head>
|