diff --git a/app/server/db/schema.ts b/app/server/db/schema.ts index 9c34d357..94041fdc 100644 --- a/app/server/db/schema.ts +++ b/app/server/db/schema.ts @@ -10,6 +10,7 @@ import type { } from "~/schemas/restic"; import type { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes"; import type { NotificationType, notificationConfigSchema } from "~/schemas/notifications"; +import type { ShortId } from "~/server/utils/branded"; /** * Users Table @@ -179,7 +180,7 @@ export const volumesTable = sqliteTable( "volumes_table", { id: int().primaryKey({ autoIncrement: true }), - shortId: text("short_id").notNull().unique(), + shortId: text("short_id").$type().notNull().unique(), name: text().notNull(), type: text().$type().notNull(), status: text().$type().notNull().default("unmounted"), @@ -210,7 +211,7 @@ export type VolumeInsert = typeof volumesTable.$inferInsert; */ export const repositoriesTable = sqliteTable("repositories_table", { id: text().primaryKey(), - shortId: text("short_id").notNull().unique(), + shortId: text("short_id").$type().notNull().unique(), name: text().notNull(), type: text().$type().notNull(), config: text("config", { mode: "json" }).$type().notNull(), @@ -243,7 +244,7 @@ export type RepositoryInsert = typeof repositoriesTable.$inferInsert; */ export const backupSchedulesTable = sqliteTable("backup_schedules_table", { id: int().primaryKey({ autoIncrement: true }), - shortId: text("short_id").notNull().unique(), + shortId: text("short_id").$type().notNull().unique(), name: text().notNull(), volumeId: int("volume_id") .notNull() diff --git a/app/server/jobs/auto-remount.ts b/app/server/jobs/auto-remount.ts index 5098a786..308ab0af 100644 --- a/app/server/jobs/auto-remount.ts +++ b/app/server/jobs/auto-remount.ts @@ -16,7 +16,7 @@ export class VolumeAutoRemountJob extends Job { if (volume.autoRemount) { try { await withContext({ organizationId: volume.organizationId }, async () => { - await volumeService.mountVolume(volume.id); + await volumeService.mountVolume(volume.shortId); }); } catch (err) { logger.error(`Failed to auto-remount volume ${volume.name}:`, err); diff --git a/app/server/jobs/healthchecks.ts b/app/server/jobs/healthchecks.ts index 7491ca98..83d253c2 100644 --- a/app/server/jobs/healthchecks.ts +++ b/app/server/jobs/healthchecks.ts @@ -15,12 +15,16 @@ export class VolumeHealthCheckJob extends Job { }); for (const volume of volumes) { - await withContext({ organizationId: volume.organizationId }, async () => { - const { status } = await volumeService.checkHealth(volume.id); - if (status === "error" && volume.autoRemount) { - await volumeService.mountVolume(volume.id); - } - }); + try { + await withContext({ organizationId: volume.organizationId }, async () => { + const { status } = await volumeService.checkHealth(volume.shortId); + if (status === "error" && volume.autoRemount) { + await volumeService.mountVolume(volume.shortId); + } + }); + } catch (error) { + logger.error(`Health check failed for volume ${volume.name}:`, error); + } } return { done: true, timestamp: new Date() }; diff --git a/app/server/jobs/repository-healthchecks.ts b/app/server/jobs/repository-healthchecks.ts index bb0aa80e..8e3a3f9a 100644 --- a/app/server/jobs/repository-healthchecks.ts +++ b/app/server/jobs/repository-healthchecks.ts @@ -15,7 +15,7 @@ export class RepositoryHealthCheckJob extends Job { for (const repository of repositories) { try { await withContext({ organizationId: repository.organizationId }, async () => { - await repositoriesService.checkHealth(repository.id); + await repositoriesService.checkHealth(repository.shortId); }); } catch (error) { logger.error(`Health check failed for repository ${repository.name}:`, error); diff --git a/app/server/modules/backups/backups.controller.ts b/app/server/modules/backups/backups.controller.ts index 177cd3e3..43a531a9 100644 --- a/app/server/modules/backups/backups.controller.ts +++ b/app/server/modules/backups/backups.controller.ts @@ -44,6 +44,7 @@ import { notificationsService } from "../notifications/notifications.service"; import { requireAuth } from "../auth/auth.middleware"; import { backupsExecutionService } from "./backups.execution"; import { logger } from "~/server/utils/logger"; +import { asShortId } from "~/server/utils/branded"; export const backupScheduleController = new Hono() .use(requireAuth) @@ -53,13 +54,13 @@ export const backupScheduleController = new Hono() return c.json(schedules, 200); }) .get("/:shortId", getBackupScheduleDto, async (c) => { - const shortId = c.req.param("shortId"); + const shortId = asShortId(c.req.param("shortId")); const schedule = await backupsService.getScheduleByShortId(shortId); return c.json(schedule, 200); }) .get("/volume/:volumeShortId", getBackupScheduleForVolumeDto, async (c) => { - const volumeShortId = c.req.param("volumeShortId"); + const volumeShortId = asShortId(c.req.param("volumeShortId")); const schedule = await backupsService.getScheduleForVolume(volumeShortId); return c.json(schedule, 200); @@ -71,20 +72,20 @@ export const backupScheduleController = new Hono() return c.json(schedule, 201); }) .patch("/:shortId", updateBackupScheduleDto, validator("json", updateBackupScheduleBody), async (c) => { - const shortId = c.req.param("shortId"); + const shortId = asShortId(c.req.param("shortId")); const body = c.req.valid("json"); const schedule = await backupsService.updateSchedule(shortId, body); return c.json(schedule, 200); }) .delete("/:shortId", deleteBackupScheduleDto, async (c) => { - const shortId = c.req.param("shortId"); + const shortId = asShortId(c.req.param("shortId")); await backupsService.deleteSchedule(shortId); return c.json({ success: true }, 200); }) .post("/:shortId/run", runBackupNowDto, async (c) => { - const shortId = c.req.param("shortId"); + const shortId = asShortId(c.req.param("shortId")); const schedule = await backupsService.getScheduleByShortId(shortId); const result = await backupsExecutionService.validateBackupExecution(schedule.id, true); @@ -103,21 +104,21 @@ export const backupScheduleController = new Hono() return c.json({ success: true }, 200); }) .post("/:shortId/stop", stopBackupDto, async (c) => { - const shortId = c.req.param("shortId"); + const shortId = asShortId(c.req.param("shortId")); const schedule = await backupsService.getScheduleByShortId(shortId); await backupsExecutionService.stopBackup(schedule.id); return c.json({ success: true }, 200); }) .post("/:shortId/forget", runForgetDto, async (c) => { - const shortId = c.req.param("shortId"); + const shortId = asShortId(c.req.param("shortId")); const schedule = await backupsService.getScheduleByShortId(shortId); await backupsExecutionService.runForget(schedule.id); return c.json({ success: true }, 200); }) .get("/:shortId/notifications", getScheduleNotificationsDto, async (c) => { - const shortId = c.req.param("shortId"); + const shortId = asShortId(c.req.param("shortId")); const schedule = await backupsService.getScheduleByShortId(shortId); const assignments = await notificationsService.getScheduleNotifications(schedule.id); @@ -128,7 +129,7 @@ export const backupScheduleController = new Hono() updateScheduleNotificationsDto, validator("json", updateScheduleNotificationsBody), async (c) => { - const shortId = c.req.param("shortId"); + const shortId = asShortId(c.req.param("shortId")); const schedule = await backupsService.getScheduleByShortId(shortId); const body = c.req.valid("json"); const assignments = await notificationsService.updateScheduleNotifications(schedule.id, body.assignments); @@ -137,27 +138,27 @@ export const backupScheduleController = new Hono() }, ) .get("/:shortId/mirrors", getScheduleMirrorsDto, async (c) => { - const shortId = c.req.param("shortId"); + const shortId = asShortId(c.req.param("shortId")); const mirrors = await backupsService.getMirrors(shortId); return c.json(mirrors, 200); }) .put("/:shortId/mirrors", updateScheduleMirrorsDto, validator("json", updateScheduleMirrorsBody), async (c) => { - const shortId = c.req.param("shortId"); + const shortId = asShortId(c.req.param("shortId")); const body = c.req.valid("json"); const mirrors = await backupsService.updateMirrors(shortId, body); return c.json(mirrors, 200); }) .get("/:shortId/mirrors/compatibility", getMirrorCompatibilityDto, async (c) => { - const shortId = c.req.param("shortId"); + const shortId = asShortId(c.req.param("shortId")); const compatibility = await backupsService.getMirrorCompatibility(shortId); return c.json(compatibility, 200); }) .post("/reorder", reorderBackupSchedulesDto, validator("json", reorderBackupSchedulesBody), async (c) => { const body = c.req.valid("json"); - await backupsService.reorderSchedules(body.scheduleShortIds); + await backupsService.reorderSchedules(body.scheduleShortIds.map(asShortId)); return c.json({ success: true }, 200); }); diff --git a/app/server/modules/backups/backups.service.ts b/app/server/modules/backups/backups.service.ts index 2336bd9f..3dc15b35 100644 --- a/app/server/modules/backups/backups.service.ts +++ b/app/server/modules/backups/backups.service.ts @@ -9,6 +9,7 @@ import { checkMirrorCompatibility, getIncompatibleMirrorError } from "~/server/u import { generateShortId } from "~/server/utils/id"; import { getOrganizationId } from "~/server/core/request-context"; import { calculateNextRun } from "./backup.helpers"; +import { asShortId, type ShortId } from "~/server/utils/branded"; const listSchedules = async () => { const organizationId = getOrganizationId(); @@ -38,10 +39,10 @@ const getScheduleById = async (scheduleId: number) => { return schedule; }; -const getScheduleByShortId = async (shortId: string) => { +const getScheduleByShortId = async (shortId: ShortId) => { const organizationId = getOrganizationId(); const schedule = await db.query.backupSchedulesTable.findFirst({ - where: { AND: [{ shortId }, { organizationId }] }, + where: { AND: [{ shortId: { eq: shortId } }, { organizationId }] }, with: { volume: true, repository: true }, }); @@ -60,7 +61,10 @@ const getScheduleByIdOrShortId = async (idOrShortId: string | number) => { const organizationId = getOrganizationId(); const schedule = await db.query.backupSchedulesTable.findFirst({ where: { - AND: [{ OR: [{ id: Number(idOrShortId) }, { shortId: String(idOrShortId) }] }, { organizationId }], + AND: [ + { OR: [{ id: Number(idOrShortId) }, { shortId: { eq: asShortId(String(idOrShortId)) } }] }, + { organizationId }, + ], }, with: { volume: true, repository: true }, }); @@ -94,7 +98,10 @@ const createSchedule = async (data: CreateBackupScheduleBody) => { const volume = await db.query.volumesTable.findFirst({ where: { - AND: [{ OR: [{ id: Number(data.volumeId) }, { shortId: String(data.volumeId) }] }, { organizationId }], + AND: [ + { OR: [{ id: Number(data.volumeId) }, { shortId: { eq: asShortId(String(data.volumeId)) } }] }, + { organizationId }, + ], }, }); @@ -104,7 +111,7 @@ const createSchedule = async (data: CreateBackupScheduleBody) => { const repository = await db.query.repositoriesTable.findFirst({ where: { - AND: [{ OR: [{ id: data.repositoryId }, { shortId: data.repositoryId }] }, { organizationId }], + AND: [{ OR: [{ id: data.repositoryId }, { shortId: { eq: asShortId(data.repositoryId) } }] }, { organizationId }], }, }); @@ -145,7 +152,7 @@ const updateSchedule = async (scheduleIdOrShortId: number | string, data: Update const schedule = await db.query.backupSchedulesTable.findFirst({ where: { AND: [ - { OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] }, + { OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: { eq: asShortId(String(scheduleIdOrShortId)) } }] }, { organizationId }, ], }, @@ -173,7 +180,7 @@ const updateSchedule = async (scheduleIdOrShortId: number | string, data: Update const repository = await db.query.repositoriesTable.findFirst({ where: { - AND: [{ OR: [{ id: data.repositoryId }, { shortId: data.repositoryId }] }, { organizationId }], + AND: [{ OR: [{ id: data.repositoryId }, { shortId: { eq: asShortId(data.repositoryId) } }] }, { organizationId }], }, }); @@ -202,7 +209,7 @@ const deleteSchedule = async (scheduleIdOrShortId: number | string) => { const schedule = await db.query.backupSchedulesTable.findFirst({ where: { AND: [ - { OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] }, + { OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: { eq: asShortId(String(scheduleIdOrShortId)) } }] }, { organizationId }, ], }, @@ -221,7 +228,10 @@ const getScheduleForVolume = async (volumeIdOrShortId: number | string) => { const organizationId = getOrganizationId(); const volume = await db.query.volumesTable.findFirst({ where: { - AND: [{ OR: [{ id: Number(volumeIdOrShortId) }, { shortId: String(volumeIdOrShortId) }] }, { organizationId }], + AND: [ + { OR: [{ id: Number(volumeIdOrShortId) }, { shortId: { eq: asShortId(String(volumeIdOrShortId)) } }] }, + { organizationId }, + ], }, columns: { id: true }, }); @@ -249,7 +259,7 @@ const getMirrors = async (scheduleIdOrShortId: number | string) => { const schedule = await db.query.backupSchedulesTable.findFirst({ where: { AND: [ - { OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] }, + { OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: { eq: asShortId(String(scheduleIdOrShortId)) } }] }, { organizationId }, ], }, @@ -284,7 +294,7 @@ const updateMirrors = async (scheduleIdOrShortId: number | string, data: UpdateS const schedule = await db.query.backupSchedulesTable.findFirst({ where: { AND: [ - { OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] }, + { OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: { eq: asShortId(String(scheduleIdOrShortId)) } }] }, { organizationId }, ], }, @@ -299,7 +309,10 @@ const updateMirrors = async (scheduleIdOrShortId: number | string, data: UpdateS data.mirrors.map(async (mirror) => { const repo = await db.query.repositoriesTable.findFirst({ where: { - AND: [{ OR: [{ id: mirror.repositoryId }, { shortId: mirror.repositoryId }] }, { organizationId }], + AND: [ + { OR: [{ id: mirror.repositoryId }, { shortId: { eq: asShortId(mirror.repositoryId) } }] }, + { organizationId }, + ], }, }); @@ -360,7 +373,7 @@ const getMirrorCompatibility = async (scheduleIdOrShortId: number | string) => { const schedule = await db.query.backupSchedulesTable.findFirst({ where: { AND: [ - { OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] }, + { OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: { eq: asShortId(String(scheduleIdOrShortId)) } }] }, { organizationId }, ], }, @@ -381,7 +394,7 @@ const getMirrorCompatibility = async (scheduleIdOrShortId: number | string) => { return compatibility; }; -const reorderSchedules = async (scheduleShortIds: string[]) => { +const reorderSchedules = async (scheduleShortIds: ShortId[]) => { const organizationId = getOrganizationId(); const uniqueIds = new Set(scheduleShortIds); if (uniqueIds.size !== scheduleShortIds.length) { diff --git a/app/server/modules/lifecycle/startup.ts b/app/server/modules/lifecycle/startup.ts index eb788943..fad0ae17 100644 --- a/app/server/modules/lifecycle/startup.ts +++ b/app/server/modules/lifecycle/startup.ts @@ -20,7 +20,7 @@ const ensureLatestConfigurationSchema = async () => { for (const volume of volumes) { await withContext({ organizationId: volume.organizationId }, async () => { - await volumeService.updateVolume(volume.id, volume).catch((err) => { + await volumeService.updateVolume(volume.shortId, volume).catch((err) => { logger.error(`Failed to update volume ${volume.name}: ${err}`); }); }); @@ -30,7 +30,7 @@ const ensureLatestConfigurationSchema = async () => { for (const repo of repositories) { await withContext({ organizationId: repo.organizationId }, async () => { - await repositoriesService.updateRepository(repo.id, {}).catch((err) => { + await repositoriesService.updateRepository(repo.shortId, {}).catch((err) => { logger.error(`Failed to update repository ${repo.name}: ${err}`); }); }); @@ -77,7 +77,7 @@ export const startup = async () => { for (const volume of volumes) { await withContext({ organizationId: volume.organizationId }, async () => { - await volumeService.mountVolume(volume.id).catch((err) => { + await volumeService.mountVolume(volume.shortId).catch((err) => { logger.error(`Error auto-remounting volume ${volume.name} on startup: ${err.message}`); }); }); diff --git a/app/server/modules/repositories/repositories.controller.ts b/app/server/modules/repositories/repositories.controller.ts index c8911676..7c459884 100644 --- a/app/server/modules/repositories/repositories.controller.ts +++ b/app/server/modules/repositories/repositories.controller.ts @@ -56,6 +56,7 @@ import { requireAuth, requireOrgAdmin } from "../auth/auth.middleware"; import { toMessage } from "~/server/utils/errors"; import { requireDevPanel } from "../auth/dev-panel.middleware"; import { getSnapshotDuration } from "../../utils/snapshots"; +import { asShortId } from "~/server/utils/branded"; export const repositoriesController = new Hono() .use(requireAuth) @@ -86,30 +87,31 @@ export const repositoriesController = new Hono() return c.json(remotes); }) .get("/:shortId", getRepositoryDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const res = await repositoriesService.getRepository(shortId); return c.json(res.repository, 200); }) .get("/:shortId/stats", getRepositoryStatsDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const stats = await repositoriesService.getRepositoryStats(shortId); return c.json(stats, 200); }) .delete("/:shortId", deleteRepositoryDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); await repositoriesService.deleteRepository(shortId); return c.json({ message: "Repository deleted" }, 200); }) .get("/:shortId/snapshots", listSnapshotsDto, validator("query", listSnapshotsFilters), async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const { backupId } = c.req.valid("query"); + const backupShortId = backupId ? asShortId(backupId) : undefined; const [res, retentionCategories] = await Promise.all([ - repositoriesService.listSnapshots(shortId, backupId), - repositoriesService.getRetentionCategories(shortId, backupId), + repositoriesService.listSnapshots(shortId, backupShortId), + repositoriesService.getRetentionCategories(shortId, backupShortId), ]); const snapshots = res.map((snapshot) => { @@ -132,13 +134,14 @@ export const repositoriesController = new Hono() return c.json(snapshots, 200); }) .post("/:shortId/snapshots/refresh", refreshSnapshotsDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const result = await repositoriesService.refreshSnapshots(shortId); return c.json(result, 200); }) .get("/:shortId/snapshots/:snapshotId", getSnapshotDetailsDto, async (c) => { - const { shortId, snapshotId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); + const snapshotId = c.req.param("snapshotId"); const snapshot = await repositoriesService.getSnapshotDetails(shortId, snapshotId); const duration = getSnapshotDuration(snapshot.summary); @@ -162,7 +165,8 @@ export const repositoriesController = new Hono() listSnapshotFilesDto, validator("query", listSnapshotFilesQuery), async (c) => { - const { shortId, snapshotId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); + const snapshotId = c.req.param("snapshotId"); const { path, ...query } = c.req.valid("query"); const decodedPath = path ? decodeURIComponent(path) : undefined; @@ -178,7 +182,8 @@ export const repositoriesController = new Hono() }, ) .get("/:shortId/snapshots/:snapshotId/dump", dumpSnapshotDto, validator("query", dumpSnapshotQuery), async (c) => { - const { shortId, snapshotId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); + const snapshotId = c.req.param("snapshotId"); const { path, kind } = c.req.valid("query"); const dumpStream = await repositoriesService.dumpSnapshot(shortId, snapshotId, path, kind); @@ -202,55 +207,56 @@ export const repositoriesController = new Hono() }); }) .post("/:shortId/restore", restoreSnapshotDto, validator("json", restoreSnapshotBody), async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const { snapshotId, ...options } = c.req.valid("json"); const result = await repositoriesService.restoreSnapshot(shortId, snapshotId, options); return c.json(result, 200); }) .post("/:shortId/doctor", startDoctorDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const result = await repositoriesService.startDoctor(shortId); return c.json(result, 202); }) .delete("/:shortId/doctor", cancelDoctorDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const result = await repositoriesService.cancelDoctor(shortId); return c.json(result, 200); }) .post("/:shortId/unlock", unlockRepositoryDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const result = await repositoriesService.unlockRepository(shortId); return c.json(result, 200); }) .delete("/:shortId/snapshots/:snapshotId", deleteSnapshotDto, async (c) => { - const { shortId, snapshotId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); + const snapshotId = c.req.param("snapshotId"); await repositoriesService.deleteSnapshot(shortId, snapshotId); return c.json({ message: "Snapshot deleted" }, 200); }) .delete("/:shortId/snapshots", deleteSnapshotsDto, validator("json", deleteSnapshotsBody), async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const { snapshotIds } = c.req.valid("json"); await repositoriesService.deleteSnapshots(shortId, snapshotIds); return c.json({ message: "Snapshots deleted" }, 200); }) .post("/:shortId/snapshots/tag", tagSnapshotsDto, validator("json", tagSnapshotsBody), async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const { snapshotIds, ...tags } = c.req.valid("json"); await repositoriesService.tagSnapshots(shortId, snapshotIds, tags); return c.json({ message: "Snapshots tagged" }, 200); }) .patch("/:shortId", updateRepositoryDto, validator("json", updateRepositoryBody), async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const body = c.req.valid("json"); const res = await repositoriesService.updateRepository(shortId, body); @@ -263,7 +269,7 @@ export const repositoriesController = new Hono() devPanelExecDto, validator("json", devPanelExecBody), async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const body = c.req.valid("json"); return streamSSE(c, async (stream) => { diff --git a/app/server/modules/repositories/repositories.service.ts b/app/server/modules/repositories/repositories.service.ts index b32ab0b5..1829dc17 100644 --- a/app/server/modules/repositories/repositories.service.ts +++ b/app/server/modules/repositories/repositories.service.ts @@ -28,14 +28,15 @@ import { REPOSITORY_BASE } from "~/server/core/constants"; import { findCommonAncestor } from "~/utils/common-ancestor"; import { prepareSnapshotDump } from "./helpers/dump"; import { executeDoctor } from "./helpers/doctor"; +import type { ShortId } from "~/server/utils/branded"; const runningDoctors = new Map(); -const findRepository = async (shortId: string) => { +const findRepository = async (shortId: ShortId) => { const organizationId = getOrganizationId(); return await db.query.repositoriesTable.findFirst({ where: { - AND: [{ shortId }, { organizationId }], + AND: [{ shortId: { eq: shortId } }, { organizationId }], }, }); }; @@ -184,7 +185,7 @@ const createRepository = async (name: string, config: RepositoryConfig, compress throw new InternalServerError(`Failed to initialize repository: ${errorMessage}`); }; -const getRepository = async (shortId: string) => { +const getRepository = async (shortId: ShortId) => { const repository = await findRepository(shortId); if (!repository) { @@ -194,7 +195,7 @@ const getRepository = async (shortId: string) => { return { repository }; }; -const getRepositoryStats = async (shortId: string) => { +const getRepositoryStats = async (shortId: ShortId) => { const organizationId = getOrganizationId(); const repository = await findRepository(shortId); @@ -218,7 +219,7 @@ const getRepositoryStats = async (shortId: string) => { } }; -const deleteRepository = async (shortId: string) => { +const deleteRepository = async (shortId: ShortId) => { const repository = await findRepository(shortId); if (!repository) { @@ -244,7 +245,7 @@ const deleteRepository = async (shortId: string) => { * * @returns List of snapshots */ -const listSnapshots = async (shortId: string, backupId?: string) => { +const listSnapshots = async (shortId: ShortId, backupId?: ShortId) => { const organizationId = getOrganizationId(); const repository = await findRepository(shortId); @@ -277,7 +278,7 @@ const listSnapshots = async (shortId: string, backupId?: string) => { }; const listSnapshotFiles = async ( - shortId: string, + shortId: ShortId, snapshotId: string, path?: string, options?: { offset?: number; limit?: number }, @@ -342,7 +343,7 @@ const listSnapshotFiles = async ( }; const restoreSnapshot = async ( - shortId: string, + shortId: ShortId, snapshotId: string, options?: { include?: string[]; @@ -414,7 +415,7 @@ const restoreSnapshot = async ( } }; -const dumpSnapshot = async (shortId: string, snapshotId: string, path?: string, kind?: DumpPathKind) => { +const dumpSnapshot = async (shortId: ShortId, snapshotId: string, path?: string, kind?: DumpPathKind) => { const organizationId = getOrganizationId(); const repository = await findRepository(shortId); @@ -474,7 +475,7 @@ const dumpSnapshot = async (shortId: string, snapshotId: string, path?: string, } }; -const getSnapshotDetails = async (shortId: string, snapshotId: string) => { +const getSnapshotDetails = async (shortId: ShortId, snapshotId: string) => { const organizationId = getOrganizationId(); const repository = await findRepository(shortId); @@ -506,9 +507,9 @@ const getSnapshotDetails = async (shortId: string, snapshotId: string) => { return snapshot; }; -const checkHealth = async (repositoryId: string) => { +const checkHealth = async (shortId: ShortId) => { const organizationId = getOrganizationId(); - const repository = await findRepository(repositoryId); + const repository = await findRepository(shortId); if (!repository) { throw new NotFoundError("Repository not found"); @@ -535,7 +536,7 @@ const checkHealth = async (repositoryId: string) => { } }; -const startDoctor = async (shortId: string) => { +const startDoctor = async (shortId: ShortId) => { const repository = await findRepository(shortId); if (!repository) { @@ -574,7 +575,7 @@ const startDoctor = async (shortId: string) => { return { message: "Doctor operation started", repositoryId: repository.shortId }; }; -const cancelDoctor = async (shortId: string) => { +const cancelDoctor = async (shortId: ShortId) => { const repository = await findRepository(shortId); if (!repository) { @@ -601,7 +602,7 @@ const cancelDoctor = async (shortId: string) => { return { message: "Doctor operation cancelled" }; }; -const deleteSnapshot = async (shortId: string, snapshotId: string) => { +const deleteSnapshot = async (shortId: ShortId, snapshotId: string) => { const organizationId = getOrganizationId(); const repository = await findRepository(shortId); @@ -618,7 +619,7 @@ const deleteSnapshot = async (shortId: string, snapshotId: string) => { } }; -const deleteSnapshots = async (shortId: string, snapshotIds: string[]) => { +const deleteSnapshots = async (shortId: ShortId, snapshotIds: string[]) => { const organizationId = getOrganizationId(); const repository = await findRepository(shortId); @@ -636,7 +637,7 @@ const deleteSnapshots = async (shortId: string, snapshotIds: string[]) => { }; const tagSnapshots = async ( - shortId: string, + shortId: ShortId, snapshotIds: string[], tags: { add?: string[]; remove?: string[]; set?: string[] }, ) => { @@ -656,7 +657,7 @@ const tagSnapshots = async ( } }; -const refreshSnapshots = async (shortId: string) => { +const refreshSnapshots = async (shortId: ShortId) => { const organizationId = getOrganizationId(); const repository = await findRepository(shortId); @@ -681,7 +682,7 @@ const refreshSnapshots = async (shortId: string) => { } }; -const updateRepository = async (shortId: string, updates: UpdateRepositoryBody) => { +const updateRepository = async (shortId: ShortId, updates: UpdateRepositoryBody) => { const existing = await findRepository(shortId); if (!existing) { @@ -752,7 +753,7 @@ const updateRepository = async (shortId: string, updates: UpdateRepositoryBody) return { repository: updated }; }; -const unlockRepository = async (shortId: string) => { +const unlockRepository = async (shortId: ShortId) => { const organizationId = getOrganizationId(); const repository = await findRepository(shortId); @@ -770,7 +771,7 @@ const unlockRepository = async (shortId: string) => { }; const execResticCommand = async ( - shortId: string, + shortId: ShortId, command: string, args: string[] | undefined, onStdout: (line: string) => void, @@ -800,7 +801,7 @@ const execResticCommand = async ( return { exitCode: result.exitCode }; }; -const getRetentionCategories = async (repositoryId: string, scheduleId?: string) => { +const getRetentionCategories = async (repositoryId: ShortId, scheduleId?: ShortId) => { if (!scheduleId) { return new Map(); } diff --git a/app/server/modules/volumes/__tests__/volumes.service.test.ts b/app/server/modules/volumes/__tests__/volumes.service.test.ts index 5b560da0..3eb8e50f 100644 --- a/app/server/modules/volumes/__tests__/volumes.service.test.ts +++ b/app/server/modules/volumes/__tests__/volumes.service.test.ts @@ -8,16 +8,17 @@ import os from "node:os"; import path from "node:path"; import { createTestSession } from "~/test/helpers/auth"; import { withContext } from "~/server/core/request-context"; +import { asShortId } from "~/server/utils/branded"; describe("volumeService", () => { describe("findVolume", () => { - test("should find volume by numeric id", async () => { + test("should find volume by shortId", async () => { const { organizationId, user } = await createTestSession(); const [volume] = await db .insert(volumesTable) .values({ - shortId: randomUUID().slice(0, 8), + shortId: asShortId(randomUUID().slice(0, 8)), name: `test-vol-${randomUUID().slice(0, 8)}`, type: "directory", status: "mounted", @@ -28,19 +29,19 @@ describe("volumeService", () => { .returning(); await withContext({ organizationId, userId: user.id }, async () => { - const result = await volumeService.getVolume(String(volume.id)); + const result = await volumeService.getVolume(volume.shortId); expect(result.volume.id).toBe(volume.id); expect(result.volume.shortId).toBe(volume.shortId); }); }); - test("should find volume by shortId", async () => { + test("should find volume by shortId from literal input", async () => { const { organizationId, user } = await createTestSession(); const [volume] = await db .insert(volumesTable) .values({ - shortId: "test1234", + shortId: asShortId("test1234"), name: `test-vol-${randomUUID().slice(0, 8)}`, type: "directory", status: "mounted", @@ -63,7 +64,7 @@ describe("volumeService", () => { const [volume] = await db .insert(volumesTable) .values({ - shortId: "499780", + shortId: asShortId("499780"), name: `test-vol-${randomUUID().slice(0, 8)}`, type: "directory", status: "mounted", @@ -74,9 +75,9 @@ describe("volumeService", () => { .returning(); await withContext({ organizationId, userId: user.id }, async () => { - const result = await volumeService.getVolume("499780"); + const result = await volumeService.getVolume(asShortId("499780")); expect(result.volume.id).toBe(volume.id); - expect(result.volume.shortId).toBe("499780"); + expect(result.volume.shortId).toBe(asShortId("499780")); }); }); @@ -84,7 +85,7 @@ describe("volumeService", () => { const { organizationId, user } = await createTestSession(); await withContext({ organizationId, userId: user.id }, async () => { - expect(volumeService.getVolume("nonexistent")).rejects.toThrow("Volume not found"); + expect(volumeService.getVolume(asShortId("nonexistent"))).rejects.toThrow("Volume not found"); }); }); }); @@ -105,7 +106,7 @@ describe("volumeService security", () => { const [volume] = await db .insert(volumesTable) .values({ - shortId: randomUUID().slice(0, 8), + shortId: asShortId(randomUUID().slice(0, 8)), name: `test-vol-${randomUUID().slice(0, 8)}`, type: "directory", status: "mounted", @@ -119,7 +120,7 @@ describe("volumeService security", () => { await withContext({ organizationId, userId: user.id }, async () => { const traversalPath = `../${path.basename(secretPath)}`; - expect(volumeService.listFiles(volume.id, traversalPath)).rejects.toThrow("Invalid path"); + expect(volumeService.listFiles(volume.shortId, traversalPath)).rejects.toThrow("Invalid path"); }); } finally { await fs.rm(tempRoot, { recursive: true, force: true }); diff --git a/app/server/modules/volumes/volume.controller.ts b/app/server/modules/volumes/volume.controller.ts index e36bebf2..6128de7b 100644 --- a/app/server/modules/volumes/volume.controller.ts +++ b/app/server/modules/volumes/volume.controller.ts @@ -26,6 +26,7 @@ import { import { volumeService } from "./volume.service"; import { getVolumePath } from "./helpers"; import { requireAuth } from "../auth/auth.middleware"; +import { asShortId } from "~/server/utils/branded"; export const volumeController = new Hono() .use(requireAuth) @@ -52,13 +53,13 @@ export const volumeController = new Hono() return c.json(result, 200); }) .delete("/:shortId", deleteVolumeDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); await volumeService.deleteVolume(shortId); return c.json({ message: "Volume deleted" }, 200); }) .get("/:shortId", getVolumeDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const res = await volumeService.getVolume(shortId); const response = { @@ -76,7 +77,7 @@ export const volumeController = new Hono() return c.json(response, 200); }) .put("/:shortId", updateVolumeDto, validator("json", updateVolumeBody), async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const body = c.req.valid("json"); const res = await volumeService.updateVolume(shortId, body); @@ -88,25 +89,25 @@ export const volumeController = new Hono() return c.json(response, 200); }) .post("/:shortId/mount", mountVolumeDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const { error, status } = await volumeService.mountVolume(shortId); return c.json({ error, status }, error ? 500 : 200); }) .post("/:shortId/unmount", unmountVolumeDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const { error, status } = await volumeService.unmountVolume(shortId); return c.json({ error, status }, error ? 500 : 200); }) .post("/:shortId/health-check", healthCheckDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const { error, status } = await volumeService.checkHealth(shortId); return c.json({ error, status }, 200); }) .get("/:shortId/files", validator("query", listFilesQuery), listFilesDto, async (c) => { - const { shortId } = c.req.param(); + const shortId = asShortId(c.req.param("shortId")); const { path, ...query } = c.req.valid("query"); const offset = Math.max(0, Number.parseInt(query.offset ?? "0", 10) || 0); diff --git a/app/server/modules/volumes/volume.service.ts b/app/server/modules/volumes/volume.service.ts index ed1bd3dd..ac122c35 100644 --- a/app/server/modules/volumes/volume.service.ts +++ b/app/server/modules/volumes/volume.service.ts @@ -19,6 +19,7 @@ import { volumeConfigSchema, type BackendConfig } from "~/schemas/volumes"; import { type } from "arktype"; import { getOrganizationId } from "~/server/core/request-context"; import { isNodeJSErrnoException } from "~/server/utils/fs"; +import { asShortId, type ShortId } from "~/server/utils/branded"; async function encryptSensitiveFields(config: BackendConfig): Promise { switch (config.backend) { @@ -52,11 +53,11 @@ const listVolumes = async () => { return volumes; }; -const findVolume = async (identifier: string | number) => { +const findVolume = async (shortId: ShortId) => { const organizationId = getOrganizationId(); return await db.query.volumesTable.findFirst({ where: { - AND: [{ OR: [{ id: Number(identifier) }, { shortId: String(identifier) }] }, { organizationId: organizationId }], + AND: [{ shortId: { eq: shortId } }, { organizationId: organizationId }], }, }); }; @@ -98,9 +99,9 @@ const createVolume = async (name: string, backendConfig: BackendConfig) => { return { volume: created, status: 201 }; }; -const deleteVolume = async (identifier: string | number) => { +const deleteVolume = async (shortId: ShortId) => { const organizationId = getOrganizationId(); - const volume = await findVolume(identifier); + const volume = await findVolume(shortId); if (!volume) { throw new NotFoundError("Volume not found"); @@ -113,9 +114,9 @@ const deleteVolume = async (identifier: string | number) => { .where(and(eq(volumesTable.id, volume.id), eq(volumesTable.organizationId, organizationId))); }; -const mountVolume = async (identifier: string | number) => { +const mountVolume = async (shortId: ShortId) => { const organizationId = getOrganizationId(); - const volume = await findVolume(identifier); + const volume = await findVolume(shortId); if (!volume) { throw new NotFoundError("Volume not found"); @@ -136,9 +137,9 @@ const mountVolume = async (identifier: string | number) => { return { error, status }; }; -const unmountVolume = async (identifier: string | number) => { +const unmountVolume = async (shortId: ShortId) => { const organizationId = getOrganizationId(); - const volume = await findVolume(identifier); + const volume = await findVolume(shortId); if (!volume) { throw new NotFoundError("Volume not found"); @@ -159,8 +160,8 @@ const unmountVolume = async (identifier: string | number) => { return { error, status }; }; -const getVolume = async (identifier: string | number) => { - const volume = await findVolume(identifier); +const getVolume = async (shortId: ShortId) => { + const volume = await findVolume(shortId); if (!volume) { throw new NotFoundError("Volume not found"); @@ -177,9 +178,9 @@ const getVolume = async (identifier: string | number) => { return { volume, statfs }; }; -const updateVolume = async (identifier: string | number, volumeData: UpdateVolumeBody) => { +const updateVolume = async (shortId: ShortId, volumeData: UpdateVolumeBody) => { const organizationId = getOrganizationId(); - const existing = await findVolume(identifier); + const existing = await findVolume(shortId); if (!existing) { throw new NotFoundError("Volume not found"); @@ -242,7 +243,7 @@ const testConnection = async (backendConfig: BackendConfig) => { const mockVolume = { id: 0, - shortId: "test", + shortId: asShortId("test"), name: "test-connection", path: tempDir, config: backendConfig, @@ -270,9 +271,9 @@ const testConnection = async (backendConfig: BackendConfig) => { }; }; -const checkHealth = async (identifier: string | number) => { +const checkHealth = async (shortId: ShortId) => { const organizationId = getOrganizationId(); - const volume = await findVolume(identifier); + const volume = await findVolume(shortId); if (!volume) { throw new NotFoundError("Volume not found"); @@ -296,13 +297,8 @@ const checkHealth = async (identifier: string | number) => { const DEFAULT_PAGE_SIZE = 500; const MAX_PAGE_SIZE = 500; -const listFiles = async ( - identifier: string | number, - subPath?: string, - offset: number = 0, - limit: number = DEFAULT_PAGE_SIZE, -) => { - const volume = await findVolume(identifier); +const listFiles = async (shortId: ShortId, subPath?: string, offset: number = 0, limit: number = DEFAULT_PAGE_SIZE) => { + const volume = await findVolume(shortId); if (!volume) { throw new NotFoundError("Volume not found"); diff --git a/app/server/utils/branded.ts b/app/server/utils/branded.ts new file mode 100644 index 00000000..84802d93 --- /dev/null +++ b/app/server/utils/branded.ts @@ -0,0 +1,9 @@ +declare const brand: unique symbol; + +export type Branded = T & { [brand]: B }; + +export type ShortId = Branded; + +export const asShortId = (value: string): ShortId => value as ShortId; + +export const isShortId = (value: string): value is ShortId => /^[A-Za-z0-9_-]+$/.test(value); diff --git a/app/server/utils/id.ts b/app/server/utils/id.ts index 18bc2030..b4d1a101 100644 --- a/app/server/utils/id.ts +++ b/app/server/utils/id.ts @@ -1,6 +1,7 @@ import crypto from "node:crypto"; +import type { ShortId } from "./branded"; -export const generateShortId = (length = 8): string => { +export const generateShortId = (length = 8): ShortId => { const bytesNeeded = Math.ceil((length * 3) / 4); - return crypto.randomBytes(bytesNeeded).toString("base64url").slice(0, length); + return crypto.randomBytes(bytesNeeded).toString("base64url").slice(0, length) as ShortId; }; diff --git a/app/test/helpers/backup.ts b/app/test/helpers/backup.ts index 5e257262..136c2d3a 100644 --- a/app/test/helpers/backup.ts +++ b/app/test/helpers/backup.ts @@ -4,6 +4,7 @@ import { backupSchedulesTable, type BackupScheduleInsert } from "~/server/db/sch import { createTestOrganization, ensureTestOrganization, TEST_ORG_ID } from "./organization"; import { createTestVolume } from "./volume"; import { createTestRepository } from "./repository"; +import { generateShortId } from "~/server/utils/id"; export const createTestBackupSchedule = async (overrides: Partial = {}) => { const organizationId = overrides.organizationId ?? TEST_ORG_ID; @@ -22,7 +23,7 @@ export const createTestBackupSchedule = async (overrides: Partial = {}) => { await ensureTestOrganization(); @@ -9,7 +10,7 @@ export const createTestRepository = async (overrides: Partial const repository: RepositoryInsert = { id: faker.string.alphanumeric(6), name: faker.string.alphanumeric(10), - shortId: faker.string.alphanumeric(6), + shortId: generateShortId(), config: { path: `/var/lib/zerobyte/repositories/${faker.string.alphanumeric(8)}`, backend: "local", diff --git a/app/test/helpers/volume.ts b/app/test/helpers/volume.ts index a60f137d..9f7a42da 100644 --- a/app/test/helpers/volume.ts +++ b/app/test/helpers/volume.ts @@ -2,6 +2,7 @@ import { db } from "~/server/db/db"; import { faker } from "@faker-js/faker"; import { volumesTable, type VolumeInsert } from "~/server/db/schema"; import { ensureTestOrganization, TEST_ORG_ID } from "./organization"; +import { generateShortId } from "~/server/utils/id"; export const createTestVolume = async (overrides: Partial = {}) => { await ensureTestOrganization(); @@ -14,7 +15,7 @@ export const createTestVolume = async (overrides: Partial = {}) => }, status: "mounted", autoRemount: true, - shortId: faker.string.alphanumeric(6), + shortId: generateShortId(), type: "directory", organizationId: TEST_ORG_ID, ...overrides, diff --git a/bun.lock b/bun.lock index 66e1cf32..e8035c66 100644 --- a/bun.lock +++ b/bun.lock @@ -28,11 +28,9 @@ "@scalar/hono-api-reference": "^0.9.41", "@tanstack/react-hotkeys": "^0.1.0", "@tanstack/react-query": "^5.90.21", - "@tanstack/react-query-devtools": "^5.91.3", - "@tanstack/react-router": "^1.160.2", - "@tanstack/react-router-devtools": "^1.160.2", - "@tanstack/react-router-ssr-query": "^1.160.2", - "@tanstack/react-start": "^1.160.2", + "@tanstack/react-router": "^1.161.3", + "@tanstack/react-router-ssr-query": "^1.161.3", + "@tanstack/react-start": "^1.161.3", "arktype": "^2.1.28", "better-auth": "^1.4.18", "class-variance-authority": "^0.7.1", @@ -763,49 +761,41 @@ "@tanstack/query-core": ["@tanstack/query-core@5.90.20", "", {}, "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg=="], - "@tanstack/query-devtools": ["@tanstack/query-devtools@5.93.0", "", {}, "sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg=="], - "@tanstack/react-hotkeys": ["@tanstack/react-hotkeys@0.1.0", "", { "dependencies": { "@tanstack/hotkeys": "0.1.0", "@tanstack/react-store": "^0.8.0" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-Hteo+hdPKdR2G7uAwTmScu1JUHM/f03lVihxVfR/A3XMyVJmbzi4SJieRk/kLgYKeXFRGIUx/xSI3mHeVc6DbQ=="], "@tanstack/react-query": ["@tanstack/react-query@5.90.21", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg=="], - "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.91.3", "", { "dependencies": { "@tanstack/query-devtools": "5.93.0" }, "peerDependencies": { "@tanstack/react-query": "^5.90.20", "react": "^18 || ^19" } }, "sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA=="], - - "@tanstack/react-router": ["@tanstack/react-router@1.160.2", "", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/react-store": "^0.8.0", "@tanstack/router-core": "1.160.0", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-EJWAMS4qCfWKNCzzYGy6ZuWTdBATYEEWieaQdmM7zUesyOQ01j7o6aKXdmCp9rWuSKjPHXagWubEnEo+Puhi3w=="], + "@tanstack/react-router": ["@tanstack/react-router@1.161.3", "", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/react-store": "^0.9.1", "@tanstack/router-core": "1.161.3", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-evYPrkuFt4T6E0WVyBGGq83lWHJjsYy3E5SpPpfPY/uRnEgmgwfr6Xl570msRnWYMj7DIkYg8ZWFFwzqKrSlBw=="], - "@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.160.2", "", { "dependencies": { "@tanstack/router-devtools-core": "1.160.0" }, "peerDependencies": { "@tanstack/react-router": "^1.160.2", "@tanstack/router-core": "^1.160.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-uSdQD77S+LcANCiWcLsrqSxyEqTXdZhBETbciKcYJrcgd8rfkxg6AIewYi7148QPU6gb3VKQbeRlqXmBeEs5dg=="], + "@tanstack/react-router-ssr-query": ["@tanstack/react-router-ssr-query@1.161.3", "", { "dependencies": { "@tanstack/router-ssr-query-core": "1.161.3" }, "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/react-query": ">=5.90.0", "@tanstack/react-router": ">=1.127.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-0tLk3yrbQlcHBwbgSPT5I67rzq/v3KXZ4pSsQ7pkk4jgNTldE7sV8haHTiMU8yXI78HajgwMEZrfBvx0mrQfIQ=="], - "@tanstack/react-router-ssr-query": ["@tanstack/react-router-ssr-query@1.160.2", "", { "dependencies": { "@tanstack/router-ssr-query-core": "1.160.0" }, "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/react-query": ">=5.90.0", "@tanstack/react-router": ">=1.127.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-/9eDT69AtsFdSY2lSz2BjW2c8SwJMXB/VGW/HxVKGavTPYD6J4xN6Ew7iSwJqfbjB7yRer5Dp0lvKFSQbcJGtw=="], + "@tanstack/react-start": ["@tanstack/react-start@1.161.3", "", { "dependencies": { "@tanstack/react-router": "1.161.3", "@tanstack/react-start-client": "1.161.3", "@tanstack/react-start-server": "1.161.3", "@tanstack/router-utils": "^1.158.0", "@tanstack/start-client-core": "1.161.3", "@tanstack/start-plugin-core": "1.161.3", "@tanstack/start-server-core": "1.161.3", "pathe": "^2.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": ">=7.0.0" } }, "sha512-wWx+wuR4U6yHgnNsHLocjMYrsORdOWom6UuBzOsh+FmFJ1RK+NcjyNNt6WjsmWPU5ZUZ31KHKe4ZEt27R+15IA=="], - "@tanstack/react-start": ["@tanstack/react-start@1.160.2", "", { "dependencies": { "@tanstack/react-router": "1.160.2", "@tanstack/react-start-client": "1.160.2", "@tanstack/react-start-server": "1.160.2", "@tanstack/router-utils": "^1.158.0", "@tanstack/start-client-core": "1.160.0", "@tanstack/start-plugin-core": "1.160.2", "@tanstack/start-server-core": "1.160.0", "pathe": "^2.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": ">=7.0.0" } }, "sha512-+v+m33wTOlcjp0L66YcjS6YCi9tz9QZBC1ZC4L6o3eV/6S2gZqNZ32Tz6LLfsDuipr7iyyeAiXcFTwtZrfhSdQ=="], + "@tanstack/react-start-client": ["@tanstack/react-start-client@1.161.3", "", { "dependencies": { "@tanstack/react-router": "1.161.3", "@tanstack/router-core": "1.161.3", "@tanstack/start-client-core": "1.161.3", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-MMyc8WCBkoipJ5rNqgAANOB4gITsfxs/f05pY3K1BUpUAOi91s8YgNO2IBnIOj8sqNoI0Gx2lWgQBCwm4H4+wg=="], - "@tanstack/react-start-client": ["@tanstack/react-start-client@1.160.2", "", { "dependencies": { "@tanstack/react-router": "1.160.2", "@tanstack/router-core": "1.160.0", "@tanstack/start-client-core": "1.160.0", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-LZRM5hKb5nj0vKhgdydm7S0kXZG7yrv11Pyt0itzUOomXg2JuDM9ckvAw8PDl/O+sp3RJWBqfGj90Cpl5PQOuQ=="], - - "@tanstack/react-start-server": ["@tanstack/react-start-server@1.160.2", "", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/react-router": "1.160.2", "@tanstack/router-core": "1.160.0", "@tanstack/start-client-core": "1.160.0", "@tanstack/start-server-core": "1.160.0" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-y1PsLVfgD8UWNA3mFIg4mQNJkbjz6nZJTMBne5L/dndhk/SGQGBM/AA2El5n/4tKl82TgEum59bVjvKjudX6RQ=="], + "@tanstack/react-start-server": ["@tanstack/react-start-server@1.161.3", "", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/react-router": "1.161.3", "@tanstack/router-core": "1.161.3", "@tanstack/start-client-core": "1.161.3", "@tanstack/start-server-core": "1.161.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-5FY3+2LHPPlVkHrrPbhi+TKVl93UCWKC0Ta/hxhXBaYrvBU1uWhIuKgspXT+cWP6XcMPuPrc4qfVWrXHKBMIqg=="], "@tanstack/react-store": ["@tanstack/react-store@0.8.0", "", { "dependencies": { "@tanstack/store": "0.8.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow=="], - "@tanstack/router-core": ["@tanstack/router-core@1.160.0", "", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/store": "^0.8.0", "cookie-es": "^2.0.0", "seroval": "^1.4.2", "seroval-plugins": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-vbh6OsE0MG+0c+SKh2uk5yEEZlWsxT96Ub2JaTs7ixOvZp3Wu9PTEIe2BA3cShNZhEsDI0Le4NqgY4XIaHLLvA=="], - - "@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.160.0", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "@tanstack/router-core": "^1.160.0", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-P/l0GVd0qmDbskg8/UbkOrCxuFz0t69BCxv2j4+8Xfy8AcqnFtoR1LChKgYyxGPy9sWOxktAneFdy1xA3X/Q6A=="], + "@tanstack/router-core": ["@tanstack/router-core@1.161.3", "", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/store": "^0.9.1", "cookie-es": "^2.0.0", "seroval": "^1.4.2", "seroval-plugins": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-8EuaGXLUjugQE9Rsb8VrWSy+wImcs/DZ9JORqUJYCmiiWnJzbat8KedQItq/9LCjMJyx4vTLCt8NnZCL+j1Ayg=="], - "@tanstack/router-generator": ["@tanstack/router-generator@1.160.1", "", { "dependencies": { "@tanstack/router-core": "1.160.0", "@tanstack/router-utils": "1.158.0", "@tanstack/virtual-file-routes": "1.154.7", "prettier": "^3.5.0", "recast": "^0.23.11", "source-map": "^0.7.4", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-De6TicInwy3/9rQ++RZAyFOvB2oi5UV5T0iiIlxe3jgiOLFxMA4EKKVlT+alDxKnq6udTLam9xqhvGOVZ6a2hw=="], + "@tanstack/router-generator": ["@tanstack/router-generator@1.161.3", "", { "dependencies": { "@tanstack/router-core": "1.161.3", "@tanstack/router-utils": "1.158.0", "@tanstack/virtual-file-routes": "1.154.7", "prettier": "^3.5.0", "recast": "^0.23.11", "source-map": "^0.7.4", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-GKOrsOu7u5aoK1+lRu6KUUOmbb42mYF2ezfXf27QMiBjMx/yDHXln8wmdR7ZQ+FdSGz2YVubt2Ns3KuFsDsZJg=="], - "@tanstack/router-plugin": ["@tanstack/router-plugin@1.160.2", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.160.0", "@tanstack/router-generator": "1.160.1", "@tanstack/router-utils": "1.158.0", "@tanstack/virtual-file-routes": "1.154.7", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.160.2", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", "vite-plugin-solid": "^2.11.10", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-+uyjLK4hImFyENb08PwDfvnKpwfdjCA29sKrHRk/ff9H23RUm10MW0UMjIrVU+4Dql/xsbzsqrTwIyRS6zna9Q=="], + "@tanstack/router-plugin": ["@tanstack/router-plugin@1.161.3", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.161.3", "@tanstack/router-generator": "1.161.3", "@tanstack/router-utils": "1.158.0", "@tanstack/virtual-file-routes": "1.154.7", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.161.3", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", "vite-plugin-solid": "^2.11.10", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-3Uy4AxgHNYjmCGf2WYWB8Gy3C6m0YE5DV1SK2p3yUrA/PhCMYRe+xzjyD5pViMUSLUoPHQYGY6bOIM9OOPRI/Q=="], - "@tanstack/router-ssr-query-core": ["@tanstack/router-ssr-query-core@1.160.0", "", { "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/router-core": ">=1.127.0" } }, "sha512-RS2L7NchJ2/YadTWJjOA1czGiTlshMLd5CBPUZURKAlkVymGsupOHG9ds63egpyHgKI/zMQOUHgtOY74ishoLA=="], + "@tanstack/router-ssr-query-core": ["@tanstack/router-ssr-query-core@1.161.3", "", { "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/router-core": ">=1.127.0" } }, "sha512-dFEWRKnv4UVWOGY53ypFivHkuM/NKxtGuj7O0UGHaQshpVEH+oXfb/KupHeCuSbDLdREaqQYMBq0L6VfHRw5FA=="], "@tanstack/router-utils": ["@tanstack/router-utils@1.158.0", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-qZ76eaLKU6Ae9iI/mc5zizBX149DXXZkBVVO3/QRIll79uKLJZHQlMKR++2ba7JsciBWz1pgpIBcCJPE9S0LVg=="], - "@tanstack/start-client-core": ["@tanstack/start-client-core@1.160.0", "", { "dependencies": { "@tanstack/router-core": "1.160.0", "@tanstack/start-fn-stubs": "1.154.7", "@tanstack/start-storage-context": "1.160.0", "seroval": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-qVzbDT6tTED3+3rPJsyAOskszEjCW0/DkzEMoG4an8Gh3cNnEXqrCDyKWjvqYpj9zrquNTzgBh3GFZzzfgr4ZQ=="], + "@tanstack/start-client-core": ["@tanstack/start-client-core@1.161.3", "", { "dependencies": { "@tanstack/router-core": "1.161.3", "@tanstack/start-fn-stubs": "1.154.7", "@tanstack/start-storage-context": "1.161.3", "seroval": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-tE9PJCk+64CeIie70f6MZd8LP3A+5LWjjwksEaqxsZMYGN0Re6BWI/oTpZtnvRHhtCUB5ASz6K/eMZt8R9wq5A=="], "@tanstack/start-fn-stubs": ["@tanstack/start-fn-stubs@1.154.7", "", {}, "sha512-D69B78L6pcFN5X5PHaydv7CScQcKLzJeEYqs7jpuyyqGQHSUIZUjS955j+Sir8cHhuDIovCe2LmsYHeZfWf3dQ=="], - "@tanstack/start-plugin-core": ["@tanstack/start-plugin-core@1.160.2", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.28.5", "@babel/types": "^7.28.5", "@rolldown/pluginutils": "1.0.0-beta.40", "@tanstack/router-core": "1.160.0", "@tanstack/router-generator": "1.160.1", "@tanstack/router-plugin": "1.160.2", "@tanstack/router-utils": "1.158.0", "@tanstack/start-client-core": "1.160.0", "@tanstack/start-server-core": "1.160.0", "cheerio": "^1.0.0", "exsolve": "^1.0.7", "pathe": "^2.0.3", "srvx": "^0.11.2", "tinyglobby": "^0.2.15", "ufo": "^1.5.4", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", "zod": "^3.24.2" }, "peerDependencies": { "vite": ">=7.0.0" } }, "sha512-scFSvcoHHLhiz0RhH4tetBXkjZLgUR+xfAA5aeqWcC0dcMS4Cj5Irb+AxAkvbdGQPc0Afyrs77OmOUi3v4YjcA=="], + "@tanstack/start-plugin-core": ["@tanstack/start-plugin-core@1.161.3", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.28.5", "@babel/types": "^7.28.5", "@rolldown/pluginutils": "1.0.0-beta.40", "@tanstack/router-core": "1.161.3", "@tanstack/router-generator": "1.161.3", "@tanstack/router-plugin": "1.161.3", "@tanstack/router-utils": "1.158.0", "@tanstack/start-client-core": "1.161.3", "@tanstack/start-server-core": "1.161.3", "cheerio": "^1.0.0", "exsolve": "^1.0.7", "pathe": "^2.0.3", "picomatch": "^4.0.3", "source-map": "^0.7.6", "srvx": "^0.11.7", "tinyglobby": "^0.2.15", "ufo": "^1.5.4", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", "zod": "^3.24.2" }, "peerDependencies": { "vite": ">=7.0.0" } }, "sha512-HrxDuh5nn1F4LhJyQ1cHwou1VdlOsH3uOK/EEQXYBPpo+NKWGeaw06ff9fwmH6/FkpgWrh1c+4T0V1BS+T/YJA=="], - "@tanstack/start-server-core": ["@tanstack/start-server-core@1.160.0", "", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/router-core": "1.160.0", "@tanstack/start-client-core": "1.160.0", "@tanstack/start-storage-context": "1.160.0", "h3-v2": "npm:h3@2.0.1-rc.14", "seroval": "^1.4.2", "tiny-invariant": "^1.3.3" } }, "sha512-zr30GxMV5VBAFSPQ9le/VW9Ql8p6ctrv4a94avgR3hEzjnXOxOBpOO29EdzVtrTcbTPO6rxC9B0f3yOwbEcucg=="], + "@tanstack/start-server-core": ["@tanstack/start-server-core@1.161.3", "", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/router-core": "1.161.3", "@tanstack/start-client-core": "1.161.3", "@tanstack/start-storage-context": "1.161.3", "h3-v2": "npm:h3@2.0.1-rc.14", "seroval": "^1.4.2", "tiny-invariant": "^1.3.3" } }, "sha512-+/1XUd8Dvf462/OHsJJEhFZhERqd0iI/1JD9SyNPSqP6cFnkhIqIK+yaiD6ziQaGO+pkBqSmuRB2/acJg4NRTg=="], - "@tanstack/start-storage-context": ["@tanstack/start-storage-context@1.160.0", "", { "dependencies": { "@tanstack/router-core": "1.160.0" } }, "sha512-Y4mvNu0/R1poZsBoz9JCXeanwj9BVaiv4XBt33bQk12kLhdNwyTXzZ3cuuNyfiHL+tNK0/b6oQyQZFGMLfB1Hw=="], + "@tanstack/start-storage-context": ["@tanstack/start-storage-context@1.161.3", "", { "dependencies": { "@tanstack/router-core": "1.161.3" } }, "sha512-X89oEykLrrhIn+41Q3jXVYRsg9NirM+7Nr0FLajFRle3FpAYggHq6TS8XPRhrv664uLa5Dz225sxCDwC5OT+sQ=="], "@tanstack/store": ["@tanstack/store@0.8.0", "", {}, "sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ=="], @@ -1151,8 +1141,6 @@ "globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="], - "goober": ["goober@2.1.18", "", { "peerDependencies": { "csstype": "^3.0.10" } }, "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw=="], - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "h3": ["h3@2.0.1-rc.11", "", { "dependencies": { "rou3": "^0.7.12", "srvx": "^0.10.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-2myzjCqy32c1As9TjZW9fNZXtLqNedjFSrdFy2AjFBQQ3LzrnGoDdFDYfC0tV2e4vcyfJ2Sfo/F6NQhO2Ly/Mw=="], @@ -1781,6 +1769,10 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tanstack/react-router/@tanstack/react-store": ["@tanstack/react-store@0.9.1", "", { "dependencies": { "@tanstack/store": "0.9.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-YzJLnRvy5lIEFTLWBAZmcOjK3+2AepnBv/sr6NZmiqJvq7zTQggyK99Gw8fqYdMdHPQWXjz0epFKJXC+9V2xDA=="], + + "@tanstack/router-core/@tanstack/store": ["@tanstack/store@0.9.1", "", {}, "sha512-+qcNkOy0N1qSGsP7omVCW0SDrXtaDcycPqBDE726yryiA5eTDFpjBReaYjghVJwNf1pcPMyzIwTGlYjCSQR0Fg=="], + "@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@tanstack/router-plugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], @@ -1791,7 +1783,7 @@ "@tanstack/start-plugin-core/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.40", "", {}, "sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w=="], - "@tanstack/start-plugin-core/srvx": ["srvx@0.11.4", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-m/2p87bqWZ94xpRN06qNBwh0xq/D0dXajnvPDSHFqrTogxuTWYNP1UHz6Cf+oY7D+NPLY35TJAp4ESIKn0WArQ=="], + "@tanstack/start-plugin-core/srvx": ["srvx@0.11.7", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-p9qj9wkv/MqG1VoJpOsqXv1QcaVcYRk7ifsC6i3TEwDXFyugdhJN4J3KzQPZq2IJJ2ZCt7ASOB++85pEK38jRw=="], "@tanstack/start-plugin-core/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -1869,6 +1861,8 @@ "@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + "@tanstack/react-router/@tanstack/react-store/@tanstack/store": ["@tanstack/store@0.9.1", "", {}, "sha512-+qcNkOy0N1qSGsP7omVCW0SDrXtaDcycPqBDE726yryiA5eTDFpjBReaYjghVJwNf1pcPMyzIwTGlYjCSQR0Fg=="], + "@tanstack/router-plugin/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], "@tanstack/router-plugin/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], diff --git a/package.json b/package.json index c3e9d32f..8de90b03 100644 --- a/package.json +++ b/package.json @@ -49,11 +49,9 @@ "@scalar/hono-api-reference": "^0.9.41", "@tanstack/react-hotkeys": "^0.1.0", "@tanstack/react-query": "^5.90.21", - "@tanstack/react-query-devtools": "^5.91.3", - "@tanstack/react-router": "^1.160.2", - "@tanstack/react-router-devtools": "^1.160.2", - "@tanstack/react-router-ssr-query": "^1.160.2", - "@tanstack/react-start": "^1.160.2", + "@tanstack/react-router": "^1.161.3", + "@tanstack/react-router-ssr-query": "^1.161.3", + "@tanstack/react-start": "^1.161.3", "arktype": "^2.1.28", "better-auth": "^1.4.18", "class-variance-authority": "^0.7.1", diff --git a/vite.config.ts b/vite.config.ts index e4e0a2de..a3aa44e7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -13,6 +13,9 @@ export default defineConfig({ router: { routesDirectory: "routes", }, + importProtection: { + behavior: "error", + }, }), nitro({ preset: "bun",