From ec95a8e527ccc59da5f6b407c3a263c59c888ecb Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Mon, 17 Aug 2026 14:33:28 +0200 Subject: [PATCH] feat: add versioned migrations and kysely query layer --- backend/apps/api/src/migration.ts | 41 ++++- backend/jest-integration.json | 10 ++ backend/libs/database/src/index.ts | 2 + backend/libs/database/src/kysely.module.ts | 22 +++ backend/libs/database/src/schema.ts | 105 ++++++++++++ .../test/migration-runner.integration-spec.ts | 31 ++++ .../migrations/1700000001000_create-users.cjs | 16 ++ .../1700000002000_create-user-preferences.cjs | 24 +++ .../migrations/1700000003000_create-trips.cjs | 26 +++ .../1700000004000_create-trip-settings.cjs | 23 +++ .../1700000005000_create-trip-members.cjs | 25 +++ .../1700000006000_create-trip-invitations.cjs | 20 +++ .../1700000007000_create-travelers.cjs | 22 +++ ...08000_create-trip-preference-overrides.cjs | 30 ++++ backend/package.json | 5 +- docker/api.Dockerfile | 1 + pnpm-lock.yaml | 160 +++++++++++++----- 17 files changed, 513 insertions(+), 50 deletions(-) create mode 100644 backend/jest-integration.json create mode 100644 backend/libs/database/src/index.ts create mode 100644 backend/libs/database/src/kysely.module.ts create mode 100644 backend/libs/database/src/schema.ts create mode 100644 backend/libs/database/test/migration-runner.integration-spec.ts create mode 100644 backend/migrations/1700000001000_create-users.cjs create mode 100644 backend/migrations/1700000002000_create-user-preferences.cjs create mode 100644 backend/migrations/1700000003000_create-trips.cjs create mode 100644 backend/migrations/1700000004000_create-trip-settings.cjs create mode 100644 backend/migrations/1700000005000_create-trip-members.cjs create mode 100644 backend/migrations/1700000006000_create-trip-invitations.cjs create mode 100644 backend/migrations/1700000007000_create-travelers.cjs create mode 100644 backend/migrations/1700000008000_create-trip-preference-overrides.cjs diff --git a/backend/apps/api/src/migration.ts b/backend/apps/api/src/migration.ts index 6882009..1fab6b6 100644 --- a/backend/apps/api/src/migration.ts +++ b/backend/apps/api/src/migration.ts @@ -1,8 +1,41 @@ -export function runMigrations(): Promise { - console.log('No migrations configured in Phase 01'); - return Promise.resolve(); +import { join, sep } from 'node:path'; +import { runner } from 'node-pg-migrate'; +import { loadEnvironment } from '../../../libs/configuration/src'; + +/** + * Resolves the `backend/migrations` directory relative to this file's own + * location rather than `process.cwd()`, so migrations run identically from + * the compiled `dist/apps/api/src/migration.js` (production/deploy) and from + * the TypeScript source at `apps/api/src/migration.ts` (ts-jest integration + * tests), even though those two locations sit at different directory depths + * relative to the `backend/` package root. + */ +function resolveMigrationsDir(): string { + const segments = __dirname.split(sep); + const distIndex = segments.lastIndexOf('dist'); + const backendRoot = + distIndex !== -1 + ? segments.slice(0, distIndex).join(sep) + : segments.slice(0, -3).join(sep); + return join(backendRoot, 'migrations'); +} + +export async function runMigrations(): Promise { + const env = loadEnvironment(process.env); + await runner({ + databaseUrl: env.databaseUrl, + dir: resolveMigrationsDir(), + direction: 'up', + count: Infinity, + migrationsTable: 'pgmigrations', + checkOrder: true, + log: (message: string) => console.log(message), + }); } if (require.main === module) { - void runMigrations(); + void runMigrations().catch((error) => { + console.error(error); + process.exitCode = 1; + }); } diff --git a/backend/jest-integration.json b/backend/jest-integration.json new file mode 100644 index 0000000..8bdf286 --- /dev/null +++ b/backend/jest-integration.json @@ -0,0 +1,10 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".*\\.integration-spec\\.ts$", + "testPathIgnorePatterns": ["/node_modules/", "/dist/"], + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/backend/libs/database/src/index.ts b/backend/libs/database/src/index.ts new file mode 100644 index 0000000..890ec7a --- /dev/null +++ b/backend/libs/database/src/index.ts @@ -0,0 +1,2 @@ +export * from './schema'; +export * from './kysely.module'; diff --git a/backend/libs/database/src/kysely.module.ts b/backend/libs/database/src/kysely.module.ts new file mode 100644 index 0000000..2dcd115 --- /dev/null +++ b/backend/libs/database/src/kysely.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { Kysely, PostgresDialect } from 'kysely'; +import type { Pool } from 'pg'; +import { PostgresModule } from '../../infrastructure/src'; +import { POSTGRES_POOL } from '../../infrastructure/src'; +import type { Database } from './schema'; + +export const KYSELY_DB = Symbol('KYSELY_DB'); + +export const kyselyDbProvider = { + provide: KYSELY_DB, + inject: [POSTGRES_POOL], + useFactory: (pool: Pool) => + new Kysely({ dialect: new PostgresDialect({ pool }) }), +}; + +@Module({ + imports: [PostgresModule], + providers: [kyselyDbProvider], + exports: [kyselyDbProvider], +}) +export class DatabaseModule {} diff --git a/backend/libs/database/src/schema.ts b/backend/libs/database/src/schema.ts new file mode 100644 index 0000000..b080b35 --- /dev/null +++ b/backend/libs/database/src/schema.ts @@ -0,0 +1,105 @@ +import type { ColumnType, Generated } from 'kysely'; + +export interface UsersTable { + id: Generated; + external_subject_id: string; + display_name: string; + email: string; + created_at: ColumnType; + updated_at: ColumnType; +} + +export interface UserPreferencesTable { + user_id: string; + preferred_pace: string | null; + preferred_budget_level: string | null; + max_walking_distance_km: number | null; + preferred_start_time: string | null; + child_friendly_preferred: boolean; + interests: string[]; + notes: string | null; + created_at: ColumnType; + updated_at: ColumnType; +} + +export interface TripsTable { + id: Generated; + name: string; + description: string | null; + owner_id: string; + start_date: string | null; + end_date: string | null; + status: string; + planning_stage: string | null; + currency: string; + version: Generated; + created_at: ColumnType; + updated_at: ColumnType; +} + +export interface TripSettingsTable { + trip_id: string; + web_research_enabled: boolean; + periodic_agent_review_enabled: boolean; + notification_email_enabled: boolean; + notification_push_enabled: boolean; + default_research_depth: string | null; + default_planning_style: string | null; + created_at: ColumnType; + updated_at: ColumnType; +} + +export interface TripMembersTable { + id: Generated; + trip_id: string; + user_id: string; + role: string; + status: string; + joined_at: ColumnType | null; + created_at: ColumnType; + updated_at: ColumnType; +} + +export interface TripInvitationsTable { + id: Generated; + trip_id: string; + email: string; + invited_by_user_id: string; + token_hash: string; + expires_at: ColumnType; + accepted_at: ColumnType | null; + created_at: ColumnType; + updated_at: ColumnType; +} + +export interface TravelersTable { + id: Generated; + trip_id: string; + linked_user_id: string | null; + display_name: string; + traveler_type: string; + created_by_user_id: string; + created_at: ColumnType; + updated_at: ColumnType; +} + +export interface TripPreferenceOverridesTable { + id: Generated; + trip_id: string; + user_id: string | null; + traveler_id: string | null; + overrides: unknown; + created_at: ColumnType; + updated_at: ColumnType; +} + +export interface Database { + users: UsersTable; + user_preferences: UserPreferencesTable; + trips: TripsTable; + trip_settings: TripSettingsTable; + trip_members: TripMembersTable; + trip_invitations: TripInvitationsTable; + travelers: TravelersTable; + trip_preference_overrides: TripPreferenceOverridesTable; +} diff --git a/backend/libs/database/test/migration-runner.integration-spec.ts b/backend/libs/database/test/migration-runner.integration-spec.ts new file mode 100644 index 0000000..4f26330 --- /dev/null +++ b/backend/libs/database/test/migration-runner.integration-spec.ts @@ -0,0 +1,31 @@ +import { Pool } from 'pg'; +import { runMigrations } from '../../../apps/api/src/migration'; + +describe('runMigrations (integration)', () => { + it('applies all pending migrations idempotently against a real database', async () => { + await runMigrations(); + await runMigrations(); // must be safe to run twice + + const pool = new Pool({ connectionString: process.env.DATABASE_URL }); + try { + const tables = await pool.query<{ table_name: string }>( + `select table_name from information_schema.tables where table_schema = 'public'`, + ); + const names = tables.rows.map((r) => r.table_name); + for (const expected of [ + 'users', + 'user_preferences', + 'trips', + 'trip_settings', + 'trip_members', + 'trip_invitations', + 'travelers', + 'trip_preference_overrides', + ]) { + expect(names).toContain(expected); + } + } finally { + await pool.end(); + } + }); +}); diff --git a/backend/migrations/1700000001000_create-users.cjs b/backend/migrations/1700000001000_create-users.cjs new file mode 100644 index 0000000..9a44a40 --- /dev/null +++ b/backend/migrations/1700000001000_create-users.cjs @@ -0,0 +1,16 @@ +exports.up = (pgm) => { + pgm.createExtension('pgcrypto', { ifNotExists: true }); + pgm.createTable('users', { + id: { type: 'uuid', primaryKey: true, default: pgm.func('gen_random_uuid()') }, + external_subject_id: { type: 'text', notNull: true }, + display_name: { type: 'text', notNull: true }, + email: { type: 'text', notNull: true }, + created_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + updated_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + }); + pgm.addConstraint('users', 'users_external_subject_id_key', 'UNIQUE(external_subject_id)'); +}; + +exports.down = (pgm) => { + pgm.dropTable('users'); +}; diff --git a/backend/migrations/1700000002000_create-user-preferences.cjs b/backend/migrations/1700000002000_create-user-preferences.cjs new file mode 100644 index 0000000..059571f --- /dev/null +++ b/backend/migrations/1700000002000_create-user-preferences.cjs @@ -0,0 +1,24 @@ +exports.up = (pgm) => { + pgm.createTable('user_preferences', { + user_id: { + type: 'uuid', + primaryKey: true, + notNull: true, + references: 'users(id)', + onDelete: 'CASCADE', + }, + preferred_pace: { type: 'text' }, + preferred_budget_level: { type: 'text' }, + max_walking_distance_km: { type: 'numeric' }, + preferred_start_time: { type: 'text' }, + child_friendly_preferred: { type: 'boolean', notNull: true, default: false }, + interests: { type: 'text[]', notNull: true, default: '{}' }, + notes: { type: 'text' }, + created_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + updated_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + }); +}; + +exports.down = (pgm) => { + pgm.dropTable('user_preferences'); +}; diff --git a/backend/migrations/1700000003000_create-trips.cjs b/backend/migrations/1700000003000_create-trips.cjs new file mode 100644 index 0000000..b627c56 --- /dev/null +++ b/backend/migrations/1700000003000_create-trips.cjs @@ -0,0 +1,26 @@ +exports.up = (pgm) => { + pgm.createTable('trips', { + id: { type: 'uuid', primaryKey: true, default: pgm.func('gen_random_uuid()') }, + name: { type: 'text', notNull: true }, + description: { type: 'text' }, + owner_id: { type: 'uuid', notNull: true, references: 'users(id)' }, + start_date: { type: 'date' }, + end_date: { type: 'date' }, + status: { type: 'text', notNull: true, default: 'DRAFT' }, + planning_stage: { type: 'text' }, + currency: { type: 'text', notNull: true, default: 'EUR' }, + version: { type: 'integer', notNull: true, default: 1 }, + created_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + updated_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + }); + pgm.addConstraint( + 'trips', + 'trips_status_check', + "CHECK (status IN ('DRAFT','PLANNING','BOOKING','UPCOMING','ACTIVE','COMPLETED','ARCHIVED'))", + ); + pgm.createIndex('trips', 'owner_id'); +}; + +exports.down = (pgm) => { + pgm.dropTable('trips'); +}; diff --git a/backend/migrations/1700000004000_create-trip-settings.cjs b/backend/migrations/1700000004000_create-trip-settings.cjs new file mode 100644 index 0000000..999802c --- /dev/null +++ b/backend/migrations/1700000004000_create-trip-settings.cjs @@ -0,0 +1,23 @@ +exports.up = (pgm) => { + pgm.createTable('trip_settings', { + trip_id: { + type: 'uuid', + primaryKey: true, + notNull: true, + references: 'trips(id)', + onDelete: 'CASCADE', + }, + web_research_enabled: { type: 'boolean', notNull: true, default: false }, + periodic_agent_review_enabled: { type: 'boolean', notNull: true, default: false }, + notification_email_enabled: { type: 'boolean', notNull: true, default: true }, + notification_push_enabled: { type: 'boolean', notNull: true, default: true }, + default_research_depth: { type: 'text' }, + default_planning_style: { type: 'text' }, + created_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + updated_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + }); +}; + +exports.down = (pgm) => { + pgm.dropTable('trip_settings'); +}; diff --git a/backend/migrations/1700000005000_create-trip-members.cjs b/backend/migrations/1700000005000_create-trip-members.cjs new file mode 100644 index 0000000..f615ebe --- /dev/null +++ b/backend/migrations/1700000005000_create-trip-members.cjs @@ -0,0 +1,25 @@ +exports.up = (pgm) => { + pgm.createTable('trip_members', { + id: { type: 'uuid', primaryKey: true, default: pgm.func('gen_random_uuid()') }, + trip_id: { type: 'uuid', notNull: true, references: 'trips(id)', onDelete: 'CASCADE' }, + user_id: { type: 'uuid', notNull: true, references: 'users(id)' }, + role: { type: 'text', notNull: true }, + status: { type: 'text', notNull: true }, + joined_at: { type: 'timestamptz' }, + created_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + updated_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + }); + pgm.addConstraint('trip_members', 'trip_members_trip_user_key', 'UNIQUE(trip_id, user_id)'); + pgm.addConstraint('trip_members', 'trip_members_role_check', "CHECK (role IN ('OWNER','MEMBER'))"); + pgm.addConstraint( + 'trip_members', + 'trip_members_status_check', + "CHECK (status IN ('INVITED','ACTIVE','DECLINED'))", + ); + pgm.createIndex('trip_members', 'trip_id'); + pgm.createIndex('trip_members', 'user_id'); +}; + +exports.down = (pgm) => { + pgm.dropTable('trip_members'); +}; diff --git a/backend/migrations/1700000006000_create-trip-invitations.cjs b/backend/migrations/1700000006000_create-trip-invitations.cjs new file mode 100644 index 0000000..5dcbb78 --- /dev/null +++ b/backend/migrations/1700000006000_create-trip-invitations.cjs @@ -0,0 +1,20 @@ +exports.up = (pgm) => { + pgm.createTable('trip_invitations', { + id: { type: 'uuid', primaryKey: true, default: pgm.func('gen_random_uuid()') }, + trip_id: { type: 'uuid', notNull: true, references: 'trips(id)', onDelete: 'CASCADE' }, + email: { type: 'text', notNull: true }, + invited_by_user_id: { type: 'uuid', notNull: true, references: 'users(id)' }, + token_hash: { type: 'text', notNull: true }, + expires_at: { type: 'timestamptz', notNull: true }, + accepted_at: { type: 'timestamptz' }, + created_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + updated_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + }); + pgm.addConstraint('trip_invitations', 'trip_invitations_token_hash_key', 'UNIQUE(token_hash)'); + pgm.createIndex('trip_invitations', 'trip_id'); + pgm.sql('CREATE INDEX trip_invitations_lower_email_idx ON trip_invitations (lower(email))'); +}; + +exports.down = (pgm) => { + pgm.dropTable('trip_invitations'); +}; diff --git a/backend/migrations/1700000007000_create-travelers.cjs b/backend/migrations/1700000007000_create-travelers.cjs new file mode 100644 index 0000000..ba8d673 --- /dev/null +++ b/backend/migrations/1700000007000_create-travelers.cjs @@ -0,0 +1,22 @@ +exports.up = (pgm) => { + pgm.createTable('travelers', { + id: { type: 'uuid', primaryKey: true, default: pgm.func('gen_random_uuid()') }, + trip_id: { type: 'uuid', notNull: true, references: 'trips(id)', onDelete: 'CASCADE' }, + linked_user_id: { type: 'uuid', references: 'users(id)' }, + display_name: { type: 'text', notNull: true }, + traveler_type: { type: 'text', notNull: true }, + created_by_user_id: { type: 'uuid', notNull: true, references: 'users(id)' }, + created_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + updated_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + }); + pgm.addConstraint( + 'travelers', + 'travelers_traveler_type_check', + "CHECK (traveler_type IN ('ADULT','CHILD','INFANT'))", + ); + pgm.createIndex('travelers', 'trip_id'); +}; + +exports.down = (pgm) => { + pgm.dropTable('travelers'); +}; diff --git a/backend/migrations/1700000008000_create-trip-preference-overrides.cjs b/backend/migrations/1700000008000_create-trip-preference-overrides.cjs new file mode 100644 index 0000000..ea009cf --- /dev/null +++ b/backend/migrations/1700000008000_create-trip-preference-overrides.cjs @@ -0,0 +1,30 @@ +exports.up = (pgm) => { + pgm.createTable('trip_preference_overrides', { + id: { type: 'uuid', primaryKey: true, default: pgm.func('gen_random_uuid()') }, + trip_id: { type: 'uuid', notNull: true, references: 'trips(id)', onDelete: 'CASCADE' }, + user_id: { type: 'uuid', references: 'users(id)' }, + traveler_id: { type: 'uuid', references: 'travelers(id)', onDelete: 'CASCADE' }, + overrides: { type: 'jsonb', notNull: true, default: '{}' }, + created_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + updated_at: { type: 'timestamptz', notNull: true, default: pgm.func('now()') }, + }); + pgm.addConstraint( + 'trip_preference_overrides', + 'trip_preference_overrides_subject_check', + 'CHECK (num_nonnulls(user_id, traveler_id) = 1)', + ); + pgm.createIndex('trip_preference_overrides', ['trip_id', 'user_id'], { + unique: true, + where: 'user_id IS NOT NULL', + name: 'trip_pref_overrides_trip_user_uq', + }); + pgm.createIndex('trip_preference_overrides', ['trip_id', 'traveler_id'], { + unique: true, + where: 'traveler_id IS NOT NULL', + name: 'trip_pref_overrides_trip_traveler_uq', + }); +}; + +exports.down = (pgm) => { + pgm.dropTable('trip_preference_overrides'); +}; diff --git a/backend/package.json b/backend/package.json index 9fd959b..d077224 100644 --- a/backend/package.json +++ b/backend/package.json @@ -20,13 +20,16 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./apps/api/test/jest-e2e.json" + "test:e2e": "jest --config ./apps/api/test/jest-e2e.json", + "test:integration": "jest --config jest-integration.json --runInBand" }, "dependencies": { "@nestjs/common": "^11.0.1", "@nestjs/core": "^11.0.1", "@nestjs/platform-express": "^11.0.1", "ioredis": "^6.0.0", + "kysely": "^0.29.5", + "node-pg-migrate": "^7.9.1", "pg": "^8.23.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1" diff --git a/docker/api.Dockerfile b/docker/api.Dockerfile index ab39ea4..30e0184 100644 --- a/docker/api.Dockerfile +++ b/docker/api.Dockerfile @@ -15,6 +15,7 @@ COPY --from=build /app/node_modules ./node_modules COPY --from=build /app/backend/node_modules ./backend/node_modules COPY --from=build /app/backend/package.json ./backend/package.json COPY --from=build /app/backend/dist ./backend/dist +COPY --from=build /app/backend/migrations ./backend/migrations USER node EXPOSE 3000 CMD ["node", "backend/dist/apps/api/src/main.js"] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c9e1c5..d4c9ba7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,12 @@ importers: ioredis: specifier: ^6.0.0 version: 6.0.0 + kysely: + specifier: ^0.29.5 + version: 0.29.5 + node-pg-migrate: + specifier: ^7.9.1 + version: 7.9.1(@types/pg@8.21.0)(pg@8.23.0) pg: specifier: ^8.23.0 version: 8.23.0 @@ -68,13 +74,13 @@ importers: version: 7.2.1 eslint: specifier: ^9.18.0 - version: 9.39.5 + version: 9.39.5(jiti@2.7.0) eslint-config-prettier: specifier: ^10.0.1 - version: 10.1.8(eslint@9.39.5) + version: 10.1.8(eslint@9.39.5(jiti@2.7.0)) eslint-plugin-prettier: specifier: ^5.2.2 - version: 5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.6) + version: 5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.6) globals: specifier: ^17.0.0 version: 17.11.0 @@ -107,7 +113,7 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.20.0 - version: 8.67.0(eslint@9.39.5)(typescript@5.9.3) + version: 8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) frontend: dependencies: @@ -141,7 +147,7 @@ importers: devDependencies: '@angular/build': specifier: ^21.2.6 - version: 21.2.21(@angular/compiler-cli@21.2.20(@angular/compiler@21.2.20)(typescript@5.9.3))(@angular/compiler@21.2.20)(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(@angular/platform-browser@21.2.20(@angular/common@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2)))(@angular/service-worker@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.13.3)(chokidar@5.0.0)(postcss@8.5.26)(terser@5.50.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)))(yaml@2.9.0) + version: 21.2.21(@angular/compiler-cli@21.2.20(@angular/compiler@21.2.20)(typescript@5.9.3))(@angular/compiler@21.2.20)(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(@angular/platform-browser@21.2.20(@angular/common@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2)))(@angular/service-worker@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.13.3)(chokidar@5.0.0)(jiti@2.7.0)(postcss@8.5.26)(terser@5.50.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)))(yaml@2.9.0) '@angular/cli': specifier: ^21.2.6 version: 21.2.21(@types/node@24.13.3)(chokidar@5.0.0) @@ -159,7 +165,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.8 - version: 4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)) + version: 4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)) packages: @@ -1031,6 +1037,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/cliui@9.0.0': + resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} + engines: {node: '>=18'} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -3085,6 +3095,12 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} @@ -3307,6 +3323,10 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.2.3: + resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} + engines: {node: 20 || >=22} + jest-changed-files@30.4.1: resolution: {integrity: sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -3439,6 +3459,10 @@ packages: node-notifier: optional: true + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + jose@6.2.9: resolution: {integrity: sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA==} @@ -3507,6 +3531,10 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kysely@0.29.5: + resolution: {integrity: sha512-ooa+eSbBNPTo3MycPEuW5jdrxQdQwdtB3LC3h43FiXQbIry5tR0C5lDG7eealK0E4D7XjrnOP5DIUg/LyjRMYQ==} + engines: {node: '>=22.0.0'} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -3764,6 +3792,17 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + node-pg-migrate@7.9.1: + resolution: {integrity: sha512-6z4OSN27ye8aYdX9ZU7NN2PTI5pOp34hTr+22Ej12djIYECq++gT7LPLZVOQXEeVCBOZQLqf87kC3Y36G434OQ==} + engines: {node: '>=18.19.0'} + hasBin: true + peerDependencies: + '@types/pg': '>=6.0.0 <9.0.0' + pg: '>=4.3.0 <9.0.0' + peerDependenciesMeta: + '@types/pg': + optional: true + node-releases@2.0.53: resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} engines: {node: '>=18'} @@ -5115,7 +5154,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular/build@21.2.21(@angular/compiler-cli@21.2.20(@angular/compiler@21.2.20)(typescript@5.9.3))(@angular/compiler@21.2.20)(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(@angular/platform-browser@21.2.20(@angular/common@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2)))(@angular/service-worker@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.13.3)(chokidar@5.0.0)(postcss@8.5.26)(terser@5.50.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)))(yaml@2.9.0)': + '@angular/build@21.2.21(@angular/compiler-cli@21.2.20(@angular/compiler@21.2.20)(typescript@5.9.3))(@angular/compiler@21.2.20)(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(@angular/platform-browser@21.2.20(@angular/common@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2)))(@angular/service-worker@21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@24.13.3)(chokidar@5.0.0)(jiti@2.7.0)(postcss@8.5.26)(terser@5.50.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)))(yaml@2.9.0)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2102.21(chokidar@5.0.0) @@ -5125,7 +5164,7 @@ snapshots: '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 '@inquirer/confirm': 5.1.21(@types/node@24.13.3) - '@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)) + '@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)) beasties: 0.4.1 browserslist: 4.28.8 esbuild: 0.28.1 @@ -5146,7 +5185,7 @@ snapshots: tslib: 2.8.1 typescript: 5.9.3 undici: 7.29.0 - vite: 7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0) watchpack: 2.5.1 optionalDependencies: '@angular/core': 21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2) @@ -5154,7 +5193,7 @@ snapshots: '@angular/service-worker': 21.2.20(@angular/core@21.2.20(@angular/compiler@21.2.20)(rxjs@7.8.2))(rxjs@7.8.2) lmdb: 3.5.1 postcss: 8.5.26 - vitest: 4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)) + vitest: 4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -5606,9 +5645,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5)': + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0))': dependencies: - eslint: 9.39.5 + eslint: 9.39.5(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -5832,6 +5871,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/cliui@9.0.0': {} + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.3 @@ -6756,15 +6797,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.67.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/parser': 8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.67.0 - '@typescript-eslint/type-utils': 8.67.0(eslint@9.39.5)(typescript@5.9.3) - '@typescript-eslint/utils': 8.67.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.67.0 - eslint: 9.39.5 + eslint: 9.39.5(jiti@2.7.0) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -6772,14 +6813,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.67.0(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/parser@8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.67.0 '@typescript-eslint/types': 8.67.0 '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3 - eslint: 9.39.5 + eslint: 9.39.5(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -6802,13 +6843,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.67.0(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.67.0 '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.67.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.5 + eslint: 9.39.5(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -6831,13 +6872,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.67.0(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/utils@8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.67.0 '@typescript-eslint/types': 8.67.0 '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.9.3) - eslint: 9.39.5 + eslint: 9.39.5(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -6919,9 +6960,9 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0))': + '@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0))': dependencies: - vite: 7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0) '@vitest/expect@4.1.10': dependencies: @@ -6932,13 +6973,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.10(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0))': + '@vitest/mocker@4.1.10(vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0) '@vitest/pretty-format@4.1.10': dependencies: @@ -7668,19 +7709,19 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@9.39.5): + eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)): dependencies: - eslint: 9.39.5 + eslint: 9.39.5(jiti@2.7.0) - eslint-plugin-prettier@5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.6): + eslint-plugin-prettier@5.5.6(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.6): dependencies: - eslint: 9.39.5 + eslint: 9.39.5(jiti@2.7.0) prettier: 3.9.6 prettier-linter-helpers: 1.0.1 synckit: 0.11.13 optionalDependencies: '@types/eslint': 9.6.1 - eslint-config-prettier: 10.1.8(eslint@9.39.5) + eslint-config-prettier: 10.1.8(eslint@9.39.5(jiti@2.7.0)) eslint-scope@5.1.1: dependencies: @@ -7698,9 +7739,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@9.39.5: + eslint@9.39.5(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 @@ -7734,6 +7775,8 @@ snapshots: minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -8010,6 +8053,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.2.3 + minimatch: 10.2.6 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.2 + glob@13.0.6: dependencies: minimatch: 10.2.6 @@ -8226,6 +8278,10 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@4.2.3: + dependencies: + '@isaacs/cliui': 9.0.0 + jest-changed-files@30.4.1: dependencies: execa: 5.1.1 @@ -8544,6 +8600,9 @@ snapshots: - supports-color - ts-node + jiti@2.7.0: + optional: true + jose@6.2.9: {} js-tokens@4.0.0: {} @@ -8616,6 +8675,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kysely@0.29.5: {} + leven@3.1.0: {} levn@0.4.1: @@ -8887,6 +8948,14 @@ snapshots: node-int64@0.4.0: {} + node-pg-migrate@7.9.1(@types/pg@8.21.0)(pg@8.23.0): + dependencies: + glob: 11.0.3 + pg: 8.23.0 + yargs: 17.7.3 + optionalDependencies: + '@types/pg': 8.21.0 + node-releases@2.0.53: {} nopt@9.0.0: @@ -9776,13 +9845,13 @@ snapshots: typedarray@0.0.6: {} - typescript-eslint@8.67.0(eslint@9.39.5)(typescript@5.9.3): + typescript-eslint@8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3) - '@typescript-eslint/parser': 8.67.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.67.0(eslint@9.39.5)(typescript@5.9.3) - eslint: 9.39.5 + '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) + eslint: 9.39.5(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -9859,7 +9928,7 @@ snapshots: vary@1.1.2: {} - vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0): + vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0): dependencies: esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.4) @@ -9870,14 +9939,15 @@ snapshots: optionalDependencies: '@types/node': 24.13.3 fsevents: 2.3.3 + jiti: 2.7.0 sass: 1.97.3 terser: 5.50.0 yaml: 2.9.0 - vitest@4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)): + vitest@4.1.10(@types/node@24.13.3)(jsdom@28.1.0(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.10(vite@7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.10 '@vitest/runner': 4.1.10 '@vitest/snapshot': 4.1.10 @@ -9894,7 +9964,7 @@ snapshots: tinyexec: 1.3.0 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 7.3.6(@types/node@24.13.3)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@24.13.3)(jiti@2.7.0)(sass@1.97.3)(terser@5.50.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.13.3