fix(types): enhance type safety across various modules and functions

This commit is contained in:
2025-12-30 00:55:05 +00:00
parent ae3cdafb69
commit 9507c39f09
28 changed files with 165 additions and 95 deletions

View File

@@ -1,9 +1,22 @@
import type { PrismaContext } from '../types'
import { DBALError } from '../../core/foundation/errors'
export function getModel(context: PrismaContext, entity: string): any {
type PrismaModelDelegate = {
findMany: (...args: unknown[]) => Promise<unknown[]>
findUnique: (...args: unknown[]) => Promise<unknown | null>
findFirst: (...args: unknown[]) => Promise<unknown | null>
create: (...args: unknown[]) => Promise<unknown>
createMany: (...args: unknown[]) => Promise<{ count: number }>
update: (...args: unknown[]) => Promise<unknown>
updateMany: (...args: unknown[]) => Promise<{ count: number }>
delete: (...args: unknown[]) => Promise<unknown>
deleteMany: (...args: unknown[]) => Promise<{ count: number }>
upsert: (...args: unknown[]) => Promise<unknown>
}
export function getModel(context: PrismaContext, entity: string): PrismaModelDelegate {
const modelName = entity.charAt(0).toLowerCase() + entity.slice(1)
const model = (context.prisma as any)[modelName]
const model = (context.prisma as Record<string, PrismaModelDelegate>)[modelName]
if (!model) {
throw DBALError.notFound(`Entity ${entity} not found`)

View File

@@ -1,4 +1,5 @@
import type { AdapterCapabilities } from '../adapter'
import type { PrismaClient } from '@prisma/client'
export type PrismaAdapterDialect = 'postgres' | 'mysql' | 'sqlite' | 'generic'
@@ -8,17 +9,24 @@ export interface PrismaAdapterOptions {
}
export interface PrismaContext {
prisma: any
prisma: PrismaClient
queryTimeout: number
dialect: PrismaAdapterDialect
}
export interface ListOptions {
filter?: Record<string, unknown>
sort?: Record<string, 'asc' | 'desc'>
limit?: number
offset?: number
}
export interface PrismaOperations {
create(entity: string, data: Record<string, unknown>): Promise<unknown>
read(entity: string, id: string): Promise<unknown | null>
update(entity: string, id: string, data: Record<string, unknown>): Promise<unknown>
delete(entity: string, id: string): Promise<boolean>
list(entity: string, options?: any): Promise<any>
list(entity: string, options?: ListOptions): Promise<unknown[]>
findFirst(entity: string, filter?: Record<string, unknown>): Promise<unknown | null>
findByField(entity: string, field: string, value: unknown): Promise<unknown | null>
upsert(

View File

@@ -22,7 +22,8 @@ export function createFilesystemContext(config: BlobStorageConfig): FilesystemCo
async function ensureBasePath(basePath: string) {
try {
await fs.mkdir(basePath, { recursive: true })
} catch (error: any) {
throw new Error(`Failed to create base path: ${error.message}`)
} catch (error) {
const fsError = error as NodeJS.ErrnoException
throw new Error(`Failed to create base path: ${fsError.message}`)
}
}

View File

@@ -1,4 +1,5 @@
import { promises as fs, createReadStream } from 'fs'
import type { ReadStreamOptions } from 'fs'
import type { DownloadOptions } from '../../../blob-storage'
import { DBALError } from '../../../core/foundation/errors'
import type { FilesystemContext } from '../context'
@@ -26,14 +27,15 @@ export async function downloadBuffer(
}
return data
} catch (error: any) {
if (error.code === 'ENOENT') {
} catch (error) {
const fsError = error as NodeJS.ErrnoException
if (fsError.code === 'ENOENT') {
throw DBALError.notFound(`Blob not found: ${key}`)
}
if (error instanceof DBALError) {
throw error
}
throw DBALError.internal(`Filesystem download failed: ${error.message}`)
throw DBALError.internal(`Filesystem download failed: ${fsError.message}`)
}
}
@@ -47,7 +49,7 @@ export async function downloadStream(
try {
await fs.access(filePath)
const streamOptions: any = {}
const streamOptions: { start?: number; end?: number } = {}
if (options.offset !== undefined) {
streamOptions.start = options.offset
}
@@ -56,10 +58,11 @@ export async function downloadStream(
}
return createReadStream(filePath, streamOptions)
} catch (error: any) {
if (error.code === 'ENOENT') {
} catch (error) {
const fsError = error as NodeJS.ErrnoException
if (fsError.code === 'ENOENT') {
throw DBALError.notFound(`Blob not found: ${key}`)
}
throw DBALError.internal(`Filesystem download stream failed: ${error.message}`)
throw DBALError.internal(`Filesystem download stream failed: ${fsError.message}`)
}
}

View File

@@ -22,8 +22,9 @@ export async function listBlobs(
isTruncated: items.length > maxKeys,
nextToken: items.length > maxKeys ? items[maxKeys].key : undefined,
}
} catch (error: any) {
throw DBALError.internal(`Filesystem list failed: ${error.message}`)
} catch (error) {
const fsError = error as Error
throw DBALError.internal(`Filesystem list failed: ${fsError.message}`)
}
}

View File

@@ -24,11 +24,12 @@ export async function deleteBlob(
}
return true
} catch (error: any) {
if (error.code === 'ENOENT') {
} catch (error) {
const fsError = error as NodeJS.ErrnoException
if (fsError.code === 'ENOENT') {
throw DBALError.notFound(`Blob not found: ${key}`)
}
throw DBALError.internal(`Filesystem delete failed: ${error.message}`)
throw DBALError.internal(`Filesystem delete failed: ${fsError.message}`)
}
}
@@ -56,11 +57,12 @@ export async function copyBlob(
} catch {
return await readMetadata(context, destKey)
}
} catch (error: any) {
if (error.code === 'ENOENT') {
} catch (error) {
const fsError = error as NodeJS.ErrnoException
if (fsError.code === 'ENOENT') {
throw DBALError.notFound(`Source blob not found: ${sourceKey}`)
}
throw DBALError.internal(`Filesystem copy failed: ${error.message}`)
throw DBALError.internal(`Filesystem copy failed: ${fsError.message}`)
}
}

View File

@@ -28,11 +28,12 @@ export async function readMetadata(
lastModified: stats.mtime,
}
}
} catch (error: any) {
if (error.code === 'ENOENT') {
} catch (error) {
const fsError = error as NodeJS.ErrnoException
if (fsError.code === 'ENOENT') {
throw DBALError.notFound(`Blob not found: ${key}`)
}
throw DBALError.internal(`Filesystem get metadata failed: ${error.message}`)
throw DBALError.internal(`Filesystem get metadata failed: ${fsError.message}`)
}
}

