Merge pull request #448 from trails-cool/chore/jobs-no-payload-generic
chore(ts): eliminate the remaining 4 `as any` casts in production code
This commit is contained in:
commit
5bbb8bfbaa
6 changed files with 31 additions and 16 deletions
|
|
@ -6,20 +6,22 @@ type KomootCreds =
|
||||||
| { mode: "public"; komootUserId: string }
|
| { mode: "public"; komootUserId: string }
|
||||||
| { mode: "authenticated"; email: string; encryptedPassword: string; komootUserId: string };
|
| { mode: "authenticated"; email: string; encryptedPassword: string; komootUserId: string };
|
||||||
|
|
||||||
interface KomootBulkImportData extends Record<string, unknown> {
|
interface KomootBulkImportData {
|
||||||
batchId: string;
|
batchId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
creds: KomootCreds;
|
creds: KomootCreds;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const komootBulkImportJob: JobDefinition<KomootBulkImportData> = {
|
export const komootBulkImportJob: JobDefinition = {
|
||||||
name: "komoot-bulk-import",
|
name: "komoot-bulk-import",
|
||||||
retryLimit: 1,
|
retryLimit: 1,
|
||||||
expireInSeconds: 1800,
|
expireInSeconds: 1800,
|
||||||
async handler(jobs) {
|
async handler(jobs) {
|
||||||
const batch = Array.isArray(jobs) ? jobs : [jobs];
|
const batch = Array.isArray(jobs) ? jobs : [jobs];
|
||||||
for (const job of batch) {
|
for (const job of batch) {
|
||||||
const { batchId, userId, creds } = job.data;
|
// pg-boss serialized payload — caller (enqueueOptional) wrote it
|
||||||
|
// as KomootBulkImportData. Narrow at the boundary.
|
||||||
|
const { batchId, userId, creds } = job.data as KomootBulkImportData;
|
||||||
logger.info({ batchId, userId }, "komoot bulk import job started");
|
logger.info({ batchId, userId }, "komoot bulk import job started");
|
||||||
await runKomootBulkImport(batchId, userId, creds);
|
await runKomootBulkImport(batchId, userId, creds);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,12 @@ import { generateGpx } from "@trails-cool/gpx";
|
||||||
export async function fitToGpx(buffer: Buffer, name: string): Promise<string | null> {
|
export async function fitToGpx(buffer: Buffer, name: string): Promise<string | null> {
|
||||||
const parsed = await new Promise<Record<string, unknown>>((resolve, reject) => {
|
const parsed = await new Promise<Record<string, unknown>>((resolve, reject) => {
|
||||||
const parser = new FitParser({ force: true });
|
const parser = new FitParser({ force: true });
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// fit-file-parser's typing requires `Buffer<ArrayBuffer>` specifically;
|
||||||
parser.parse(buffer as any, (error: unknown, data: any) => {
|
// a generic Node `Buffer` is structurally `Buffer<ArrayBufferLike>`.
|
||||||
|
// The runtime accepts either, so coerce the underlying buffer slot.
|
||||||
|
parser.parse(buffer as Buffer<ArrayBuffer>, (error, data) => {
|
||||||
if (error) reject(error);
|
if (error) reject(error);
|
||||||
else resolve(data ?? {});
|
else resolve((data ?? {}) as Record<string, unknown>);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,11 @@ export async function syncImportProviderAction(request: Request, provider: strin
|
||||||
const { default: FitParser } = await import("fit-file-parser");
|
const { default: FitParser } = await import("fit-file-parser");
|
||||||
const parsed = await new Promise<Record<string, unknown>>((resolve, reject) => {
|
const parsed = await new Promise<Record<string, unknown>>((resolve, reject) => {
|
||||||
const parser = new FitParser({ force: true });
|
const parser = new FitParser({ force: true });
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// See lib/connected-services/fit.ts for the same Buffer<ArrayBuffer>
|
||||||
parser.parse(buffer as any, (err: unknown, d: any) => (err ? reject(err) : resolve(d ?? {})));
|
// narrowing rationale.
|
||||||
|
parser.parse(buffer as Buffer<ArrayBuffer>, (err, d) =>
|
||||||
|
err ? reject(err) : resolve((d ?? {}) as Record<string, unknown>),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
const records = (parsed.records ?? []) as Array<{
|
const records = (parsed.records ?? []) as Array<{
|
||||||
position_lat?: number;
|
position_lat?: number;
|
||||||
|
|
|
||||||
|
|
@ -177,8 +177,7 @@ server.listen(port, async () => {
|
||||||
const { importBatchesSweepJob } = await import("./app/jobs/import-batches-sweep.ts");
|
const { importBatchesSweepJob } = await import("./app/jobs/import-batches-sweep.ts");
|
||||||
const { sendWelcomeEmailJob } = await import("./app/jobs/send-welcome-email.ts");
|
const { sendWelcomeEmailJob } = await import("./app/jobs/send-welcome-email.ts");
|
||||||
const { consumedJtiSweepJob } = await import("./app/jobs/consumed-jti-sweep.ts");
|
const { consumedJtiSweepJob } = await import("./app/jobs/consumed-jti-sweep.ts");
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
jobs.push(notificationsFanoutJob, notificationsPurgeJob, komootBulkImportJob, importBatchesSweepJob, sendWelcomeEmailJob, consumedJtiSweepJob);
|
||||||
jobs.push(notificationsFanoutJob, notificationsPurgeJob, komootBulkImportJob as any, importBatchesSweepJob, sendWelcomeEmailJob, consumedJtiSweepJob);
|
|
||||||
|
|
||||||
const boss = createBoss(getDatabaseUrl());
|
const boss = createBoss(getDatabaseUrl());
|
||||||
await startWorker(boss, jobs);
|
await startWorker(boss, jobs);
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,11 @@ function RouteMapInner({
|
||||||
const cameraRef = useRef<CameraRef>(null);
|
const cameraRef = useRef<CameraRef>(null);
|
||||||
|
|
||||||
const handleLongPress = useCallback(
|
const handleLongPress = useCallback(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// maplibre-react-native v11+ codegen `NativePressEvent` isn't
|
||||||
(event: any) => {
|
// re-exported as a type, so we declare the slice we use locally.
|
||||||
// v11 event shape: payload lives on `event.nativeEvent.lngLat`
|
// Payload moved from `event.geometry.coordinates` (v10) to
|
||||||
// (the old `event.geometry.coordinates` is gone).
|
// `event.nativeEvent.lngLat` in v11.
|
||||||
|
(event: { nativeEvent?: { lngLat?: readonly [number, number] } }) => {
|
||||||
const lngLat = event?.nativeEvent?.lngLat;
|
const lngLat = event?.nativeEvent?.lngLat;
|
||||||
if (Array.isArray(lngLat) && lngLat.length >= 2) {
|
if (Array.isArray(lngLat) && lngLat.length >= 2) {
|
||||||
onLongPress(lngLat[1] as number, lngLat[0] as number);
|
onLongPress(lngLat[1] as number, lngLat[0] as number);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
import type { Job } from "pg-boss";
|
import type { Job } from "pg-boss";
|
||||||
|
|
||||||
export interface JobDefinition<T extends object = object> {
|
/**
|
||||||
|
* A pg-boss job definition. The payload type is intentionally
|
||||||
|
* `unknown` at this boundary: a heterogeneous `JobDefinition[]` array
|
||||||
|
* (e.g. the one in `apps/journal/server.ts`) was previously forced to
|
||||||
|
* cast each typed job to `any` because of contravariance — a handler
|
||||||
|
* taking `Job<SpecificPayload>` is not assignable to one taking
|
||||||
|
* `Job<object>`. Handlers narrow internally instead.
|
||||||
|
*/
|
||||||
|
export interface JobDefinition {
|
||||||
name: string;
|
name: string;
|
||||||
handler: (jobs: Job<T>[]) => Promise<unknown>;
|
handler: (jobs: Job<unknown>[]) => Promise<unknown>;
|
||||||
cron?: string;
|
cron?: string;
|
||||||
retryLimit?: number;
|
retryLimit?: number;
|
||||||
expireInSeconds?: number;
|
expireInSeconds?: number;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue