Photo implements one method per PhotoExif field
check / check (push) Successful in 1m22s

Photo now implements a mapped type with one method per PhotoExif field,
each taking exif()'s options and giving that field's type, so the
build's type check fails when PhotoExif has a field Photo has no method
for, whatever the test fixtures hold. The test comment and TODO.md say
so.

Model: opus-5-5
This commit is contained in:
2026-10-01 21:23:35 +00:00
parent f2f3395f94
commit 1ede66e5c0
3 changed files with 16 additions and 3 deletions
+4 -2
View File
@@ -30,8 +30,10 @@ declares one.
`dateTimeOriginal()`, `offsetTimeOriginal()`, `exposureTime()`, `fNumber()`, `dateTimeOriginal()`, `offsetTimeOriginal()`, `exposureTime()`, `fNumber()`,
`iso()`, `focalLength()`, `orientation()`, `gpsLatitude()`, `gpsLongitude()` `iso()`, `focalLength()`, `orientation()`, `gpsLatitude()`, `gpsLongitude()`
and `gpsAltitude()` (issue 148). Each calls `exif()` and returns its one and `gpsAltitude()` (issue 148). Each calls `exif()` and returns its one
field, or undefined when the file lacks it. A test checks, on the JPEG and the field, or undefined when the file lacks it. `Photo` implements a type with one
HEIC, that every field `exif()` returns has a method giving the same value. method per `PhotoExif` field, so the build's type check fails when a field has
no method. A test checks, on the JPEG and the HEIC, that each method gives the
same value as `exif()`.
- 2026-10-01: Each original's save path is - 2026-10-01: Each original's save path is
`YYYY/YYYY-MM/YYYY-MM-DD/YYYY-MM-DD.<fileID>.<ext>` under the library's `YYYY/YYYY-MM/YYYY-MM-DD/YYYY-MM-DD.<fileID>.<ext>` under the library's
+8 -1
View File
@@ -42,11 +42,18 @@ const byNewest = (a: PhotoRecord, b: PhotoRecord): number =>
const byNewestAlbum = (a: AlbumRecord, b: AlbumRecord): number => const byNewestAlbum = (a: AlbumRecord, b: AlbumRecord): number =>
b.updationTime - a.updationTime || b.collectionID - a.collectionID; b.updationTime - a.updationTime || b.collectionID - a.collectionID;
// A method for each `PhotoExif` field, named after it, taking the options
// `exif()` takes and giving that field. `Photo` implements it, so the build's
// type check fails when `PhotoExif` has a field `Photo` has no method for.
type PhotoExifMethods = {
[K in keyof PhotoExif]-?: (opts?: ContentOptions) => Promise<PhotoExif[K]>;
};
// A single photo. Field access mirrors `PhotoRecord`; `record()` returns the // A single photo. Field access mirrors `PhotoRecord`; `record()` returns the
// underlying plain record for callers that need the IPC-safe value. `file` is // underlying plain record for callers that need the IPC-safe value. `file` is
// the membership the record is read from, so the save path carries the date of // the membership the record is read from, so the save path carries the date of
// `takenAt` and stays known after a refresh removes the file from the library. // `takenAt` and stays known after a refresh removes the file from the library.
export class Photo { export class Photo implements PhotoExifMethods {
constructor( constructor(
private readonly rec: PhotoRecord, private readonly rec: PhotoRecord,
private readonly file: EnteFile, private readonly file: EnteFile,
+4
View File
@@ -686,6 +686,10 @@ describe("Photo save path, local copy, content and EXIF", () => {
await lib.close(); await lib.close();
}); });
// The build's type check, not this test, makes sure `Photo` has a method
// for every `PhotoExif` field, whatever the fixtures hold: `Photo`
// implements a type with one method per field. This test checks that each
// method gives the same value as exif().
it.each([ it.each([
["JPEG", JPEG_WITH_EXIF], ["JPEG", JPEG_WITH_EXIF],
["HEIC", HEIC_WITH_EXIF], ["HEIC", HEIC_WITH_EXIF],