ronantakizawa/codereview-bench huggingface dataset:
Repository: directus/directus
File: app/src/interfaces/_system/system-input-password/input-password.vue (Vue)

Code before:
		disabled?: boolean;
	}>(),
	{
		value: null,
		autofocus: false,
		autocomplete: 'new-password',
		disabled: false,
	},
);

const { t } = useI18n();
const hidden = ref<boolean>(true);

function toggleHidePassword() {
	hidden.value = !hidden.value;
}
</script>

<template>
	<v-input
		:model-value="value"
		:type="hidden ? 'password' : 'text'"
		:autocomplete="autocomplete"
		:autofocus="autofocus"
		:placeholder="t('password')"
		:disabled="disabled"
	>
		<template #append>
			<v-icon
				v-tooltip="hidden ? t('show_password') : t('hide_password')"
				:name="hidden ? 'visibility' : 'visibility_off'"
				clickable
				@click="toggleHidePassword"
			/>
		</template>
	</v-input>
</template>


Review comment (suggestion):
The `model-value` binding requires the `@update:modelValue` event in order to propagate the value change upwards.

Code after:
		placeholder?: string;
		disabled?: boolean;
	}>(),
	{
		value: null,
		autofocus: false,
		autocomplete: 'new-password',
		disabled: false,
	},
);

const emit = defineEmits<{
	input: [value: string];
}>();

const { t } = useI18n();
const hidden = ref<boolean>(true);

function toggleHidePassword() {
	hidden.value = !hidden.value;
}
</script>

<template>
	<v-input
		:model-value="value"
		:type="hidden ? 'password' : 'text'"
		:autocomplete="autocomplete"
		:autofocus="autofocus"
		:placeholder="placeholder ?? t('password')"
		:disabled="disabled"
		@update:model-value="emit('input', $event)"
	>
		<template #append>
			<v-icon
				v-tooltip="hidden ? t('show_password') : t('hide_password')"
				:name="hidden ? 'visibility' : 'visibility_off'"
				clickable
				@click="toggleHidePassword"
			/>
		</template>
	</v-input>
</template>
Repository: directus/directus
File: api/src/schedules/retention.ts (TypeScript)

Code before:
	where?: readonly [string, string, Knex.Value | null];
	join?: readonly [string, string, string];
	timeframe?: number;
}

const env = useEnv();

const retentionLockKey = 'schedule--data-retention';
const retentionLockTimeout = 10 * 60 * 1000; // 10 mins

const RETENTION_TASKS: RetentionTask[] = [
	{
		collection: 'directus_activity',
		where: ['action', '!=', Action.RUN],
		timeframe: getMilliseconds(env['ACTIVITY_RETENTION']),
	},
	{
		collection: 'directus_activity',
		where: ['action', '=', Action.RUN],
		timeframe: getMilliseconds(env['FLOW_LOGS_RETENTION']),
	},
	{
		collection: 'directus_revisions',
		join: ['directus_activity', 'directus_revisions.activity', 'directus_activity.id'],
		timeframe: getMilliseconds(env['REVISIONS_RETENTION']),
	},
];

