Commit Graph
21 Commits
Author SHA1 Message Date
DrSmoothl 056188d63d chore: resolve async SQLite merge conflicts
Integrate the current development branch, preserve the album comment fixes, and align the changes with repository formatting and dependency tooling.
2026-10-13 09:55:16 +08:00
手瓜一十雪 e699351f90 feat: adopt async SQLite and improve messaging and runtime diagnostics 2026-10-12 15:44:55 +08:00
DrSmoothl 7c0519f75f lint 2026-10-12 15:43:34 +08:00
DrSmoothl 9544aa7761 chore(repo): standardize cross-platform development
Make line endings deterministic across macOS, Windows, and Linux while preserving the native checkout format required by Windows launchers.

Pin the tested application runtime and Node type baseline, add a shared startup preflight, and keep independently published packages on their existing compatibility ranges.

Align local scripts, pull request validation, complete package builds, dependency metadata, and contributor documentation around the same Node and pnpm contract. The root test command now covers every tested workspace package while a focused Core command remains available for local iteration.
2026-07-31 13:09:02 +08:00
DrSmoothl 216213e102 ci(release): redesign cross-platform release notes
Present Windows x64, Linux x64, and Linux arm64 as fully supported release targets in a concise download matrix.

Add quick-start and unattended-consent guidance, link commits and version comparisons, and filter merge and release housekeeping from the change list.

Refresh the adjacent packaging documentation so it matches current platform support.
2026-07-13 23:15:20 +08:00
DrSmoothl feaa94bec7 build(core,runtime): native websocket-darwin-arm64.node + drop build hatch
WebSocket native is now built for darwin-arm64 — source is at
github.com/SnowLuma/snowluma-websocket (cmake-js + node-addon-api,
clones to dev/snowluma-websocket and builds with `npm install`).

  npm install in dev/snowluma-websocket → build/Release/snowluma_ws.node
  cp → packages/runtime/native/websocket-darwin-arm64.node (102 KB)

Reverts the SNOWLUMA_BUILD_SKIP_NATIVES escape hatch in vite.config.ts
that 67f8bf3 added — no longer needed; the darwin native binary is
present. Keeps `packages/core/config/.gitignore` (independently useful
runtime-state hygiene). vite build now runs clean on darwin with no
env overrides; the runtime SNOWLUMA_DEV_NO_NATIVE=1 stub also becomes
optional (only needed if the operator wants to bypass WebSocket I/O
specifically).

Existing native binaries unchanged. CI invariant restored: every
release build must include every platform's native set.
2026-06-25 16:25:56 +08:00
DrSmoothl 67f8bf3941 build(core): SNOWLUMA_BUILD_SKIP_NATIVES=1 escape hatch for darwin dev
Two architectural cleanups that complement the bridge darwin support in
7343ae6, both opt-in / default-safe:

1) `vite.config.ts` — gate the missing-native build error behind
   `SNOWLUMA_BUILD_SKIP_NATIVES=1`. Default behaviour (no env) still
   throws as before, so the CI invariant "release builds must carry the
   full native set" is untouched. Darwin developers without prebuilt
   `websocket-darwin-arm64.node` (source lives in a separate private
   build repo) can now run `SNOWLUMA_BUILD_SKIP_NATIVES=1 pnpm build`
   and pair it with the runtime `SNOWLUMA_DEV_NO_NATIVE=1` stub at
   `node dist/index.mjs` boot. The qq-hook path (separate addon) is
   independent and unaffected.

2) `packages/core/config/.gitignore` — `runtime.json` is the only
   tracked deploy-time override file in that dir; every other config
   file is per-instance runtime state written by the daemon/dev server
   (admin password hash, EULA consent, per-uin OneBot binding, WebUI
   layout). Previously they showed up as 5 untracked files after every
   `pnpm dev:mode`, polluting `git status` and risking accidental
   commit of local creds via `git add -A`. Pattern: ignore everything
   except `runtime.json` (and the .gitignore itself).

