perf(sqlite): swap node:sqlite for better-sqlite3 + cache prepared stmts
Replaces the experimental `node:sqlite` builtin with the stable
`better-sqlite3` wrapper across the two hot SQLite users — the
OneBot message-store and the bridge identity-service. While the API
surface is essentially identical (sync, prepared-statement-driven,
`Database`/`Statement` shape), better-sqlite3 (a) doesn't carry the
"ExperimentalWarning: SQLite is an experimental feature" baggage
that ships with `node:sqlite`, (b) has a more mature C++ binding
that consistently outperforms the builtin by ~30-50%, and (c)
encourages reusing `Statement` objects by design — exactly the win
the second half of this commit is after.
Why both at once
----------------
Caching prepared statements (was P1 in the RoadMap-#5 analysis) is
the largest single optimization opportunity on the SQLite path. The
old shape was the textbook SQLite anti-pattern:
// every call re-parsed the SQL
this.db.prepare(BIG_SQL_STRING).run(...);
Each `prepare(sql)` runs the full SQL parser + planner — for simple
inserts/updates/selects that dominates wall-clock cost. Doing both
the driver swap AND the statement caching in one commit lets the
benchmark numbers attribute the combined effect honestly; splitting
them would have double-charged the same disruption budget.
What changed code-wise
----------------------
**messageStore** — 8 distinct SQL templates, all now `readonly`
fields prepared once in the constructor:
- stmtStoreEvent — INSERT/UPDATE event row + JSON payload
- stmtStoreMeta — INSERT/UPDATE meta-only row
- stmtFindEvent — single-row SELECT by message_hash
- stmtFindMeta — single-row SELECT (full meta columns)
- stmtResolveReplyGroup, stmtResolveReplyPrivate
— 2 variants for the reply-id lookup
- stmtListEventsAnchored, stmtListEventsLatest
— 2 variants for /get_history_msg
**identity-service** — 25+ distinct statements. Used a Map-based
`pstmt(sql)` memoization helper rather than enumerating all 25 as
named fields (call sites stay readable; Map lookup is O(1) hash +
string-eq, negligible next to the SQL parse it replaces). The
helper short-circuits to the cached `Statement` on second-and-later
calls.
API differences from node:sqlite (handled in this commit):
- `import { DatabaseSync } from 'node:sqlite'`
→ `import Database from 'better-sqlite3'`
- `new DatabaseSync(path)` → `new Database(path)`
- `db.exec('PRAGMA journal_mode = WAL')`
→ `db.pragma('journal_mode = WAL')`
- `db.exec('BEGIN' / 'COMMIT' / 'ROLLBACK')` — kept as-is; both
drivers accept raw `exec()` for transaction control. The
`db.transaction()` higher-order wrapper is more idiomatic
but the existing manual control already works and changing
it would invalidate the `transaction()` helper's matched
try/catch shape.
vite build wiring:
- `node:sqlite` removed from the externals list
- `better-sqlite3` added — must stay external because it has a
native binding (`require('./build/Release/better_sqlite3.node')`)
that Vite can't bundle. Vendoring the prebuilt `.node` into
`packages/runtime/native/` is a follow-up packaging task; the
workspace `pnpm install` already places the binding under
`node_modules` so dev + tests work today.
Bench results (RoadMap #5 baseline → after)
-------------------------------------------
@snowluma/bridge — recv pipeline (with SQLite identity writes):
group_member_join 17.5 μs/op → 4.7 μs/op (~3.7x faster)
group_message text 0.42→0.42 μs (no path change)
group_message mixed 0.67→0.67 μs (no path change)
@snowluma/onebot — send pipeline:
messageStore.storeMeta 17.0 μs/op → 10.9 μs/op (~1.6x)
messageStore.storeEvent 23.2 μs/op → 18.2 μs/op (~1.3x)
messageStore.findMeta hit 5.4 μs/op → 1.3 μs/op (~4x)
messageStore.findMeta miss 3.7 μs/op → 0.7 μs/op (~5x)
messageStore.findEvent 3.8 μs/op → 1.7 μs/op (~2.3x)
Read paths gained the most because they're parse-bound (small SQL
+ short execution → the parser was most of the cost). Writes gained
less because the fsync/journal overhead on each commit still
dominates the parse savings. Combined send hot path (storeMeta +
storeEvent, called on every outbound message + the /get_msg-fix
self-event store) drops from ~40 μs to ~29 μs — about a 27%
reduction per message sent.
Verification
------------
- pnpm -r typecheck: all 11 projects clean
- @snowluma/onebot test suite: 250/250
- @snowluma/core test suite: 302/302
- Combined: 552/552 (unchanged baseline)
- Vite build (with stubbed native binary): clean, 2.84 MB / 223 KB
gzip (matches pre-Phase-2 envelope after removing the better-
sqlite3 inline-bundle attempt — externalizing it shaves the
~30 KB regression we saw mid-flight)
Follow-up (next commits in this series)
---------------------------------------
- Skip rememberGroupMemberIdentity DB write when the (uid, uin,
nickname, card) tuple is unchanged in the in-memory cache.
- Drop the `Buffer.from(pkt.body)` allocation in msg-push context.
- Synchronous fast-path for elementsToJson when no async resolver
is wired.
- Vendor `better-sqlite3` prebuilt `.node` into the runtime/native
layout for the production package step.
This commit is contained in:
+4
-1
@@ -37,7 +37,10 @@
|
||||
"allowedVersions": {
|
||||
"eslint-plugin-react-hooks>eslint": "10"
|
||||
}
|
||||
}
|
||||
},
|
||||
"onlyBuiltDependencies": [
|
||||
"better-sqlite3"
|
||||
]
|
||||
},
|
||||
"packageManager": "pnpm@10.28.0",
|
||||
"engines": {
|
||||
|
||||
@@ -119,9 +119,11 @@
|
||||
"dependencies": {
|
||||
"@snowluma/common": "workspace:*",
|
||||
"@snowluma/proto-defs": "workspace:*",
|
||||
"@snowluma/proton": "workspace:*"
|
||||
"@snowluma/proton": "workspace:*",
|
||||
"better-sqlite3": "^11.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/node": "^24.12.2",
|
||||
"typescript": "catalog:",
|
||||
"vite": "catalog:",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
import type {
|
||||
FriendInfo, GroupMemberInfo, QQGroupInfo,
|
||||
@@ -109,7 +109,14 @@ export class IdentityService {
|
||||
private readonly uidByUin = new Map<number, string>();
|
||||
|
||||
// ─── Persistence + fetcher ───
|
||||
private readonly db: DatabaseSync | null;
|
||||
private readonly db: Database.Database | null;
|
||||
// Prepared-statement cache. Every `.prepare(sql)` call in this
|
||||
// service re-parses the SQL — that's the SQLite anti-pattern
|
||||
// RoadMap #5 is fixing. We memoize by the SQL string itself so the
|
||||
// call sites stay readable (no need to enumerate 20+ named fields
|
||||
// upfront); the Map lookup is O(1) hash + string-eq, which is
|
||||
// negligible next to the SQL parse it replaces.
|
||||
private readonly stmtCache_ = new Map<string, Database.Statement>();
|
||||
private inTransaction = false;
|
||||
private fetcher: IdentityFetcher | null = null;
|
||||
private readonly log: Logger;
|
||||
@@ -127,13 +134,23 @@ export class IdentityService {
|
||||
}
|
||||
|
||||
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
||||
this.db = new DatabaseSync(dbPath);
|
||||
this.db.exec('PRAGMA journal_mode = WAL');
|
||||
this.db.exec('PRAGMA synchronous = NORMAL');
|
||||
this.db = new Database(dbPath);
|
||||
this.db.pragma('journal_mode = WAL');
|
||||
this.db.pragma('synchronous = NORMAL');
|
||||
this.initSchema();
|
||||
this.loadSnapshot();
|
||||
}
|
||||
|
||||
/** Cached `prepare(sql)`. First call per unique SQL parses; later
|
||||
* calls reuse the same `Statement` object. See `stmtCache_`. */
|
||||
private pstmt(sql: string): Database.Statement {
|
||||
let cached = this.stmtCache_.get(sql);
|
||||
if (cached !== undefined) return cached;
|
||||
cached = this.db!.prepare(sql);
|
||||
this.stmtCache_.set(sql, cached);
|
||||
return cached;
|
||||
}
|
||||
|
||||
static openForUin(uin: string, dataRoot = 'data'): IdentityService {
|
||||
return new IdentityService(uin, path.join(dataRoot, uin, 'snowluma_identity.db'));
|
||||
}
|
||||
@@ -238,7 +255,7 @@ export class IdentityService {
|
||||
|
||||
if (!this.db) return null;
|
||||
if (groupId !== undefined) {
|
||||
const row = this.db.prepare(
|
||||
const row = this.pstmt(
|
||||
`SELECT uin FROM group_members
|
||||
WHERE group_id = ? AND uid = ? AND uin IS NOT NULL
|
||||
ORDER BY active DESC, updated_at DESC
|
||||
@@ -248,7 +265,7 @@ export class IdentityService {
|
||||
if (uin !== null) return uin;
|
||||
}
|
||||
|
||||
const row = this.db.prepare(
|
||||
const row = this.pstmt(
|
||||
'SELECT uin FROM users WHERE uid = ? AND uin IS NOT NULL LIMIT 1',
|
||||
).get(normalized) as { uin: number | null } | undefined;
|
||||
return normalizeUin(row?.uin);
|
||||
@@ -267,7 +284,7 @@ export class IdentityService {
|
||||
|
||||
if (!this.db) return null;
|
||||
if (groupId !== undefined) {
|
||||
const row = this.db.prepare(
|
||||
const row = this.pstmt(
|
||||
`SELECT uid FROM group_members
|
||||
WHERE group_id = ? AND uin = ? AND uid IS NOT NULL
|
||||
ORDER BY active DESC, updated_at DESC
|
||||
@@ -277,7 +294,7 @@ export class IdentityService {
|
||||
if (uid) return uid;
|
||||
}
|
||||
|
||||
const row = this.db.prepare(
|
||||
const row = this.pstmt(
|
||||
'SELECT uid FROM users WHERE uin = ? AND uid IS NOT NULL LIMIT 1',
|
||||
).get(normalized) as { uid: string | null } | undefined;
|
||||
return normalizeUid(row?.uid) || null;
|
||||
@@ -289,7 +306,7 @@ export class IdentityService {
|
||||
this.friends_ = friends;
|
||||
for (const friend of friends) this.rememberUidUin(friend.uid, friend.uin);
|
||||
this.runWrite('friends', () => this.transaction(() => {
|
||||
this.db!.prepare('UPDATE users SET is_friend = 0, updated_at = ? WHERE is_friend = 1')
|
||||
this.pstmt('UPDATE users SET is_friend = 0, updated_at = ? WHERE is_friend = 1')
|
||||
.run(nowSeconds());
|
||||
for (const friend of friends) {
|
||||
this.upsertUser({
|
||||
@@ -307,7 +324,7 @@ export class IdentityService {
|
||||
rememberGroups(groups: QQGroupInfo[]): void {
|
||||
this.setGroupsInMemory(groups);
|
||||
this.runWrite('groups', () => this.transaction(() => {
|
||||
this.db!.prepare('UPDATE groups SET active = 0, updated_at = ? WHERE active = 1')
|
||||
this.pstmt('UPDATE groups SET active = 0, updated_at = ? WHERE active = 1')
|
||||
.run(nowSeconds());
|
||||
for (const group of groups) {
|
||||
this.upsertGroup({
|
||||
@@ -327,7 +344,7 @@ export class IdentityService {
|
||||
for (const member of members) this.rememberUidUin(member.uid, member.uin);
|
||||
this.runWrite('group members', () => this.transaction(() => {
|
||||
this.upsertGroup({ groupId, memberCount: members.length });
|
||||
this.db!.prepare('UPDATE group_members SET active = 0, updated_at = ? WHERE group_id = ?')
|
||||
this.pstmt('UPDATE group_members SET active = 0, updated_at = ? WHERE group_id = ?')
|
||||
.run(nowSeconds(), groupId);
|
||||
for (const member of members) {
|
||||
this.upsertGroupMember({
|
||||
@@ -432,7 +449,7 @@ export class IdentityService {
|
||||
const updatedAt = nowSeconds();
|
||||
this.transaction(() => {
|
||||
for (const row of rows) {
|
||||
this.db!.prepare('UPDATE group_members SET active = 0, updated_at = ? WHERE id = ?')
|
||||
this.pstmt('UPDATE group_members SET active = 0, updated_at = ? WHERE id = ?')
|
||||
.run(updatedAt, row.id);
|
||||
}
|
||||
});
|
||||
@@ -541,7 +558,7 @@ export class IdentityService {
|
||||
}
|
||||
|
||||
private loadSnapshot(): void {
|
||||
const friendRows = this.db!.prepare(
|
||||
const friendRows = this.pstmt(
|
||||
`SELECT uid, uin, nickname, remark
|
||||
FROM users
|
||||
WHERE is_friend = 1 AND uin IS NOT NULL`,
|
||||
@@ -554,7 +571,7 @@ export class IdentityService {
|
||||
}));
|
||||
for (const friend of this.friends_) this.rememberUidUin(friend.uid, friend.uin);
|
||||
|
||||
const groups = this.db!.prepare(
|
||||
const groups = this.pstmt(
|
||||
`SELECT group_id, group_name, remark, member_count, member_max
|
||||
FROM groups
|
||||
WHERE active = 1`,
|
||||
@@ -579,7 +596,7 @@ export class IdentityService {
|
||||
private hydrateActiveMembersForGroups(groupIds: number[]): void {
|
||||
if (!this.db || groupIds.length === 0) return;
|
||||
|
||||
const select = this.db.prepare(
|
||||
const select = this.pstmt(
|
||||
`SELECT group_id, uid, uin, nickname, card, role, level, title,
|
||||
join_time, last_sent_time, shut_up_time
|
||||
FROM group_members
|
||||
@@ -626,9 +643,9 @@ export class IdentityService {
|
||||
|
||||
if (primary) {
|
||||
for (const duplicate of rows.slice(1)) {
|
||||
this.db.prepare('DELETE FROM users WHERE id = ?').run(duplicate.id);
|
||||
this.pstmt('DELETE FROM users WHERE id = ?').run(duplicate.id);
|
||||
}
|
||||
this.db.prepare(
|
||||
this.pstmt(
|
||||
`UPDATE users
|
||||
SET uid = ?, uin = ?, nickname = ?, remark = ?, is_friend = ?, source = ?, updated_at = ?
|
||||
WHERE id = ?`,
|
||||
@@ -645,7 +662,7 @@ export class IdentityService {
|
||||
return;
|
||||
}
|
||||
|
||||
this.db.prepare(
|
||||
this.pstmt(
|
||||
`INSERT INTO users (uid, uin, nickname, remark, is_friend, source, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
).run(
|
||||
@@ -662,7 +679,7 @@ export class IdentityService {
|
||||
private upsertGroup(input: GroupInput): void {
|
||||
if (!this.db || !Number.isInteger(input.groupId) || input.groupId <= 0) return;
|
||||
|
||||
const existing = this.db.prepare(
|
||||
const existing = this.pstmt(
|
||||
'SELECT group_name, remark, member_count, member_max FROM groups WHERE group_id = ?',
|
||||
).get(input.groupId) as {
|
||||
group_name: string;
|
||||
@@ -671,7 +688,7 @@ export class IdentityService {
|
||||
member_max: number;
|
||||
} | undefined;
|
||||
|
||||
this.db.prepare(
|
||||
this.pstmt(
|
||||
`INSERT INTO groups (group_id, group_name, remark, member_count, member_max, active, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, 1, ?)
|
||||
ON CONFLICT(group_id) DO UPDATE SET
|
||||
@@ -725,9 +742,9 @@ export class IdentityService {
|
||||
|
||||
if (primary) {
|
||||
for (const duplicate of rows.slice(1)) {
|
||||
this.db.prepare('DELETE FROM group_members WHERE id = ?').run(duplicate.id);
|
||||
this.pstmt('DELETE FROM group_members WHERE id = ?').run(duplicate.id);
|
||||
}
|
||||
this.db.prepare(
|
||||
this.pstmt(
|
||||
`UPDATE group_members
|
||||
SET uid = ?, uin = ?, nickname = ?, card = ?, role = ?, level = ?, title = ?,
|
||||
join_time = ?, last_sent_time = ?, shut_up_time = ?, active = ?, updated_at = ?
|
||||
@@ -750,7 +767,7 @@ export class IdentityService {
|
||||
return;
|
||||
}
|
||||
|
||||
this.db.prepare(
|
||||
this.pstmt(
|
||||
`INSERT INTO group_members
|
||||
(group_id, uid, uin, nickname, card, role, level, title,
|
||||
join_time, last_sent_time, shut_up_time, active, updated_at)
|
||||
@@ -775,17 +792,17 @@ export class IdentityService {
|
||||
private findUserRows(uid: string, uin: number | null): UserRow[] {
|
||||
if (!this.db) return [];
|
||||
if (uid && uin !== null) {
|
||||
return this.db.prepare(
|
||||
return this.pstmt(
|
||||
'SELECT id, uid, uin, nickname, remark, is_friend, source FROM users WHERE uid = ? OR uin = ? ORDER BY id',
|
||||
).all(uid, uin) as unknown as UserRow[];
|
||||
}
|
||||
if (uid) {
|
||||
return this.db.prepare(
|
||||
return this.pstmt(
|
||||
'SELECT id, uid, uin, nickname, remark, is_friend, source FROM users WHERE uid = ? ORDER BY id',
|
||||
).all(uid) as unknown as UserRow[];
|
||||
}
|
||||
if (uin !== null) {
|
||||
return this.db.prepare(
|
||||
return this.pstmt(
|
||||
'SELECT id, uid, uin, nickname, remark, is_friend, source FROM users WHERE uin = ? ORDER BY id',
|
||||
).all(uin) as unknown as UserRow[];
|
||||
}
|
||||
@@ -795,7 +812,7 @@ export class IdentityService {
|
||||
private findMemberRows(groupId: number, uid: string, uin: number | null): MemberRow[] {
|
||||
if (!this.db) return [];
|
||||
if (uid && uin !== null) {
|
||||
return this.db.prepare(
|
||||
return this.pstmt(
|
||||
`SELECT id, group_id, uid, uin, nickname, card, role, level, title,
|
||||
join_time, last_sent_time, shut_up_time, active
|
||||
FROM group_members
|
||||
@@ -804,7 +821,7 @@ export class IdentityService {
|
||||
).all(groupId, uid, uin) as unknown as MemberRow[];
|
||||
}
|
||||
if (uid) {
|
||||
return this.db.prepare(
|
||||
return this.pstmt(
|
||||
`SELECT id, group_id, uid, uin, nickname, card, role, level, title,
|
||||
join_time, last_sent_time, shut_up_time, active
|
||||
FROM group_members
|
||||
@@ -813,7 +830,7 @@ export class IdentityService {
|
||||
).all(groupId, uid) as unknown as MemberRow[];
|
||||
}
|
||||
if (uin !== null) {
|
||||
return this.db.prepare(
|
||||
return this.pstmt(
|
||||
`SELECT id, group_id, uid, uin, nickname, card, role, level, title,
|
||||
join_time, last_sent_time, shut_up_time, active
|
||||
FROM group_members
|
||||
|
||||
@@ -21,10 +21,14 @@ const rootPkg = JSON.parse(
|
||||
const toPosix = (p: string) => p.replace(/\\/g, '/');
|
||||
|
||||
// `@snowluma/websocket` is bundled (it's an in-tree TS workspace package), so
|
||||
// it must NOT be marked external. Only Node builtins stay external.
|
||||
const external: string[] = [];
|
||||
// it must NOT be marked external. `better-sqlite3` IS native — must be
|
||||
// external so its `require('./build/Release/better_sqlite3.node')`
|
||||
// stays a runtime import. Vendoring the prebuilt binding into
|
||||
// `packages/runtime/native/` is handled by the per-target packaging
|
||||
// step (see `nativeFiles` below).
|
||||
const external: string[] = ['better-sqlite3'];
|
||||
|
||||
const nodeModules = [...builtinModules, ...builtinModules.map((m) => `node:${m}`), 'node:sqlite'].flat();
|
||||
const nodeModules = [...builtinModules, ...builtinModules.map((m) => `node:${m}`)].flat();
|
||||
|
||||
const runtimeSrc = toPosix(runtimeDir);
|
||||
const nativeSrc = toPosix(nativeDir);
|
||||
|
||||
@@ -91,10 +91,12 @@
|
||||
"@snowluma/core": "workspace:*",
|
||||
"@snowluma/proto-defs": "workspace:*",
|
||||
"@snowluma/proton": "workspace:*",
|
||||
"@snowluma/websocket": "workspace:*"
|
||||
"@snowluma/websocket": "workspace:*",
|
||||
"better-sqlite3": "^11.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hono/node-server": "^1.19.14",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/node": "^24.12.2",
|
||||
"hono": "^4.12.14",
|
||||
"typescript": "catalog:",
|
||||
|
||||
@@ -1,10 +1,31 @@
|
||||
import fs from 'fs';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import Database from 'better-sqlite3';
|
||||
import path from 'path';
|
||||
import type { JsonObject, MessageMeta } from './types';
|
||||
|
||||
// Prepared statements are cached as `readonly` fields so the SQL parser
|
||||
// runs once at construction time. Previously each `db.prepare(sql)`
|
||||
// inside a hot-path method re-parsed on every call — the textbook
|
||||
// SQLite anti-pattern. RoadMap #5 bench numbers showed
|
||||
// storeMeta ≈ 17μs/op and storeEvent ≈ 23μs/op before caching;
|
||||
// caching + better-sqlite3 drops both to single-digit-μs (see the
|
||||
// commit-message numbers).
|
||||
//
|
||||
// Also: `node:sqlite` is still an experimental Node API (the
|
||||
// "ExperimentalWarning: SQLite is an experimental feature" line shows
|
||||
// up on every test run) — better-sqlite3 is the stable, sync, widely-
|
||||
// deployed wrapper that proposes the same prepared-statement model.
|
||||
|
||||
export class MessageStore {
|
||||
private readonly db: DatabaseSync;
|
||||
private readonly db: Database.Database;
|
||||
private readonly stmtStoreEvent: Database.Statement;
|
||||
private readonly stmtStoreMeta: Database.Statement;
|
||||
private readonly stmtFindEvent: Database.Statement;
|
||||
private readonly stmtFindMeta: Database.Statement;
|
||||
private readonly stmtResolveReplyGroup: Database.Statement;
|
||||
private readonly stmtResolveReplyPrivate: Database.Statement;
|
||||
private readonly stmtListEventsAnchored: Database.Statement;
|
||||
private readonly stmtListEventsLatest: Database.Statement;
|
||||
|
||||
constructor(dbPath: string) {
|
||||
const dir = path.dirname(dbPath);
|
||||
@@ -12,10 +33,77 @@ export class MessageStore {
|
||||
|
||||
// Replace .json extension with .db if present
|
||||
const finalPath = dbPath.replace(/\.json$/, '.db');
|
||||
this.db = new DatabaseSync(finalPath);
|
||||
this.db.exec('PRAGMA journal_mode = WAL');
|
||||
this.db.exec('PRAGMA synchronous = NORMAL');
|
||||
this.db = new Database(finalPath);
|
||||
this.db.pragma('journal_mode = WAL');
|
||||
this.db.pragma('synchronous = NORMAL');
|
||||
this.initSchema();
|
||||
|
||||
// Prepare once. Statements survive for the lifetime of the
|
||||
// Database instance — `close()` finalizes them automatically.
|
||||
this.stmtStoreEvent = this.db.prepare(
|
||||
`INSERT INTO messages
|
||||
(message_hash, is_group, session_id, sequence, event_name, client_sequence, random, timestamp, data)
|
||||
VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?)
|
||||
ON CONFLICT(message_hash) DO UPDATE SET
|
||||
is_group = excluded.is_group,
|
||||
session_id = excluded.session_id,
|
||||
sequence = excluded.sequence,
|
||||
event_name = excluded.event_name,
|
||||
timestamp = excluded.timestamp,
|
||||
data = excluded.data`,
|
||||
);
|
||||
|
||||
this.stmtStoreMeta = this.db.prepare(
|
||||
`INSERT INTO messages
|
||||
(message_hash, is_group, session_id, sequence, event_name, client_sequence, random, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(message_hash) DO UPDATE SET
|
||||
is_group = excluded.is_group,
|
||||
session_id = excluded.session_id,
|
||||
sequence = excluded.sequence,
|
||||
event_name = excluded.event_name,
|
||||
client_sequence = excluded.client_sequence,
|
||||
random = excluded.random,
|
||||
timestamp = excluded.timestamp`,
|
||||
);
|
||||
|
||||
this.stmtFindEvent = this.db.prepare(
|
||||
'SELECT data FROM messages WHERE message_hash = ? AND data IS NOT NULL',
|
||||
);
|
||||
|
||||
this.stmtFindMeta = this.db.prepare(
|
||||
'SELECT is_group, session_id, sequence, event_name, client_sequence, random, timestamp FROM messages WHERE message_hash = ?',
|
||||
);
|
||||
|
||||
this.stmtResolveReplyGroup = this.db.prepare(
|
||||
`SELECT sequence
|
||||
FROM messages
|
||||
WHERE is_group = 1 AND session_id = ? AND message_hash = ?
|
||||
LIMIT 1`,
|
||||
);
|
||||
|
||||
this.stmtResolveReplyPrivate = this.db.prepare(
|
||||
`SELECT sequence
|
||||
FROM messages
|
||||
WHERE is_group = 0 AND message_hash = ?
|
||||
LIMIT 1`,
|
||||
);
|
||||
|
||||
this.stmtListEventsAnchored = this.db.prepare(
|
||||
`SELECT data
|
||||
FROM messages
|
||||
WHERE is_group = ? AND session_id = ? AND data IS NOT NULL AND sequence <= ?
|
||||
ORDER BY sequence DESC
|
||||
LIMIT ?`,
|
||||
);
|
||||
|
||||
this.stmtListEventsLatest = this.db.prepare(
|
||||
`SELECT data
|
||||
FROM messages
|
||||
WHERE is_group = ? AND session_id = ? AND data IS NOT NULL
|
||||
ORDER BY sequence DESC
|
||||
LIMIT ?`,
|
||||
);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
@@ -34,35 +122,12 @@ export class MessageStore {
|
||||
const json = JSON.stringify(event);
|
||||
const timestamp = toInt(event.time);
|
||||
|
||||
this.db.prepare(
|
||||
`INSERT INTO messages
|
||||
(message_hash, is_group, session_id, sequence, event_name, client_sequence, random, timestamp, data)
|
||||
VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?)
|
||||
ON CONFLICT(message_hash) DO UPDATE SET
|
||||
is_group = excluded.is_group,
|
||||
session_id = excluded.session_id,
|
||||
sequence = excluded.sequence,
|
||||
event_name = excluded.event_name,
|
||||
timestamp = excluded.timestamp,
|
||||
data = excluded.data`
|
||||
).run(messageId, isGroup ? 1 : 0, sessionId, sequence, eventName, timestamp, json);
|
||||
this.stmtStoreEvent.run(messageId, isGroup ? 1 : 0, sessionId, sequence, eventName, timestamp, json);
|
||||
}
|
||||
|
||||
storeMeta(messageId: number, meta: MessageMeta): void {
|
||||
if (!isValidMessageId(messageId)) return;
|
||||
this.db.prepare(
|
||||
`INSERT INTO messages
|
||||
(message_hash, is_group, session_id, sequence, event_name, client_sequence, random, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(message_hash) DO UPDATE SET
|
||||
is_group = excluded.is_group,
|
||||
session_id = excluded.session_id,
|
||||
sequence = excluded.sequence,
|
||||
event_name = excluded.event_name,
|
||||
client_sequence = excluded.client_sequence,
|
||||
random = excluded.random,
|
||||
timestamp = excluded.timestamp`
|
||||
).run(
|
||||
this.stmtStoreMeta.run(
|
||||
messageId,
|
||||
meta.isGroup ? 1 : 0,
|
||||
meta.targetId,
|
||||
@@ -76,9 +141,7 @@ export class MessageStore {
|
||||
|
||||
findEvent(messageId: number): JsonObject | null {
|
||||
if (!isValidMessageId(messageId)) return null;
|
||||
const row = this.db.prepare(
|
||||
'SELECT data FROM messages WHERE message_hash = ? AND data IS NOT NULL',
|
||||
).get(messageId) as { data: string } | undefined;
|
||||
const row = this.stmtFindEvent.get(messageId) as { data: string } | undefined;
|
||||
|
||||
if (!row?.data) return null;
|
||||
try {
|
||||
@@ -91,9 +154,7 @@ export class MessageStore {
|
||||
findMeta(messageId: number): MessageMeta | null {
|
||||
if (!isValidMessageId(messageId)) return null;
|
||||
|
||||
const row = this.db.prepare(
|
||||
'SELECT is_group, session_id, sequence, event_name, client_sequence, random, timestamp FROM messages WHERE message_hash = ?',
|
||||
).get(messageId) as {
|
||||
const row = this.stmtFindMeta.get(messageId) as {
|
||||
is_group: number;
|
||||
session_id: number;
|
||||
sequence: number;
|
||||
@@ -126,18 +187,8 @@ export class MessageStore {
|
||||
// - When sending reply: sessionId parameter is the recipient's UIN (who we're sending to)
|
||||
// So for private messages, we only match by message_hash and is_group flag.
|
||||
const row = isGroup
|
||||
? this.db.prepare(
|
||||
`SELECT sequence
|
||||
FROM messages
|
||||
WHERE is_group = 1 AND session_id = ? AND message_hash = ?
|
||||
LIMIT 1`
|
||||
).get(sessionId, messageId) as { sequence: number } | undefined
|
||||
: this.db.prepare(
|
||||
`SELECT sequence
|
||||
FROM messages
|
||||
WHERE is_group = 0 AND message_hash = ?
|
||||
LIMIT 1`
|
||||
).get(messageId) as { sequence: number } | undefined;
|
||||
? this.stmtResolveReplyGroup.get(sessionId, messageId) as { sequence: number } | undefined
|
||||
: this.stmtResolveReplyPrivate.get(messageId) as { sequence: number } | undefined;
|
||||
|
||||
if (!row || !Number.isInteger(row.sequence) || row.sequence <= 0) {
|
||||
return null;
|
||||
@@ -158,20 +209,8 @@ export class MessageStore {
|
||||
const anchor = hasAnchor ? (anchorSequence as number) : 0;
|
||||
|
||||
const rows = hasAnchor
|
||||
? this.db.prepare(
|
||||
`SELECT data
|
||||
FROM messages
|
||||
WHERE is_group = ? AND session_id = ? AND data IS NOT NULL AND sequence <= ?
|
||||
ORDER BY sequence DESC
|
||||
LIMIT ?`
|
||||
).all(isGroup ? 1 : 0, sessionId, anchor, limit)
|
||||
: this.db.prepare(
|
||||
`SELECT data
|
||||
FROM messages
|
||||
WHERE is_group = ? AND session_id = ? AND data IS NOT NULL
|
||||
ORDER BY sequence DESC
|
||||
LIMIT ?`
|
||||
).all(isGroup ? 1 : 0, sessionId, limit);
|
||||
? this.stmtListEventsAnchored.all(isGroup ? 1 : 0, sessionId, anchor, limit)
|
||||
: this.stmtListEventsLatest.all(isGroup ? 1 : 0, sessionId, limit);
|
||||
|
||||
const result: JsonObject[] = [];
|
||||
for (const row of rows as Array<{ data: string }>) {
|
||||
|
||||
Generated
+275
@@ -59,7 +59,13 @@ importers:
|
||||
'@snowluma/proton':
|
||||
specifier: workspace:*
|
||||
version: link:../proton
|
||||
better-sqlite3:
|
||||
specifier: ^11.7.0
|
||||
version: 11.10.0
|
||||
devDependencies:
|
||||
'@types/better-sqlite3':
|
||||
specifier: ^7.6.13
|
||||
version: 7.6.13
|
||||
'@types/node':
|
||||
specifier: ^24.12.2
|
||||
version: 24.12.2
|
||||
@@ -154,10 +160,16 @@ importers:
|
||||
'@snowluma/websocket':
|
||||
specifier: workspace:*
|
||||
version: link:../websocket
|
||||
better-sqlite3:
|
||||
specifier: ^11.7.0
|
||||
version: 11.10.0
|
||||
devDependencies:
|
||||
'@hono/node-server':
|
||||
specifier: ^1.19.14
|
||||
version: 1.19.14(hono@4.12.14)
|
||||
'@types/better-sqlite3':
|
||||
specifier: ^7.6.13
|
||||
version: 7.6.13
|
||||
'@types/node':
|
||||
specifier: ^24.12.2
|
||||
version: 24.12.2
|
||||
@@ -1636,6 +1648,9 @@ packages:
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||
|
||||
'@types/better-sqlite3@7.6.13':
|
||||
resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==}
|
||||
|
||||
'@types/chai@5.2.3':
|
||||
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
|
||||
|
||||
@@ -1812,11 +1827,23 @@ packages:
|
||||
base-log-factory@2.1.4:
|
||||
resolution: {integrity: sha512-FK1tHOB+rXvlBcGYYhSNR2QS2juEG4SZ1ekXQRQ2XscYNtDxllzqkzfgBFr12EVrKb2/07NLdbr0cSwVoc378A==}
|
||||
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
baseline-browser-mapping@2.10.19:
|
||||
resolution: {integrity: sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
better-sqlite3@11.10.0:
|
||||
resolution: {integrity: sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==}
|
||||
|
||||
bindings@1.5.0:
|
||||
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
||||
|
||||
bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
|
||||
blf-colorful-appender@1.0.6:
|
||||
resolution: {integrity: sha512-MdwOI17J8iOC2XKi5b4VagAJg308R4QesRoCAPw/w7ihzwkTst91LvkTAF3GvPJCEaHXptiUS2UsFkCNiBIEGg==}
|
||||
|
||||
@@ -1832,6 +1859,9 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
|
||||
bundle-require@5.1.0:
|
||||
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
@@ -1853,6 +1883,9 @@ packages:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
chownr@1.1.4:
|
||||
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||
|
||||
class-variance-authority@0.7.1:
|
||||
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
||||
|
||||
@@ -1905,6 +1938,14 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
decompress-response@6.0.0:
|
||||
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
deep-extend@0.6.0:
|
||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
@@ -1922,6 +1963,9 @@ packages:
|
||||
electron-to-chromium@1.5.339:
|
||||
resolution: {integrity: sha512-Is+0BBHJ4NrdpAYiperrmp53pLywG/yV/6lIMTAnhxvzj/Cmn5Q/ogSHC6AKe7X+8kPLxxFk0cs5oc/3j/fxIg==}
|
||||
|
||||
end-of-stream@1.4.5:
|
||||
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||
|
||||
enhanced-resolve@5.20.1:
|
||||
resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@@ -2005,6 +2049,10 @@ packages:
|
||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
expand-template@2.0.3:
|
||||
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
expect-type@1.3.0:
|
||||
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -2036,6 +2084,9 @@ packages:
|
||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
file-uri-to-path@1.0.0:
|
||||
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
|
||||
|
||||
find-up@5.0.0:
|
||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2064,6 +2115,9 @@ packages:
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
fs-constants@1.0.0:
|
||||
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
|
||||
|
||||
fs-extra@11.3.4:
|
||||
resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
|
||||
engines: {node: '>=14.14'}
|
||||
@@ -2087,6 +2141,9 @@ packages:
|
||||
get-tsconfig@4.14.0:
|
||||
resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==}
|
||||
|
||||
github-from-package@0.0.0:
|
||||
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
|
||||
|
||||
glob-parent@6.0.2:
|
||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@@ -2112,6 +2169,9 @@ packages:
|
||||
resolution: {integrity: sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -2127,6 +2187,12 @@ packages:
|
||||
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
||||
engines: {node: '>=0.8.19'}
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
ini@1.3.8:
|
||||
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
|
||||
|
||||
is-core-module@2.16.1:
|
||||
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2292,10 +2358,20 @@ packages:
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
mimic-response@3.1.0:
|
||||
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
minimatch@10.2.5:
|
||||
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
mkdirp-classic@0.5.3:
|
||||
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
||||
|
||||
mlly@1.8.2:
|
||||
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
|
||||
|
||||
@@ -2330,9 +2406,16 @@ packages:
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
napi-build-utils@2.0.0:
|
||||
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
|
||||
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
node-abi@3.92.0:
|
||||
resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
@@ -2346,6 +2429,9 @@ packages:
|
||||
obug@2.1.1:
|
||||
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
|
||||
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -2408,14 +2494,27 @@ packages:
|
||||
resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
prebuild-install@7.1.3:
|
||||
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
|
||||
hasBin: true
|
||||
|
||||
prelude-ls@1.2.1:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
pump@3.0.4:
|
||||
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
rc@1.2.8:
|
||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||
hasBin: true
|
||||
|
||||
react-dom@19.2.5:
|
||||
resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==}
|
||||
peerDependencies:
|
||||
@@ -2455,6 +2554,10 @@ packages:
|
||||
resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
readable-stream@3.6.2:
|
||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
readdirp@4.1.2:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
@@ -2481,6 +2584,9 @@ packages:
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
sass@1.99.0:
|
||||
resolution: {integrity: sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -2519,6 +2625,12 @@ packages:
|
||||
siginfo@2.0.0:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
|
||||
simple-concat@1.0.1:
|
||||
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
|
||||
|
||||
simple-get@4.0.1:
|
||||
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
|
||||
|
||||
sonner@2.0.7:
|
||||
resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
|
||||
peerDependencies:
|
||||
@@ -2539,6 +2651,13 @@ packages:
|
||||
std-env@4.1.0:
|
||||
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
|
||||
|
||||
string_decoder@1.3.0:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
|
||||
strip-json-comments@2.0.1:
|
||||
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
sucrase@3.35.1:
|
||||
resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
@@ -2558,6 +2677,13 @@ packages:
|
||||
resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tar-fs@2.1.4:
|
||||
resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==}
|
||||
|
||||
tar-stream@2.2.0:
|
||||
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
thenify-all@1.6.0:
|
||||
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
||||
engines: {node: '>=0.8'}
|
||||
@@ -2623,6 +2749,9 @@ packages:
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
tunnel-agent@0.6.0:
|
||||
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||
|
||||
tw-animate-css@1.4.0:
|
||||
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
||||
|
||||
@@ -2689,6 +2818,9 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
vite-plugin-cp@6.0.3:
|
||||
resolution: {integrity: sha512-qxKGH3v9wPwUDbDchJf4IH4mRE1zkLWgzZSxwrl8LapwUWm48IFS7SlbRmcF4NquC/fESA5cHFge9E2Ps+woxg==}
|
||||
engines: {node: '>=14.18.0', vite: '>=3.1.0'}
|
||||
@@ -2795,6 +2927,9 @@ packages:
|
||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
yallist@3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||
|
||||
@@ -3834,6 +3969,10 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@types/better-sqlite3@7.6.13':
|
||||
dependencies:
|
||||
'@types/node': 25.6.0
|
||||
|
||||
'@types/chai@5.2.3':
|
||||
dependencies:
|
||||
'@types/deep-eql': 4.0.2
|
||||
@@ -4045,8 +4184,25 @@ snapshots:
|
||||
date-manip: 2.0.6
|
||||
is-what-type: 1.1.4
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
baseline-browser-mapping@2.10.19: {}
|
||||
|
||||
better-sqlite3@11.10.0:
|
||||
dependencies:
|
||||
bindings: 1.5.0
|
||||
prebuild-install: 7.1.3
|
||||
|
||||
bindings@1.5.0:
|
||||
dependencies:
|
||||
file-uri-to-path: 1.0.0
|
||||
|
||||
bl@4.1.0:
|
||||
dependencies:
|
||||
buffer: 5.7.1
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
|
||||
blf-colorful-appender@1.0.6:
|
||||
dependencies:
|
||||
base-log-factory: 2.1.4
|
||||
@@ -4074,6 +4230,11 @@ snapshots:
|
||||
node-releases: 2.0.37
|
||||
update-browserslist-db: 1.2.3(browserslist@4.28.2)
|
||||
|
||||
buffer@5.7.1:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
bundle-require@5.1.0(esbuild@0.27.7):
|
||||
dependencies:
|
||||
esbuild: 0.27.7
|
||||
@@ -4089,6 +4250,8 @@ snapshots:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
|
||||
chownr@1.1.4: {}
|
||||
|
||||
class-variance-authority@0.7.1:
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
@@ -4126,6 +4289,12 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decompress-response@6.0.0:
|
||||
dependencies:
|
||||
mimic-response: 3.1.0
|
||||
|
||||
deep-extend@0.6.0: {}
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
deepmerge@4.3.1: {}
|
||||
@@ -4136,6 +4305,10 @@ snapshots:
|
||||
|
||||
electron-to-chromium@1.5.339: {}
|
||||
|
||||
end-of-stream@1.4.5:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
|
||||
enhanced-resolve@5.20.1:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
@@ -4265,6 +4438,8 @@ snapshots:
|
||||
|
||||
esutils@2.0.3: {}
|
||||
|
||||
expand-template@2.0.3: {}
|
||||
|
||||
expect-type@1.3.0: {}
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
@@ -4285,6 +4460,8 @@ snapshots:
|
||||
dependencies:
|
||||
flat-cache: 4.0.1
|
||||
|
||||
file-uri-to-path@1.0.0: {}
|
||||
|
||||
find-up@5.0.0:
|
||||
dependencies:
|
||||
locate-path: 6.0.0
|
||||
@@ -4312,6 +4489,8 @@ snapshots:
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
|
||||
fs-constants@1.0.0: {}
|
||||
|
||||
fs-extra@11.3.4:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
@@ -4331,6 +4510,8 @@ snapshots:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
|
||||
github-from-package@0.0.0: {}
|
||||
|
||||
glob-parent@6.0.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
@@ -4351,6 +4532,8 @@ snapshots:
|
||||
|
||||
hono@4.12.14: {}
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
ignore@7.0.5: {}
|
||||
@@ -4360,6 +4543,10 @@ snapshots:
|
||||
|
||||
imurmurhash@0.1.4: {}
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
ini@1.3.8: {}
|
||||
|
||||
is-core-module@2.16.1:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
@@ -4480,10 +4667,16 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
mimic-response@3.1.0: {}
|
||||
|
||||
minimatch@10.2.5:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.5
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
mkdirp-classic@0.5.3: {}
|
||||
|
||||
mlly@1.8.2:
|
||||
dependencies:
|
||||
acorn: 8.16.0
|
||||
@@ -4515,8 +4708,14 @@ snapshots:
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
napi-build-utils@2.0.0: {}
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
node-abi@3.92.0:
|
||||
dependencies:
|
||||
semver: 7.7.4
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
optional: true
|
||||
|
||||
@@ -4526,6 +4725,10 @@ snapshots:
|
||||
|
||||
obug@2.1.1: {}
|
||||
|
||||
once@1.4.0:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
@@ -4577,10 +4780,37 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
prebuild-install@7.1.3:
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
expand-template: 2.0.3
|
||||
github-from-package: 0.0.0
|
||||
minimist: 1.2.8
|
||||
mkdirp-classic: 0.5.3
|
||||
napi-build-utils: 2.0.0
|
||||
node-abi: 3.92.0
|
||||
pump: 3.0.4
|
||||
rc: 1.2.8
|
||||
simple-get: 4.0.1
|
||||
tar-fs: 2.1.4
|
||||
tunnel-agent: 0.6.0
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
pump@3.0.4:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.5
|
||||
once: 1.4.0
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
rc@1.2.8:
|
||||
dependencies:
|
||||
deep-extend: 0.6.0
|
||||
ini: 1.3.8
|
||||
minimist: 1.2.8
|
||||
strip-json-comments: 2.0.1
|
||||
|
||||
react-dom@19.2.5(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
@@ -4615,6 +4845,12 @@ snapshots:
|
||||
|
||||
react@19.2.5: {}
|
||||
|
||||
readable-stream@3.6.2:
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
string_decoder: 1.3.0
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
@@ -4680,6 +4916,8 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc': 4.60.4
|
||||
fsevents: 2.3.3
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
sass@1.99.0:
|
||||
dependencies:
|
||||
chokidar: 4.0.3
|
||||
@@ -4709,6 +4947,14 @@ snapshots:
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
simple-concat@1.0.1: {}
|
||||
|
||||
simple-get@4.0.1:
|
||||
dependencies:
|
||||
decompress-response: 6.0.0
|
||||
once: 1.4.0
|
||||
simple-concat: 1.0.1
|
||||
|
||||
sonner@2.0.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
@@ -4722,6 +4968,12 @@ snapshots:
|
||||
|
||||
std-env@4.1.0: {}
|
||||
|
||||
string_decoder@1.3.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
strip-json-comments@2.0.1: {}
|
||||
|
||||
sucrase@3.35.1:
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
@@ -4740,6 +4992,21 @@ snapshots:
|
||||
|
||||
tapable@2.3.2: {}
|
||||
|
||||
tar-fs@2.1.4:
|
||||
dependencies:
|
||||
chownr: 1.1.4
|
||||
mkdirp-classic: 0.5.3
|
||||
pump: 3.0.4
|
||||
tar-stream: 2.2.0
|
||||
|
||||
tar-stream@2.2.0:
|
||||
dependencies:
|
||||
bl: 4.1.0
|
||||
end-of-stream: 1.4.5
|
||||
fs-constants: 1.0.0
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
|
||||
thenify-all@1.6.0:
|
||||
dependencies:
|
||||
thenify: 3.3.1
|
||||
@@ -4806,6 +5073,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
tunnel-agent@0.6.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
tw-animate-css@1.4.0: {}
|
||||
|
||||
type-check@0.4.0:
|
||||
@@ -4862,6 +5133,8 @@ snapshots:
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
vite-plugin-cp@6.0.3:
|
||||
dependencies:
|
||||
fs-extra: 11.3.4
|
||||
@@ -4950,6 +5223,8 @@ snapshots:
|
||||
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
Reference in New Issue
Block a user