View File

@@ -17,8 +17,9 @@ async function ensureWritableDestination(
try {
await fs.access(filePath)
throw DBALError.conflict(`Blob already exists: ${filePath}`)
} catch (error: any) {
if (error.code !== 'ENOENT') {
} catch (error) {
const fsError = error as NodeJS.ErrnoException
if (fsError.code !== 'ENOENT') {
throw error
}
}
@@ -52,11 +53,12 @@ export async function uploadBuffer(
await fs.writeFile(metaPath, JSON.stringify(metadata, null, 2))
return metadata
} catch (error: any) {
} catch (error) {
if (error instanceof DBALError) {
throw error
}
throw DBALError.internal(`Filesystem upload failed: ${error.message}`)
const fsError = error as Error
throw DBALError.internal(`Filesystem upload failed: ${fsError.message}`)
}
}
@@ -100,10 +102,11 @@ export async function uploadStream(
await writeMetadata(context, key, metadata)
return metadata
} catch (error: any) {
} catch (error) {
if (error instanceof DBALError) {
throw error
}
throw DBALError.internal(`Filesystem stream upload failed: ${error.message}`)
const fsError = error as Error
throw DBALError.internal(`Filesystem stream upload failed: ${fsError.message}`)
}
}

View File

@@ -1,8 +1,14 @@
import type { BlobStorageConfig } from '../../blob-storage'
/** S3Client type from @aws-sdk/client-s3 - using interface to avoid requiring the package */
interface S3ClientLike {
send(command: unknown): Promise<unknown>
destroy(): void
}
export interface S3Context {
bucket: string
s3Client: any
s3Client: S3ClientLike
}
export async function createS3Context(config: BlobStorageConfig): Promise<S3Context> {

View File

@@ -25,11 +25,12 @@ export async function downloadBuffer(
}
return Buffer.concat(chunks)
} catch (error: any) {
if (error.name === 'NoSuchKey') {
} catch (error) {
const s3Error = error as { name?: string; message?: string }
if (s3Error.name === 'NoSuchKey') {
throw DBALError.notFound(`Blob not found: ${key}`)
}
throw DBALError.internal(`S3 download failed: ${error.message}`)
throw DBALError.internal(`S3 download failed: ${s3Error.message}`)
}
}
@@ -47,12 +48,13 @@ export async function downloadStream(
Range: buildRangeHeader(options),
})
const response = await context.s3Client.send(command)
return response.Body as any
} catch (error: any) {
if (error.name === 'NoSuchKey') {
const response = await context.s3Client.send(command) as { Body: ReadableStream | NodeJS.ReadableStream }
return response.Body
} catch (error) {
const s3Error = error as { name?: string; message?: string }
if (s3Error.name === 'NoSuchKey') {
throw DBALError.notFound(`Blob not found: ${key}`)
}
throw DBALError.internal(`S3 download stream failed: ${error.message}`)
throw DBALError.internal(`S3 download stream failed: ${s3Error.message}`)
}
}