export async function handleRetentionJob() {
	const database = getDatabase();
	const logger = useLogger();
	const lock = useLock();
	const batch = Number(env['RETENTION_BATCH']);
	const lockTime = await lock.get(retentionLockKey);
	const now = Date.now();

	if (lockTime && Number(lockTime) > now - retentionLockTimeout) {
		// ensure only one connected process
		return;
	}

	await lock.set(retentionLockKey, Date.now());

	for (const task of RETENTION_TASKS) {
		let count = 0;

		if (task.timeframe === undefined) {
			// skip disabled tasks
			continue;
		}


Review comment (suggestion):
Should this task for `REVISIONS_RETENTION` be skipped if the `ACTIVITY_RETENTION` is enabled as the deletions are cascaded? 🤔 

Code after:
	where?: readonly [string, string, Knex.Value | null];
	join?: readonly [string, string, string];
	timeframe: number | undefined;
}

const env = useEnv();

const retentionLockKey = 'schedule--data-retention';
const retentionLockTimeout = 10 * 60 * 1000; // 10 mins

const ACTIVITY_RETENTION_TIMEFRAME = getMilliseconds(env['ACTIVITY_RETENTION']);
const FLOW_LOGS_RETENTION_TIMEFRAME = getMilliseconds(env['FLOW_LOGS_RETENTION']);
const REVISIONS_RETENTION_TIMEFRAME = getMilliseconds(env['REVISIONS_RETENTION']);

const retentionTasks: RetentionTask[] = [
	{
		collection: 'directus_activity',
		where: ['action', '!=', Action.RUN],
		timeframe: ACTIVITY_RETENTION_TIMEFRAME,
	},
	{
		collection: 'directus_activity',
		where: ['action', '=', Action.RUN],
		timeframe: FLOW_LOGS_RETENTION_TIMEFRAME,
	},
];

export async function handleRetentionJob() {
	const database = getDatabase();
	const logger = useLogger();
	const lock = useLock();
	const batch = Number(env['RETENTION_BATCH']);
	const lockTime = await lock.get(retentionLockKey);
	const now = Date.now();

	if (lockTime && Number(lockTime) > now - retentionLockTimeout) {
		// ensure only one connected process
		return;
	}

	await lock.set(retentionLockKey, Date.now());

	for (const task of retentionTasks) {
		let count = 0;

		if (task.timeframe === undefined) {
			// skip disabled tasks
			continue;
		}

		do {
Repository: directus/directus
File: api/src/schedules/tus.ts (TypeScript)

Code before:
import { RESUMABLE_UPLOADS } from '../constants.js';
import { getSchema } from '../utils/get-schema.js';
import { createTusServer } from '../services/tus/index.js';
import { scheduleSynchronizedJob, validateCron } from '../utils/schedule.js';

/**
 * Schedule the tus cleanup
 *
 * @returns Whether or not telemetry has been initialized
 */
export default function schedule() {
	if (!RESUMABLE_UPLOADS.ENABLED) return false;

	if (validateCron(RESUMABLE_UPLOADS.SCHEDULE)) {
		scheduleSynchronizedJob('tus-cleanup', RESUMABLE_UPLOADS.SCHEDULE, async () => {
			const [tusServer, cleanupServer] = await createTusServer({
				schema: await getSchema(),
			});

			await tusServer.cleanUpExpiredUploads();

			cleanupServer();
		});
	}

	return true;
}


Review comment (suggestion):
shall we add the return types to the functions? I don't think TS is inferring this well enough, so better be explicit if it's not void

Code after:
import { RESUMABLE_UPLOADS } from '../constants.js';
import { getSchema } from '../utils/get-schema.js';
import { createTusServer } from '../services/tus/index.js';
import { scheduleSynchronizedJob, validateCron } from '../utils/schedule.js';

/**
 * Schedule the tus cleanup
 *
 * @returns Whether or not tus cleanup has been initialized
 */
export default async function schedule(): Promise<boolean> {
	if (!RESUMABLE_UPLOADS.ENABLED) return false;

	if (validateCron(RESUMABLE_UPLOADS.SCHEDULE)) {
		scheduleSynchronizedJob('tus-cleanup', RESUMABLE_UPLOADS.SCHEDULE, async () => {
			const [tusServer, cleanupServer] = await createTusServer({
				schema: await getSchema(),
			});

			await tusServer.cleanUpExpiredUploads();

			cleanupServer();
		});
	}

	return true;
}
Repository: directus/directus
File: api/src/schedules/retention.test.ts (TypeScript)

Code before:
		vi.mocked(useEnv).mockReturnValue({ RETENTION_ENABLED: false, RETENTION_SCHEDULE: '0 0 * * *' });

		const res = retentionSchedule();

		expect(schedule.validateCron).not.toHaveBeenCalled();
		expect(res).toBe(false);
	});

	test('Returns early for invalid retention schedule', () => {
		vi.mocked(useEnv).mockReturnValue({ RETENTION_ENABLED: true, RETENTION_SCHEDULE: '#' });

		const res = retentionSchedule();

		expect(schedule.validateCron).toHaveBeenCalledWith('#');
		expect(res).toBe(false);
	});

	test('Schedules synchronized job', () => {
		retentionSchedule();

		expect(schedule.validateCron).toHaveBeenCalledWith('0 0 * * *');
		expect(schedule.scheduleSynchronizedJob).toHaveBeenCalledWith('retention', '0 0 * * *', handleRetentionJob);
	});

	test('Returns true on successful init', () => {
		const res = retentionSchedule();

		expect(res).toBe(true);
	});
});


