style: apply prettier across the tree

One-shot prettier pass so the new format:check gate starts from a clean
baseline. Formatting only — no behaviour change. Generator-owned and
vendored artifacts stay untouched via .prettierignore.
This commit is contained in:
Marc-André Bombeck committed 2026-06-19 22:20:31 +02:00
1 parent b3fea2ef59
commit 1f4ba5939d
1000 files changed
+12130 -8385

No files matched your search

@@ -1,9 +1,6 @@
import { describe, expect, it } from "vitest";
import {
createBiomarkerSchema,
updateBiomarkerSchema,
} from "../biomarkers";
import { createBiomarkerSchema, updateBiomarkerSchema } from "../biomarkers";
describe("createBiomarkerSchema", () => {
it("accepts a minimal marker (name + unit only)", () => {
@@ -12,17 +12,16 @@ const NOW = Date.parse("2026-06-14T12:00:00.000Z");
describe("isPlausibleEntryInstant", () => {
it("accepts a recent past instant", () => {
expect(
isPlausibleEntryInstant(new Date(NOW - 60_000), { now: NOW }),
).toBe(true);
expect(isPlausibleEntryInstant(new Date(NOW - 60_000), { now: NOW })).toBe(
true,
);
});
it("accepts an instant inside the clock-skew tolerance", () => {
expect(
isPlausibleEntryInstant(
new Date(NOW + ENTRY_INSTANT_CLOCK_SKEW_MS - 1),
{ now: NOW },
),
isPlausibleEntryInstant(new Date(NOW + ENTRY_INSTANT_CLOCK_SKEW_MS - 1), {
now: NOW,
}),
).toBe(true);
});
@@ -37,10 +36,9 @@ describe("isPlausibleEntryInstant", () => {
it("rejects an instant before the 1900 far-past floor", () => {
expect(
isPlausibleEntryInstant(
new Date(ENTRY_INSTANT_FAR_PAST.getTime() - 1),
{ now: NOW },
),
isPlausibleEntryInstant(new Date(ENTRY_INSTANT_FAR_PAST.getTime() - 1), {
now: NOW,
}),
).toBe(false);
});
@@ -462,7 +462,10 @@ describe("measurement validation", () => {
const issue = parsed.error.issues.find((i) =>
i.path.includes("source"),
);
expect(issue, `expected a source-path rejection for ${source}`).toBeDefined();
expect(
issue,
`expected a source-path rejection for ${source}`,
).toBeDefined();
}
}
});
@@ -103,12 +103,12 @@ describe("medication schemas — dosesPerUnit cap 1000 + unitsPerDose 1–100",
});
it("update accepts dosesPerUnit 1000 and rejects 1001", () => {
expect(updateMedicationSchema.safeParse({ dosesPerUnit: 1000 }).success).toBe(
true,
);
expect(updateMedicationSchema.safeParse({ dosesPerUnit: 1001 }).success).toBe(
false,
);
expect(
updateMedicationSchema.safeParse({ dosesPerUnit: 1000 }).success,
).toBe(true);
expect(
updateMedicationSchema.safeParse({ dosesPerUnit: 1001 }).success,
).toBe(false);
});
it("create bounds unitsPerDose to 1–100", () => {
@@ -167,9 +167,9 @@ describe("medication schemas — dosesPerUnit cap 1000 + unitsPerDose 1–100",
unitsPerDose: v,
}).success,
).toBe(true);
expect(updateMedicationSchema.safeParse({ unitsPerDose: v }).success).toBe(
true,
);
expect(
updateMedicationSchema.safeParse({ unitsPerDose: v }).success,
).toBe(true);
}
});
@@ -196,9 +196,21 @@ describe("medication schemas — dosesPerUnit cap 1000 + unitsPerDose 1–100",
describe("glp1InventoryPostSchema — the legacy pen-ledger delta keeps ±100", () => {
it("accepts ±100 and rejects ±101", () => {
expect(glp1InventoryPostSchema.safeParse({ delta: 100, reason: "purchased" }).success).toBe(true);
expect(glp1InventoryPostSchema.safeParse({ delta: -100, reason: "damaged" }).success).toBe(true);
expect(glp1InventoryPostSchema.safeParse({ delta: 101, reason: "purchased" }).success).toBe(false);
expect(glp1InventoryPostSchema.safeParse({ delta: -101, reason: "damaged" }).success).toBe(false);
expect(
glp1InventoryPostSchema.safeParse({ delta: 100, reason: "purchased" })
.success,
).toBe(true);
expect(
glp1InventoryPostSchema.safeParse({ delta: -100, reason: "damaged" })
.success,
).toBe(true);
expect(
glp1InventoryPostSchema.safeParse({ delta: 101, reason: "purchased" })
.success,
).toBe(false);
expect(
glp1InventoryPostSchema.safeParse({ delta: -101, reason: "damaged" })
.success,
).toBe(false);
});
});
@@ -29,7 +29,8 @@ describe("createMoodEntrySchema — note + structured tagKeys (v1.8.5)", () => {
it("rejects an over-long note and an over-large tagKeys set", () => {
expect(
createMoodEntrySchema.safeParse({ ...base, note: "x".repeat(501) }).success,
createMoodEntrySchema.safeParse({ ...base, note: "x".repeat(501) })
.success,
).toBe(false);
expect(
createMoodEntrySchema.safeParse({
@@ -242,8 +242,12 @@ describe("DEFAULT_SOURCE_PRIORITY", () => {
const ladder = DEFAULT_SOURCE_PRIORITY[key];
expect(ladder).toContain("OURA");
expect(ladder).toContain("POLAR");
expect(ladder.indexOf("OURA")).toBeLessThan(ladder.indexOf("APPLE_HEALTH"));
expect(ladder.indexOf("POLAR")).toBeLessThan(ladder.indexOf("APPLE_HEALTH"));
expect(ladder.indexOf("OURA")).toBeLessThan(
ladder.indexOf("APPLE_HEALTH"),
);
expect(ladder.indexOf("POLAR")).toBeLessThan(
ladder.indexOf("APPLE_HEALTH"),
);
expect(ladder.indexOf("WHOOP")).toBeLessThan(ladder.indexOf("OURA"));
}
});
@@ -451,10 +455,7 @@ describe("getSourceLadder + getDeviceTypeLadder helpers", () => {
WEIGHT: ["scale", "watch"],
},
});
expect(getDeviceTypeLadder(resolved, "WEIGHT")).toEqual([
"scale",
"watch",
]);
expect(getDeviceTypeLadder(resolved, "WEIGHT")).toEqual(["scale", "watch"]);
});
it("getDeviceTypeLadder falls back to the user default ladder", () => {
@@ -501,9 +502,9 @@ describe("parseSourcePriority — stored-ladder reconciliation (v1.16.11)", () =
expect(resolved.metricPriority.sleep).toContain("WHOOP");
expect(resolved.metricPriority.sleep).toContain("FITBIT");
expect(resolved.metricPriority.sleep.indexOf("WHOOP")).toBeGreaterThan(3);
expect(
resolved.metricPriority.sleep.indexOf("WHOOP"),
).toBeLessThan(resolved.metricPriority.sleep.indexOf("FITBIT"));
expect(resolved.metricPriority.sleep.indexOf("WHOOP")).toBeLessThan(
resolved.metricPriority.sleep.indexOf("FITBIT"),
);
});
it("leaves a complete stored ladder byte-identical", () => {
@@ -529,13 +530,7 @@ describe("parseSourcePriority — duplicate tolerance (v1.16.11)", () => {
it("collapses duplicates so a resolved ladder can never overflow the schema cap", () => {
const resolved = parseSourcePriority({
metricPriority: {
weight: [
"MANUAL",
"MANUAL",
"WITHINGS",
"MANUAL",
"WITHINGS",
],
weight: ["MANUAL", "MANUAL", "WITHINGS", "MANUAL", "WITHINGS"],
},
});
const ladder = resolved.metricPriority.weight;
+7 -13
View File
@@ -135,9 +135,7 @@ describe("workoutHrSamplesSchema", () => {
it("rejects an out-of-range heart rate", () => {
expect(() =>
workoutHrSamplesSchema.parse([
{ t: "2026-05-14T07:00:00.000Z", hr: 5 },
]),
workoutHrSamplesSchema.parse([{ t: "2026-05-14T07:00:00.000Z", hr: 5 }]),
).toThrow();
});
@@ -155,13 +153,10 @@ describe("workoutHrSamplesSchema", () => {
});
it("accepts a series exactly at the cap", () => {
const atCap = Array.from(
{ length: MAX_WORKOUT_HR_SAMPLES },
(_v, i) => ({
t: new Date(Date.UTC(2026, 4, 14, 7, 0, 0) + i * 1000).toISOString(),
hr: 140,
}),
);
const atCap = Array.from({ length: MAX_WORKOUT_HR_SAMPLES }, (_v, i) => ({
t: new Date(Date.UTC(2026, 4, 14, 7, 0, 0) + i * 1000).toISOString(),
hr: 140,
}));
expect(workoutHrSamplesSchema.parse(atCap)).toHaveLength(
MAX_WORKOUT_HR_SAMPLES,
);
@@ -370,9 +365,8 @@ describe("createBatchWorkoutSchema", () => {
});
it("accepts a batch at the cap (100 workouts)", () => {
const workouts = Array.from(
{ length: MAX_WORKOUTS_PER_BATCH },
(_, i) => minimalRun(`hk-uuid-${i}`),
const workouts = Array.from({ length: MAX_WORKOUTS_PER_BATCH }, (_, i) =>
minimalRun(`hk-uuid-${i}`),
);
const parsed = createBatchWorkoutSchema.parse({ workouts });
expect(parsed.workouts).toHaveLength(MAX_WORKOUTS_PER_BATCH);
+4 -7
View File
@@ -44,13 +44,10 @@ export const consentPostBody = z.object({
artefact: z
.string()
.min(1, "artefact must not be empty")
.refine(
(value) => Buffer.byteLength(value, "utf8") <= ARTEFACT_MAX_BYTES,
{ message: "artefact exceeds 64 KB cap (UTF-8 byte length)" },
),
signedAt: z.iso
.datetime({ offset: true })
.transform((s) => new Date(s)),
.refine((value) => Buffer.byteLength(value, "utf8") <= ARTEFACT_MAX_BYTES, {
message: "artefact exceeds 64 KB cap (UTF-8 byte length)",
}),
signedAt: z.iso.datetime({ offset: true }).transform((s) => new Date(s)),
});
export type ConsentPostBody = z.infer<typeof consentPostBody>;
+2 -6
View File
@@ -117,16 +117,12 @@ export const illnessEpisodeResolveSchema = z.object({
resolvedAt: boundedInstant.optional(),
});
export type IllnessEpisodeResolve = z.infer<
typeof illnessEpisodeResolveSchema
>;
export type IllnessEpisodeResolve = z.infer<typeof illnessEpisodeResolveSchema>;
/** History/list query — newest-first, bounded. */
export const illnessEpisodeListQuerySchema = z.object({
limit: z.coerce.number().int().min(1).max(100).optional(),
includeResolved: z
.union([z.literal("true"), z.literal("false")])
.optional(),
includeResolved: z.union([z.literal("true"), z.literal("false")]).optional(),
});
/* ── day-log capture (one row per day of an episode) ─────────────── */
+1 -6
View File
@@ -18,12 +18,7 @@ export const INVITE_MAX_USES_CAP = 50;
export const inviteCreateSchema = z.object({
/** Lifetime in days. The admin UI offers 7 / 14 / 30; the API accepts
* any integer 1–30 so an operator can script shorter invites. */
expiresInDays: z
.number()
.int()
.min(1)
.max(INVITE_MAX_TTL_DAYS)
.default(7),
expiresInDays: z.number().int().min(1).max(INVITE_MAX_TTL_DAYS).default(7),
/** How many signups this invite admits. Default: single-use. */
maxUses: z.number().int().min(1).max(INVITE_MAX_USES_CAP).default(1),
});
+4 -7
View File
@@ -36,12 +36,7 @@ const takenAtField = validateEntryInstant(
);
/** Trimmed, bounded free-text field with a min-length guard. */
const requiredText = (max: number) =>
z
.string()
.trim()
.min(1)
.max(max);
const requiredText = (max: number) => z.string().trim().min(1).max(max);
const optionalPanel = z
.string()
@@ -160,7 +155,9 @@ export function isInvertedRange(
effectiveHigh: number | null,
): boolean {
return (
effectiveLow !== null && effectiveHigh !== null && effectiveLow > effectiveHigh
effectiveLow !== null &&
effectiveHigh !== null &&
effectiveLow > effectiveHigh
);
}
+6 -12
View File
@@ -183,10 +183,7 @@ export const measurementSourceEnum = z.enum([
* POST validates `source` against this set; the batch route mirrors the same
* exclusion with its own narrower `{APPLE_HEALTH, MANUAL}` allowlist.
*/
export const WRITABLE_MEASUREMENT_SOURCES = [
"MANUAL",
"APPLE_HEALTH",
] as const;
export const WRITABLE_MEASUREMENT_SOURCES = ["MANUAL", "APPLE_HEALTH"] as const;
/**
* v1.10.0 QA — the Zod enum the single-entry POST validates `source` against.
@@ -698,14 +695,11 @@ export const listMeasurementsSchema = z
// the admin drain route, so a malformed CLI invocation has the
// same blast radius. Reject the impossible shapes at the
// validator instead.
.refine(
(s) => {
const parsed = new Date(`${s}T00:00:00Z`);
if (Number.isNaN(parsed.getTime())) return false;
return s === parsed.toISOString().slice(0, 10);
},
"dayKey must be a real calendar date (YYYY-MM-DD)",
)
.refine((s) => {
const parsed = new Date(`${s}T00:00:00Z`);
if (Number.isNaN(parsed.getTime())) return false;
return s === parsed.toISOString().slice(0, 10);
}, "dayKey must be a real calendar date (YYYY-MM-DD)")
.optional(),
})
.refine(
+25 -26
View File
@@ -85,7 +85,8 @@ export const MEDICATION_CATEGORY_VALUES = [
"ANTIBIOTIC",
"OTHER",
] as const;
export type MedicationCategoryValue = (typeof MEDICATION_CATEGORY_VALUES)[number];
export type MedicationCategoryValue =
(typeof MEDICATION_CATEGORY_VALUES)[number];
/**
* v1.4.25 W4d — Prisma-level treatment class. Orthogonal to
@@ -116,9 +117,7 @@ export const UNITS_PER_DOSE_FRACTIONS = [
export const UNITS_PER_DOSE_MAX_WHOLE = 100;
export function isSupportedUnitsPerDose(value: number): boolean {
if (
(UNITS_PER_DOSE_FRACTIONS as readonly number[]).includes(value)
) {
if ((UNITS_PER_DOSE_FRACTIONS as readonly number[]).includes(value)) {
return true;
}
return (
@@ -168,7 +167,9 @@ export const doseWindowEntrySchema = z
timeOfDay: z
.string()
.regex(timeRegex, "Format: HH:mm")
.describe("The dose time this window applies to (matches a `timesOfDay` entry)."),
.describe(
"The dose time this window applies to (matches a `timesOfDay` entry).",
),
start: z
.string()
.regex(timeRegex, "Format: HH:mm")
@@ -176,7 +177,9 @@ export const doseWindowEntrySchema = z
end: z
.string()
.regex(timeRegex, "Format: HH:mm")
.describe("On-time band upper bound (HH:mm, user local). Must be >= `start`."),
.describe(
"On-time band upper bound (HH:mm, user local). Must be >= `start`.",
),
})
.refine((w) => hhmmToMinutes(w.start) <= hhmmToMinutes(w.end), {
message: "Window start must be on or before end (same day)",
@@ -241,7 +244,7 @@ export const scheduleSchema = z
.string()
.max(50)
.optional()
.describe("Optional human label (e.g. \"Morning\", \"Evening\")."),
.describe('Optional human label (e.g. "Morning", "Evening").'),
dose: z
.string()
.max(50)
@@ -336,7 +339,7 @@ export const scheduleSchema = z
.max(52)
.optional()
.describe(
"Cyclic \"on\" weeks (1..52). Required when `scheduleType` is CYCLIC; ignored otherwise.",
'Cyclic "on" weeks (1..52). Required when `scheduleType` is CYCLIC; ignored otherwise.',
),
/** v1.7.0 — cyclic "off" weeks. Required when `scheduleType === "CYCLIC"`. */
cyclicOffWeeks: z
@@ -346,7 +349,7 @@ export const scheduleSchema = z
.max(52)
.optional()
.describe(
"Cyclic \"off\" weeks (0..52). Required when `scheduleType` is CYCLIC; ignored otherwise.",
'Cyclic "off" weeks (0..52). Required when `scheduleType` is CYCLIC; ignored otherwise.',
),
/**
* v1.15.18 — per-dose configurable on-time intake window (the maintainer's
@@ -364,14 +367,10 @@ export const scheduleSchema = z
"Per-dose on-time intake windows. Each `{ timeOfDay, start, end }` HH:mm triple sets the explicit on-time band for the matching dose time; a dose time with no entry keeps the symmetric ±1h default. `timeOfDay` must match one of `timesOfDay` (or `windowStart`); `start <= end`. Up to 8 entries. Absent leaves every slot on the default derivation. The late tail stays cadence-derived.",
),
})
.refine(
(s) => !(s.rrule && s.rollingIntervalDays),
{
message:
"A schedule can be calendar-anchored (rrule) or rolling, not both",
path: ["rrule"],
},
)
.refine((s) => !(s.rrule && s.rollingIntervalDays), {
message: "A schedule can be calendar-anchored (rrule) or rolling, not both",
path: ["rrule"],
})
.refine(
(s) =>
s.scheduleType !== "CYCLIC" ||
@@ -409,12 +408,15 @@ export const scheduleSchema = z
// (mirrors the engine's `effectiveTimesOfDay`).
if (!s.doseWindows || s.doseWindows.length === 0) return true;
const times = new Set(
s.timesOfDay && s.timesOfDay.length > 0 ? s.timesOfDay : [s.windowStart],
s.timesOfDay && s.timesOfDay.length > 0
? s.timesOfDay
: [s.windowStart],
);
return s.doseWindows.every((w) => times.has(w.timeOfDay));
},
{
message: "Each doseWindows.timeOfDay must match one of the schedule's timesOfDay",
message:
"Each doseWindows.timeOfDay must match one of the schedule's timesOfDay",
path: ["doseWindows"],
},
)
@@ -614,16 +616,14 @@ export const createMedicationSchema = z
path: ["asNeeded"],
})
.refine(
(b) =>
b.asNeeded === true || (!!b.schedules && b.schedules.length >= 1),
(b) => b.asNeeded === true || (!!b.schedules && b.schedules.length >= 1),
{
message: "Mindestens ein Zeitfenster",
path: ["schedules"],
},
)
.refine(
(b) =>
b.asNeeded !== true || !b.schedules || b.schedules.length === 0,
(b) => b.asNeeded !== true || !b.schedules || b.schedules.length === 0,
{
message: AS_NEEDED_SCHEDULES_MESSAGE,
path: ["schedules"],
@@ -632,7 +632,7 @@ export const createMedicationSchema = z
.meta({
id: "CreateMedicationRequest",
description:
"Create-medication body. The route enforces the v1.5 cross-field invariants on top of the per-schedule `rrule_xor_rolling` Zod refine: a `oneShot:true` medication may carry at most one schedule and that schedule must not declare a recurrence; `endsOn` is normalised to equal `startsOn` for one-shot doses; a recurring schedule with no `rrule`, `rollingIntervalDays`, or legacy `daysOfWeek` defaults to `rrule = \"FREQ=DAILY\"`; and `timesOfDay` is dual-written from `windowStart` when the caller omits it. v1.16.11 — `asNeeded: true` creates a PRN medication with ZERO schedules (`schedules` must be absent or empty, 422 otherwise); a scheduled medication still requires at least one schedule entry.",
'Create-medication body. The route enforces the v1.5 cross-field invariants on top of the per-schedule `rrule_xor_rolling` Zod refine: a `oneShot:true` medication may carry at most one schedule and that schedule must not declare a recurrence; `endsOn` is normalised to equal `startsOn` for one-shot doses; a recurring schedule with no `rrule`, `rollingIntervalDays`, or legacy `daysOfWeek` defaults to `rrule = "FREQ=DAILY"`; and `timesOfDay` is dual-written from `windowStart` when the caller omits it. v1.16.11 — `asNeeded: true` creates a PRN medication with ZERO schedules (`schedules` must be absent or empty, 422 otherwise); a scheduled medication still requires at least one schedule entry.',
});
export const updateMedicationSchema = z
@@ -742,8 +742,7 @@ export const updateMedicationSchema = z
path: ["asNeeded"],
})
.refine(
(b) =>
b.asNeeded !== true || !b.schedules || b.schedules.length === 0,
(b) => b.asNeeded !== true || !b.schedules || b.schedules.length === 0,
{
message: AS_NEEDED_SCHEDULES_MESSAGE,
path: ["schedules"],
+5 -1
View File
@@ -269,7 +269,11 @@ export const webhookSettingsSchema = z.object({
* the only per-user value is the recipient address.
*/
export const emailSettingsSchema = z.object({
recipient: z.email("Ungültige E-Mail-Adresse").max(254).optional().or(z.literal("")),
recipient: z
.email("Ungültige E-Mail-Adresse")
.max(254)
.optional()
.or(z.literal("")),
enabled: z.boolean(),
});
+7 -7
View File
@@ -54,7 +54,8 @@ export const scheduleRevisionCreateSchema = z
),
})
.refine(
(body) => new Date(body.validFrom).getTime() < new Date(body.validUntil).getTime(),
(body) =>
new Date(body.validFrom).getTime() < new Date(body.validUntil).getTime(),
{
message: "validFrom must lie before validUntil",
path: ["validUntil"],
@@ -80,12 +81,11 @@ export type ScheduleRevisionCreateInput = z.infer<
* row. Field-for-field identical to the create schema — only the meta
* id differs so the OpenAPI document names the two requests apart.
*/
export const scheduleRevisionUpdateSchema = scheduleRevisionCreateSchema
.meta({
id: "UpdateScheduleRevisionRequest",
description:
"Corrected schedule-era payload: replacement era bounds plus the daily dose times that were live during the era.",
});
export const scheduleRevisionUpdateSchema = scheduleRevisionCreateSchema.meta({
id: "UpdateScheduleRevisionRequest",
description:
"Corrected schedule-era payload: replacement era bounds plus the daily dose times that were live during the era.",
});
export type ScheduleRevisionUpdateInput = z.infer<
typeof scheduleRevisionUpdateSchema
+1 -6
View File
@@ -22,12 +22,7 @@ export const seriesBatchQuerySchema = z
.map((t) => t.trim())
.filter((t) => t.length > 0),
)
.pipe(
z
.array(measurementTypeEnum)
.min(1)
.max(SERIES_BATCH_MAX_TYPES),
)
.pipe(z.array(measurementTypeEnum).min(1).max(SERIES_BATCH_MAX_TYPES))
.describe(
"Comma-separated list of MeasurementType values (1.." +
`${SERIES_BATCH_MAX_TYPES}). Each is read as a daily series through ` +
+29 -10
View File
@@ -51,9 +51,7 @@ import {
// The server logging module registers the real `annotate` on first import
// (via `registerSourcePriorityParseObserver`); the browser leaves it as
// the no-op default and never traces `node:async_hooks`.
type ParseFailureObserver = (entry: {
meta: Record<string, unknown>;
}) => void;
type ParseFailureObserver = (entry: { meta: Record<string, unknown> }) => void;
let parseFailureObserver: ParseFailureObserver = () => {};
/**
@@ -142,10 +140,12 @@ export type SourcePriorityMetricKey =
// from `SOURCE_PRIORITY_METRIC_KEYS` so the schema and the enum cannot
// drift. Adding a new metric class is now a single-line constant edit
// instead of a two-place change that the lint chain doesn't enforce.
const metricPriorityShape: Record<SourcePriorityMetricKey, typeof metricSourceLadder> =
Object.fromEntries(
SOURCE_PRIORITY_METRIC_KEYS.map((key) => [key, metricSourceLadder]),
) as Record<SourcePriorityMetricKey, typeof metricSourceLadder>;
const metricPriorityShape: Record<
SourcePriorityMetricKey,
typeof metricSourceLadder
> = Object.fromEntries(
SOURCE_PRIORITY_METRIC_KEYS.map((key) => [key, metricSourceLadder]),
) as Record<SourcePriorityMetricKey, typeof metricSourceLadder>;
const metricPriorityObjectSchema = z.object(metricPriorityShape).partial();
@@ -222,7 +222,14 @@ export const DEFAULT_SOURCE_PRIORITY: Required<MetricPriority> = {
// steps) but as legitimate sources above MANUAL, so an Oura/Polar-only day
// is not dropped when another source coexists.
steps: ["APPLE_HEALTH", "WITHINGS", "FITBIT", "OURA", "POLAR", "MANUAL"],
activeEnergy: ["APPLE_HEALTH", "WITHINGS", "FITBIT", "OURA", "POLAR", "MANUAL"],
activeEnergy: [
"APPLE_HEALTH",
"WITHINGS",
"FITBIT",
"OURA",
"POLAR",
"MANUAL",
],
walkingRunningDistance: [
"APPLE_HEALTH",
"WITHINGS",
@@ -263,7 +270,15 @@ export const DEFAULT_SOURCE_PRIORITY: Required<MetricPriority> = {
// Withings ScanWatch pulse-ox is the primary SpO2 sensor; WHOOP second,
// Fitbit third, Oura's ring fourth. v1.17.1 — Polar Elixir pulse-ox slots
// below the established straps and above the iPhone-relay / manual sources.
spo2: ["WITHINGS", "WHOOP", "FITBIT", "OURA", "POLAR", "APPLE_HEALTH", "MANUAL"],
spo2: [
"WITHINGS",
"WHOOP",
"FITBIT",
"OURA",
"POLAR",
"APPLE_HEALTH",
"MANUAL",
],
vo2Max: ["WITHINGS", "APPLE_HEALTH", "FITBIT", "MANUAL"],
// v1.11.0 — new WHOOP-overlapping keys. ScanWatch dermal reading is the
// primary skin-temperature sensor; WHOOP's strap is second, Fitbit third,
@@ -370,7 +385,11 @@ export function parseSourcePriority(raw: unknown): ResolvedSourcePriority {
deviceTypePriority,
...flatMetricLadder
} = parsed.data;
return buildResolved(flatMetricLadder, nested ?? {}, deviceTypePriority ?? {});
return buildResolved(
flatMetricLadder,
nested ?? {},
deviceTypePriority ?? {},
);
}
function buildResolved(
+1 -4
View File
@@ -226,10 +226,7 @@ export const MAX_WORKOUTS_PER_BATCH = 100;
* workout; an optional nested `route` carries the GeoJSON LineString.
*/
export const createBatchWorkoutSchema = z.object({
workouts: z
.array(createWorkoutSchema)
.min(1)
.max(MAX_WORKOUTS_PER_BATCH),
workouts: z.array(createWorkoutSchema).min(1).max(MAX_WORKOUTS_PER_BATCH),
});
export type CreateBatchWorkoutInput = z.infer<typeof createBatchWorkoutSchema>;