From 62489fb80e73fad99f776ec17e7e1e3aeabb1a8e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 16:01:46 +0000 Subject: [PATCH] Upgrade zod to 4.5.2 and compile hot-path schemas Bumps zod for the 4.5 memory-footprint improvements (lazy method memoization) and adopts z.compile() on the validation schemas used for device upload endpoints and the logbook config, so repeated parses skip the interpreter for the fast path. Also hoists traccarUpload's schemas to module scope so they are only constructed (and compiled) once instead of on every request. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01NCUNhjSkdTDxBbdC9FHpi6 --- website/app/logbook/config.ts | 4 +- website/app/routes/api/appUpload.ts | 36 +++++----- website/app/routes/api/flespiUpload.ts | 34 ++++----- website/app/routes/api/traccarUpload.ts | 70 +++++++++--------- website/package-lock.json | 95 ++----------------------- website/package.json | 4 +- 6 files changed, 83 insertions(+), 160 deletions(-) diff --git a/website/app/logbook/config.ts b/website/app/logbook/config.ts index 76777df..97b488b 100644 --- a/website/app/logbook/config.ts +++ b/website/app/logbook/config.ts @@ -68,7 +68,7 @@ const voltageSourceSchema = z.object({ bands: z.array(voltageBandSchema).min(1).max(10), }); -export const logbookConfigSchema = z.object({ +export const logbookConfigSchema = z.compile(z.object({ stationary: z .object({ /** A run of fixes staying within this distance of its first fix counts as stopped. */ @@ -138,7 +138,7 @@ export const logbookConfigSchema = z.object({ .default({ noiseFloorMeters: LOGBOOK_DEFAULT_DISTANCE_NOISE_FLOOR_METERS, }), -}); +})); export type LogbookConfig = z.infer; export type LogbookVoltageSource = LogbookConfig["voltage"]["sources"][number]; diff --git a/website/app/routes/api/appUpload.ts b/website/app/routes/api/appUpload.ts index bdd2e64..a9cbe1e 100644 --- a/website/app/routes/api/appUpload.ts +++ b/website/app/routes/api/appUpload.ts @@ -7,25 +7,27 @@ import { getH3IndexForLocation, toUtcDateString } from "~/utils/h3"; export const loader = async ({}: Route.LoaderArgs) => redirect("/"); -const validator = zod.object({ - location: zod.object({ - coords: zod.object({ - accuracy: zod.number(), - longitude: zod.number(), - altitude: zod.number(), - heading: zod.number(), - latitude: zod.number(), - altitudeAccuracy: zod.number(), - speed: zod.number(), +const validator = zod.compile( + zod.object({ + location: zod.object({ + coords: zod.object({ + accuracy: zod.number(), + longitude: zod.number(), + altitude: zod.number(), + heading: zod.number(), + latitude: zod.number(), + altitudeAccuracy: zod.number(), + speed: zod.number(), + }), + mocked: zod.boolean(), + timestamp: zod.number(), + }), + battery: zod.object({ + percentage: zod.number(), + charging: zod.boolean(), }), - mocked: zod.boolean(), - timestamp: zod.number(), - }), - battery: zod.object({ - percentage: zod.number(), - charging: zod.boolean(), }), -}); +); export const action = async ({ context, request }: Route.ActionArgs) => { return data({}, 200); diff --git a/website/app/routes/api/flespiUpload.ts b/website/app/routes/api/flespiUpload.ts index 994cb9f..3c3a279 100644 --- a/website/app/routes/api/flespiUpload.ts +++ b/website/app/routes/api/flespiUpload.ts @@ -8,22 +8,24 @@ import { eq, inArray } from "drizzle-orm"; import type { Route } from "./+types/flespiUpload"; import { getH3IndexForLocation, toUtcDateString } from "~/utils/h3"; -const rawMessageSchema = zod.record(zod.string(), zod.unknown()); - -const normalizedMessageSchema = zod.object({ - timestamp: zod.coerce.number().positive(), - latitude: zod.coerce.number().min(-90).max(90), - longitude: zod.coerce.number().min(-180).max(180), - altitude: zod.coerce.number().optional(), - speed: zod.coerce.number().min(0).optional(), - heading: zod.coerce.number().min(0).max(360).optional(), - accuracy: zod.coerce.number().min(0).optional(), - batteryPercentage: zod.coerce.number().min(0).max(100).optional(), - batteryCharging: zod.coerce.boolean().optional(), - batteryVoltage: zod.coerce.number().min(0).optional(), - identifier: zod.string().min(1), - deviceTypeId: zod.coerce.string().min(1).optional(), -}); +const rawMessageSchema = zod.compile(zod.record(zod.string(), zod.unknown())); + +const normalizedMessageSchema = zod.compile( + zod.object({ + timestamp: zod.coerce.number().positive(), + latitude: zod.coerce.number().min(-90).max(90), + longitude: zod.coerce.number().min(-180).max(180), + altitude: zod.coerce.number().optional(), + speed: zod.coerce.number().min(0).optional(), + heading: zod.coerce.number().min(0).max(360).optional(), + accuracy: zod.coerce.number().min(0).optional(), + batteryPercentage: zod.coerce.number().min(0).max(100).optional(), + batteryCharging: zod.coerce.boolean().optional(), + batteryVoltage: zod.coerce.number().min(0).optional(), + identifier: zod.string().min(1), + deviceTypeId: zod.coerce.string().min(1).optional(), + }), +); const INSERT_CHUNK_SIZE = 200; diff --git a/website/app/routes/api/traccarUpload.ts b/website/app/routes/api/traccarUpload.ts index 0f2e5fc..26dc306 100644 --- a/website/app/routes/api/traccarUpload.ts +++ b/website/app/routes/api/traccarUpload.ts @@ -5,8 +5,8 @@ import { Events } from "~/database/schema/Events"; import type { Route } from "./+types/traccarUpload"; import { getH3IndexForLocation, toUtcDateString } from "~/utils/h3"; -export const loader = async ({ context, request }: Route.LoaderArgs) => { - const getRequestParameters = zod.object({ +const getRequestParameters = zod.compile( + zod.object({ name: zod.string().optional(), uniqueId: zod.string().optional(), status: zod.string().optional(), @@ -25,7 +25,42 @@ export const loader = async ({ context, request }: Route.LoaderArgs) => { address: zod.string().optional(), attributes: zod.string().optional(), gprmc: zod.string().optional(), - }); + }), +); + +const postPayloadSchema = zod.compile( + zod.object({ + event: zod.object({ + id: zod.coerce.number(), + attributes: zod.object({}).optional(), + deviceId: zod.coerce.number(), + type: zod.string(), + eventTime: zod.string(), + positionId: zod.coerce.number(), + geofenceId: zod.coerce.number(), + maintenanceId: zod.coerce.number(), + }), + device: zod.object({ + id: zod.coerce.number(), + attributes: zod.object({}).optional(), + groupId: zod.coerce.number(), + calendarId: zod.coerce.number(), + name: zod.string(), + uniqueId: zod.string(), + status: zod.string(), + lastUpdate: zod.string(), + positionId: zod.coerce.number(), + phone: zod.string().optional(), + model: zod.string().optional(), + contact: zod.string().optional(), + category: zod.string().optional(), + disabled: zod.string(), + expirationTime: zod.string().optional(), + }), + }), +); + +export const loader = async ({ context, request }: Route.LoaderArgs) => { // Get parameters from the request const url = new URL(request.url); console.log( @@ -77,35 +112,6 @@ export const loader = async ({ context, request }: Route.LoaderArgs) => { export const action = async ({ context, request }: Route.ActionArgs) => { if (request.method === "POST") { - const postPayloadSchema = zod.object({ - event: zod.object({ - id: zod.coerce.number(), - attributes: zod.object({}).optional(), - deviceId: zod.coerce.number(), - type: zod.string(), - eventTime: zod.string(), - positionId: zod.coerce.number(), - geofenceId: zod.coerce.number(), - maintenanceId: zod.coerce.number(), - }), - device: zod.object({ - id: zod.coerce.number(), - attributes: zod.object({}).optional(), - groupId: zod.coerce.number(), - calendarId: zod.coerce.number(), - name: zod.string(), - uniqueId: zod.string(), - status: zod.string(), - lastUpdate: zod.string(), - positionId: zod.coerce.number(), - phone: zod.string().optional(), - model: zod.string().optional(), - contact: zod.string().optional(), - category: zod.string().optional(), - disabled: zod.string(), - expirationTime: zod.string().optional(), - }), - }); let payload: unknown; try { payload = await request.json(); diff --git a/website/package-lock.json b/website/package-lock.json index b58d1cf..6f916ce 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -30,7 +30,7 @@ "react-router": "^8.1.0", "recharts": "^3.0.0", "remix-utils": "^10.0.0", - "zod": "^4.4.3", + "zod": "^4.5.2", "zod-form-data": "^2.0.5" }, "devDependencies": { @@ -1717,9 +1717,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1737,9 +1734,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1757,9 +1751,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1777,9 +1768,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1797,9 +1785,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1817,9 +1802,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1837,9 +1819,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1857,9 +1836,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1877,9 +1853,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1903,9 +1876,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1929,9 +1899,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1955,9 +1922,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1981,9 +1945,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2007,9 +1968,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2033,9 +1991,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2059,9 +2014,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2575,9 +2527,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2592,9 +2541,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2609,9 +2555,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2626,9 +2569,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2643,9 +2583,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2660,9 +2597,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2677,9 +2611,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2694,9 +2625,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2711,9 +2639,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2728,9 +2653,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2745,9 +2667,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2762,9 +2681,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2779,9 +2695,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7558,9 +7471,9 @@ } }, "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.2.tgz", + "integrity": "sha512-XkYXCol10+ba/6F/cueWV+TezUeOqXW0hdeJt5CdXjTYeAgAQg5N03RQdJ80mhfFE72+pblvYMW4wy2Qp4Qbrg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/website/package.json b/website/package.json index 3b7ec87..b7b484b 100644 --- a/website/package.json +++ b/website/package.json @@ -34,9 +34,9 @@ "react-dom": "^19.2.7", "react-leaflet": "^5.0.0", "react-router": "^8.1.0", - "remix-utils": "^10.0.0", "recharts": "^3.0.0", - "zod": "^4.4.3", + "remix-utils": "^10.0.0", + "zod": "^4.5.2", "zod-form-data": "^2.0.5" }, "devDependencies": {