Review comment (bug):
Tests will fail because `retentionSchedule` is async and needs to be awaited.

Code after:
		vi.mocked(useEnv).mockReturnValue({ RETENTION_ENABLED: false, RETENTION_SCHEDULE: '0 0 * * *' });

		const res = await retentionSchedule();

		expect(schedule.validateCron).not.toHaveBeenCalled();
		expect(res).toBe(false);
	});

	test('Returns early for invalid retention schedule', async () => {
		vi.mocked(useEnv).mockReturnValue({ RETENTION_ENABLED: true, RETENTION_SCHEDULE: '#' });

		const res = await retentionSchedule();

		expect(schedule.validateCron).toHaveBeenCalledWith('#');
		expect(res).toBe(false);
	});

	test('Schedules synchronized job', async () => {
		await retentionSchedule();

		expect(schedule.validateCron).toHaveBeenCalledWith('0 0 * * *');
		expect(schedule.scheduleSynchronizedJob).toHaveBeenCalledWith('retention', '0 0 * * *', handleRetentionJob);
	});

	test('Returns true on successful init', async () => {
		const res = await retentionSchedule();

		expect(res).toBe(true);
	});
});
Repository: directus/directus
File: sdk/src/types/utils.ts (TypeScript)

Code before:

/**
 * Test for any
 */
export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
export type IsAny<T> = IfAny<T, true, never>;

export type IsNullable<T, Y = true, N = never> = T | null extends T ? Y : N;
export type IsDateTime<T, Y, N> = T extends 'datetime' ? Y : N;
export type IsNumber<T, Y, N> = T extends number ? Y : N;
export type IsString<T, Y, N> = T extends string ? Y : N;

export type NestedPartial<Item extends object> = {
	[Key in keyof Item]?: NonNullable<Item[Key]> extends infer NestedItem
		? NestedItem extends object[]
			? NestedPartial<UnpackList<NestedItem>>[] | Exclude<Item[Key], NestedItem>
			: NestedItem extends object
			  ? NestedPartial<NestedItem> | Exclude<Item[Key], NestedItem>
			  : Item[Key]
		: Item[Key];
};

/**
 * Resolve type to its final object
 */
export type Identity<U> = U extends infer A ? A : U;

export type Prettify<T> = {
	[K in keyof T]: T[K];
} & unknown;


Review comment (suggestion):
Let's add a JSDoc description:

```suggestion
/**
 * Reduces a complex object type to make it readable in IDEs.
 */
export type Prettify<T> = {
```

Code after:

/**
 * Test for any
 */
export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
export type IsAny<T> = IfAny<T, true, never>;

export type IsNullable<T, Y = true, N = never> = T | null extends T ? Y : N;
export type IsDateTime<T, Y, N> = T extends 'datetime' ? Y : N;
export type IsNumber<T, Y, N> = T extends number ? Y : N;
export type IsString<T, Y, N> = T extends string ? Y : N;

export type NestedPartial<Item extends object> = {
	[Key in keyof Item]?: NonNullable<Item[Key]> extends infer NestedItem
		? NestedItem extends object[]
			? NestedPartial<UnpackList<NestedItem>>[] | Exclude<Item[Key], NestedItem>
			: NestedItem extends object
			  ? NestedPartial<NestedItem> | Exclude<Item[Key], NestedItem>
			  : Item[Key]
		: Item[Key];
};

/**
 * Reduces a complex object type to make it readable in IDEs.
 */
export type Prettify<T> = {
	[K in keyof T]: T[K];
} & unknown;
Repository: directus/directus
File: app/src/interfaces/select-multiple-dropdown/select-multiple-dropdown.vue (Vue)

Code before:
		allowOther?: boolean;
		previewThreshold?: number;
	}>(),
	{
		previewThreshold: 3,
	},
);

const emit = defineEmits(['input']);

const { t } = useI18n();

const items = computed(() => props.choices || []);

function updateValue(value: string[]) {
	const sortedValue = sortBy(value, (val) => {
		const sortIndex = items.value!.findIndex((choice) => val === choice.value);
		return sortIndex !== -1 ? sortIndex : value.length;
	});

	emit('input', sortedValue);
}
</script>

