reimplemented most db command functions with ORM (migrate from tauri command invoke

This commit is contained in:
Huakun Shen 2025-03-26 11:26:35 -04:00
parent dda783b9f6
commit 9757b6975c
No known key found for this signature in database
12 changed files with 548 additions and 23 deletions

View File

@ -39,7 +39,7 @@
"lz-string": "^1.5.0", "lz-string": "^1.5.0",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"semver": "^7.7.1", "semver": "^7.7.1",
"svelte-inspect-value": "^0.3.0", "svelte-inspect-value": "^0.5.0",
"svelte-sonner": "^0.3.28", "svelte-sonner": "^0.3.28",
"sveltekit-superforms": "^2.23.1", "sveltekit-superforms": "^2.23.1",
"tauri-plugin-clipboard-api": "^2.1.11", "tauri-plugin-clipboard-api": "^2.1.11",

View File

@ -242,6 +242,23 @@ export const rawBuiltinCmds: BuiltinCmd[] = [
}, },
keywords: ["extension", "troubleshooter"] keywords: ["extension", "troubleshooter"]
}, },
{
name: "ORM Troubleshooter",
icon: {
type: IconEnum.Iconify,
value: "material-symbols:database"
},
description: "",
flags: {
developer: true,
dev: true
},
function: async () => {
appState.clearSearchTerm()
goto(i18n.resolveRoute("/app/troubleshooters/orm"))
},
keywords: ["extension", "troubleshooter", "database", "orm"]
},
{ {
name: "Create Quicklink", name: "Create Quicklink",
icon: { icon: {

View File

@ -0,0 +1,343 @@
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)
// }

View File

@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { db as dbCmd } from "@kksh/api/commands"
import * as schema from "@kksh/drizzle/schema" import * as schema from "@kksh/drizzle/schema"
import * as dbCmd from "@kunkunapi/src/commands/db" import { error } from "@tauri-apps/plugin-log"
import { drizzle } from "drizzle-orm/sqlite-proxy" import { drizzle } from "drizzle-orm/sqlite-proxy"
/** /**
@ -15,21 +16,22 @@ export const db = drizzle<typeof schema>(
async (sql, params, method) => { async (sql, params, method) => {
let rows: any = [] let rows: any = []
let results = [] let results = []
// console.log({ console.log({
// sql, sql,
// params, params,
// method method
// }) })
console.log(sql)
// If the query is a SELECT, use the select method // If the query is a SELECT, use the select method
if (isSelectQuery(sql)) { if (isSelectQuery(sql)) {
rows = await dbCmd.select(sql, params).catch((e) => { rows = await dbCmd.select(sql, params).catch((e) => {
console.error("SQL Error:", e) error("SQL Error:", e)
return [] return []
}) })
} else { } else {
// Otherwise, use the execute method // Otherwise, use the execute method
rows = await dbCmd.execute(sql, params).catch((e) => { rows = await dbCmd.execute(sql, params).catch((e) => {
console.error("SQL Error:", e) error("SQL Error:", e)
return [] return []
}) })
return { rows: [] } return { rows: [] }

View File

@ -5,6 +5,7 @@
import { systemCommands, systemCommandsFiltered } from "@/cmds/system" import { systemCommands, systemCommandsFiltered } from "@/cmds/system"
import AppsCmds from "@/components/main/AppsCmds.svelte" import AppsCmds from "@/components/main/AppsCmds.svelte"
import { i18n } from "@/i18n" import { i18n } from "@/i18n"
import { getUniqueExtensionByIdentifier } from "@/orm/cmds"
import { db } from "@/orm/database" import { db } from "@/orm/database"
import * as m from "@/paraglide/messages" import * as m from "@/paraglide/messages"
import { import {
@ -32,6 +33,7 @@
SystemCmds SystemCmds
} from "@kksh/ui/main" } from "@kksh/ui/main"
import { cn } from "@kksh/ui/utils" import { cn } from "@kksh/ui/utils"
import { Ext } from "@kunkunapi/src/models/extension"
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow" import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
import { getCurrentWindow, Window } from "@tauri-apps/api/window" import { getCurrentWindow, Window } from "@tauri-apps/api/window"
import { platform } from "@tauri-apps/plugin-os" import { platform } from "@tauri-apps/plugin-os"
@ -46,6 +48,7 @@
} from "lucide-svelte" } from "lucide-svelte"
import { onMount } from "svelte" import { onMount } from "svelte"
import { Inspect } from "svelte-inspect-value" import { Inspect } from "svelte-inspect-value"
import * as v from "valibot"
const win = getCurrentWindow() const win = getCurrentWindow()
let inputEle: HTMLInputElement | null = $state(null) let inputEle: HTMLInputElement | null = $state(null)

View File

@ -0,0 +1,123 @@
<script lang="ts">
import {
getAllCmds,
getAllExtensions,
getExtensionDataById,
getUniqueExtensionByIdentifier,
getUniqueExtensionByPath,
searchExtensionData,
updateCmdByID
} from "@/orm/cmds"
import * as schema from "@kksh/drizzle/schema"
import { Button, Input } from "@kksh/svelte5"
import { CmdTypeEnum, Ext } from "@kunkunapi/src/models/extension"
import { SearchModeEnum } from "@kunkunapi/src/models/sql"
import { db } from "$lib/orm/database"
import * as orm from "drizzle-orm"
import { Inspect } from "svelte-inspect-value"
import { toast } from "svelte-sonner"
import * as v from "valibot"
let searchText = $state("")
let data: any = $state(null)
let inspectTitle = $state("")
</script>
<main class="container space-y-2">
<Button
onclick={async () => {
getAllCmds()
.then((cmds) => {
console.log(cmds)
data = cmds
inspectTitle = "All Commands"
})
.catch((e) => {
console.error(e)
toast.error("Failed to get all commands", {
description: "See console for more details"
})
})
}}
>
Get All Commands
</Button>
<Button
onclick={() => {
getAllExtensions()
.then((exts) => {
data = exts
inspectTitle = "All Extensions"
})
.catch((e) => {
console.error(e)
toast.error("Failed to get all extensions", {
description: "See console for more details"
})
})
}}
>
Get All Extensions
</Button>
<Button
onclick={async () => {
// get all extensions with path not null
const exts = await getAllExtensions()
for (const ext of exts) {
if (ext.path === null) continue
const _ext = await getUniqueExtensionByIdentifier(ext.identifier)
console.log(_ext)
if (ext.path) {
const __ext = await getUniqueExtensionByPath(ext.path)
console.log(__ext)
}
}
// data = exts
}}
>
Get Unique Extension By Identifier and Path
</Button>
<!-- <Button
onclick={async () => {
updateCmdByID({
cmdId: 1,
name: "google",
cmdType: CmdTypeEnum.QuickLink,
data: `{"link":"https://google.com/search?query={argument}","icon":{"type":"remote-url","value":"https://google.com/favicon.ico","invert":false}}`,
enabled: true
})
}}
>
Update Command By ID
</Button> -->
<Button
onclick={async () => {
const _data = await getExtensionDataById(1, ["search_text", "data"])
data = _data
inspectTitle = "Extension Data"
}}
>
Get Extension Data By ID
</Button>
<div class="flex gap-2">
<Input class="" bind:value={searchText} placeholder="Search Text" />
<Button
class=""
onclick={async () => {
const _data = await searchExtensionData({
extId: 1,
searchMode: SearchModeEnum.FTS,
searchText: searchText,
limit: 10
})
console.log(_data)
data = _data
inspectTitle = "Search Results"
}}
>
Search Extension Data
</Button>
</div>
<Inspect name={inspectTitle} value={data} />
</main>

View File

@ -6,6 +6,7 @@
import { Constants } from "@kksh/ui" import { Constants } from "@kksh/ui"
import { ArrowLeftIcon } from "lucide-svelte" import { ArrowLeftIcon } from "lucide-svelte"
import AppWindow from "lucide-svelte/icons/app-window" import AppWindow from "lucide-svelte/icons/app-window"
import DB from "lucide-svelte/icons/database"
import Loader from "lucide-svelte/icons/loader" import Loader from "lucide-svelte/icons/loader"
import Network from "lucide-svelte/icons/network" import Network from "lucide-svelte/icons/network"
@ -25,6 +26,11 @@
title: m.troubleshooters_sidebar_mdns_debugger_title(), title: m.troubleshooters_sidebar_mdns_debugger_title(),
url: i18n.resolveRoute("/app/troubleshooters/mdns-debugger"), url: i18n.resolveRoute("/app/troubleshooters/mdns-debugger"),
icon: Network icon: Network
},
{
title: "ORM",
url: i18n.resolveRoute("/app/troubleshooters/orm"),
icon: DB
} }
] ]
let currentItem = $state(items.find((item) => window.location.pathname === item.url)) let currentItem = $state(items.find((item) => window.location.pathname === item.url))

View File

@ -18,8 +18,11 @@ export const Ext = v.object({
extId: v.number(), extId: v.number(),
identifier: v.string(), identifier: v.string(),
version: v.string(), version: v.string(),
enabled: v.boolean(), enabled: v.pipe(
installed_at: v.string(), v.number(),
v.transform((input) => Boolean(input))
),
installedAt: v.string(),
path: v.optional(v.nullable(v.string())), path: v.optional(v.nullable(v.string())),
data: v.optional(v.any()) data: v.optional(v.any())
}) })
@ -46,7 +49,10 @@ export const ExtCmd = v.object({
data: v.string(), data: v.string(),
alias: v.optional(v.nullable(v.string())), alias: v.optional(v.nullable(v.string())),
hotkey: v.optional(v.nullable(v.string())), hotkey: v.optional(v.nullable(v.string())),
enabled: v.boolean() enabled: v.pipe(
v.number(),
v.transform((input) => Boolean(input))
)
}) })
export type ExtCmd = v.InferOutput<typeof ExtCmd> export type ExtCmd = v.InferOutput<typeof ExtCmd>

View File

@ -11,3 +11,25 @@ bunx drizzle-kit pull
We are using sqlite with fts5, which drizzle doesn't support yet, so pushing the schema will destroy the existing schema. We are using sqlite with fts5, which drizzle doesn't support yet, so pushing the schema will destroy the existing schema.
We only use pulled schema to generate sql queries. We only use pulled schema to generate sql queries.
## Update Schema
After `drizzle-kit pull` the schema may have problem with JSON type and boolean type.
Will need to manually update the following
### JSON
```diff lang="ts"
+ data: text({ mode: "json" }).notNull(),
+ metadata: text({ mode: "json" }),
- data: numeric().notNull(),
- metadata: numeric(),
```
### Boolean
```diff lang="ts"
+ enabled: integer({ mode: "boolean" }),
- enabled: numeric().default(sql`(TRUE)`),
```

View File

@ -29,7 +29,8 @@ export const commands = sqliteTable("commands", {
.notNull() .notNull()
.references(() => extensions.extId, { onDelete: "cascade" }), .references(() => extensions.extId, { onDelete: "cascade" }),
name: text().notNull(), name: text().notNull(),
enabled: numeric().default(sql`(TRUE)`), enabled: integer({ mode: "boolean" }),
// enabled: numeric().default(sql`(TRUE)`),
alias: text(), alias: text(),
hotkey: text(), hotkey: text(),
type: text().notNull(), type: text().notNull(),
@ -42,8 +43,10 @@ export const extensionData = sqliteTable("extension_data", {
.notNull() .notNull()
.references(() => extensions.extId, { onDelete: "cascade" }), .references(() => extensions.extId, { onDelete: "cascade" }),
dataType: text("data_type").notNull(), dataType: text("data_type").notNull(),
data: numeric().notNull(), // data: text({ mode: "json" }).notNull(),
metadata: numeric(), // metadata: text({ mode: "json" }),
data: text("data").notNull(),
metadata: text("metadata"),
searchText: text("search_text"), searchText: text("search_text"),
createdAt: numeric("created_at").default(sql`(CURRENT_TIMESTAMP)`), createdAt: numeric("created_at").default(sql`(CURRENT_TIMESTAMP)`),
updatedAt: numeric("updated_at").default(sql`(CURRENT_TIMESTAMP)`) updatedAt: numeric("updated_at").default(sql`(CURRENT_TIMESTAMP)`)

View File

@ -96,7 +96,7 @@
"remark-math": "^6.0.0", "remark-math": "^6.0.0",
"shiki-magic-move": "^0.5.2", "shiki-magic-move": "^0.5.2",
"svelte-exmarkdown": "^4.0.3", "svelte-exmarkdown": "^4.0.3",
"svelte-inspect-value": "^0.3.0", "svelte-inspect-value": "^0.5.0",
"svelte-motion": "^0.12.2", "svelte-motion": "^0.12.2",
"valibot": "^1.0.0" "valibot": "^1.0.0"
} }

14
pnpm-lock.yaml generated
View File

@ -279,8 +279,8 @@ importers:
specifier: ^7.7.1 specifier: ^7.7.1
version: 7.7.1 version: 7.7.1
svelte-inspect-value: svelte-inspect-value:
specifier: ^0.3.0 specifier: ^0.5.0
version: 0.3.0(svelte@5.20.5) version: 0.5.0(svelte@5.20.5)
svelte-sonner: svelte-sonner:
specifier: ^0.3.28 specifier: ^0.3.28
version: 0.3.28(svelte@5.20.5) version: 0.3.28(svelte@5.20.5)
@ -1290,8 +1290,8 @@ importers:
specifier: ^4.0.3 specifier: ^4.0.3
version: 4.0.3(svelte@5.20.5) version: 4.0.3(svelte@5.20.5)
svelte-inspect-value: svelte-inspect-value:
specifier: ^0.3.0 specifier: ^0.5.0
version: 0.3.0(svelte@5.20.5) version: 0.5.0(svelte@5.20.5)
svelte-motion: svelte-motion:
specifier: ^0.12.2 specifier: ^0.12.2
version: 0.12.2(svelte@5.20.5) version: 0.12.2(svelte@5.20.5)
@ -11715,8 +11715,8 @@ packages:
peerDependencies: peerDependencies:
svelte: ^5.1.3 svelte: ^5.1.3
svelte-inspect-value@0.3.0: svelte-inspect-value@0.5.0:
resolution: {integrity: sha512-nHv+7+FRePs86sgL2I8jlbSrs8/uJmHJ2uxnMk9tVipWdZYYcmGhsmU+7U8lm/1RAZFS63/xSKdceMDyE09y0A==} resolution: {integrity: sha512-ZWbu/TZl/gGAPe8Xjmg0YvERSpEC+q07HV8m0xhp51auTNh8mjaf07bcmcl0coBb0wnJqcAB4uWJ1GDdtGQrQw==}
peerDependencies: peerDependencies:
svelte: ^5.19.0 svelte: ^5.19.0
@ -25530,7 +25530,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
svelte-inspect-value@0.3.0(svelte@5.20.5): svelte-inspect-value@0.5.0(svelte@5.20.5):
dependencies: dependencies:
esm-env: 1.2.2 esm-env: 1.2.2
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3