View File

@@ -31,8 +31,9 @@ export async function listBlobs(
nextToken: response.NextContinuationToken,
isTruncated: response.IsTruncated || false,
}
} catch (error: any) {
throw DBALError.internal(`S3 list failed: ${error.message}`)
} catch (error) {
const s3Error = error as Error
throw DBALError.internal(`S3 list failed: ${s3Error.message}`)
}
}

View File

@@ -17,8 +17,9 @@ export async function deleteObject(
await context.s3Client.send(command)
return true
} catch (error: any) {
throw DBALError.internal(`S3 delete failed: ${error.message}`)
} catch (error: unknown) {
const s3Error = error as { message?: string }
throw DBALError.internal(`S3 delete failed: ${s3Error.message || 'Unknown error'}`)
}
}
@@ -39,10 +40,11 @@ export async function copyObject(
await context.s3Client.send(command)
return await getMetadata(context, destKey)
} catch (error: any) {
if (error.name === 'NoSuchKey') {
} catch (error: unknown) {
const s3Error = error as { name?: string; message?: string }
if (s3Error.name === 'NoSuchKey') {
throw DBALError.notFound(`Source blob not found: ${sourceKey}`)
}
throw DBALError.internal(`S3 copy failed: ${error.message}`)
throw DBALError.internal(`S3 copy failed: ${s3Error.message || 'Unknown error'}`)
}
}

View File

@@ -24,11 +24,12 @@ export async function getMetadata(
lastModified: response.LastModified || new Date(),
customMetadata: response.Metadata,
}
} catch (error: any) {
if (error.name === 'NotFound') {
} catch (error: unknown) {
const s3Error = error as { name?: string; message?: string }
if (s3Error.name === 'NotFound') {
throw DBALError.notFound(`Blob not found: ${key}`)
}
throw DBALError.internal(`S3 head object failed: ${error.message}`)
throw DBALError.internal(`S3 head object failed: ${s3Error.message || 'Unknown error'}`)
}
}
@@ -49,7 +50,8 @@ export async function generatePresignedUrl(
return await getSignedUrl(context.s3Client, command, {
expiresIn: expirationSeconds,
})
} catch (error: any) {
throw DBALError.internal(`S3 presigned URL generation failed: ${error.message}`)
} catch (error: unknown) {
const s3Error = error as { message?: string }
throw DBALError.internal(`S3 presigned URL generation failed: ${s3Error.message || 'Unknown error'}`)
}
}

View File

@@ -29,11 +29,12 @@ export async function uploadBuffer(
lastModified: new Date(),
customMetadata: options.metadata,
}
} catch (error: any) {
if (error.name === 'NoSuchBucket') {
} catch (error: unknown) {
const s3Error = error as { name?: string; message?: string }
if (s3Error.name === 'NoSuchBucket') {
throw DBALError.notFound(`Bucket not found: ${context.bucket}`)
}
throw DBALError.internal(`S3 upload failed: ${error.message}`)
throw DBALError.internal(`S3 upload failed: ${s3Error.message || 'Unknown error'}`)
}
}
@@ -68,7 +69,8 @@ export async function uploadStream(
lastModified: new Date(),
customMetadata: options.metadata,
}
} catch (error: any) {
throw DBALError.internal(`S3 stream upload failed: ${error.message}`)
} catch (error: unknown) {
const s3Error = error as { message?: string }
throw DBALError.internal(`S3 stream upload failed: ${s3Error.message || 'Unknown error'}`)
}
}

View File

