Skip to content

Commit 9757b69

Browse files
committed
reimplemented most db command functions with ORM (migrate from tauri command invoke
1 parent dda783b commit 9757b69

File tree

12 files changed

+548
-23
lines changed

12 files changed

+548
-23
lines changed

apps/desktop/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"lz-string": "^1.5.0",
4040
"pretty-bytes": "^6.1.1",
4141
"semver": "^7.7.1",
42-
"svelte-inspect-value": "^0.3.0",
42+
"svelte-inspect-value": "^0.5.0",
4343
"svelte-sonner": "^0.3.28",
4444
"sveltekit-superforms": "^2.23.1",
4545
"tauri-plugin-clipboard-api": "^2.1.11",

apps/desktop/src/lib/cmds/builtin.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,23 @@ export const rawBuiltinCmds: BuiltinCmd[] = [
242242
},
243243
keywords: ["extension", "troubleshooter"]
244244
},
245+
{
246+
name: "ORM Troubleshooter",
247+
icon: {
248+
type: IconEnum.Iconify,
249+
value: "material-symbols:database"
250+
},
251+
description: "",
252+
flags: {
253+
developer: true,
254+
dev: true
255+
},
256+
function: async () => {
257+
appState.clearSearchTerm()
258+
goto(i18n.resolveRoute("/app/troubleshooters/orm"))
259+
},
260+
keywords: ["extension", "troubleshooter", "database", "orm"]
261+
},
245262
{
246263
name: "Create Quicklink",
247264
icon: {

apps/desktop/src/lib/orm/cmds.ts

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
import * as relations from "@kksh/drizzle/relations"
2+
import * as schema from "@kksh/drizzle/schema"
3+
import { CmdType, Ext, ExtCmd, ExtData, SearchMode, SearchModeEnum, SQLSortOrder, SQLSortOrderEnum } from "@kunkunapi/src/models"
4+
import * as orm from "drizzle-orm"
5+
import type { SelectedFields } from "drizzle-orm/sqlite-core"
6+
import * as v from "valibot"
7+
import { db } from "./database"
8+
9+
/* -------------------------------------------------------------------------- */
10+
/* Built-in Extensions */
11+
/* -------------------------------------------------------------------------- */
12+
13+
/* -------------------------------------------------------------------------- */
14+
/* Extension CRUD */
15+
/* -------------------------------------------------------------------------- */
16+
export async function getUniqueExtensionByIdentifier(identifier: string): Promise<Ext | undefined> {
17+
const ext = await db
18+
.select()
19+
.from(schema.extensions)
20+
.where(orm.eq(schema.extensions.identifier, identifier))
21+
.get()
22+
return v.parse(v.optional(Ext), ext)
23+
}
24+
25+
/**
26+
* Use this function when you expect the extension to exist. Such as builtin extensions.
27+
* @param identifier
28+
* @returns
29+
*/
30+
export function getExtensionByIdentifierExpectExists(identifier: string): Promise<Ext> {
31+
return getUniqueExtensionByIdentifier(identifier).then((ext) => {
32+
if (!ext) {
33+
throw new Error(`Unexpexted Error: Extension ${identifier} not found`)
34+
}
35+
return ext
36+
})
37+
}
38+
39+
export async function getAllExtensions(): Promise<Ext[]> {
40+
const exts = await db.select().from(schema.extensions).all()
41+
return v.parse(v.array(Ext), exts)
42+
}
43+
44+
/**
45+
* There can be duplicate extensions with the same identifier. Store and Dev extensions can have the same identifier.
46+
* But install path must be unique.
47+
* @param path
48+
*/
49+
export async function getUniqueExtensionByPath(path: string) {
50+
const ext = await db
51+
.select()
52+
.from(schema.extensions)
53+
.where(orm.eq(schema.extensions.path, path))
54+
.get()
55+
return v.parse(Ext, ext)
56+
}
57+
58+
export function getAllExtensionsByIdentifier(identifier: string): Promise<Ext[]> {
59+
return db
60+
.select()
61+
.from(schema.extensions)
62+
.where(orm.eq(schema.extensions.identifier, identifier))
63+
.all()
64+
.then((exts) => v.parse(v.array(Ext), exts))
65+
}
66+
67+
export function deleteExtensionByPath(path: string): Promise<void> {
68+
return db
69+
.delete(schema.extensions)
70+
.where(orm.eq(schema.extensions.path, path))
71+
.run()
72+
.then(() => undefined)
73+
}
74+
75+
export function deleteExtensionByExtId(extId: number): Promise<void> {
76+
return db
77+
.delete(schema.extensions)
78+
.where(orm.eq(schema.extensions.extId, extId))
79+
.run()
80+
.then(() => undefined)
81+
}
82+
83+
/* -------------------------------------------------------------------------- */
84+
/* Extension Command CRUD */
85+
/* -------------------------------------------------------------------------- */
86+
87+
// export async function getExtensionWithCmdsByIdentifier(identifier: string): Promise<ExtWithCmds> {
88+
// const ext = await db
89+
// .select({
90+
// ...schema.extensions,
91+
// commands: relations.commandsRelations
92+
// })
93+
// .from(schema.extensions)
94+
// .leftJoin(schema.commands, orm.eq(schema.extensions.extId, schema.commands.extId))
95+
// .where(orm.eq(schema.extensions.identifier, identifier))
96+
// .get()
97+
98+
// // return v.parse(v.nullable(ExtWithCmds), ext);
99+
// }
100+
101+
export async function getCmdById(cmdId: number): Promise<ExtCmd> {
102+
const cmd = await db
103+
.select()
104+
.from(schema.commands)
105+
.where(orm.eq(schema.commands.cmdId, cmdId))
106+
.get()
107+
return v.parse(ExtCmd, cmd)
108+
}
109+
110+
export async function getAllCmds(): Promise<ExtCmd[]> {
111+
const cmds = await db.select().from(schema.commands).all()
112+
return v.parse(v.array(ExtCmd), cmds)
113+
}
114+
115+
export function getCommandsByExtId(extId: number) {
116+
return db
117+
.select()
118+
.from(schema.commands)
119+
.where(orm.eq(schema.commands.extId, extId))
120+
.all()
121+
.then((cmds) => v.parse(v.array(ExtCmd), cmds))
122+
}
123+
124+
export function deleteCmdById(cmdId: number) {
125+
return db
126+
.delete(schema.commands)
127+
.where(orm.eq(schema.commands.cmdId, cmdId))
128+
.run()
129+
.then(() => undefined)
130+
}
131+
132+
export function updateCmdByID(data: {
133+
cmdId: number
134+
name: string
135+
cmdType: CmdType
136+
data: string
137+
alias?: string
138+
hotkey?: string
139+
enabled: boolean
140+
}) {
141+
return db
142+
.update(schema.commands)
143+
.set({
144+
name: data.name,
145+
type: data.cmdType,
146+
data: data.data,
147+
alias: data.alias, // optional
148+
hotkey: data.hotkey, // optional
149+
enabled: data.enabled
150+
// in drizzle schema, use integer({ mode: 'boolean' }) for boolean sqlite
151+
// enabled: data.enabled ? String(data.enabled) : undefined
152+
})
153+
.where(orm.eq(schema.commands.cmdId, data.cmdId))
154+
.run()
155+
.then(() => undefined)
156+
}
157+
158+
/* -------------------------------------------------------------------------- */
159+
/* Extension Data CRUD */
160+
/* -------------------------------------------------------------------------- */
161+
export const ExtDataField = v.union([v.literal("data"), v.literal("search_text")])
162+
export type ExtDataField = v.InferOutput<typeof ExtDataField>
163+
164+
function convertRawExtDataToExtData(rawData?: {
165+
createdAt: string
166+
updatedAt: string
167+
data: null | string
168+
searchText?: null | string
169+
dataId: number
170+
extId: number
171+
dataType: string
172+
}): ExtData | undefined {
173+
if (!rawData) {
174+
return rawData
175+
}
176+
const parsedRes = v.safeParse(ExtData, {
177+
...rawData,
178+
createdAt: new Date(rawData.createdAt),
179+
updatedAt: new Date(rawData.updatedAt),
180+
data: rawData.data ?? undefined,
181+
searchText: rawData.searchText ?? undefined
182+
})
183+
if (parsedRes.success) {
184+
return parsedRes.output
185+
} else {
186+
console.error("Extension Data Parse Failure", parsedRes.issues)
187+
throw new Error("Fail to parse extension data")
188+
}
189+
}
190+
191+
export function createExtensionData(data: {
192+
extId: number
193+
dataType: string
194+
data: string
195+
searchText?: string
196+
}) {
197+
return db.insert(schema.extensionData).values(data).run()
198+
}
199+
200+
export function getExtensionDataById(dataId: number, fields?: ExtDataField[]) {
201+
const _fields = fields ?? []
202+
const selectQuery: SelectedFields = {
203+
dataId: schema.extensionData.dataId,
204+
extId: schema.extensionData.extId,
205+
dataType: schema.extensionData.dataType,
206+
metadata: schema.extensionData.metadata,
207+
createdAt: schema.extensionData.createdAt,
208+
updatedAt: schema.extensionData.updatedAt
209+
// data: schema.extensionData.data,
210+
// searchText: schema.extensionData.searchText
211+
}
212+
if (_fields.includes("data")) {
213+
selectQuery["data"] = schema.extensionData.data
214+
}
215+
if (_fields.includes("search_text")) {
216+
selectQuery["searchText"] = schema.extensionData.searchText
217+
}
218+
return db
219+
.select(selectQuery)
220+
.from(schema.extensionData)
221+
.where(orm.eq(schema.extensionData.dataId, dataId))
222+
.get()
223+
.then((rawData) => {
224+
console.log("Raw Data", rawData)
225+
// @ts-expect-error - rawData is unknown, but will be safe parsed with valibot
226+
return convertRawExtDataToExtData(rawData)
227+
})
228+
}
229+
230+
export async function searchExtensionData(searchParams: {
231+
extId: number
232+
searchMode: SearchMode
233+
dataId?: number
234+
dataType?: string
235+
searchText?: string
236+
afterCreatedAt?: string
237+
beforeCreatedAt?: string
238+
limit?: number
239+
offset?: number
240+
orderByCreatedAt?: SQLSortOrder
241+
orderByUpdatedAt?: SQLSortOrder
242+
fields?: ExtDataField[]
243+
}): Promise<ExtData[]> {
244+
const fields = v.parse(v.optional(v.array(ExtDataField), []), searchParams.fields)
245+
const _fields = fields ?? []
246+
247+
// Build the select query based on fields
248+
const selectQuery: SelectedFields = {
249+
dataId: schema.extensionData.dataId,
250+
extId: schema.extensionData.extId,
251+
dataType: schema.extensionData.dataType,
252+
createdAt: schema.extensionData.createdAt,
253+
updatedAt: schema.extensionData.updatedAt
254+
}
255+
256+
if (_fields.includes("data")) {
257+
selectQuery["data"] = schema.extensionData.data
258+
}
259+
if (_fields.includes("search_text")) {
260+
selectQuery["searchText"] = schema.extensionData.searchText
261+
}
262+
263+
// Build the query
264+
const query = db.select(selectQuery).from(schema.extensionData)
265+
266+
// Add conditions
267+
const conditions = [orm.eq(schema.extensionData.extId, searchParams.extId)]
268+
269+
if (searchParams.dataId) {
270+
conditions.push(orm.eq(schema.extensionData.dataId, searchParams.dataId))
271+
}
272+
273+
if (searchParams.dataType) {
274+
conditions.push(orm.eq(schema.extensionData.dataType, searchParams.dataType))
275+
}
276+
277+
if (searchParams.searchText) {
278+
switch (searchParams.searchMode) {
279+
case SearchModeEnum.ExactMatch:
280+
conditions.push(orm.eq(schema.extensionData.searchText, searchParams.searchText))
281+
break
282+
case SearchModeEnum.Like:
283+
conditions.push(orm.like(schema.extensionData.searchText, `%${searchParams.searchText}%`))
284+
break
285+
case SearchModeEnum.FTS:
286+
// For FTS, we need to use a raw SQL query since Drizzle doesn't support MATCH directly
287+
conditions.push(orm.sql`${schema.extensionDataFts.searchText} MATCH ${searchParams.searchText}`)
288+
break
289+
}
290+
}
291+
292+
if (searchParams.afterCreatedAt) {
293+
conditions.push(orm.gt(schema.extensionData.createdAt, searchParams.afterCreatedAt))
294+
}
295+
296+
if (searchParams.beforeCreatedAt) {
297+
conditions.push(orm.lt(schema.extensionData.createdAt, searchParams.beforeCreatedAt))
298+
}
299+
300+
// Add ordering
301+
if (searchParams.orderByCreatedAt) {
302+
query.orderBy(
303+
searchParams.orderByCreatedAt === SQLSortOrderEnum.Asc
304+
? orm.asc(schema.extensionData.createdAt)
305+
: orm.desc(schema.extensionData.createdAt)
306+
)
307+
}
308+
309+
if (searchParams.orderByUpdatedAt) {
310+
query.orderBy(
311+
searchParams.orderByUpdatedAt === SQLSortOrderEnum.Asc
312+
? orm.asc(schema.extensionData.updatedAt)
313+
: orm.desc(schema.extensionData.updatedAt)
314+
)
315+
}
316+
317+
// Add limit and offset
318+
if (searchParams.limit) {
319+
query.limit(searchParams.limit)
320+
}
321+
322+
if (searchParams.offset) {
323+
query.offset(searchParams.offset)
324+
}
325+
326+
// Execute query and convert results
327+
const results = await query.where(orm.and(...conditions)).all()
328+
return results.map((rawData) => {
329+
// @ts-expect-error - rawData is unknown, but will be safe parsed with valibot
330+
return convertRawExtDataToExtData(rawData)
331+
}).filter((item): item is ExtData => item !== undefined)
332+
}
333+
334+
// export async function getNCommands(n: number):
335+
// export function createExtension(ext: {
336+
// identifier: string
337+
// version: string
338+
// enabled?: boolean
339+
// path?: string
340+
// data?: any
341+
// }) {
342+
// return invoke<void>(generateJarvisPluginCommand("create_extension"), ext)
343+
// }

0 commit comments

Comments
 (0)