No code paths changed; both are operator-experience fixes for running
SnowLuma on darwin against the nnphook DYLD_INSERT hook.
2026-06-25 14:27:59 +08:00
DrSmoothl 8c7efc34a9 feat(webui): EULA/PRIVACY consent gate — backend
Server-side consent gate: GET /api/agreements (texts + content-hash version +
consentRequired) and POST /api/agreements/record-consent, persisted to
config/consent.json. Enforced in the auth middleware (CONSENT_ALLOWLIST,
ordered before the must-change-password gate) so the gate is real, not just a
frontend convention. The version is content-derived, so consent is stable
across app upgrades and re-prompts only when the agreement text changes. The
EULA/PRIVACY docs are bundled into dist/ (build fails if they are missing).
2026-06-19 02:26:48 +08:00
DrSmoothl 74b6fe97e5 build(core): externalize node:sqlite instead of SSR-bundling
Addresses MliKiowa's review on PR #68 ("单独排除 sqlite 就行了"). That PR
fixed the `DatabaseSync is not a constructor` crash by switching the core
bundle to SSR mode (build.ssr + ssr.noExternal + target node22 + a
catch-all isNodeBuiltin external fn). The root cause was narrower: on
Node 22.x `node:sqlite` is experimental and absent from `builtinModules`,
so the `nodeModules` sweep didn't externalize it and Vite swapped in a
browser shell. The SSR switch also self-inflicted the @hono/node-server
MODULE_NOT_FOUND it then had to paper over with noExternal — the original
config already bundled all non-builtin deps.

Revert vite.config.ts to the pre-PR state and just add 'node:sqlite'
(+ bare 'sqlite') to the external list. Verified: `SNOWLUMA_TARGET=linux-x64
vite build` succeeds and dist/index.mjs now carries a real
`import { DatabaseSync } from "node:sqlite"` rather than a shimmed shell.
2026-05-29 20:22:06 +08:00
H3CoF6 bf932477dd build(core): fix SSR bundle and node:sqlite resolution in Vite config 2026-05-28 02:02:29 +08:00
手瓜一十雪 f2b7ef0056 Switch from @snowluma/sqlite to node:sqlite (sync)
Remove the in-tree @snowluma/sqlite wrapper and vendored better-sqlite3 prebuilts; migrate consumers to node:sqlite's synchronous API. Replaced imports and types (Database/Statement -> DatabaseSync/StatementSync), updated PRAGMA calls to use exec, and adjusted code in onebot (media/message/reaction stores) and protocol (identity service). Vite config no longer bundles sqlite native binaries and references to the sqlite workspace were removed. Also delete the packages/sqlite package and its native prebuild artifacts, and add a couple of unrelated AST improvements in proton: use a module-level shared wrapper cache and add an inheritance cache to avoid repeated scans. Minor import reordering/cleanup applied where needed.
2026-05-27 20:02:40 +08:00
DrSmoothl 5f1d4400dc fix(build): inject createRequire banner so bundled CJS requires resolve
better-sqlite3's JS wrapper is pure CJS — its `require('fs')` /
`require('path')` / `require('bindings')` calls get inlined into the
ESM bundle as `__require("fs")` shims by rolldown. The shim's
fast-path checks `typeof require !== "undefined"` (true in CJS, false
in our `.mjs` output) and otherwise throws "Calling `require` for
\"fs\" in an environment that doesn't expose the `require` function".
The launcher crashed at startup the moment `@snowluma/sqlite` resolved
its `import BetterSqlite3 from 'better-sqlite3'`.

Inject `const require = createRequire(import.meta.url)` via
`rollupOptions.output.banner` so every inlined CJS require routes to
Node's CJS loader. No call-site changes; the only diff in the emitted
bundle is two new top-of-file import/const lines.
2026-05-21 14:04:14 +08:00
DrSmoothl 4e51b4bd7d build(sqlite): wrap better-sqlite3 with @snowluma/sqlite + vendor prebuilts
Replaces "runtime npm dependency that gets `npm install`-ed into
dist/" with "TS wrapper that loads the native `.node` from
`dist/native/` like every other native binding in the project".

