mirror of
https://github.com/kunkunsh/kunkun.git
synced 2025-07-02 22:41:31 +00:00
344 lines
10 KiB
TypeScript
344 lines
10 KiB
TypeScript
import * as relations from "@kksh/drizzle/relations"
|
|
import * as schema from "@kksh/drizzle/schema"
|
|
import { CmdType, Ext, ExtCmd, ExtData, SearchMode, SearchModeEnum, SQLSortOrder, SQLSortOrderEnum } from "@kunkunapi/src/models"
|
|
import * as orm from "drizzle-orm"
|
|
import type { SelectedFields } from "drizzle-orm/sqlite-core"
|
|
import * as v from "valibot"
|
|
import { db } from "./database"
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Built-in Extensions */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Extension CRUD */
|
|
/* -------------------------------------------------------------------------- */
|
|
export async function getUniqueExtensionByIdentifier(identifier: string): Promise<Ext | undefined> {
|
|
const ext = await db
|
|
.select()
|
|
.from(schema.extensions)
|
|
.where(orm.eq(schema.extensions.identifier, identifier))
|
|
.get()
|
|
return v.parse(v.optional(Ext), ext)
|
|
}
|
|
|
|
/**
|
|
* Use this function when you expect the extension to exist. Such as builtin extensions.
|
|
* @param identifier
|
|
* @returns
|
|
*/
|
|
export function getExtensionByIdentifierExpectExists(identifier: string): Promise<Ext> {
|
|
return getUniqueExtensionByIdentifier(identifier).then((ext) => {
|
|
if (!ext) {
|
|
throw new Error(`Unexpexted Error: Extension ${identifier} not found`)
|
|
}
|
|
return ext
|
|
})
|
|
}
|
|
|
|
export async function getAllExtensions(): Promise<Ext[]> {
|
|
const exts = await db.select().from(schema.extensions).all()
|
|
return v.parse(v.array(Ext), exts)
|
|
}
|
|
|
|
/**
|
|
* There can be duplicate extensions with the same identifier. Store and Dev extensions can have the same identifier.
|
|
* But install path must be unique.
|
|
* @param path
|
|
*/
|
|
export async function getUniqueExtensionByPath(path: string) {
|
|
const ext = await db
|
|
.select()
|
|
.from(schema.extensions)
|
|
.where(orm.eq(schema.extensions.path, path))
|
|
.get()
|
|
return v.parse(Ext, ext)
|
|
}
|
|
|
|
export function getAllExtensionsByIdentifier(identifier: string): Promise<Ext[]> {
|
|
return db
|
|
.select()
|
|
.from(schema.extensions)
|
|
.where(orm.eq(schema.extensions.identifier, identifier))
|
|
.all()
|
|
.then((exts) => v.parse(v.array(Ext), exts))
|
|
}
|
|
|
|
export function deleteExtensionByPath(path: string): Promise<void> {
|
|
return db
|
|
.delete(schema.extensions)
|
|
.where(orm.eq(schema.extensions.path, path))
|
|
.run()
|
|
.then(() => undefined)
|
|
}
|
|
|
|
export function deleteExtensionByExtId(extId: number): Promise<void> {
|
|
return db
|
|
.delete(schema.extensions)
|
|
.where(orm.eq(schema.extensions.extId, extId))
|
|
.run()
|
|
.then(() => undefined)
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Extension Command CRUD */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
// export async function getExtensionWithCmdsByIdentifier(identifier: string): Promise<ExtWithCmds> {
|
|
// const ext = await db
|
|
// .select({
|
|
// ...schema.extensions,
|
|
// commands: relations.commandsRelations
|
|
// })
|
|
// .from(schema.extensions)
|
|
// .leftJoin(schema.commands, orm.eq(schema.extensions.extId, schema.commands.extId))
|
|
// .where(orm.eq(schema.extensions.identifier, identifier))
|
|
// .get()
|
|
|
|
// // return v.parse(v.nullable(ExtWithCmds), ext);
|
|
// }
|
|
|
|
export async function getCmdById(cmdId: number): Promise<ExtCmd> {
|
|
const cmd = await db
|
|
.select()
|
|
.from(schema.commands)
|
|
.where(orm.eq(schema.commands.cmdId, cmdId))
|
|
.get()
|
|
return v.parse(ExtCmd, cmd)
|
|
}
|
|
|
|
export async function getAllCmds(): Promise<ExtCmd[]> {
|
|
const cmds = await db.select().from(schema.commands).all()
|
|
return v.parse(v.array(ExtCmd), cmds)
|
|
}
|
|
|
|
export function getCommandsByExtId(extId: number) {
|
|
return db
|
|
.select()
|
|
.from(schema.commands)
|
|
.where(orm.eq(schema.commands.extId, extId))
|
|
.all()
|
|
.then((cmds) => v.parse(v.array(ExtCmd), cmds))
|
|
}
|
|
|
|
export function deleteCmdById(cmdId: number) {
|
|
return db
|
|
.delete(schema.commands)
|
|
.where(orm.eq(schema.commands.cmdId, cmdId))
|
|
.run()
|
|
.then(() => undefined)
|
|
}
|
|
|
|
export function updateCmdByID(data: {
|
|
cmdId: number
|
|
name: string
|
|
cmdType: CmdType
|
|
data: string
|
|
alias?: string
|
|
hotkey?: string
|
|
enabled: boolean
|
|
}) {
|
|
return db
|
|
.update(schema.commands)
|
|
.set({
|
|
name: data.name,
|
|
type: data.cmdType,
|
|
data: data.data,
|
|
alias: data.alias, // optional
|
|
hotkey: data.hotkey, // optional
|
|
enabled: data.enabled
|
|
// in drizzle schema, use integer({ mode: 'boolean' }) for boolean sqlite
|
|
// enabled: data.enabled ? String(data.enabled) : undefined
|
|
})
|
|
.where(orm.eq(schema.commands.cmdId, data.cmdId))
|
|
.run()
|
|
.then(() => undefined)
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Extension Data CRUD */
|
|
/* -------------------------------------------------------------------------- */
|
|
export const ExtDataField = v.union([v.literal("data"), v.literal("search_text")])
|
|
export type ExtDataField = v.InferOutput<typeof ExtDataField>
|
|
|
|
function convertRawExtDataToExtData(rawData?: {
|
|
createdAt: string
|
|
updatedAt: string
|
|
data: null | string
|
|
searchText?: null | string
|
|
dataId: number
|
|
extId: number
|
|
dataType: string
|
|
}): ExtData | undefined {
|
|
if (!rawData) {
|
|
return rawData
|
|
}
|
|
const parsedRes = v.safeParse(ExtData, {
|
|
...rawData,
|
|
createdAt: new Date(rawData.createdAt),
|
|
updatedAt: new Date(rawData.updatedAt),
|
|
data: rawData.data ?? undefined,
|
|
searchText: rawData.searchText ?? undefined
|
|
})
|
|
if (parsedRes.success) {
|
|
return parsedRes.output
|
|
} else {
|
|
console.error("Extension Data Parse Failure", parsedRes.issues)
|
|
throw new Error("Fail to parse extension data")
|
|
}
|
|
}
|
|
|
|
export function createExtensionData(data: {
|
|
extId: number
|
|
dataType: string
|
|
data: string
|
|
searchText?: string
|
|
}) {
|
|
return db.insert(schema.extensionData).values(data).run()
|
|
}
|
|
|
|
export function getExtensionDataById(dataId: number, fields?: ExtDataField[]) {
|
|
const _fields = fields ?? []
|
|
const selectQuery: SelectedFields = {
|
|
dataId: schema.extensionData.dataId,
|
|
extId: schema.extensionData.extId,
|
|
dataType: schema.extensionData.dataType,
|
|
metadata: schema.extensionData.metadata,
|
|
createdAt: schema.extensionData.createdAt,
|
|
updatedAt: schema.extensionData.updatedAt
|
|
// data: schema.extensionData.data,
|
|
// searchText: schema.extensionData.searchText
|
|
}
|
|
if (_fields.includes("data")) {
|
|
selectQuery["data"] = schema.extensionData.data
|
|
}
|
|
if (_fields.includes("search_text")) {
|
|
selectQuery["searchText"] = schema.extensionData.searchText
|
|
}
|
|
return db
|
|
.select(selectQuery)
|
|
.from(schema.extensionData)
|
|
.where(orm.eq(schema.extensionData.dataId, dataId))
|
|
.get()
|
|
.then((rawData) => {
|
|
console.log("Raw Data", rawData)
|
|
// @ts-expect-error - rawData is unknown, but will be safe parsed with valibot
|
|
return convertRawExtDataToExtData(rawData)
|
|
})
|
|
}
|
|
|
|
export async function searchExtensionData(searchParams: {
|
|
extId: number
|
|
searchMode: SearchMode
|
|
dataId?: number
|
|
dataType?: string
|
|
searchText?: string
|
|
afterCreatedAt?: string
|
|
beforeCreatedAt?: string
|
|
limit?: number
|
|
offset?: number
|
|
orderByCreatedAt?: SQLSortOrder
|
|
orderByUpdatedAt?: SQLSortOrder
|
|
fields?: ExtDataField[]
|
|
}): Promise<ExtData[]> {
|
|
const fields = v.parse(v.optional(v.array(ExtDataField), []), searchParams.fields)
|
|
const _fields = fields ?? []
|
|
|
|
// Build the select query based on fields
|
|
const selectQuery: SelectedFields = {
|
|
dataId: schema.extensionData.dataId,
|
|
extId: schema.extensionData.extId,
|
|
dataType: schema.extensionData.dataType,
|
|
createdAt: schema.extensionData.createdAt,
|
|
updatedAt: schema.extensionData.updatedAt
|
|
}
|
|
|
|
if (_fields.includes("data")) {
|
|
selectQuery["data"] = schema.extensionData.data
|
|
}
|
|
if (_fields.includes("search_text")) {
|
|
selectQuery["searchText"] = schema.extensionData.searchText
|
|
}
|
|
|
|
// Build the query
|
|
const query = db.select(selectQuery).from(schema.extensionData)
|
|
|
|
// Add conditions
|
|
const conditions = [orm.eq(schema.extensionData.extId, searchParams.extId)]
|
|
|
|
if (searchParams.dataId) {
|
|
conditions.push(orm.eq(schema.extensionData.dataId, searchParams.dataId))
|
|
}
|
|
|
|
if (searchParams.dataType) {
|
|
conditions.push(orm.eq(schema.extensionData.dataType, searchParams.dataType))
|
|
}
|
|
|
|
if (searchParams.searchText) {
|
|
switch (searchParams.searchMode) {
|
|
case SearchModeEnum.ExactMatch:
|
|
conditions.push(orm.eq(schema.extensionData.searchText, searchParams.searchText))
|
|
break
|
|
case SearchModeEnum.Like:
|
|
conditions.push(orm.like(schema.extensionData.searchText, `%${searchParams.searchText}%`))
|
|
break
|
|
case SearchModeEnum.FTS:
|
|
// For FTS, we need to use a raw SQL query since Drizzle doesn't support MATCH directly
|
|
conditions.push(orm.sql`${schema.extensionDataFts.searchText} MATCH ${searchParams.searchText}`)
|
|
break
|
|
}
|
|
}
|
|
|
|
if (searchParams.afterCreatedAt) {
|
|
conditions.push(orm.gt(schema.extensionData.createdAt, searchParams.afterCreatedAt))
|
|
}
|
|
|
|
if (searchParams.beforeCreatedAt) {
|
|
conditions.push(orm.lt(schema.extensionData.createdAt, searchParams.beforeCreatedAt))
|
|
}
|
|
|
|
// Add ordering
|
|
if (searchParams.orderByCreatedAt) {
|
|
query.orderBy(
|
|
searchParams.orderByCreatedAt === SQLSortOrderEnum.Asc
|
|
? orm.asc(schema.extensionData.createdAt)
|
|
: orm.desc(schema.extensionData.createdAt)
|
|
)
|
|
}
|
|
|
|
if (searchParams.orderByUpdatedAt) {
|
|
query.orderBy(
|
|
searchParams.orderByUpdatedAt === SQLSortOrderEnum.Asc
|
|
? orm.asc(schema.extensionData.updatedAt)
|
|
: orm.desc(schema.extensionData.updatedAt)
|
|
)
|
|
}
|
|
|
|
// Add limit and offset
|
|
if (searchParams.limit) {
|
|
query.limit(searchParams.limit)
|
|
}
|
|
|
|
if (searchParams.offset) {
|
|
query.offset(searchParams.offset)
|
|
}
|
|
|
|
// Execute query and convert results
|
|
const results = await query.where(orm.and(...conditions)).all()
|
|
return results.map((rawData) => {
|
|
// @ts-expect-error - rawData is unknown, but will be safe parsed with valibot
|
|
return convertRawExtDataToExtData(rawData)
|
|
}).filter((item): item is ExtData => item !== undefined)
|
|
}
|
|
|
|
// export async function getNCommands(n: number):
|
|
// export function createExtension(ext: {
|
|
// identifier: string
|
|
// version: string
|
|
// enabled?: boolean
|
|
// path?: string
|
|
// data?: any
|
|
// }) {
|
|
// return invoke<void>(generateJarvisPluginCommand("create_extension"), ext)
|
|
// }
|