refactor(ocr): restructure ocr types into modular files for better maintainability

- Split monolithic ocr.ts into separate files for base types, providers, models, and layers (api, data, business)
- Update related imports and references across the codebase
- Rename API request/response types to be more consistent (Patch->Update, Put->Replace)
- Adjust repository and service implementations to match new type structure
This commit is contained in:
icarus 2025-10-20 20:39:24 +08:00
parent 327d0dab7f
commit e0781e1bb0
17 changed files with 435 additions and 432 deletions

View File

@ -7,10 +7,10 @@ import type {
ListOcrProvidersQuery,
ListOcrProvidersResponse,
OcrProviderId,
PatchOcrProviderRequest,
PatchOcrProviderResponse,
PutOcrProviderRequest,
PutOcrProviderResponse
ReplaceOcrProviderRequest,
ReplaceOcrProviderResponse,
UpdateOcrProviderRequest,
UpdateOcrProviderResponse
} from '@types'
import type { BodyForPath, ConcreteApiPaths, QueryParamsForPath, ResponseForPath } from './apiPaths'
@ -377,13 +377,13 @@ export interface ApiSchemas {
}
PATCH: {
params: { id: OcrProviderId }
body: PatchOcrProviderRequest
response: PatchOcrProviderResponse
body: UpdateOcrProviderRequest
response: UpdateOcrProviderResponse
}
PUT: {
params: { id: OcrProviderId }
body: PutOcrProviderRequest
response: PutOcrProviderResponse
body: ReplaceOcrProviderRequest
response: ReplaceOcrProviderResponse
}
DELETE: {
params: { id: OcrProviderId }

View File

@ -2,16 +2,12 @@ import { dbService } from '@data/db/DbService'
import { ocrProviderTable } from '@data/db/schemas/ocr/provider'
import { loggerService } from '@logger'
import type {
CreateOcrProviderRequest,
CreateOcrProviderResponse,
DbOcrProvider,
ListOcrProvidersQuery,
ListOcrProvidersResponse,
DbOcrProviderCreate,
DbOcrProviderReplace,
DbOcrProviderUpdate,
OcrProviderId,
PatchOcrProviderRequest,
PatchOcrProviderResponse,
PutOcrProviderRequest,
PutOcrProviderResponse
UpdateOcrProviderResponse
} from '@types'
import { BuiltinOcrProviderIds, isDbOcrProvider } from '@types'
import dayjs from 'dayjs'
@ -28,17 +24,11 @@ export class OcrProviderRepository {
/**
* Get all OCR providers
*/
public async findAll(query?: ListOcrProvidersQuery): Promise<ListOcrProvidersResponse> {
public async findAll(): Promise<DbOcrProvider[]> {
try {
const providers = await dbService.getDb().select().from(ocrProviderTable)
if (query?.registered) {
// Filter by registered providers (this would need to be implemented)
// For now, return all providers
return { data: providers }
}
return { data: providers }
return providers
} catch (error) {
logger.error('Failed to find all OCR providers', error as Error)
throw error
@ -90,16 +80,16 @@ export class OcrProviderRepository {
/**
* Create new OCR provider
*/
public async create(data: CreateOcrProviderRequest): Promise<CreateOcrProviderResponse> {
public async create(param: DbOcrProviderCreate): Promise<DbOcrProvider> {
try {
// Check if provider already exists
if (await this.exists(data.id)) {
throw new Error(`OCR provider ${data.id} already exists`)
if (await this.exists(param.id)) {
throw new Error(`OCR provider ${param.id} already exists`)
}
const timestamp = dayjs().valueOf()
const newProvider = {
...data,
...param,
createdAt: timestamp,
updatedAt: timestamp
} satisfies DbOcrProvider
@ -111,10 +101,10 @@ export class OcrProviderRepository {
const [created] = await dbService.getDb().insert(ocrProviderTable).values(newProvider).returning()
logger.info(`Created OCR provider: ${data.id}`)
return { data: created }
logger.info(`Created OCR provider: ${param.id}`)
return created
} catch (error) {
logger.error(`Failed to create OCR provider ${data.id}`, error as Error)
logger.error(`Failed to create OCR provider ${param.id}`, error as Error)
throw error
}
}
@ -122,12 +112,12 @@ export class OcrProviderRepository {
/**
* Update OCR provider (partial update)
*/
public async update(id: OcrProviderId, data: Partial<PatchOcrProviderRequest>): Promise<PatchOcrProviderResponse> {
public async update(id: OcrProviderId, update: DbOcrProviderUpdate): Promise<UpdateOcrProviderResponse> {
try {
const existing = await this.findById(id)
const newProvider = {
...merge({}, existing, data),
...merge({}, existing, update),
updatedAt: dayjs().valueOf()
} satisfies DbOcrProvider
@ -154,7 +144,7 @@ export class OcrProviderRepository {
/**
* Replace OCR provider (full update)
*/
public async replace(data: PutOcrProviderRequest): Promise<PutOcrProviderResponse> {
public async replace(data: DbOcrProviderReplace): Promise<DbOcrProvider> {
try {
// Check if it's a built-in provider
if (BuiltinOcrProviderIds.some((pid) => pid === data.id)) {
@ -199,7 +189,7 @@ export class OcrProviderRepository {
.returning()
logger.info(`Replaced OCR provider: ${data.id}`)
return { data: saved }
return saved
} catch (error) {
logger.error(`Failed to replace OCR provider ${data.id}`, error as Error)
throw error

View File

@ -4,17 +4,18 @@ import type {
CreateOcrProviderRequest,
CreateOcrProviderResponse,
DbOcrProvider,
GetOcrProviderResponse,
ListOcrProvidersQuery,
ListOcrProvidersResponse,
OcrParams,
OcrProvider,
OcrProviderId,
OcrProviderKeyBusiness,
OcrResult,
PatchOcrProviderRequest,
PatchOcrProviderResponse,
PutOcrProviderRequest,
PutOcrProviderResponse,
SupportedOcrFile
ReplaceOcrProviderRequest,
ReplaceOcrProviderResponse,
SupportedOcrFile,
UpdateOcrProviderRequest,
UpdateOcrProviderResponse
} from '@types'
import { BuiltinOcrProviderIdMap } from '@types'
@ -31,7 +32,7 @@ const logger = loggerService.withContext('OcrService')
* Handles OCR provider registration, orchestration, and core OCR functionality
*/
class OcrService {
private registry: Map<OcrProviderId, OcrBaseService> = new Map()
private registry: Map<OcrProviderKeyBusiness, OcrBaseService> = new Map()
private initialized: boolean = false
constructor() {
@ -83,7 +84,7 @@ class OcrService {
/**
* Register an OCR provider service
*/
private register(providerId: OcrProviderId, service: OcrBaseService): void {
private register(providerId: OcrProviderKeyBusiness, service: OcrBaseService): void {
if (this.registry.has(providerId)) {
logger.warn(`Provider ${providerId} already registered. Overwriting.`)
}
@ -104,14 +105,14 @@ class OcrService {
/**
* Get all registered provider IDs
*/
public getRegisteredProviderIds(): OcrProviderId[] {
public getRegisteredProviderIds(): OcrProviderKeyBusiness[] {
return Array.from(this.registry.keys())
}
/**
* Check if a provider is registered
*/
public isProviderRegistered(providerId: OcrProviderId): boolean {
public isProviderRegistered(providerId: OcrProviderKeyBusiness): boolean {
return this.registry.has(providerId)
}
@ -121,16 +122,17 @@ class OcrService {
public async listProviders(query?: ListOcrProvidersQuery): Promise<ListOcrProvidersResponse> {
try {
await this.ensureInitialized()
const result = await ocrProviderRepository.findAll(query)
const providers = await ocrProviderRepository.findAll()
let result = providers
if (query?.registered) {
// Filter by registered providers
const registeredIds = this.getRegisteredProviderIds()
result.data = result.data.filter((provider) => registeredIds.includes(provider.id))
result = providers.filter((provider) => registeredIds.includes(provider.id))
}
logger.debug(`Listed ${result.data.length} OCR providers`)
return result
logger.debug(`Listed ${result.length} OCR providers`)
return { data: result }
} catch (error) {
logger.error('Failed to list OCR providers', error as Error)
throw error
@ -140,7 +142,7 @@ class OcrService {
/**
* Get OCR provider by ID
*/
public async getProvider(providerId: OcrProviderId): Promise<{ data: DbOcrProvider }> {
public async getProvider(providerId: OcrProviderKeyBusiness): Promise<GetOcrProviderResponse> {
try {
await this.ensureInitialized()
const provider = await ocrProviderRepository.findById(providerId)
@ -160,7 +162,7 @@ class OcrService {
await this.ensureInitialized()
const result = await ocrProviderRepository.create(data)
logger.info(`Created OCR provider: ${data.id}`)
return result
return { data: result }
} catch (error) {
logger.error(`Failed to create OCR provider ${data.id}`, error as Error)
throw error
@ -171,9 +173,9 @@ class OcrService {
* Update OCR provider (partial update)
*/
public async updateProvider(
id: OcrProviderId,
data: Partial<PatchOcrProviderRequest>
): Promise<PatchOcrProviderResponse> {
id: OcrProviderKeyBusiness,
data: UpdateOcrProviderRequest
): Promise<UpdateOcrProviderResponse> {
try {
await this.ensureInitialized()
const result = await ocrProviderRepository.update(id, data)
@ -188,7 +190,7 @@ class OcrService {
/**
* Replace OCR provider (full update)
*/
public async replaceProvider(data: PutOcrProviderRequest): Promise<PutOcrProviderResponse> {
public async replaceProvider(data: ReplaceOcrProviderRequest): Promise<ReplaceOcrProviderResponse> {
try {
await this.ensureInitialized()
const result = await ocrProviderRepository.replace(data)
@ -203,7 +205,7 @@ class OcrService {
/**
* Delete OCR provider
*/
public async deleteProvider(id: OcrProviderId): Promise<void> {
public async deleteProvider(id: OcrProviderKeyBusiness): Promise<void> {
try {
await this.ensureInitialized()
await ocrProviderRepository.delete(id)
@ -242,7 +244,7 @@ class OcrService {
/**
* Check if a provider is available and ready
*/
public async isProviderAvailable(providerId: OcrProviderId): Promise<boolean> {
public async isProviderAvailable(providerId: OcrProviderKeyBusiness): Promise<boolean> {
try {
const service = this.registry.get(providerId)
if (!service) {

View File

@ -0,0 +1,52 @@
import * as z from 'zod'
import { DbOcrProviderSchema } from './data'
import { OcrProviderSchema } from './provider/base'
import { OcrProviderNameSchema } from './provider/base'
import { OcrProviderIdSchema } from './provider/base'
import { OcrProviderConfigSchema } from './provider/base'
// ==========================================================
// API layer Types
// ==========================================================
export const TimestampExtendShape = {
createdAt: z.number().nullable(),
updatedAt: z.number().nullable()
}
export type ListOcrProvidersQuery = { registered?: boolean }
export const ListOcrProvidersResponseSchema = z.object({
data: z.array(DbOcrProviderSchema)
})
export type ListOcrProvidersResponse = z.infer<typeof ListOcrProvidersResponseSchema>
export const GetOcrProviderResponseSchema = z.object({
data: DbOcrProviderSchema
})
export type GetOcrProviderResponse = z.infer<typeof GetOcrProviderResponseSchema> /**
* Request payload for updating an OCR provider.
* Only the following fields are modifiable:
* - `name`: provider display name
* - `config`: provider-specific configuration object (all properties optional)
*/
export const UpdateOcrProviderRequestSchema = z.object({
id: OcrProviderIdSchema,
name: OcrProviderNameSchema.optional(),
config: OcrProviderConfigSchema.partial().optional()
})
export type UpdateOcrProviderRequest = z.infer<typeof UpdateOcrProviderRequestSchema>
export const UpdateOcrProviderResponseSchema = z.object({
data: DbOcrProviderSchema
})
export type UpdateOcrProviderResponse = z.infer<typeof UpdateOcrProviderResponseSchema>
export const CreateOcrProviderRequestSchema = OcrProviderSchema
export type CreateOcrProviderRequest = z.infer<typeof CreateOcrProviderRequestSchema>
export const CreateOcrProviderResponseSchema = z.object({
data: DbOcrProviderSchema
})
export type CreateOcrProviderResponse = z.infer<typeof CreateOcrProviderResponseSchema>
export const ReplaceOcrProviderRequestSchema = OcrProviderSchema
export type ReplaceOcrProviderRequest = z.infer<typeof ReplaceOcrProviderRequestSchema>
export const ReplaceOcrProviderResponseSchema = z.object({
data: DbOcrProviderSchema
})
export type ReplaceOcrProviderResponse = z.infer<typeof ReplaceOcrProviderResponseSchema>

View File

@ -0,0 +1,20 @@
import type { FileMetadata, ImageFileMetadata } from '..'
import { isImageFileMetadata } from '..'
export type SupportedOcrFile = ImageFileMetadata
export const isSupportedOcrFile = (file: FileMetadata): file is SupportedOcrFile => {
return isImageFileMetadata(file)
}
export type OcrParams = {
providerId: string
}
export type OcrResult = {
text: string
}
export type OcrHandler = (file: SupportedOcrFile) => Promise<OcrResult>
export type OcrImageHandler = (file: ImageFileMetadata) => Promise<OcrResult>

View File

@ -0,0 +1,31 @@
import type { DbOcrProviderCreate } from './data'
import type { DbOcrProviderUpdate } from './data'
import type { DbOcrProviderReplace } from './data'
import type { DbOcrProviderKey } from './data'
import type { DbOcrProvider } from './data'
// ==========================================================
// Business layer Types
// ==========================================================
/**
* Business-level representation of an OCR provider.
* Mirrors the data layer but is intended for use in domain/business logic.
*/
export type OcrProviderBusiness = DbOcrProvider /**
* Business-level representation of an OCR provider creation payload.
*/
export type OcrProviderCreateBusiness = DbOcrProviderCreate /**
* Business-level representation of an OCR provider update payload.
*/
export type OcrProviderUpdateBusiness = DbOcrProviderUpdate /**
* Business-level representation of an OCR provider replacement payload.
*/
export type OcrProviderReplaceBusiness = DbOcrProviderReplace /**
* Business-level key type for identifying an OCR provider.
*/
export type OcrProviderKeyBusiness = DbOcrProviderKey

View File

@ -0,0 +1,22 @@
import type * as z from 'zod'
import { TimestampExtendShape, type UpdateOcrProviderRequest } from './api'
import { type OcrProvider } from './provider/base'
import { OcrProviderSchema } from './provider/base'
// ==========================================================
// Data layer Types
//
// NOTE: Timestamp operations are not exposed to outside.
// ==========================================================
export const DbOcrProviderSchema = OcrProviderSchema.extend(TimestampExtendShape)
export type DbOcrProvider = z.infer<typeof DbOcrProviderSchema>
export function isDbOcrProvider(p: unknown): p is DbOcrProvider {
return DbOcrProviderSchema.safeParse(p).success
}
export type DbOcrProviderCreate = OcrProvider
export type DbOcrProviderUpdate = UpdateOcrProviderRequest
export type DbOcrProviderReplace = OcrProvider
export type DbOcrProviderKey = DbOcrProvider['id']

View File

@ -1 +1,6 @@
export * from './ocr'
export * from './api'
export * from './base'
export * from './business'
export * from './data'
export * from './model'
export * from './provider'

View File

@ -0,0 +1,17 @@
import * as z from 'zod'
import type { OcrProviderCapabilityRecord } from './provider/base'
import { OcrProviderCapabilityRecordSchema } from './provider/base'
// OCR models and providers share the same type definition.
// A provider can offer capabilities to process multiple file types,
// while a model belonging to that provider may be limited to processing only one specific file type.
export type OcrModelCapabilityRecord = OcrProviderCapabilityRecord
export const OcrModelSchema = z.object({
id: z.string(),
name: z.string(),
providerId: z.string(),
capabilities: OcrProviderCapabilityRecordSchema
})
export type OcrModel = z.infer<typeof OcrModelSchema>

View File

@ -1,373 +0,0 @@
import type Tesseract from 'tesseract.js'
import * as z from 'zod'
import type { FileMetadata, ImageFileMetadata, TranslateLanguageCode } from '..'
import { isImageFileMetadata, objectValues, TranslateLanguageCodeSchema } from '..'
export const BuiltinOcrProviderIdMap = {
tesseract: 'tesseract',
system: 'system',
paddleocr: 'paddleocr',
ovocr: 'ovocr'
} as const satisfies Record<string, BuiltinOcrProviderId>
export const BuiltinOcrProviderIds = Object.freeze(objectValues(BuiltinOcrProviderIdMap))
export const BuiltinOcrProviderIdSchema = z.enum(['tesseract', 'system', 'paddleocr', 'ovocr'])
export type BuiltinOcrProviderId = z.infer<typeof BuiltinOcrProviderIdSchema>
export const isBuiltinOcrProviderId = (id: string): id is BuiltinOcrProviderId => {
return BuiltinOcrProviderIdSchema.safeParse(id).success
}
// extensible
export const OcrProviderCapabilities = {
image: 'image'
// pdf: 'pdf'
} as const satisfies Record<string, OcrProviderCapability>
export const OcrProviderCapabilitySchema = z.enum(['image'])
export type OcrProviderCapability = z.infer<typeof OcrProviderCapabilitySchema>
export const isOcrProviderCapability = (cap: string): cap is OcrProviderCapability => {
return OcrProviderCapabilitySchema.safeParse(cap).success
}
export const OcrProviderCapabilityRecordSchema = z.partialRecord(OcrProviderCapabilitySchema, z.boolean())
export type OcrProviderCapabilityRecord = z.infer<typeof OcrProviderCapabilityRecordSchema>
// OCR models and providers share the same type definition.
// A provider can offer capabilities to process multiple file types,
// while a model belonging to that provider may be limited to processing only one specific file type.
export type OcrModelCapabilityRecord = OcrProviderCapabilityRecord
export const OcrModelSchema = z.object({
id: z.string(),
name: z.string(),
providerId: z.string(),
capabilities: OcrProviderCapabilityRecordSchema
})
export type OcrModel = z.infer<typeof OcrModelSchema>
/**
* Extend this type to define provider-specefic config types.
*/
export const OcrProviderApiConfigSchema = z.object({
apiKey: z.string(),
apiHost: z.string(),
apiVersion: z.string().optional()
})
export type OcrProviderApiConfig = z.infer<typeof OcrProviderApiConfigSchema>
export const isOcrProviderApiConfig = (config: unknown): config is OcrProviderApiConfig => {
return OcrProviderApiConfigSchema.safeParse(config).success
}
/**
* For future. Model based ocr, api based ocr. May different api client.
*
* Extend this type to define provider-specific config types.
*/
export const OcrProviderBaseConfigSchema = z.object({
enabled: z.boolean().default(false)
})
export type OcrProviderBaseConfig = z.infer<typeof OcrProviderBaseConfigSchema>
export const OcrProviderConfigSchema = OcrProviderBaseConfigSchema.loose()
export type OcrProviderConfig = z.infer<typeof OcrProviderConfigSchema>
const OcrProviderIdSchema = z.string()
export type OcrProviderId = z.infer<typeof OcrProviderIdSchema>
const OcrProviderNameSchema = z.string()
export const OcrProviderSchema = z.object({
id: OcrProviderIdSchema,
name: OcrProviderNameSchema,
capabilities: OcrProviderCapabilityRecordSchema,
config: OcrProviderConfigSchema
})
export type OcrProvider = z.infer<typeof OcrProviderSchema>
export const isOcrProvider = (p: unknown): p is OcrProvider => {
return OcrProviderSchema.safeParse(p).success
}
export const OcrApiProviderConfigSchema = OcrProviderBaseConfigSchema.extend({
api: OcrProviderApiConfigSchema
})
export type OcrApiProviderConfig = z.infer<typeof OcrApiProviderConfigSchema>
export const isOcrApiProviderConfig = (config: unknown): config is OcrApiProviderConfig => {
return OcrApiProviderConfigSchema.safeParse(config).success
}
export const OcrApiProviderSchema = OcrProviderSchema
/** Currently, there is no API provider yet, but we've left room for expansion. */
export type OcrApiProvider = z.infer<typeof OcrApiProviderSchema>
export const isOcrApiProvider = (p: unknown): p is OcrApiProvider => {
return OcrApiProviderSchema.safeParse(p).success
}
export type BuiltinOcrProvider = OcrProvider & {
id: BuiltinOcrProviderId
}
export const isBuiltinOcrProvider = (p: OcrProvider): p is BuiltinOcrProvider => {
return isBuiltinOcrProviderId(p.id)
}
// Not sure compatible api endpoint exists. May not support custom ocr provider
export type CustomOcrProvider = OcrProvider & {
id: Exclude<string, BuiltinOcrProviderId>
}
export type ImageOcrProvider = OcrProvider & {
capabilities: OcrProviderCapabilityRecord & {
[OcrProviderCapabilities.image]: true
}
}
// export type PdfOcrProvider = OcrProvider & {
// capabilities: OcrProviderCapabilityRecord & {
// [OcrProviderCapabilities.pdf]: true
// }
// }
export const isImageOcrProvider = (p: OcrProvider): p is ImageOcrProvider => {
return p.capabilities.image === true
}
export type SupportedOcrFile = ImageFileMetadata
export const isSupportedOcrFile = (file: FileMetadata): file is SupportedOcrFile => {
return isImageFileMetadata(file)
}
export type OcrParams = {
providerId: string
}
export type OcrResult = {
text: string
}
export type OcrHandler = (file: SupportedOcrFile) => Promise<OcrResult>
export type OcrImageHandler = (file: ImageFileMetadata) => Promise<OcrResult>
// ==========================================================
// Tesseract OCR Types
// ==========================================================
export const OcrTesseractConfigSchema = OcrProviderBaseConfigSchema.extend({
langs: z.record(TranslateLanguageCodeSchema, z.boolean()).optional()
})
export type OcrTesseractConfig = z.infer<typeof OcrTesseractConfigSchema>
export type OcrTesseractProvider = {
id: 'tesseract'
config: OcrTesseractConfig
} & ImageOcrProvider &
BuiltinOcrProvider
export const isOcrTesseractProvider = (p: OcrProvider): p is OcrTesseractProvider => {
return p.id === BuiltinOcrProviderIdMap.tesseract
}
export type TesseractLangCode = Tesseract.LanguageCode
// ==========================================================
// System OCR Types
// ==========================================================
export interface OcrSystemConfig extends OcrProviderBaseConfig {
langs?: TranslateLanguageCode[]
}
export type OcrSystemProvider = {
id: 'system'
config: OcrSystemConfig
} & ImageOcrProvider &
// PdfOcrProvider &
BuiltinOcrProvider
export const isOcrSystemProvider = (p: OcrProvider): p is OcrSystemProvider => {
return p.id === BuiltinOcrProviderIdMap.system
}
// ==========================================================
// PaddleOCR Types
// ==========================================================
export const OcrPpocrConfigSchema = OcrProviderBaseConfigSchema.extend({
apiUrl: z.string().optional(),
accessToken: z.string().optional()
})
export type OcrPpocrConfig = z.infer<typeof OcrPpocrConfigSchema>
export const isOcrPpocrConfig = (config: unknown): config is OcrPpocrConfig => {
return OcrPpocrConfigSchema.safeParse(config).success
}
export type OcrPpocrProvider = {
id: 'paddleocr'
config: OcrPpocrConfig
} & ImageOcrProvider &
// PdfOcrProvider &
BuiltinOcrProvider
export const isOcrPpocrProvider = (p: OcrProvider): p is OcrPpocrProvider => {
return p.id === BuiltinOcrProviderIdMap.paddleocr
}
// ==========================================================
// OV OCR Types
// ==========================================================
export const OcrOvConfigSchema = OcrProviderBaseConfigSchema.extend({
// It's not configurable for now.
// langs: z.array(TranslateLanguageCodeSchema).optional()
})
export type OcrOvConfig = z.infer<typeof OcrOvConfigSchema>
export const isOcrOvConfig = (config: unknown): config is OcrOvConfig => {
return OcrOvConfigSchema.safeParse(config).success
}
export type OcrOvProvider = {
id: 'ovocr'
config: OcrOvConfig
} & ImageOcrProvider &
// PdfOcrProvider &
BuiltinOcrProvider
export const isOcrOVProvider = (p: OcrProvider): p is OcrOvProvider => {
return p.id === BuiltinOcrProviderIdMap.ovocr
}
// ==========================================================
// API layer Types
// ==========================================================
const TimestampExtendShape = {
createdAt: z.number().nullable(),
updatedAt: z.number().nullable()
}
export const DbOcrProviderSchema = OcrProviderSchema.extend(TimestampExtendShape)
export type DbOcrProvider = z.infer<typeof DbOcrProviderSchema>
export const isDbOcrProvider = (p: unknown): p is DbOcrProvider => {
return DbOcrProviderSchema.safeParse(p).success
}
export type ListOcrProvidersQuery = { registered?: boolean }
export const ListOcrProvidersResponseSchema = z.object({
data: z.array(DbOcrProviderSchema)
})
export type ListOcrProvidersResponse = z.infer<typeof ListOcrProvidersResponseSchema>
export const GetOcrProviderResponseSchema = z.object({
data: DbOcrProviderSchema
})
export type GetOcrProviderResponse = z.infer<typeof GetOcrProviderResponseSchema>
/**
* Request payload for updating an OCR provider.
* Only the following fields are modifiable:
* - `name`: provider display name
* - `config`: provider-specific configuration object (all properties optional)
*/
export const UpdateOcrProviderRequestSchema = z.object({
id: OcrProviderIdSchema,
name: OcrProviderNameSchema.optional(),
config: OcrProviderConfigSchema.partial().optional()
})
export type UpdateOcrProviderRequest = z.infer<typeof UpdateOcrProviderRequestSchema>
export const UpdateOcrProviderResponseSchema = z.object({
data: DbOcrProviderSchema
})
export type UpdateOcrProviderResponse = z.infer<typeof UpdateOcrProviderResponseSchema>
export const CreateOcrProviderRequestSchema = OcrProviderSchema
export type CreateOcrProviderRequest = z.infer<typeof CreateOcrProviderRequestSchema>
export const CreateOcrProviderResponseSchema = z.object({
data: DbOcrProviderSchema
})
export type CreateOcrProviderResponse = z.infer<typeof CreateOcrProviderResponseSchema>
export const ReplaceOcrProviderRequestSchema = OcrProviderSchema
export type ReplaceOcrProviderRequest = z.infer<typeof ReplaceOcrProviderRequestSchema>
export const ReplaceOcrProviderResponseSchema = z.object({
data: DbOcrProviderSchema
})
export type ReplaceOcrProviderResponse = z.infer<typeof ReplaceOcrProviderResponseSchema>
// ==========================================================
// Business layer Types
// ==========================================================
/**
* Business-level representation of an OCR provider.
* Mirrors the data layer but is intended for use in domain/business logic.
*/
export type OcrProviderBusiness = DbOcrProvider
/**
* Business-level representation of an OCR provider creation payload.
*/
export type OcrProviderCreateBusiness = DbOcrProviderCreate
/**
* Business-level representation of an OCR provider update payload.
*/
export type OcrProviderUpdateBusiness = DbOcrProviderUpdate
/**
* Business-level representation of an OCR provider replacement payload.
*/
export type OcrProviderReplaceBusiness = DbOcrProviderReplace
/**
* Business-level key type for identifying an OCR provider.
*/
export type OcrProviderKeyBusiness = DbOcrProviderKey
// ==========================================================
// Data layer Types
//
// NOTE: Timestamp operations are not exposed to outside.
// ==========================================================
export type DbOcrProviderCreate = OcrProvider
export type DbOcrProviderUpdate = UpdateOcrProviderRequest
export type DbOcrProviderReplace = OcrProvider
export type DbOcrProviderKey = DbOcrProvider['id']

View File

@ -0,0 +1,118 @@
import { objectValues } from '@types'
import * as z from 'zod'
export const BuiltinOcrProviderIdMap = {
tesseract: 'tesseract',
system: 'system',
paddleocr: 'paddleocr',
ovocr: 'ovocr'
} as const satisfies Record<string, BuiltinOcrProviderId>
export const BuiltinOcrProviderIds = Object.freeze(objectValues(BuiltinOcrProviderIdMap))
export const BuiltinOcrProviderIdSchema = z.enum(['tesseract', 'system', 'paddleocr', 'ovocr'])
export type BuiltinOcrProviderId = z.infer<typeof BuiltinOcrProviderIdSchema>
export const isBuiltinOcrProviderId = (id: string): id is BuiltinOcrProviderId => {
return BuiltinOcrProviderIdSchema.safeParse(id).success
} // extensible
export const OcrProviderCapabilities = {
image: 'image'
// pdf: 'pdf'
} as const satisfies Record<string, OcrProviderCapability>
export const OcrProviderCapabilitySchema = z.enum(['image'])
export type OcrProviderCapability = z.infer<typeof OcrProviderCapabilitySchema>
export const isOcrProviderCapability = (cap: string): cap is OcrProviderCapability => {
return OcrProviderCapabilitySchema.safeParse(cap).success
}
export const OcrProviderCapabilityRecordSchema = z.partialRecord(OcrProviderCapabilitySchema, z.boolean())
export type OcrProviderCapabilityRecord = z.infer<typeof OcrProviderCapabilityRecordSchema>
/**
* Extend this type to define provider-specefic config types.
*/
export const OcrProviderApiConfigSchema = z.object({
apiKey: z.string(),
apiHost: z.string(),
apiVersion: z.string().optional()
})
export type OcrProviderApiConfig = z.infer<typeof OcrProviderApiConfigSchema>
export const isOcrProviderApiConfig = (config: unknown): config is OcrProviderApiConfig => {
return OcrProviderApiConfigSchema.safeParse(config).success
} /**
* For future. Model based ocr, api based ocr. May different api client.
*
* Extend this type to define provider-specific config types.
*/
export const OcrProviderBaseConfigSchema = z.object({
enabled: z.boolean().default(false)
})
export type OcrProviderBaseConfig = z.infer<typeof OcrProviderBaseConfigSchema>
export const OcrProviderConfigSchema = OcrProviderBaseConfigSchema.loose()
export type OcrProviderConfig = z.infer<typeof OcrProviderConfigSchema>
export const OcrProviderIdSchema = z.string()
export type OcrProviderId = z.infer<typeof OcrProviderIdSchema>
export const OcrProviderNameSchema = z.string()
export const OcrProviderSchema = z.object({
id: OcrProviderIdSchema,
name: OcrProviderNameSchema,
capabilities: OcrProviderCapabilityRecordSchema,
config: OcrProviderConfigSchema
})
export type OcrProvider = z.infer<typeof OcrProviderSchema>
export const isOcrProvider = (p: unknown): p is OcrProvider => {
return OcrProviderSchema.safeParse(p).success
}
export const OcrApiProviderConfigSchema = OcrProviderBaseConfigSchema.extend({
api: OcrProviderApiConfigSchema
})
export type OcrApiProviderConfig = z.infer<typeof OcrApiProviderConfigSchema>
export const isOcrApiProviderConfig = (config: unknown): config is OcrApiProviderConfig => {
return OcrApiProviderConfigSchema.safeParse(config).success
}
export const OcrApiProviderSchema = OcrProviderSchema
/** Currently, there is no API provider yet, but we've left room for expansion. */
export type OcrApiProvider = z.infer<typeof OcrApiProviderSchema>
export const isOcrApiProvider = (p: unknown): p is OcrApiProvider => {
return OcrApiProviderSchema.safeParse(p).success
}
export type BuiltinOcrProvider = OcrProvider & {
id: BuiltinOcrProviderId
}
export const isBuiltinOcrProvider = (p: OcrProvider): p is BuiltinOcrProvider => {
return isBuiltinOcrProviderId(p.id)
}
// Not sure compatible api endpoint exists. May not support custom ocr provider
export type CustomOcrProvider = OcrProvider & {
id: Exclude<string, BuiltinOcrProviderId>
}
export type ImageOcrProvider = OcrProvider & {
capabilities: OcrProviderCapabilityRecord & {
[OcrProviderCapabilities.image]: true
}
}
export const isImageOcrProvider = (p: OcrProvider): p is ImageOcrProvider => {
return p.capabilities.image === true
}

View File

@ -0,0 +1,5 @@
export * from './base'
export * from './ov'
export * from './paddle'
export * from './system'
export * from './tesseract'

View File

@ -0,0 +1,29 @@
import type * as z from 'zod'
import type { ImageOcrProvider } from './base'
import type { BuiltinOcrProvider } from './base'
import type { OcrProvider } from './base'
import { OcrProviderBaseConfigSchema } from './base'
import { BuiltinOcrProviderIdMap } from './base'
// ==========================================================
// OV OCR Types
// ==========================================================
export const OcrOvConfigSchema = OcrProviderBaseConfigSchema.extend({
// It's not configurable for now.
// langs: z.array(TranslateLanguageCodeSchema).optional()
})
export type OcrOvConfig = z.infer<typeof OcrOvConfigSchema>
export const isOcrOvConfig = (config: unknown): config is OcrOvConfig => {
return OcrOvConfigSchema.safeParse(config).success
}
export type OcrOvProvider = {
id: 'ovocr'
config: OcrOvConfig
} & ImageOcrProvider &
// PdfOcrProvider &
BuiltinOcrProvider
export const isOcrOVProvider = (p: OcrProvider): p is OcrOvProvider => {
return p.id === BuiltinOcrProviderIdMap.ovocr
}

View File

@ -0,0 +1,29 @@
import * as z from 'zod'
import type { ImageOcrProvider } from './base'
import type { BuiltinOcrProvider } from './base'
import type { OcrProvider } from './base'
import { OcrProviderBaseConfigSchema } from './base'
import { BuiltinOcrProviderIdMap } from './base'
// ==========================================================
// PaddleOCR Types
// ==========================================================
export const OcrPpocrConfigSchema = OcrProviderBaseConfigSchema.extend({
apiUrl: z.string().optional(),
accessToken: z.string().optional()
})
export type OcrPpocrConfig = z.infer<typeof OcrPpocrConfigSchema>
export const isOcrPpocrConfig = (config: unknown): config is OcrPpocrConfig => {
return OcrPpocrConfigSchema.safeParse(config).success
}
export type OcrPpocrProvider = {
id: 'paddleocr'
config: OcrPpocrConfig
} & ImageOcrProvider &
// PdfOcrProvider &
BuiltinOcrProvider
export const isOcrPpocrProvider = (p: OcrProvider): p is OcrPpocrProvider => {
return p.id === BuiltinOcrProviderIdMap.paddleocr
}

View File

@ -0,0 +1,25 @@
import type { TranslateLanguageCode } from '@types'
import type { OcrProvider } from './base'
import { type ImageOcrProvider } from './base'
import { type BuiltinOcrProvider } from './base'
import { type OcrProviderBaseConfig } from './base'
import { BuiltinOcrProviderIdMap } from './base'
// ==========================================================
// System OCR Types
// ==========================================================
export interface OcrSystemConfig extends OcrProviderBaseConfig {
langs?: TranslateLanguageCode[]
}
export type OcrSystemProvider = {
id: 'system'
config: OcrSystemConfig
} & ImageOcrProvider &
// PdfOcrProvider &
BuiltinOcrProvider
export const isOcrSystemProvider = (p: OcrProvider): p is OcrSystemProvider => {
return p.id === BuiltinOcrProviderIdMap.system
}

View File

@ -0,0 +1,31 @@
import { TranslateLanguageCodeSchema } from '@types'
import type Tesseract from 'tesseract.js'
import * as z from 'zod'
import type { ImageOcrProvider } from './base'
import type { BuiltinOcrProvider } from './base'
import type { OcrProvider } from './base'
import { OcrProviderBaseConfigSchema } from './base'
import { BuiltinOcrProviderIdMap } from './base'
// ==========================================================
// Tesseract OCR Types
// ==========================================================
export const OcrTesseractConfigSchema = OcrProviderBaseConfigSchema.extend({
langs: z.record(TranslateLanguageCodeSchema, z.boolean()).optional()
})
export type OcrTesseractConfig = z.infer<typeof OcrTesseractConfigSchema>
export type OcrTesseractProvider = {
id: 'tesseract'
config: OcrTesseractConfig
} & ImageOcrProvider &
BuiltinOcrProvider
export const isOcrTesseractProvider = (p: OcrProvider): p is OcrTesseractProvider => {
return p.id === BuiltinOcrProviderIdMap.tesseract
}
export type TesseractLangCode = Tesseract.LanguageCode

View File

@ -11,7 +11,7 @@
"packages/mcp-trace/**/*",
"src/renderer/src/services/traceApi.ts",
"tests/__mocks__/**/*"
, "src/renderer/src/types/ocr/ocr.ts" ],
, "src/renderer/src/types/ocr/base.ts" ],
"compilerOptions": {
"composite": true,
"incremental": true,