Why the change
--------------
The previous approach (better-sqlite3 declared in
packages/runtime/package.json, materialized by `npm install
--omit=dev` in dist/) worked but produced an artifact that needed
runtime npm install — a step the rest of SnowLuma's native bindings
don't need (websocket / snowluma-hook / ffmpeg are all standalone
`.node` files copied into `dist/native/` by vite-plugin-cp). The
asymmetry was friction: dev mode required `pnpm dist:install`,
build artifacts carried a `dist/node_modules/` tree the other
modules didn't need, and Docker images had to special-case the
glibc-vs-musl ABI lookup that prebuild-install runs.

The new shape:
  - `@snowluma/sqlite` (packages/sqlite/) — TS wrapper. Has
    `better-sqlite3` as a devDep (for the JS-layer types + the
    actual JS wrapper code that vite inlines). At runtime, calls
    `createRequire().require()` against
    `<dist|repo>/native/better-sqlite3-v<abi>-<platform>-<arch>.node`
    and passes the resolved addon to `new Database(path, {
    nativeBinding })`.
  - `packages/runtime/native/better-sqlite3-v<abi>-<triplet>.node`
    — prebuilt binaries vendored into the repo. Two ABIs ship side-
    by-side (v127 = Node 22 LTS used by CI, v137 = Node 24 used by
    dev boxes) so `process.versions.modules` picks the matching one
    at load time.
  - `tools/fetch-sqlite-prebuilts.mjs` — fetches all 5 platforms ×
    N ABIs from WiseLibs's GitHub releases. Idempotent: existing
    `.node` files skip. Accepts extra ABI args:
    `node tools/fetch-sqlite-prebuilts.mjs 12.10.0 v127 v137 v115`.

What was unwound
----------------
- packages/runtime/package.json — dependencies removed (runtime
  artifact is now self-contained, no `npm install` step needed).
- root package.json — `dist:install` script removed (no node_modules
  to install).
- .github/workflows/dev-build.yml — `Bundle runtime dependencies`
  step removed.
- .github/workflows/release.yml — kept the `Bundle runtime
  dependencies` step (was a no-op already, but harmless safety net
  in case a future runtime dep shows up).
- packages/core/vite.config.ts — `better-sqlite3` no longer
  external; vite inlines the JS wrapper (+~33 KB to bundle, ~2.84
  MB → 2.87 MB total).

Naming
------
Mirrors the existing `websocket-${triplet}.node` convention (flat
dir under `runtime/native/`, dash-separated `<platform>-<arch>`).
Adds a `v<abi>` segment because better-sqlite3 is NOT N-API — one
binary per Node ABI. Naming:

  better-sqlite3-v127-darwin-arm64.node    Node 22 / macOS arm
  better-sqlite3-v127-darwin-x64.node      Node 22 / macOS intel
  better-sqlite3-v127-linux-arm64.node     Node 22 / Linux arm
  better-sqlite3-v127-linux-x64.node       Node 22 / Linux x64
  better-sqlite3-v127-win32-x64.node       Node 22 / Windows x64
  better-sqlite3-v137-... (5)              Node 24 / same 5 hosts

Total: 10 binaries × ~1.9 MB = ~19 MB committed. Aligned with the
existing native-vendoring footprint (websocket ~2 MB × 5, snowluma-
hook ~10 MB × Windows + Linux, ffmpeg ~10-30 MB × 4).

What changes for consumers
--------------------------
Source-side: nothing. `import Database, { type Database, type
Statement } from '@snowluma/sqlite'` is a drop-in for the previous
`import Database from 'better-sqlite3'`. The wrapper re-exports the
same types and the `Database` callable factory has the same
overload set.

Runtime: `node` finds the binding at
`<dist|repo>/native/better-sqlite3-v${process.versions.modules}-
${process.platform}-${process.arch}.node`. If the ABI doesn't
match a vendored binary, the wrapper throws a clear error pointing
at `tools/fetch-sqlite-prebuilts.mjs` with the missing ABI/triplet.