<template>
	<v-notice v-if="!items" type="warning">
		{{ t('choices_option_configured_incorrectly') }}
	</v-notice>
	<v-select
		v-else
		multiple
		:model-value="value"
		:items="items"
		:disabled="disabled"
		:show-deselect="allowNone"
		:placeholder="placeholder"
		:allow-other="allowOther"
		:close-on-content-click="false"
		:multiple-preview-threshold="previewThreshold"
		@update:model-value="updateValue($event)"
	>
		<template v-if="icon" #prepend>
			<v-icon :name="icon" />
		</template>
	</v-select>
</template>


Review comment (suggestion):
This won't work anymore, since items is now always at least an empty array, which is always truthy. I wonder if it is possible to even show this error anymore, since, through conditions it can be of length 0. We should probably go for the same length === 0 notice we have in the other interfaces

Code after:
		previewThreshold?: number;
	}>(),
	{
		previewThreshold: 3,
	},
);

const emit = defineEmits(['input']);

const items = computed(() => props.choices || []);

function updateValue(value: string[]) {
	const sortedValue = sortBy(value, (val) => {
		const sortIndex = items.value!.findIndex((item) => val === item.value);
		return sortIndex !== -1 ? sortIndex : value.length;
	});

	emit('input', sortedValue);
}
</script>

<template>
	<v-select
		multiple
		:model-value="value"
		:items="items"
		:disabled="disabled"
		:show-deselect="allowNone"
		:placeholder="placeholder"
		:allow-other="allowOther"
		:close-on-content-click="false"
		:multiple-preview-threshold="previewThreshold"
		@update:model-value="updateValue($event)"
	>
		<template v-if="icon" #prepend>
			<v-icon :name="icon" />
		</template>
	</v-select>
</template>
Repository: directus/directus
File: packages/extensions/src/shared/utils/define-extension.ts (TypeScript)

Code before:
import type { Prettify } from '@directus/types';
import type {
	DisplayConfig,
	EndpointConfig,
	HookConfig,
	InterfaceConfig,
	LayoutConfig,
	ModuleConfig,
	OperationApiConfig,
	OperationAppConfig,
	PanelConfig,
} from '../types/index.js';

type CustomConfig<T extends object> = { [K in string]: K extends keyof T ? never : unknown };

type ExtendedConfig<T, C> = Prettify<T & Omit<C, keyof T>>;

export function defineInterface<Custom extends CustomConfig<InterfaceConfig>>(
	config: ExtendedConfig<InterfaceConfig, Custom>,
): ExtendedConfig<InterfaceConfig, Custom> {
	return config;
}

export function defineDisplay<Custom extends CustomConfig<DisplayConfig>>(
	config: ExtendedConfig<DisplayConfig, Custom>,
): ExtendedConfig<DisplayConfig, Custom> {
	return config;
}

export function defineLayout<Options = any, Query = any>(
	config: LayoutConfig<Options, Query>,
): LayoutConfig<Options, Query> {
	return config;
}

