@getstrata/core 2.2.16 → 2.2.18
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 +8 -0
- package/dist/core/database/migrations/rollbackGuard.d.ts +10 -0
- package/dist/core/database/migrations/runner.d.ts +2 -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 +38 -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 +42 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @getstrata/core changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.18
|
|
4
|
+
|
|
5
|
+
Add optional typed guardMigrationRollback for application-owned rollback history predicates; skip history reads only during explicit development fresh resets, propagate predicate failures, and reuse the contract for encryption-checkpoint archival protection. Document guard placement and unchanged reset/rollback safety; cover type, runner and real SQLite history regressions.
|
|
6
|
+
|
|
7
|
+
## 2.2.17
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
3
11
|
## 2.2.16
|
|
4
12
|
|
|
5
13
|
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.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MigrationDownContext } from "./types.ts";
|
|
2
|
+
interface MigrationRollbackGuard {
|
|
3
|
+
/** Application-owned history check. It is not evaluated during an explicit fresh reset. */
|
|
4
|
+
hasHistory(): boolean | Promise<boolean>;
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
/** Await before destructive DDL. Predicate failures propagate; ordinary rollback remains guarded. */
|
|
8
|
+
declare function guardMigrationRollback(context: MigrationDownContext | undefined, guard: MigrationRollbackGuard): Promise<void>;
|
|
9
|
+
export type { MigrationRollbackGuard };
|
|
10
|
+
export { guardMigrationRollback };
|
|
@@ -20,6 +20,8 @@ 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";
|
|
26
|
+
export { guardMigrationRollback, type MigrationRollbackGuard } from "./rollbackGuard.ts";
|
|
25
27
|
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) {
|
|
@@ -243,6 +265,13 @@ async function withMigrationLock(db, callback, lockKey = MIGRATION_LOCK_KEY) {
|
|
|
243
265
|
await db.unsafe("SELECT pg_advisory_unlock($1)", [lockKey]);
|
|
244
266
|
}
|
|
245
267
|
}
|
|
268
|
+
// ../../src/core/database/migrations/rollbackGuard.ts
|
|
269
|
+
async function guardMigrationRollback(context, guard) {
|
|
270
|
+
if (context?.operation === "fresh")
|
|
271
|
+
return;
|
|
272
|
+
if (await guard.hasHistory())
|
|
273
|
+
throw new Error(guard.message);
|
|
274
|
+
}
|
|
246
275
|
|
|
247
276
|
// ../../src/core/database/migrations/runner.ts
|
|
248
277
|
var MIGRATIONS_TABLE = "framework_migrations";
|
|
@@ -346,13 +375,19 @@ async function rollbackDatabase(db, migrations, options = {}) {
|
|
|
346
375
|
return rolledBack;
|
|
347
376
|
}
|
|
348
377
|
async function freshDatabase(db, migrations, options = {}) {
|
|
378
|
+
if (options.destructive && isProductionEnv()) {
|
|
379
|
+
throw new Error("Destructive migration reset is only available in development/test environments.");
|
|
380
|
+
}
|
|
349
381
|
const runFresh = async () => {
|
|
350
382
|
const applied = await getAppliedMigrations(db);
|
|
351
383
|
const appliedNames = new Set(applied.map(({ name }) => name));
|
|
352
384
|
const appliedMigrations = migrations.filter(({ name }) => appliedNames.has(name));
|
|
353
385
|
for (const migration of [...appliedMigrations].reverse()) {
|
|
354
386
|
options.onMigration?.(migration.name);
|
|
355
|
-
|
|
387
|
+
if (options.destructive)
|
|
388
|
+
await migration.down(db, { operation: "fresh" });
|
|
389
|
+
else
|
|
390
|
+
await migration.down(db);
|
|
356
391
|
}
|
|
357
392
|
if (appliedMigrations.length > 0) {
|
|
358
393
|
await db.unsafe(`DELETE FROM ${MIGRATIONS_TABLE}`);
|
|
@@ -370,6 +405,7 @@ export {
|
|
|
370
405
|
freshDatabase,
|
|
371
406
|
getAppliedMigrations,
|
|
372
407
|
getMigrationStatus,
|
|
408
|
+
guardMigrationRollback,
|
|
373
409
|
loadMigrationsFromDirectory,
|
|
374
410
|
migrateDatabase,
|
|
375
411
|
rollbackDatabase,
|
|
@@ -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);
|
|
@@ -6369,6 +6369,16 @@ var baseRepository_default = BaseRepository;
|
|
|
6369
6369
|
|
|
6370
6370
|
// ../../src/core/crypto/fieldEncryptionRotation.ts
|
|
6371
6371
|
init_boundConnection();
|
|
6372
|
+
|
|
6373
|
+
// ../../src/core/database/migrations/rollbackGuard.ts
|
|
6374
|
+
async function guardMigrationRollback(context, guard) {
|
|
6375
|
+
if (context?.operation === "fresh")
|
|
6376
|
+
return;
|
|
6377
|
+
if (await guard.hasHistory())
|
|
6378
|
+
throw new Error(guard.message);
|
|
6379
|
+
}
|
|
6380
|
+
|
|
6381
|
+
// ../../src/core/crypto/fieldEncryptionRotation.ts
|
|
6372
6382
|
init_repositoryConnection();
|
|
6373
6383
|
|
|
6374
6384
|
// ../../src/core/database/schema/columnDefinition.ts
|
|
@@ -7164,8 +7174,14 @@ function createFieldEncryptionRotationMigration(name, driver, options = {}) {
|
|
|
7164
7174
|
if (options.rls)
|
|
7165
7175
|
await db.unsafe(enableTenantRlsSql(checkpointTable.name));
|
|
7166
7176
|
},
|
|
7167
|
-
async down() {
|
|
7168
|
-
|
|
7177
|
+
async down(db, context) {
|
|
7178
|
+
await guardMigrationRollback(context, {
|
|
7179
|
+
hasHistory: () => true,
|
|
7180
|
+
message: "Encryption rotation checkpoints require explicit administrative archival."
|
|
7181
|
+
});
|
|
7182
|
+
await Schema.run(db, driver, (schema) => {
|
|
7183
|
+
schema.drop(checkpointTable.name);
|
|
7184
|
+
});
|
|
7169
7185
|
}
|
|
7170
7186
|
};
|
|
7171
7187
|
}
|
|
@@ -7520,10 +7536,10 @@ async function withMigrationLock(db, callback, lockKey = MIGRATION_LOCK_KEY) {
|
|
|
7520
7536
|
}
|
|
7521
7537
|
}
|
|
7522
7538
|
// ../../src/core/database/migrations/runner.ts
|
|
7523
|
-
init_dialect();
|
|
7524
7539
|
import { readdir } from "fs/promises";
|
|
7525
7540
|
import { join } from "path";
|
|
7526
7541
|
import { pathToFileURL } from "url";
|
|
7542
|
+
init_dialect();
|
|
7527
7543
|
var MIGRATIONS_TABLE = "framework_migrations";
|
|
7528
7544
|
function migrationsNameColumn() {
|
|
7529
7545
|
return currentSqlDialect().driver === "mysql" ? "VARCHAR(255) PRIMARY KEY" : "TEXT PRIMARY KEY";
|
|
@@ -7625,13 +7641,19 @@ async function rollbackDatabase(db, migrations, options = {}) {
|
|
|
7625
7641
|
return rolledBack;
|
|
7626
7642
|
}
|
|
7627
7643
|
async function freshDatabase(db, migrations, options = {}) {
|
|
7644
|
+
if (options.destructive && isProductionEnv()) {
|
|
7645
|
+
throw new Error("Destructive migration reset is only available in development/test environments.");
|
|
7646
|
+
}
|
|
7628
7647
|
const runFresh = async () => {
|
|
7629
7648
|
const applied = await getAppliedMigrations(db);
|
|
7630
7649
|
const appliedNames = new Set(applied.map(({ name }) => name));
|
|
7631
7650
|
const appliedMigrations = migrations.filter(({ name }) => appliedNames.has(name));
|
|
7632
7651
|
for (const migration of [...appliedMigrations].reverse()) {
|
|
7633
7652
|
options.onMigration?.(migration.name);
|
|
7634
|
-
|
|
7653
|
+
if (options.destructive)
|
|
7654
|
+
await migration.down(db, { operation: "fresh" });
|
|
7655
|
+
else
|
|
7656
|
+
await migration.down(db);
|
|
7635
7657
|
}
|
|
7636
7658
|
if (appliedMigrations.length > 0) {
|
|
7637
7659
|
await db.unsafe(`DELETE FROM ${MIGRATIONS_TABLE}`);
|
|
@@ -12339,12 +12361,25 @@ function configureWebErrorView(options) {
|
|
|
12339
12361
|
function escapeHtml(value) {
|
|
12340
12362
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
12341
12363
|
}
|
|
12364
|
+
function errorReturnPath(request) {
|
|
12365
|
+
if (!request)
|
|
12366
|
+
return "/";
|
|
12367
|
+
try {
|
|
12368
|
+
const current = new URL(request.url);
|
|
12369
|
+
const previous = new URL(request.headers.get("referer") ?? "");
|
|
12370
|
+
if (previous.origin !== current.origin || (request.method === "GET" || request.method === "HEAD") && previous.pathname === current.pathname)
|
|
12371
|
+
return "/";
|
|
12372
|
+
return sanitizeInternalPath(previous.pathname, "/");
|
|
12373
|
+
} catch {
|
|
12374
|
+
return "/";
|
|
12375
|
+
}
|
|
12376
|
+
}
|
|
12342
12377
|
function renderKernelErrorChrome(input) {
|
|
12343
12378
|
const title = escapeHtml(input.title);
|
|
12344
12379
|
const message = escapeHtml(input.message);
|
|
12345
12380
|
const errorLines = Object.entries(input.errors ?? {}).flatMap(([field, messages]) => messages.map((item) => `${field}: ${item}`)).map((line) => `<li>${escapeHtml(line)}</li>`).join("");
|
|
12346
12381
|
const details = errorLines ? `<ul class="error-list">${errorLines}</ul>` : "";
|
|
12347
|
-
const goBack = input.status === 422 ? `<p><a href="
|
|
12382
|
+
const goBack = input.status === 422 ? `<p><a href="${escapeHtml(errorReturnPath(input.request ?? currentRequestMeta().request))}">Go back</a></p>` : "";
|
|
12348
12383
|
return `<!doctype html>
|
|
12349
12384
|
<html lang="en">
|
|
12350
12385
|
<head>
|