import type { Readable } from 'node:stream'; import { z } from 'zod'; import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; import { registerErrorDomain, type ErrorDomain } from '#/_base/errors/codes'; import { Error2 } from '#/_base/errors/errors'; import { isoDateTimeSchema } from '#/_base/utils/isoDateTime'; export const fileMetaSchema = z.object({ id: z.string().min(1), name: z.string().min(1), media_type: z.string().min(1), size: z.number().int().nonnegative(), created_at: isoDateTimeSchema, expires_at: isoDateTimeSchema.optional(), }); export type FileMeta = z.infer; export interface SaveOptions { readonly name?: string; readonly mimeType?: string; readonly expiresInSec?: number; } export interface GetResult { readonly meta: FileMeta; readonly stream: (range?: FileReadRange) => Readable; } export interface FileReadRange { readonly start: number; readonly end: number; } export interface IFileService { readonly _serviceBrand: undefined; save(source: Readable, filename: string, options?: SaveOptions): Promise; get(fileId: string): Promise; delete(fileId: string): Promise; } export const IFileService: ServiceIdentifier = createDecorator('fileService'); export const FILE_ID_REGEX = /^f_[A-Za-z0-9][A-Za-z0-9_-]*$/; export function isFileId(value: string): boolean { return FILE_ID_REGEX.test(value); } export const FileErrors = { codes: { FILE_NOT_FOUND: 'file.not_found', }, info: { 'file.not_found': { title: 'File not found', retryable: false, public: true, action: 'Check the file_id or upload the file again.', }, }, } as const satisfies ErrorDomain; registerErrorDomain(FileErrors); export class FileError extends Error2 { constructor( code: (typeof FileErrors.codes)[keyof typeof FileErrors.codes], message: string, details?: Record, ) { super(code, message, { details }); this.name = 'FileError'; } } export function fileNotFoundError(fileId: string): FileError { return new FileError(FileErrors.codes.FILE_NOT_FOUND, `file not found: ${fileId}`, { fileId }); } export function isFileError(error: unknown, code: (typeof FileErrors.codes)[keyof typeof FileErrors.codes]): boolean { return error instanceof Error2 && error.code === code; }