export function defineModule<Custom extends CustomConfig<ModuleConfig>>(
	config: ExtendedConfig<ModuleConfig, Custom>,
): ExtendedConfig<ModuleConfig, Custom> {
	return config;

Review comment (suggestion):
`extends object` is unnecessary since the subsequent definition already narrows it down to a plain object

```suggestion
type CustomConfig<T> = { [K in string]: K extends keyof T ? never : unknown };
```

Code after:
import type { Prettify } from '@directus/types';
import type {
	DisplayConfig,
	EndpointConfig,
	HookConfig,
	InterfaceConfig,
	LayoutConfig,
	ModuleConfig,
	OperationApiConfig,
	OperationAppConfig,
	PanelConfig,
} from '../types/index.js';

type CustomConfig<T> = { [K in string]: K extends keyof T ? never : unknown };

type ExtendedConfig<T, C> = Prettify<T & Omit<C, keyof T>>;

export function defineInterface<Custom extends CustomConfig<InterfaceConfig>>(
	config: ExtendedConfig<InterfaceConfig, Custom>,
): ExtendedConfig<InterfaceConfig, Custom> {
	return config;
}

export function defineDisplay<Custom extends CustomConfig<DisplayConfig>>(
	config: ExtendedConfig<DisplayConfig, Custom>,
): ExtendedConfig<DisplayConfig, Custom> {
	return config;
}

export function defineLayout<Options = any, Query = any>(
	config: LayoutConfig<Options, Query>,
): LayoutConfig<Options, Query> {
	return config;
}

export function defineModule<Custom extends CustomConfig<ModuleConfig>>(
	config: ExtendedConfig<ModuleConfig, Custom>,
): ExtendedConfig<ModuleConfig, Custom> {
	return config;
Repository: directus/directus
File: api/src/database/migrations/20240924B-populate-versioning-deltas.ts (TypeScript)

Code before:
import { parseJSON } from '@directus/utils';
import type { Knex } from 'knex';
import { assign } from 'lodash-es';

export async function up(knex: Knex): Promise<void> {
	const rowsLimit = 50;
	let hasMore = true;

	while (hasMore) {
		const missingDeltaVersions = await knex.select('*').from('directus_versions').whereNull('delta').limit(rowsLimit);

		if (missingDeltaVersions.length === 0) {
			hasMore = false;
			break;
		}

		await knex.transaction(async (trx) => {
			for (const missingDeltaVersion of missingDeltaVersions) {
				const revisions = await trx
					.select('delta')
					.from('directus_revisions')
					.where('version', '=', missingDeltaVersion.id)
					.orderBy('id');

				const deltas = revisions.map((revision) => parseJSON(revision.delta));
				const consolidatedDelta = assign({}, ...deltas);

				await trx('directus_versions')
					.update({
						delta: JSON.stringify(consolidatedDelta),
					})
					.where('id', '=', missingDeltaVersion.id);
			}
		});
	}

Review comment (suggestion):
As it looks like we are only using the version id we should reduce the amount of data we are storing/retrieving by selecting the used/required fields only.

```suggestion
		const missingDeltaVersions = await knex.select('id').from('directus_versions').whereNull('delta').limit(rowsLimit);
```

Code after:
import { parseJSON } from '@directus/utils';
import type { Knex } from 'knex';
import { assign } from 'lodash-es';

export async function up(knex: Knex): Promise<void> {
	const rowsLimit = 50;
	let hasMore = true;

	while (hasMore) {
		const missingDeltaVersions = await knex.select('id').from('directus_versions').whereNull('delta').limit(rowsLimit);

		if (missingDeltaVersions.length === 0) {
			hasMore = false;
			break;
		}

		await knex.transaction(async (trx) => {
			for (const missingDeltaVersion of missingDeltaVersions) {
				const revisions = await trx
					.select('delta')
					.from('directus_revisions')
					.where('version', '=', missingDeltaVersion.id)
					.orderBy('id');

				const deltas = revisions.map((revision) => parseJSON(revision.delta));
				const consolidatedDelta = assign({}, ...deltas);

				await trx('directus_versions')
					.update({
						delta: JSON.stringify(consolidatedDelta),
					})
					.where('id', '=', missingDeltaVersion.id);
			}
		});
	}
Repository: directus/directus
File: packages/extensions/src/shared/utils/define-extension.ts (TypeScript)

Code before:
import type { Prettify } from '@directus/types';
import type {
	DisplayConfig,
	EndpointConfig,
	HookConfig,
	InterfaceConfig,
	LayoutConfig,
	ModuleConfig,
	OperationApiConfig,
	OperationAppConfig,
	PanelConfig,
} from '../types/index.js';

type CustomConfig<T extends object> = Record<string, unknown> & T;

type ExtendedConfig<T extends object, C> = Prettify<T & Omit<C, keyof T>>;

export function defineInterface<Custom extends CustomConfig<InterfaceConfig>>(
	config: Custom,
): ExtendedConfig<InterfaceConfig, Custom> {
	return config;
}

export function defineDisplay<Custom extends CustomConfig<DisplayConfig>>(
	config: Custom,
): ExtendedConfig<DisplayConfig, Custom> {
	return config;
}

export function defineLayout<Options = any, Query = any>(
	config: LayoutConfig<Options, Query>,
): LayoutConfig<Options, Query> {
	return config;
}

export function defineModule<Custom extends CustomConfig<ModuleConfig>>(
	config: Custom,
): ExtendedConfig<ModuleConfig, Custom> {
	return config;

Review comment (suggestion):
Reminder to self: discuss experimental type changes with hannes

Code after:
import type { Prettify } from '@directus/types';
import type {
	DisplayConfig,
	EndpointConfig,
	HookConfig,
	InterfaceConfig,
	LayoutConfig,
	ModuleConfig,
	OperationApiConfig,
	OperationAppConfig,
	PanelConfig,
} from '../types/index.js';

type CustomConfig<T extends object> = { [K in string]: K extends keyof T ? never : unknown };

type ExtendedConfig<T extends object, C> = Prettify<T & Omit<C, keyof T>>;

export function defineInterface<Custom extends CustomConfig<InterfaceConfig>>(
	config: ExtendedConfig<InterfaceConfig, Custom>,
): ExtendedConfig<InterfaceConfig, Custom> {
	return config;
}

export function defineDisplay<Custom extends CustomConfig<DisplayConfig>>(
	config: ExtendedConfig<DisplayConfig, Custom>,
): ExtendedConfig<DisplayConfig, Custom> {
	return config;
}

export function defineLayout<Options = any, Query = any>(
	config: LayoutConfig<Options, Query>,
): LayoutConfig<Options, Query> {
	return config;
}

export function defineModule<Custom extends CustomConfig<ModuleConfig>>(
	config: ExtendedConfig<ModuleConfig, Custom>,
): ExtendedConfig<ModuleConfig, Custom> {
	return config;
Repository: directus/directus
File: packages/storage-driver-gcs/src/index.ts (TypeScript)

Code before:
import type { ChunkedUploadContext, ReadOptions, TusDriver } from '@directus/storage';
import { normalizePath } from '@directus/utils';
import type { Bucket, CreateReadStreamOptions, GetFilesOptions } from '@google-cloud/storage';
import { Storage } from '@google-cloud/storage';
import { join } from 'node:path';
import { type Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';

const DEFAULT_CHUNK_SIZE = 8388608; // 8mb in bytes
const MINIMUM_CHUNK_SIZE = 262144; // 256kb in bytes

export type DriverGCSConfig = {
	root?: string;
	bucket: string;
	apiEndpoint?: string;
	tus?: {
		chunkSize?: number;
	};
};

export class DriverGCS implements TusDriver {
	private root: string;
	private bucket: Bucket;

	// TUS specific members
	private readonly preferredChunkSize: number;

	constructor(config: DriverGCSConfig) {
		const { bucket, root, tus, ...storageOptions } = config;

		this.root = root ? normalizePath(root, { removeLeading: true }) : '';

		const storage = new Storage(storageOptions);
		this.bucket = storage.bucket(bucket);


Review comment (suggestion):
To be consistent with other large numeric constants.

```suggestion
const DEFAULT_CHUNK_SIZE = 8_388_608; // 8mb in bytes
const MINIMUM_CHUNK_SIZE = 262_144; // 256kb in bytes
```

alternatively...
```suggestion
const DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024; // 8mb in bytes
const MINIMUM_CHUNK_SIZE = 256 * 1024; // 256kb in bytes
```

Code after:
import type { ChunkedUploadContext, ReadOptions, TusDriver } from '@directus/storage';
import { normalizePath } from '@directus/utils';
import type { Bucket, CreateReadStreamOptions, GetFilesOptions } from '@google-cloud/storage';
import { Storage } from '@google-cloud/storage';
import { join } from 'node:path';
import { type Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';

const DEFAULT_CHUNK_SIZE = 8_388_608; // 8mb in bytes
const MINIMUM_CHUNK_SIZE = 262_144; // 256kb in bytes

export type DriverGCSConfig = {
	root?: string;
	bucket: string;
	apiEndpoint?: string;
	tus?: {
		chunkSize?: number;
	};
};

export class DriverGCS implements TusDriver {
	private root: string;
	private bucket: Bucket;

	// TUS specific members
	private readonly preferredChunkSize: number;

	constructor(config: DriverGCSConfig) {
		const { bucket, root, tus, ...storageOptions } = config;

		this.root = root ? normalizePath(root, { removeLeading: true }) : '';

		const storage = new Storage(storageOptions);
		this.bucket = storage.bucket(bucket);