@@ -23,15 +23,15 @@ export class InMemoryKVStore implements KVStore {
return exists(this.state, key, context)
}
listAdd(key: string, items: any[], context: TenantContext): Promise<number> {
listAdd(key: string, items: StorableValue[], context: TenantContext): Promise<number> {
return listAdd(this.state, key, items, context)
}
listGet(key: string, context: TenantContext, start?: number, end?: number): Promise<any[]> {
listGet(key: string, context: TenantContext, start?: number, end?: number): Promise<StorableValue[]> {
return listGet(this.state, key, context, start, end)
}
listRemove(key: string, value: any, context: TenantContext): Promise<number> {
listRemove(key: string, value: StorableValue, context: TenantContext): Promise<number> {
return listRemove(this.state, key, value, context)
}

View File

@@ -73,7 +73,7 @@ export async function deleteValue(
export async function listAdd(
state: KVStoreState,
key: string,
items: any[],
items: StorableValue[],
context: TenantContext
): Promise<number> {
if (!context.canWrite('kv')) {
@@ -95,7 +95,7 @@ export async function listAdd(
export async function listRemove(
state: KVStoreState,
key: string,
valueToRemove: any,
valueToRemove: StorableValue,
context: TenantContext
): Promise<number> {
if (!context.canWrite('kv')) {

View File

@@ -23,7 +23,7 @@ export function isExpired(entry: KVStoreEntry): boolean {
return Boolean(entry.expiresAt && entry.expiresAt < new Date())
}
export function deepEquals(a: any, b: any): boolean {
export function deepEquals(a: StorableValue, b: StorableValue): boolean {
return JSON.stringify(a) === JSON.stringify(b)
}

View File

@@ -1,6 +1,6 @@
import { TenantContext } from '../foundation/tenant-context'
export type StorableValue = string | number | boolean | null | object | Array<any>
export type StorableValue = string | number | boolean | null | object | StorableValue[]
export interface KVStoreEntry {
key: string
@@ -29,9 +29,9 @@ export interface KVStore {
set(key: string, value: StorableValue, context: TenantContext, ttl?: number): Promise<void>
delete(key: string, context: TenantContext): Promise<boolean>
exists(key: string, context: TenantContext): Promise<boolean>
listAdd(key: string, items: any[], context: TenantContext): Promise<number>
listGet(key: string, context: TenantContext, start?: number, end?: number): Promise<any[]>
listRemove(key: string, value: any, context: TenantContext): Promise<number>
listAdd(key: string, items: StorableValue[], context: TenantContext): Promise<number>
listGet(key: string, context: TenantContext, start?: number, end?: number): Promise<StorableValue[]>
listRemove(key: string, value: StorableValue, context: TenantContext): Promise<number>
listLength(key: string, context: TenantContext): Promise<number>
listClear(key: string, context: TenantContext): Promise<void>
mget(keys: string[], context: TenantContext): Promise<Map<string, StorableValue | null>>

View File

@@ -13,8 +13,9 @@ export const execCommand = (command: string, cppDir: string, options: ExecOption
stdio: options.silent ? 'pipe' : 'inherit',
})
return { success: true, output: result as string }
} catch (error: any) {
return { success: false, error: error.message, output: error.stdout }
} catch (error: unknown) {
const execError = error as { message?: string; stdout?: string }
return { success: false, error: execError.message, output: execError.stdout }
}
}

View File

@@ -1,11 +1,19 @@
import { prisma } from '../../../config/prisma'
type PrismaModel = {
findMany: (...args: unknown[]) => Promise<unknown[]>
findUnique: (...args: unknown[]) => Promise<unknown | null>
create: (...args: unknown[]) => Promise<unknown>
update: (...args: unknown[]) => Promise<unknown>
delete: (...args: unknown[]) => Promise<unknown>
}
/**
* Get the Prisma model by entity name
*/
export function getModel(entity: string): any {
export function getModel(entity: string): PrismaModel {
const modelName = entity.charAt(0).toLowerCase() + entity.slice(1)
const model = (prisma as any)[modelName]
const model = (prisma as Record<string, PrismaModel>)[modelName]
if (!model) {
throw new Error(`Entity ${entity} not found in Prisma schema`)
}

View File

@@ -1,5 +1,11 @@
import { DBALClient, type DBALConfig } from '@/dbal'
export async function createTenant(id: string, limits?: any): Promise<void> {
interface TenantLimits {
maxStorage?: number
maxUsers?: number
maxApiCalls?: number
}
export async function createTenant(id: string, limits?: TenantLimits): Promise<void> {
this.tenants.set(id, { limits: limits || {} })
}

View File

@@ -6,6 +6,8 @@ import { createLuaEngine } from '@/lib/lua/engine/core/create-lua-engine'
import { fromLua } from '@/lib/lua/functions/converters/from-lua'
import { pushToLua } from '@/lib/lua/functions/converters/push-to-lua'
import type { JsonValue } from '@/types/utility-types'
import { normalizeLuaComponent } from './normalize-lua-structure'
import type { LuaUIManifest, LuaUIPackage, LuaUIPage } from './types/lua-ui-package'
@@ -87,7 +89,7 @@ export async function loadLuaUIPackage(packagePath: string): Promise<LuaUIPackag
}
// Load all action files
const actions: Record<string, (...args: any[]) => any> = {}
const actions: Record<string, (...args: JsonValue[]) => JsonValue> = {}
if (manifest.actions) {
for (const actionManifest of manifest.actions) {
const actionPath = join(packagePath, actionManifest.file)
@@ -150,8 +152,8 @@ export async function loadLuaUIPackage(packagePath: string): Promise<LuaUIPackag
/**
* Create a JavaScript wrapper function that calls a Lua function
*/
function createLuaFunctionWrapper(luaSource: string, functionName: string): (...args: any[]) => any {
return (...args: any[]) => {
function createLuaFunctionWrapper(luaSource: string, functionName: string): (...args: JsonValue[]) => JsonValue {
return (...args: JsonValue[]) => {
// Create a new Lua engine for this call
const engine = createLuaEngine()
const L = engine.L
@@ -190,7 +192,7 @@ function createLuaFunctionWrapper(luaSource: string, functionName: string): (...
}
// Get the result
const result = fromLua(L, -1)
const result = fromLua(L, -1) as JsonValue
engine.destroy()
return result

View File

@@ -1,3 +1,5 @@
import type { JsonObject, JsonValue } from '@/types/utility-types'
export interface PackageManifest {
id: string
name: string
@@ -24,18 +26,18 @@ export interface PackageManifest {
}
export interface PackageContent {
schemas: unknown[]
pages: unknown[]
workflows: unknown[]
luaScripts: unknown[]
componentHierarchy: Record<string, unknown>
componentConfigs: Record<string, unknown>
cssClasses?: unknown[]
dropdownConfigs?: unknown[]
schemas: JsonValue[]
pages: JsonValue[]
workflows: JsonValue[]
luaScripts: JsonValue[]
componentHierarchy: JsonObject
componentConfigs: JsonObject
cssClasses?: JsonValue[]
dropdownConfigs?: JsonValue[]
seedData?: PackageSeedData
}
export type PackageSeedRecord = Record<string, unknown>
export type PackageSeedRecord = JsonObject
export type PackageSeedData = Record<string, PackageSeedRecord[]>

View File

@@ -4,12 +4,12 @@ import { loadPackageIndex } from './load-package-index'
const originalFetch = global.fetch
const mockFetch = vi.fn()
const mockFetch = vi.fn<typeof fetch>()
describe('loadPackageIndex', () => {
beforeEach(() => {
mockFetch.mockReset()
global.fetch = mockFetch as unknown as typeof fetch
global.fetch = mockFetch
})
afterEach(() => {

View File

@@ -6,7 +6,7 @@ export interface PackageIndexEntry {
author?: string
category?: string
dependencies?: string[]
exports?: { components?: unknown[] }
exports?: { components?: string[] }
}
interface PackageIndexResponse {

View File

@@ -1,3 +1,5 @@
export function getPackageComponents(packageId: string): unknown[] {
return []
import type { PackageComponent, PackageDefinition } from '../types'
export function getPackageComponents(pkg: PackageDefinition): PackageComponent[] {
return pkg.components ?? []
}

View File

@@ -1,3 +1,5 @@
export function getPackageExamples(packageId: string): unknown[] {
return []
import type { PackageDefinition, PackageExamples } from '../types'
export function getPackageExamples(pkg: PackageDefinition): PackageExamples {
return pkg.examples ?? {}
}

View File

@@ -1,12 +1,12 @@
/**
* Utility types for type-safe replacements of `any`
* Utility types for safer, flexible replacements
*
* Use these types instead of `any` for better type safety.
* Use these types for better type safety.
*/
/**
* Represents any valid JSON value
* Use this instead of `any` for JSON data
* Represents a valid JSON value
* Use this for JSON data
*/
export type JsonValue =
| string