chore(deps): update dependency eslint-plugin-unicorn to v78 (#32588)

Co-authored-by: Jason Rasmussen <jason@rasm.me>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
(cherry picked from commit 72e2bc8b0bc4a7e0a84ac16c0bde7f5e538729fb)
This commit is contained in:
renovate[bot]
2026-10-28 20:41:28 +03:00
committed by Codex
parent 3127dbec71
commit 7fb7699c06
141 changed files with 654 additions and 761 deletions
+1 -1
View File
@@ -156,7 +156,7 @@
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-import-x": "^4.17.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^75.0.0",
"eslint-plugin-unicorn": "^78.0.0",
"globals": "^17.0.0",
"mock-fs": "^5.2.0",
"pngjs": "^7.0.0",
+5 -7
View File
@@ -28,14 +28,12 @@ export async function configureExpress(
ssr,
}: {
/**
* Whether to allow swagger module to write to the specs.json
* This is not desirable when the API is not available
* @default true
*/
Whether to allow swagger module to write to the specs.json
This is not desirable when the API is not available
@default true
*/
permitSwaggerWrite?: boolean;
/**
* Service to use for server-side rendering
*/
/** Service to use for server-side rendering */
ssr: typeof ApiService | typeof MaintenanceWorkerService;
},
) {
@@ -102,7 +102,7 @@ export class PromptConfirmMoveQuestions {
message: 'Do you want to proceed? [Y/n]',
name: 'value',
})
value(value: string): boolean {
return ['yes', 'y'].includes((value || 'y').toLowerCase());
value(value: string = 'y'): boolean {
return ['yes', 'y'].includes(value.toLowerCase());
}
}
+11 -11
View File
@@ -32,11 +32,11 @@ export const UpdatedAtTrigger = (name: string) =>
export const DATABASE_PARAMETER_CHUNK_SIZE = 65_500;
/**
* Chunks an array or set into smaller collections of the same type and specified size.
*
* @param collection The collection to chunk.
* @param size The size of each chunk.
*/
Chunks an array or set into smaller collections of the same type and specified size.
@param collection The collection to chunk.
@param size The size of each chunk.
*/
function chunks<T>(collection: Array<T>, size: number): Array<Array<T>>;
function chunks<T>(collection: Set<T>, size: number): Array<Set<T>>;
function chunks<T>(collection: Array<T> | Set<T>, size: number): Array<Array<T>> | Array<Set<T>> {
@@ -60,12 +60,12 @@ function chunks<T>(collection: Array<T> | Set<T>, size: number): Array<Array<T>>
}
/**
* Wraps a method that takes a collection of parameters and sequentially calls it with chunks of the collection,
* to overcome the maximum number of parameters allowed by the database driver.
*
* @param options.paramIndex The index of the function parameter to chunk. Defaults to 0.
* @param options.flatten Whether to flatten the results. Defaults to false.
*/
Wraps a method that takes a collection of parameters and sequentially calls it with chunks of the collection,
to overcome the maximum number of parameters allowed by the database driver.
@param options.paramIndex The index of the function parameter to chunk. Defaults to 0.
@param options.flatten Whether to flatten the results. Defaults to false.
*/
export function Chunked(
options: { paramIndex?: number; chunkSize?: number; mergeFn?: (results: any) => any } = {},
): MethodDecorator {
+6 -6
View File
@@ -8,9 +8,9 @@ import { JsonParsed, isoDatetimeToDate, stringToBool } from 'src/validation.js';
export enum AssetMediaSize {
Original = 'original',
/**
* An full-sized image extracted/converted from non-web-friendly formats like RAW/HIF.
* or otherwise the original image itself.
*/
An full-sized image extracted/converted from non-web-friendly formats like RAW/HIF.
or otherwise the original image itself.
*/
FULLSIZE = 'fullsize',
PREVIEW = 'preview',
THUMBNAIL = 'thumbnail',
@@ -41,9 +41,9 @@ const AssetMediaBaseSchema = z.object({
duration: z.coerce.number().int().min(0).optional().describe('Duration in milliseconds (for videos)'),
filename: z.string().optional().describe('Filename'),
/**
* The properties below are added to correctly generate the API docs and client SDKs. Validation should be handled
* in the controller. File parts never reach the request body, so they must be optional here
*/
The properties below are added to correctly generate the API docs and client SDKs. Validation should be handled
in the controller. File parts never reach the request body, so they must be optional here
*/
[UploadFieldName.ASSET_DATA]: z
.any()
.optional()
+4 -4
View File
@@ -14,10 +14,10 @@ enum DatabaseSslMode {
const DatabaseSslModeSchema = z.enum(DatabaseSslMode).describe('Database SSL mode').meta({ id: 'DatabaseSslMode' });
const absolutePath = z.string().regex(/^\//, 'Must be an absolute path').optional();
/**
* Treat certain strings as booleans and coerce them to boolean
* Ideal for environment variables that are strings but should be treated as booleans
* @docs https://zod.dev/api?id=stringbool
*/
Treat certain strings as booleans and coerce them to boolean
Ideal for environment variables that are strings but should be treated as booleans
@docs https://zod.dev/api?id=stringbool
*/
const stringBool = z.stringbool();
const trustedProxiesSchema = z
+3 -3
View File
@@ -323,9 +323,9 @@ export const isFullyAlbumConfined = (filter: SearchFilter): boolean =>
isAlbumConfined(filter) || (!!filter.or?.length && filter.or.every((branch) => isAlbumConfined(branch)));
/**
* The structured shape and the deprecated flat search fields are mutually exclusive
* TODO(v4): remove together with the deprecated flat fields.
*/
The structured shape and the deprecated flat search fields are mutually exclusive
TODO(v4): remove together with the deprecated flat fields.
*/
const withShapeExclusivity = <T extends z.ZodObject<z.ZodRawShape>>(schema: T) => {
const deprecatedFields = Object.keys(schema.shape).filter(
(field) => (schema.shape[field] as z.ZodType).meta()?.deprecated,
+8 -12
View File
@@ -59,9 +59,7 @@ export enum ChecksumAlgorithm {
}
export enum AssetFileType {
/**
* An full/large-size image extracted/converted from RAW photos
*/
/** An full/large-size image extracted/converted from RAW photos */
FullSize = 'fullsize',
Preview = 'preview',
Thumbnail = 'thumbnail',
@@ -362,9 +360,9 @@ export enum SharedLinkType {
Album = 'ALBUM',
/**
* Individual asset
* or group of assets that are not in an album
*/
Individual asset
or group of assets that are not in an album
*/
Individual = 'INDIVIDUAL',
}
@@ -818,9 +816,9 @@ export enum DvProfile {
}
/**
* Dolby Vision base-layer signal-compatibility ID from the DOVI configuration record.
* Identifies what the base HEVC/AVC layer renders as on a non-DV decoder.
*/
Dolby Vision base-layer signal-compatibility ID from the DOVI configuration record.
Identifies what the base HEVC/AVC layer renders as on a non-DV decoder.
*/
export enum DvSignalCompatibility {
None = 0,
Hdr10 = 1,
@@ -1227,9 +1225,7 @@ export enum AssetVisibility {
Archive = 'archive',
Timeline = 'timeline',
/**
* Video part of the LivePhotos and MotionPhotos
*/
/** Video part of the LivePhotos and MotionPhotos */
Hidden = 'hidden',
Locked = 'locked',
}
+7 -15
View File
@@ -10,23 +10,15 @@ import { SystemMetadataRepository } from 'src/repositories/system-metadata.repos
import { type DB } from 'src/schema/index.js';
import { getKyselyConfig } from 'src/utils/database.js';
/**
* Manages worker lifecycle
*/
/** Manages worker lifecycle */
class Workers {
/**
* Currently running workers
*/
/** Currently running workers */
workers: Partial<Record<ImmichWorker, { kill: (signal: NodeJS.Signals) => Promise<void> | void }>> = {};
/**
* Fail-safe in case anything dies during restart
*/
/** Fail-safe in case anything dies during restart */
restarting = false;
/**
* Boot all enabled workers
*/
/** Boot all enabled workers */
async bootstrap() {
const isMaintenanceMode = await this.isMaintenanceMode();
const { workers } = new ConfigRepository().getEnv();
@@ -92,9 +84,9 @@ class Workers {
}
/**
* Start an individual worker
* @param name Worker
*/
Start an individual worker
@param name Worker
*/
private startWorker(name: ImmichWorker) {
console.log(`Starting ${name} worker`);
@@ -43,9 +43,7 @@ export class MaintenanceWorkerController {
private databaseBackupService: DatabaseBackupService,
) {}
/**
* {@link _ServerController.getServerConfig }
*/
/** {@link _ServerController.getServerConfig } */
@Get('server/config')
getServerConfig(): ServerConfigDto {
return this.service.getSystemConfig();
@@ -61,18 +59,14 @@ export class MaintenanceWorkerController {
return this.service.getVersion();
}
/**
* {@link _DatabaseBackupController.listDatabaseBackups}
*/
/** {@link _DatabaseBackupController.listDatabaseBackups} */
@Get('admin/database-backups')
@MaintenanceRoute()
listDatabaseBackups(): Promise<DatabaseBackupListResponseDto> {
return this.databaseBackupService.listBackups();
}
/**
* {@link _DatabaseBackupController.downloadDatabaseBackup}
*/
/** {@link _DatabaseBackupController.downloadDatabaseBackup} */
@Get('admin/database-backups/:filename')
@MaintenanceRoute()
async downloadDatabaseBackup(
@@ -83,18 +77,14 @@ export class MaintenanceWorkerController {
await sendFile(res, next, () => this.databaseBackupService.downloadBackup(filename), this.logger);
}
/**
* {@link _DatabaseBackupController.deleteDatabaseBackup}
*/
/** {@link _DatabaseBackupController.deleteDatabaseBackup} */
@Delete('admin/database-backups')
@MaintenanceRoute()
async deleteDatabaseBackup(@Body() dto: DatabaseBackupDeleteDto): Promise<void> {
return this.databaseBackupService.deleteBackup(dto.backups);
}
/**
* {@link _DatabaseBackupController.uploadDatabaseBackup}
*/
/** {@link _DatabaseBackupController.uploadDatabaseBackup} */
@Post('admin/database-backups/upload')
@MaintenanceRoute()
@UseInterceptors(FileInterceptor('file'))
@@ -282,9 +282,7 @@ describe(MaintenanceWorkerService.name, () => {
describe.skip('setAction'); // just calls setStatus+runAction
/**
* Actions
*/
/** Actions */
describe('action: start', () => {
it('should not do anything', async () => {
@@ -33,9 +33,7 @@ import { getConfig } from 'src/utils/config.js';
import { createMaintenanceLoginUrl, detectPriorInstall } from 'src/utils/maintenance.js';
import { getExternalDomain } from 'src/utils/misc.js';
/**
* This service is available inside of maintenance mode to manage maintenance mode
*/
/** This service is available inside of maintenance mode to manage maintenance mode */
@Injectable()
export class MaintenanceWorkerService {
#secret: string | null = null;
@@ -87,9 +85,7 @@ export class MaintenanceWorkerService {
}
}
/**
* {@link _BaseService.configRepos}
*/
/** {@link _BaseService.configRepos} */
private get configRepos() {
return {
configRepo: this.configRepository,
@@ -98,25 +94,19 @@ export class MaintenanceWorkerService {
};
}
/**
* {@link _BaseService.prototype.getConfig}
*/
/** {@link _BaseService.prototype.getConfig} */
private getConfig(options: { withCache: boolean }) {
return getConfig(this.configRepos, options);
}
/**
* {@link _ServerService.getSystemConfig}
*/
/** {@link _ServerService.getSystemConfig} */
getSystemConfig() {
return {
maintenanceMode: true,
} as ServerConfigDto;
}
/**
* {@link _VersionService.getVersion}
*/
/** {@link _VersionService.getVersion} */
getVersion() {
return ServerVersionResponseDto.fromSemVer(serverVersion);
}
@@ -125,9 +115,7 @@ export class MaintenanceWorkerService {
return { res: 'pong' };
}
/**
* {@link _ApiService.ssr}
*/
/** {@link _ApiService.ssr} */
ssr(excludePaths: string[]) {
const { resourcePaths } = this.configRepository.getEnv();
@@ -158,9 +146,7 @@ export class MaintenanceWorkerService {
};
}
/**
* {@link _StorageService.detectMediaLocation}
*/
/** {@link _StorageService.detectMediaLocation} */
detectMediaLocation(): string {
const envData = this.configRepository.getEnv();
if (envData.storage.mediaLocation) {
+16 -16
View File
@@ -227,12 +227,12 @@ export class AlbumRepository {
}
/**
* Get asset IDs for the given album ID.
*
* @param albumId Album ID to get asset IDs for.
* @param assetIds Optional list of asset IDs to filter on.
* @returns Set of Asset IDs for the given album ID.
*/
Get asset IDs for the given album ID.
@param albumId Album ID to get asset IDs for.
@param assetIds Optional list of asset IDs to filter on.
@returns Set of Asset IDs for the given album ID.
*/
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
@ChunkedSet({ paramIndex: 1 })
async getAssetIds(albumId: string, assetIds: string[]): Promise<Set<string>> {
@@ -348,13 +348,13 @@ export class AlbumRepository {
}
/**
* Makes sure all thumbnails for albums are updated by:
* - Removing thumbnails from albums without assets
* - Removing references of thumbnails to assets outside the album
* - Setting a thumbnail when none is set and the album contains assets
*
* @returns Amount of updated album thumbnails or undefined when unknown
*/
Makes sure all thumbnails for albums are updated by:
- Removing thumbnails from albums without assets
- Removing references of thumbnails to assets outside the album
- Setting a thumbnail when none is set and the album contains assets
@returns Amount of updated album thumbnails or undefined when unknown
*/
async updateThumbnails(): Promise<number | undefined> {
// Subquery for getting a new thumbnail.
@@ -399,9 +399,9 @@ export class AlbumRepository {
}
/**
* Get per-user asset contribution counts for a single album.
* Excludes deleted assets, orders by count desc.
*/
Get per-user asset contribution counts for a single album.
Excludes deleted assets, orders by count desc.
*/
@GenerateSql({ params: [DummyValue.UUID] })
getContributorCounts(id: string) {
return this.db
+7 -7
View File
@@ -73,10 +73,10 @@ export class MediaRepository {
}
/**
*
* @param input file path to the input image
* @returns ExtractResult if succeeded, or null if failed
*/
@param input file path to the input image
@returns ExtractResult if succeeded, or null if failed
*/
async extract(input: string): Promise<ExtractResult | null> {
for (const { tag, format } of [
{ tag: 'JpgFromRaw2', format: RawExtractedFormat.Jpeg },
@@ -305,9 +305,9 @@ export class MediaRepository {
}
/**
* Needed for accurate segments, especially when remuxing, seeking and/or VFR is involved.
* Scanning packets for keyframes in JS is much faster than -skip_frame nokey since it avoids decoding the video.
*/
Needed for accurate segments, especially when remuxing, seeking and/or VFR is involved.
Scanning packets for keyframes in JS is much faster than -skip_frame nokey since it avoids decoding the video.
*/
probePackets(input: string, streamIndex: number): Promise<VideoPacketInfo | null> {
const ffprobe = spawn(
'ffprobe',
+22 -22
View File
@@ -38,28 +38,28 @@ import { ImmichFileResponse } from 'src/utils/file.js';
import { batched, handlePromiseError } from 'src/utils/misc.js';
/**
* Untracked Files:
* Files are detected in /data/encoded-video, /data/library, /data/upload
* Checked against the asset table
* Files are detected in /data/thumbs
* Checked against the asset_file table
*
* * Can perform download or delete of files
*
* Missing Files:
* Paths are queried from asset(originalPath, encodedVideoPath), asset_file(path)
* Check whether files exist on disk
*
* * Reports must include origin (asset or asset_file) & ID for further action
* * Can perform trash (asset) or delete (asset_file)
*
* Checksum Mismatch:
* Paths & checksums are queried from asset(originalPath, checksum)
* Check whether files match checksum, missing files ignored
*
* * Reports must include origin (as above) for further action
* * Can perform download or trash (asset)
*/
Untracked Files:
Files are detected in /data/encoded-video, /data/library, /data/upload
Checked against the asset table
Files are detected in /data/thumbs
Checked against the asset_file table
Can perform download or delete of files
Missing Files:
Paths are queried from asset(originalPath, encodedVideoPath), asset_file(path)
Check whether files exist on disk
Reports must include origin (asset or asset_file) & ID for further action
Can perform trash (asset) or delete (asset_file)
Checksum Mismatch:
Paths & checksums are queried from asset(originalPath, checksum)
Check whether files match checksum, missing files ignored
Reports must include origin (as above) for further action
Can perform download or trash (asset)
*/
@Injectable()
export class IntegrityService extends BaseService {
+1 -3
View File
@@ -98,9 +98,7 @@ export class JobService extends BaseService {
}
}
/**
* Queue follow up jobs
*/
/** Queue follow up jobs */
private async onDone(item: JobItem) {
switch (item.name) {
case JobName.SidecarCheck: {
+1 -3
View File
@@ -18,9 +18,7 @@ import {
} from 'src/utils/maintenance.js';
import { getExternalDomain } from 'src/utils/misc.js';
/**
* This service is available outside of maintenance mode to manage maintenance mode
*/
/** This service is available outside of maintenance mode to manage maintenance mode */
@Injectable()
export class MaintenanceService extends BaseService {
getMaintenanceMode(): Promise<MaintenanceModeState> {
+3 -4
View File
@@ -169,8 +169,7 @@ export class MediaService extends BaseService {
let thumbhash: Buffer | undefined = generated?.thumbhash;
if (!thumbhash) {
const extractedImage = await this.extractOriginalImage(asset, config.image);
const { info, data, colorspace } = extractedImage;
const { info, data, colorspace } = await this.extractOriginalImage(asset, config.image);
thumbhash = await this.mediaRepository.generateThumbhash(
{ data, info },
@@ -290,8 +289,8 @@ export class MediaService extends BaseService {
private async generateImageThumbnails(asset: ThumbnailAsset, { image }: SystemConfig, useEdits: boolean = false) {
// Handle embedded preview extraction for RAW files
const extractedImage = await this.extractOriginalImage(asset, image, useEdits);
const { info, data, colorspace, generateFullsize, convertFullsize, extracted, isTransparent } = extractedImage;
const { info, data, colorspace, generateFullsize, convertFullsize, extracted, isTransparent } =
await this.extractOriginalImage(asset, image, useEdits);
const previewFormat = image.preview.format;
this.warnOnTransparencyLoss(isTransparent, previewFormat, asset.id);
+15 -15
View File
@@ -268,7 +268,7 @@ export class StorageTemplateService extends BaseService {
private async getTemplatePath(
asset: StorageAsset,
metadata: MoveAssetMetadata,
stillPhoto?: StorageAsset,
stillPhoto: StorageAsset = asset,
): Promise<string> {
const { storageLabel, filename } = metadata;
@@ -310,7 +310,7 @@ export class StorageTemplateService extends BaseService {
let albumName = null;
let albumStartDate = null;
let albumEndDate = null;
const assetForMetadata = stillPhoto || asset;
const assetForMetadata = stillPhoto;
if (this.template.needsAlbum) {
// For motion videos, use the still photo's album information since motion videos
@@ -360,19 +360,19 @@ export class StorageTemplateService extends BaseService {
}
/**
* In case of migrating duplicate filename to a new path, we need to check if it is already migrated
* Due to the mechanism of appending +1, +2, +3, etc to the filename
*
* Example:
* Source = upload/abc/def/FullSizeRender+7.heic
* Expected Destination = upload/abc/def/FullSizeRender.heic
*
* The file is already at the correct location, but since there are other FullSizeRender.heic files in the
* destination, it was renamed to FullSizeRender+7.heic.
*
* The lines below will be used to check if the differences between the source and destination is only the
* +7 suffix, and if so, it will be considered as already migrated.
*/
In case of migrating duplicate filename to a new path, we need to check if it is already migrated
Due to the mechanism of appending +1, +2, +3, etc to the filename
Example:
Source = upload/abc/def/FullSizeRender+7.heic
Expected Destination = upload/abc/def/FullSizeRender.heic
The file is already at the correct location, but since there are other FullSizeRender.heic files in the
destination, it was renamed to FullSizeRender+7.heic.
The lines below will be used to check if the differences between the source and destination is only the
+7 suffix, and if so, it will be considered as already migrated.
*/
if (source.startsWith(fullPath) && source.endsWith(`.${extension}`)) {
const diff = source.replace(fullPath, '').replace(`.${extension}`, '');
const hasDuplicationAnnotation = /^\+\d+$/.test(diff);
@@ -420,14 +420,14 @@ describe(SystemConfigService.name, () => {
},
];
for (const { should, externalDomain, result } of externalDomainTests) {
for (const { should, externalDomain, result = 'https://demo.immich.app' } of externalDomainTests) {
it(`should normalize an external domain ${should}`, async () => {
mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
const partialConfig = { server: { externalDomain } };
mocks.systemMetadata.readFile.mockResolvedValue(JSON.stringify(partialConfig));
const config = await sut.getAdminConfig();
expect(config.server.externalDomain).toEqual(result ?? 'https://demo.immich.app');
expect(config.server.externalDomain).toEqual(result);
});
}
@@ -98,7 +98,6 @@ export class WorkflowExecutionService extends BaseService {
throw new Error('Hostname did not match any listed in methods[].allowedHosts in the plugin manifest');
}
// eslint-disable-next-line unicorn/no-invalid-argument-count
const res = await fetch(...args);
return {
+4 -4
View File
@@ -355,8 +355,8 @@ export function withTags(eb: ExpressionBuilder<DB, 'asset'>) {
).as('tags');
}
export function truncatedDate<O>(order: AssetOrderBy = AssetOrderBy.TakenAt, size?: 'DAY' | 'MONTH') {
return sql<O>`date_trunc(${sql.lit(size ?? 'MONTH')}, ${sql.ref(order === AssetOrderBy.CreatedAt ? 'asset.createdAt' : 'localDateTime')} AT TIME ZONE 'UTC') AT TIME ZONE 'UTC'`;
export function truncatedDate<O>(order: AssetOrderBy = AssetOrderBy.TakenAt, size: 'DAY' | 'MONTH' = 'MONTH') {
return sql<O>`date_trunc(${sql.lit(size)}, ${sql.ref(order === AssetOrderBy.CreatedAt ? 'asset.createdAt' : 'localDateTime')} AT TIME ZONE 'UTC') AT TIME ZONE 'UTC'`;
}
export function withTagId<O>(qb: SelectQueryBuilder<DB, 'asset', O>, tagId: string) {
@@ -1106,14 +1106,14 @@ export type ReindexVectorIndexOptions = { indexName: string; lists?: number };
type VectorIndexQueryOptions = { table: string; vectorExtension: VectorExtension } & ReindexVectorIndexOptions;
export function vectorIndexQuery({ vectorExtension, table, indexName, lists }: VectorIndexQueryOptions): string {
export function vectorIndexQuery({ vectorExtension, table, indexName, lists = 1 }: VectorIndexQueryOptions): string {
switch (vectorExtension) {
case DatabaseExtension.VectorChord: {
return `
CREATE INDEX IF NOT EXISTS ${indexName} ON ${table} USING vchordrq (embedding vector_cosine_ops) WITH (options = $$
residual_quantization = false
[build.internal]
lists = [${lists ?? 1}]
lists = [${lists}]
spherical_centroids = true
build_threads = 4
sampling_factor = 1024
+3 -9
View File
@@ -1,23 +1,17 @@
import { DateTime } from 'luxon';
import { isoDateToDate, isoDatetimeToDate } from 'src/validation.js';
/**
* Convert a date to a ISO 8601 datetime string.
*/
/** Convert a date to a ISO 8601 datetime string. */
export const asDateTimeString = <T extends Date | string | undefined | null>(x: T) => {
return x instanceof Date ? isoDatetimeToDate.encode(x) : (x as Exclude<T, Date>);
};
/**
* Convert a date to a date string (yyyy-mm-dd).
*/
/** Convert a date to a date string (yyyy-mm-dd). */
export const asDateString = (x: Date | string | null): string | null => {
return x instanceof Date ? isoDateToDate.encode(x) : x;
};
/**
* People born on February 29th are celebrated on February 28th in non-leap years.
*/
/** People born on February 29th are celebrated on February 28th in non-leap years. */
export const isLeapDayObserved = ({ year, month, day }: { year: number; month: number; day: number }) => {
return month === 2 && day === 28 && !DateTime.local(year).isInLeapYear;
};
+22 -22
View File
@@ -1,27 +1,27 @@
import { AssetResponseDto } from 'src/dtos/asset-response.dto.js';
/**
* Counts all truthy values in the exifInfo object.
* This matches the client implementation in web/src/lib/utils/exif-utils.ts
*
* @param asset Asset with optional exifInfo
* @returns Count of truthy EXIF values
*/
Counts all truthy values in the exifInfo object.
This matches the client implementation in web/src/lib/utils/exif-utils.ts
@param asset Asset with optional exifInfo
@returns Count of truthy EXIF values
*/
export const getExifCount = (asset: AssetResponseDto): number => {
return Object.values(asset.exifInfo ?? {}).filter(Boolean).length;
};
/**
* Suggests the best duplicate asset to keep from a list of duplicates.
* This is a direct port of the client logic from web/src/lib/utils/duplicate-utils.ts
*
* The best asset is determined by the following criteria:
* 1. Largest image file size in bytes
* 2. Largest count of EXIF data (as tie-breaker)
*
* @param assets List of duplicate assets
* @returns The best asset to keep, or undefined if empty list
*/
Suggests the best duplicate asset to keep from a list of duplicates.
This is a direct port of the client logic from web/src/lib/utils/duplicate-utils.ts
The best asset is determined by the following criteria:
1. Largest image file size in bytes
2. Largest count of EXIF data (as tie-breaker)
@param assets List of duplicate assets
@returns The best asset to keep, or undefined if empty list
*/
export const suggestDuplicate = (assets: AssetResponseDto[]): AssetResponseDto | undefined => {
if (assets.length === 0) {
return undefined;
@@ -48,12 +48,12 @@ export const suggestDuplicate = (assets: AssetResponseDto[]): AssetResponseDto |
};
/**
* Suggests the best duplicate asset IDs to keep from a list of duplicates.
* Returns an array with a single asset ID (the best candidate), or empty if no assets.
*
* @param assets List of duplicate assets
* @returns Array of suggested asset IDs to keep (0 or 1 element)
*/
Suggests the best duplicate asset IDs to keep from a list of duplicates.
Returns an array with a single asset ID (the best candidate), or empty if no assets.
@param assets List of duplicate assets
@returns Array of suggested asset IDs to keep (0 or 1 element)
*/
export const suggestDuplicateKeepAssetIds = (assets: AssetResponseDto[]): string[] => {
const suggested = suggestDuplicate(assets);
return suggested ? [suggested.id] : [];
+1
View File
@@ -71,6 +71,7 @@ export class BaseConfig implements VideoCodecSWConfig {
readonly presets = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast'];
protected constructor(
protected config: ConfigFFmpegDto,
// eslint-disable-next-line unicorn/no-object-as-default-parameter
protected tune: VideoTuning = { strictGop: false, lowLatency: false },
) {}
+4 -4
View File
@@ -35,10 +35,10 @@ const raw = {
};
/**
* list of supported image extensions from https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types excluding svg
* @TODO share with the client
* @see {@link web/src/lib/utils/asset-utils.ts#L329}
**/
list of supported image extensions from https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types excluding svg
@TODO share with the client
@see {@link web/src/lib/utils/asset-utils.ts#L329}
*/
const webSupportedImage = {
'.avif': ['image/avif'],
'.bmp': ['image/bmp'],
+1 -3
View File
@@ -52,9 +52,7 @@ export const getMethodNames = (instance: any) => {
export const getExternalDomain = (server: SystemConfig['server'], defaultDomain = 'https://my.immich.app') =>
server.externalDomain || defaultDomain;
/**
* @returns a list of strings representing the keys of the object in dot notation
*/
/** @returns a list of strings representing the keys of the object in dot notation */
export const getKeysDeep = (target: unknown, path: string[] = []) => {
if (!target || typeof target !== 'object') {
return [];
+2 -6
View File
@@ -1,15 +1,11 @@
import { isEqual, isPlainObject } from 'lodash-es';
/**
* Deeply clones and converts a class instance to a plain object.
*/
/** Deeply clones and converts a class instance to a plain object. */
export function toPlainObject<T extends object>(obj: T): T {
return isPlainObject(obj) ? obj : structuredClone(obj);
}
/**
* Performs a deep comparison between objects, converting them to plain objects first if needed.
*/
/** Performs a deep comparison between objects, converting them to plain objects first if needed. */
export function isEqualObject(value: object, other: object): boolean {
return isEqual(toPlainObject(value), toPlainObject(other));
}
+4 -6
View File
@@ -25,9 +25,7 @@ const canMatch = <T>(condition: EnumCondition<T>, value: T): boolean => {
);
};
/**
* The conditions that decide which `field` values the filter can return. A top-level condition decides alone, otherwise each branch itself.
*/
/** The conditions that decide which `field` values the filter can return. A top-level condition decides alone, otherwise each branch itself. */
const decidingConditions = <F extends EnumField>(filter: SearchFilter, field: F) => {
if (filter[field] !== undefined) {
return [filter[field]];
@@ -39,9 +37,9 @@ const decidingConditions = <F extends EnumField>(filter: SearchFilter, field: F)
};
/**
* Keeps locked assets out of search results unless the session is elevated: a filter that asks for
* them is rejected with 401, and any other filter gets `visibility != locked` ANDed in.
*/
Keeps locked assets out of search results unless the session is elevated: a filter that asks for
them is rejected with 401, and any other filter gets `visibility != locked` ANDed in.
*/
export const applyLockedVisibilityPolicy = (auth: AuthDto, filter: SearchFilter): SearchFilter => {
if (auth.session?.hasElevatedPermission) {
return filter;
+2 -2
View File
@@ -29,6 +29,6 @@ export const serialize = <T extends keyof SyncItem, D extends SyncItem[T]>({
type,
data,
ids,
ackType,
ackType = type,
}: SerializeOptions<T, D>) =>
mapJsonLine({ type, data, ack: toAck({ type: ackType ?? type, updateId: ids[0], extraId: ids[1] }) });
mapJsonLine({ type, data, ack: toAck({ type: ackType, updateId: ids[0], extraId: ids[1] }) });
+3 -3
View File
@@ -72,9 +72,9 @@ type TransformState = {
};
/**
* Transforms an array of points through a series of edit operations (crop, rotate, mirror).
* Points should be in absolute pixel coordinates relative to the starting dimensions.
*/
Transforms an array of points through a series of edit operations (crop, rotate, mirror).
Points should be in absolute pixel coordinates relative to the starting dimensions.
*/
export const transformPoints = (
points: Point[],
edits: AssetEditActionItem[],
+71 -73
View File
@@ -18,14 +18,14 @@ function isIPOrRange(value: string, options?: IsIPRangeOptions): boolean {
}
/**
* Zod schema that validates an array of strings as IP addresses or IP/CIDR ranges.
* When requireCIDR is true (default), plain IPs are rejected; only CIDR ranges are allowed.
*
* @example
* z.string().optional().transform(...).pipe(IsIPRange())
* @example
* z.string().optional().transform(...).pipe(IsIPRange({ requireCIDR: false }))
*/
Zod schema that validates an array of strings as IP addresses or IP/CIDR ranges.
When requireCIDR is true (default), plain IPs are rejected; only CIDR ranges are allowed.
@example
z.string().optional().transform(...).pipe(IsIPRange())
@example
z.string().optional().transform(...).pipe(IsIPRange({ requireCIDR: false }))
*/
export function IsIPRange(options?: IsIPRangeOptions) {
return z
.array(z.string())
@@ -33,12 +33,12 @@ export function IsIPRange(options?: IsIPRangeOptions) {
}
/**
* Like z.object().partial(), but rejects objects where every field is undefined.
* Use for update/patch DTOs where at least one field must be provided.
*
* @example
* nonEmptyPartial({ name: z.string(), bio: z.string() }).meta({ id: 'UpdateDto' });
*/
Like z.object().partial(), but rejects objects where every field is undefined.
Use for update/patch DTOs where at least one field must be provided.
@example
nonEmptyPartial({ name: z.string(), bio: z.string() }).meta({ id: 'UpdateDto' });
*/
export function nonEmptyPartial<T extends z.ZodRawShape>(shape: T) {
return z
.object(shape)
@@ -49,14 +49,14 @@ export function nonEmptyPartial<T extends z.ZodRawShape>(shape: T) {
}
/**
* Zod schema that validates sibling-exclusion for object schemas.
* Validation passes when the target property is missing, or when none of the sibling properties are present.
* Use with .pipe() like IsIPRange.
*
* @example
* const Schema = z.object({ a: z.string().optional(), b: z.string().optional() });
* Schema.pipe(IsNotSiblingOf(Schema, 'a', ['b']));
*/
Zod schema that validates sibling-exclusion for object schemas.
Validation passes when the target property is missing, or when none of the sibling properties are present.
Use with .pipe() like IsIPRange.
@example
const Schema = z.object({ a: z.string().optional(), b: z.string().optional() });
Schema.pipe(IsNotSiblingOf(Schema, 'a', ['b']));
*/
export function IsNotSiblingOf<
TSchema extends z.ZodObject<z.ZodRawShape>,
TKey extends z.infer<ReturnType<TSchema['keyof']>> & keyof z.infer<TSchema>,
@@ -122,18 +122,18 @@ const FilenameParamSchema = z.object({
export class FilenameParamDto extends createZodDto(FilenameParamSchema) {}
/**
* The HTML5 email regex, but with unicode support, so that international email addresses
* (unicode local parts and internationalized domain names) are accepted.
* @see {@link z.regexes.html5Email}
*/
The HTML5 email regex, but with unicode support, so that international email addresses
(unicode local parts and internationalized domain names) are accepted.
@see {@link z.regexes.html5Email}
*/
const unicodeEmail =
/^[\p{L}\p{M}\p{N}.!#$%&'*+/=?^_`{|}~-]+@[\p{L}\p{N}](?:[\p{L}\p{M}\p{N}-]{0,61}[\p{L}\p{M}\p{N}])?(?:\.[\p{L}\p{N}](?:[\p{L}\p{M}\p{N}-]{0,61}[\p{L}\p{M}\p{N}])?)*$/u;
/**
* Unified email validation
* Converts email strings to lowercase and validates against a unicode aware email regex
* @docs https://zod.dev/api?id=email
*/
Unified email validation
Converts email strings to lowercase and validates against a unicode aware email regex
@docs https://zod.dev/api?id=email
*/
export const toEmail = z
.email({
pattern: unicodeEmail,
@@ -142,9 +142,9 @@ export const toEmail = z
.transform((val) => val.toLowerCase());
/**
* Parse ISO 8601 datetime strings to Date objects
* @docs https://zod.dev/api?id=codec
*/
Parse ISO 8601 datetime strings to Date objects
@docs https://zod.dev/api?id=codec
*/
export const isoDatetimeToDate = z
.codec(
z.iso.datetime({
@@ -160,9 +160,9 @@ export const isoDatetimeToDate = z
.meta({ example: '2024-01-01T00:00:00.000Z' });
/**
* Parse ISO date strings to Date objects
* @docs https://zod.dev/api?id=codec
*/
Parse ISO date strings to Date objects
@docs https://zod.dev/api?id=codec
*/
export const isoDateToDate = z
.codec(
z.iso.date({
@@ -177,46 +177,44 @@ export const isoDateToDate = z
.meta({ example: '2024-01-01' });
/**
* Latitude in range [-90, 90]. Reuse for body or query params.
*
* @example
* // Regular (body): optional coordinates
* latitudeSchema.optional().describe('Latitude coordinate')
*
* @example
* // Pipe (query): coerce string to number then validate range
* z.coerce.number().pipe(latitudeSchema).describe('Latitude (-90 to 90)')
*/
Latitude in range [-90, 90]. Reuse for body or query params.
@example
// Regular (body): optional coordinates
latitudeSchema.optional().describe('Latitude coordinate')
@example
// Pipe (query): coerce string to number then validate range
z.coerce.number().pipe(latitudeSchema).describe('Latitude (-90 to 90)')
*/
export const latitudeSchema = z.number().meta({ format: 'double' }).min(-90).max(90);
/**
* Longitude in range [-180, 180]. Reuse for body or query params.
*
* @example
* // Regular (body): optional coordinates
* longitudeSchema.optional().describe('Longitude coordinate')
*
* @example
* // Pipe (query): coerce string to number then validate range
* z.coerce.number().pipe(longitudeSchema).describe('Longitude (-180 to 180)')
*/
Longitude in range [-180, 180]. Reuse for body or query params.
@example
// Regular (body): optional coordinates
longitudeSchema.optional().describe('Longitude coordinate')
@example
// Pipe (query): coerce string to number then validate range
z.coerce.number().pipe(longitudeSchema).describe('Longitude (-180 to 180)')
*/
export const longitudeSchema = z.number().meta({ format: 'double' }).min(-180).max(180);
/**
* Parse string to boolean
* This should be used for boolean query parameters and path parameters, but not for boolean request body parameters, as the first are always string.
* We don't use z.coerce.boolean() as any truthy value is considered true
* z.stringbool() is a more robust way to parse strings to booleans as it lets you specify the truthy and falsy values and the case sensitivity.
* @docs https://zod.dev/api?id=coercion
* @docs https://zod.dev/api?id=stringbool
*/
Parse string to boolean
This should be used for boolean query parameters and path parameters, but not for boolean request body parameters, as the first are always string.
We don't use z.coerce.boolean() as any truthy value is considered true
z.stringbool() is a more robust way to parse strings to booleans as it lets you specify the truthy and falsy values and the case sensitivity.
@docs https://zod.dev/api?id=coercion
@docs https://zod.dev/api?id=stringbool
*/
export const stringToBool = z
.stringbool({ truthy: ['true'], falsy: ['false'], case: 'sensitive' })
.meta({ type: 'boolean' });
/**
* Parse JSON strings from multipart/form-data
*/
/** Parse JSON strings from multipart/form-data */
export const JsonParsed = z.transform((val, ctx) => {
if (typeof val === 'string') {
try {
@@ -234,13 +232,13 @@ export const JsonParsed = z.transform((val, ctx) => {
});
/**
* Hex color validation and normalization.
* Accepts formats: #RGB, #RGBA, #RRGGBB, #RRGGBBAA (with or without # prefix).
* Normalizes output to always include the # prefix.
*
* @example
* hexColor.optional()
*/
Hex color validation and normalization.
Accepts formats: #RGB, #RGBA, #RRGGBB, #RRGGBBAA (with or without # prefix).
Normalizes output to always include the # prefix.
@example
hexColor.optional()
*/
const hexColorRegex = /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
export const hexColor = z
.string()
@@ -9,6 +9,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
const { ctx } = newMediumService(BaseService, {
database: db || defaultDatabase,
@@ -15,6 +15,7 @@ const consume = async <T>(generator: AsyncIterableIterator<T>) => {
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
const { ctx } = newMediumService(BaseService, {
database: db || defaultDatabase,
@@ -10,6 +10,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
const { ctx } = newMediumService(BaseService, {
database: db || defaultDatabase,
@@ -9,6 +9,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
const { ctx } = newMediumService(BaseService, {
database: db || defaultDatabase,
@@ -13,6 +13,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
const { ctx } = newMediumService(BaseService, {
database: db || defaultDatabase,
@@ -8,6 +8,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
const { ctx } = newMediumService(BaseService, {
database: db || defaultDatabase,
@@ -15,6 +15,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(ActivityService, {
database: db || defaultDatabase,
@@ -12,6 +12,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(AlbumService, {
database: db || defaultDatabase,
@@ -11,6 +11,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(ApiKeyService, {
database: db || defaultDatabase,
@@ -22,6 +22,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(AssetMediaService, {
database: db || defaultDatabase,
@@ -23,6 +23,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(AssetService, {
database: db || defaultDatabase,
@@ -9,6 +9,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(AuthAdminService, {
database: db || defaultDatabase,
@@ -22,6 +22,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(AuthService, {
database: db || defaultDatabase,
@@ -20,6 +20,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
const { sut, ctx } = newMediumService(DuplicateService, {
database: db || defaultDatabase,
@@ -20,6 +20,7 @@ import { getKyselyDB, makeStream } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(IntegrityService, {
database: db || defaultDatabase,
@@ -30,10 +30,10 @@ let defaultDatabase: Kysely<DB>;
const fileModifiedAt = new Date(1_700_000_000_000);
const createFile = async (filePath: string, modifiedAt: Date = fileModifiedAt) => {
const createFile = async (filePath: string) => {
await mkdir(dirname(filePath), { recursive: true });
await writeFile(filePath, 'test');
await utimes(filePath, modifiedAt, modifiedAt);
await utimes(filePath, fileModifiedAt, fileModifiedAt);
return filePath;
};
@@ -103,6 +103,7 @@ class LibraryTestContext extends MediumTestContext<typeof LibraryService> {
}
}
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
const ctx = new LibraryTestContext(db || defaultDatabase);
return { sut: ctx.sut, ctx };
@@ -19,6 +19,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(MemoryService, {
database: db || defaultDatabase,
@@ -47,10 +48,14 @@ const create = async (ctx: ReturnType<typeof setup>['ctx']) => {
const newPersonAsset = async (
ctx: ReturnType<typeof setup>['ctx'],
{ ownerId, personGroupId, localDateTime }: { ownerId: string; personGroupId: string; localDateTime?: string },
{
ownerId,
personGroupId,
localDateTime = '2024-06-13T12:00:00.000Z',
}: { ownerId: string; personGroupId: string; localDateTime?: string },
) => {
const assetRepo = ctx.get(AssetRepository);
const { asset } = await ctx.newAsset({ ownerId, localDateTime: localDateTime ?? '2024-06-13T12:00:00.000Z' });
const { asset } = await ctx.newAsset({ ownerId, localDateTime });
await Promise.all([
ctx.newExif({ assetId: asset.id, make: 'Canon' }),
ctx.newJobStatus({ assetId: asset.id }),
@@ -15,6 +15,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(OcrService, {
database: db || defaultDatabase,
@@ -12,6 +12,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(PartnerService, {
database: db || defaultDatabase,
@@ -49,6 +49,7 @@ const authenticationResponse = {
},
};
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(PasskeyService, {
database: db || defaultDatabase,
@@ -13,6 +13,7 @@ let defaultDatabase: Kysely<DB>;
const wasmBytes = Buffer.from('some-wasm-binary-data');
const sha256hash = Buffer.from('some-manifest-hash');
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(PluginService, {
database: db || defaultDatabase,
@@ -18,6 +18,7 @@ let defaultDatabase: Kysely<DB>;
const unitVector = (index: number) => JSON.stringify(Array.from({ length: 512 }, (_, i) => (i === index ? 1 : 0)));
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(SearchService, {
database: db || defaultDatabase,
@@ -16,6 +16,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(SharedLinkService, {
database: db || defaultDatabase,
@@ -13,6 +13,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(SyncService, {
database: db || defaultDatabase,
@@ -16,6 +16,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(TagService, {
database: db || defaultDatabase,
@@ -14,6 +14,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(TimelineService, {
database: db || defaultDatabase,
@@ -13,6 +13,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(VersionService, {
database: db || defaultDatabase,
@@ -13,6 +13,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = (db?: Kysely<DB>) => {
return newMediumService(WorkflowService, {
database: db || defaultDatabase,
@@ -9,6 +9,7 @@ import { getKyselyDB, wait } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -8,6 +8,7 @@ import { getKyselyDB, wait } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -8,6 +8,7 @@ import { getKyselyDB, wait } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -7,6 +7,7 @@ import { getKyselyDB, wait } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -8,6 +8,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -9,6 +9,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -7,6 +7,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -8,6 +8,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -7,6 +7,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -8,6 +8,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -8,6 +8,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -22,6 +22,7 @@ class LineAutoClosingStream extends Writable {
private count: number;
lines: string[] = [];
// eslint-disable-next-line unicorn/no-unnecessary-parameters
constructor({ after }: { after: number }) {
super({ highWaterMark: 1 });
this.count = after;
@@ -10,6 +10,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -7,6 +7,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -7,6 +7,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -7,6 +7,7 @@ import { getKyselyDB, wait } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -10,6 +10,7 @@ import { getKyselyDB, wait } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -10,6 +10,7 @@ import { getKyselyDB, wait } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -8,6 +8,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -8,6 +8,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -7,6 +7,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -7,6 +7,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();
@@ -7,6 +7,7 @@ import { getKyselyDB } from 'test/utils.js';
let defaultDatabase: Kysely<DB>;
// eslint-disable-next-line unicorn/no-unnecessary-parameters
const setup = async (db?: Kysely<DB>) => {
const ctx = new SyncTestContext(db || defaultDatabase);
const { auth, user, session } = await ctx.newSyncAuthUser();