Bumped better-sqlite3 from 11.10.0 → 12.10.0 because v11 only had
prebuilt up to Node ABI v127 (no v137 = no Node 24). v12.10.0
ships both. Both versions are API-compat (the breaking changes
between v11 and v12 were either internal or in error-handling
paths SnowLuma doesn't exercise).

Verification
------------
- pnpm -r typecheck (12 projects): clean
- @snowluma/core test: 302/302
- @snowluma/onebot test: 250/250 (552/552 combined, unchanged from
  the perf-series baseline)
- Bench numbers preserved — wrapper indirection is sub-μs:
  storeMeta ~11 μs, storeEvent ~17 μs, findMeta hit ~1.1 μs (all
  within noise of the post-RoadMap-#5 measurements).
- Vite build clean, bundle = 2.87 MB / 230 KB gzip
- `dist/index.mjs` no longer contains a `node:sqlite` import (it
  did before — media-store hadn't migrated; now neither it nor
  message-store reach into the Node builtin).

Bonus side-fix: `packages/onebot/src/media-store.ts` also moved
off `node:sqlite` (caught by the post-build bundle grep — the
file was using `DatabaseSync` + `StatementSync` from the builtin
even though it had already been written to cache its prepared
statements). Its hot paths now go through the same wrapper.
2026-05-21 13:49:19 +08:00
DrSmoothl 0b6541ca36 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.
2026-05-21 10:34:37 +08:00
DrSmoothl 70715763bd fix(core): wire @snowluma/proton vite plugin into the production build
The proton plugin was only in `vitest.config.ts`, so tests and
typecheck passed but the bundled `dist/index.mjs` shipped with the
unsubstituted generic shells — every `protobuf_encode<T>()` /
`protobuf_decode<T>()` call hit the runtime fallback and threw:

  "protobuf_encode<T>() was not transformed by the
   @snowluma/proton Vite plugin. …"

at every msg-push, rkey/friend/group/profile warmup, etc. on the
Win/Linux releases.

Add `protobufVitePlugin()` to the shared `BaseConfigPlugin` so the
transform runs in both dev (`vite build --mode development`) and
prod (`vite build`). Sanity-check on a fresh `vite build` shows the
bundle now contains 481 distinct monomorphized codecs (704 encode +
511 decode call-site replacements), and the only remaining
"was not transformed" string is the dead runtime fallback that no
longer has a caller.
2026-05-19 11:20:47 +08:00
DrSmoothl 61e3867197 chore(release): single-source app version + add pnpm bump
- WebUI sidebar/login display and OneBot get_version_info now read the
  root package.json version via Vite `define`, removing hardcoded
  literals in webui/src/types.ts and core/onebot/actions/info.ts.
- tools/bump-version.mjs (exposed as `pnpm bump <ver>`) syncs the
  version field across the root and all five workspace packages.
- Bump everything to 1.7.8.
2026-05-13 16:51:44 +08:00
手瓜一十雪 c39b3f5c4e Add PTT (voice) upload support with FFmpeg addon
Introduce NT SILK-based voice (PTT) upload pipeline and native FFmpeg addon wrapper.

- Add ffmpeg-addon wrapper (packages/core/src/bridge/highway/ffmpeg-addon.ts) to load platform/arch .node binaries, detect SILK files, and convert inputs to NT SILK via the native addon. Provides encodeSilk, isSilkFile and default temp dir helpers.
- Implement ptt-upload pipeline (packages/core/src/bridge/highway/ptt-upload.ts) to: resolve/fetch audio bytes, convert to NT SILK if needed, send OIDB upload request, perform highway HTTP upload when required, and produce Encodable MsgInfo bytes for commonElem.
- Wire record element support into element-builder: uploadPttMsgInfo is used to build a commonElem for 'record' sends; when SendContext is absent, record sends are skipped with a warning (graceful no-bridge path).
- Update message parsing to accept record segments from data.file, data.url or data.path and drop empty sources.
- Enable canSendRecord in OneBot instance context.
- Add tests covering record parsing and the graceful send path (packages/core/tests/message-parser.test.ts).
- Include prebuilt native ffmpeg addon binaries under packages/runtime/native/ffmpeg and update vite config to validate & copy the ffmpeg addon into the dist layout.

These changes enable sending voice messages by converting/encoding sources to the expected NT SILK format and uploading them through the existing highway/NTV2 flows.
2026-05-09 19:32:13 +08:00
DrSmoothl 1426d7adb2 feat: add settings page and theme toggle component
- Implemented a new SettingsPage component for user preferences including appearance, behavior, and account security settings.
- Added a ThemeToggle component to switch between light, dark, and system modes.
- Created reusable UI components: AlertDialog, Avatar, Badge, Button, Card, Dialog, Input, Label, Progress, ScrollArea, Separator, Sheet, Skeleton, Tooltip.
- Introduced utility functions for class name merging and formatting bytes/uptime.
- Added a custom hook for media query handling
2026-05-05 00:39:52 +08:00
手瓜一十雪 82c1721466 CI: add matrix builds & cross-platform packaging
Restructure GitHub Actions to build Windows and Linux matrix targets and produce platform-specific artifacts.

- .github/workflows/dev-build.yml: split cleanup/build jobs, add matrix (win-x64, linux-x64), set SNOWLUMA_TARGET for builds, produce full and -lite archives (.zip on Windows, .tar.gz on Linux), unify upload step and ensure errors when artifacts missing.
- .github/workflows/release.yml: convert release build to matrix, produce platform archives, download/collect artifacts, generate multi-platform release notes (bash), and create the GitHub release with both .zip and .tar.gz assets.
- packages/core/vite.config.ts: add fs import and SNOWLUMA_TARGET handling, pick platform-specific runtime files (launcher.bat or launcher.sh), enumerate native files per target, fail CI if expected native binaries are missing, and copy native files into dist/native.
- packages/runtime/launcher.sh: add a portable launcher for Linux that prefers a bundled node binary or falls back to PATH and execs index.mjs.

These changes enable cross-target CI packaging and enforce that native binaries are present before releasing.
2026-05-03 16:20:39 +08:00
手瓜一十雪 252a65b833 Add @snowluma/websocket TS package and integrate
Introduce an in-tree TypeScript WebSocket package (@snowluma/websocket): move JS websocket implementation into packages/websocket/src (client, server, websocket, extensions, native, index) and add package.json + tsconfig. Replace direct 'ws' dependency in core with the workspace package and update runtime manifest/description. Implement a native addon loader for platform-specific binaries, add typings/refinements across client/server/websocket (more strict types, handshake parsing, permessage-deflate plumbing, buffer/raw data handling), and wire WebSocketClient as the package default export while exposing internal types. Update vite config to bundle the new package and copy only platform-target native binaries into dist/native. Remove legacy JS entrypoints and native loader, and adjust imports/usages in core (ws-transport) to accept broader raw data types.
2026-05-03 15:49:42 +08:00
手瓜一十雪 39f1672b29 Migrate project to pnpm monorepo
Restructure the repository into a pnpm workspace under packages/ (core, webui, runtime). Move source files for core and webui into packages/*, add package manifests for @snowluma/core and @snowluma/runtime, and remove the old launcher package.json. Update root package.json to be a monorepo controller (scripts delegate to pnpm filters, set packageManager and Node engine). Configure build outputs to emit into the repo-root dist/ (vite config changes) so the existing release pipeline continues to work. Add .npmrc with mirrors, expand .gitignore for monorepo layout, add tsconfig base and package-level tsconfigs, and add vitest config for the core package. Adjust injector native search paths and update tests import paths to the new package layout. Small CI/workflow comment added to clarify build behavior and README updated with monorepo structure and common commands.
2026-05-03 14:02:29 +08:00