Add a cheap AST probe in file-pipeline to detect if a file contains extending classes (using fileHasExtendingClass) and return early to avoid the expensive resolveImports recursive descent when not needed.
Update subclass-wrapper tests to import beforeAll and cache the plugin transform output and its stripped JS once in a beforeAll hook, replacing repeated transform calls with reused transformedOutput/strippedJs to reduce test runtime.
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.
Refactor subclass-wrapper resolution to an AST-only pipeline and remove the TypeChecker-based program cache.
- Move subclass-wrapper logic from typecheck/ to ast/: add ast/file-pipeline.ts and ast/subclass-wrapper.ts, and update codegen/imports accordingly.
- Implement AST-only cross-file extends resolution using import-resolver's fileCache (findImportSourcePath) and a WrapperLookupCache; subclass methods must now have explicit type annotations (no TypeChecker inference).
- Remove TypeChecker-dependent program-cache and typecheck/file-pipeline; plugin no longer preloads a ts.Program or accepts tsconfig option.
- Simplify plugin cache startup and HMR handling in src/index.ts to use the AST pipeline and transform cache only.
- Update tests to use the new AST pipeline entrypoints and adjust expectations.
This eliminates the dependency on a baked ts.Program for wrapper resolution, simplifies startup, and relies on the existing import-resolver for cross-file traversal. Partial wrapper resolutions are still all-or-nothing; explicit annotations are required for AST-only resolution.
Consolidate and simplify import extraction and wrapper/typecheck internals. Introduce collectImportClauses that drives both type+value and value-only import extraction with per-file caching; reintroduce extractImports/extractValueImports as thin wrappers. Clean up BARE_SPECIFIER_COMPILER_OPTIONS comment. Replace the containsTypeParameter/firstTypeParameterIndex pair with a single findTypeParameterIndex helper. Remove collectExportedWrappers and call collectWrappers directly from parseFileForDefinitions; ensure imported wrapper collection skips type-only imports to avoid eager parsing.
Streamline static-wrapper representations: ClassWrapperInfo is now a Map<string, WrapperMethodInfo>, and related detection helpers updated and documented. Simplify subclass-wrapper/typechecker APIs: drop wrapperOwner and SourceFile parameters where unused, adjust function signatures (collectInheritedWrappers, resolveSubclassWrappers, resolveWrapperCodecCalls, extractSubclassMemberTypeNode) to operate with fewer arguments and return plain WrapperMethodInfo lists. Update file-pipeline to match reordered args.
Refactor plugin context creation: add buildContext helper, createPluginContext/createStandaloneContext now delegate to it; remove programPrepared flag and adjust prepareProgramForTsconfig invocation site. Update tests to reflect Map API changes and revised function signatures.
Overall this is an internal refactor to reduce duplication, avoid eager work for type-only imports, remove unused params, and clarify interfaces without changing behavior.
Export and centralize small utility helpers and remove duplicate implementations: identityImportedTypeName was exported from utils, and isVarint64/isFixed64BigInt were added to types and imported into encoder/decoder. Minor import reorders and formatting cleanups across collector, monomorphizer, import-resolver, static-wrapper, subclass-override and file-pipeline. Comments and JSDoc were tightened for clarity; behavior remains the same but common logic is now deduplicated and easier to maintain.
Introduce disk caching and startup program preparation for the Proton Vite plugin. Add ProtobufVitePluginOptions (root, tsconfig, cache) and configResolved hook to prebuild/prepare a TypeScript Program (program cache) and create a transform cache per-file (transform cache) keyed by source/config hashes and dependency mtimes. Track dependencyFiles in import-resolver so transform cache can validate mtimes. Update program-cache to support incremental tsbuildinfo and expose prepareProgramForTsconfig. Add cache read/write, hashing, path normalization and scope checks in the plugin; update tests and README to cover scope and cache behavior. Also add ts-morph dependency and update lockfile.
Introduce a TextEdit type and applyTextEdits utility for safe, reverse-ordered string edits and refactor replacer to produce and consume edit lists (collectReplacementEdits) instead of performing manual slicing. Propagate ts.SourceFile through ParsedFileEntry/ImportedDefinitions and use cached extraction of imports/exports to avoid repeated AST traversal when resolving imports; analyzer now prefers an imported sourceFile when available. Wire text edits into the plugin pipeline (compose replacement + override edits) and remove the previous applyOverrideInsertions helper. Add a tsconfig lookup cache in program-cache to speed up upward tsconfig searches and invalidate it when the program cache is cleared. README updated to list the new files.
Move instantiateWrapperTypePattern and the WrapperBinding type into a new wrapper-binding.ts module and register it for reuse. Update analyzer.ts and import-resolver.ts to import the function/type from the new module and simplify call-site/type-root collection logic (use entrySourceFile and a cached callable name). Also update README: add ast-helpers.ts and wrapper-binding.ts to the file list and bump test count in the example.
Avoid the expensive TypeChecker/program startup for files that don't declare extending classes by performing a cheap top-level scan first. index.ts now imports fileHasExtendingClass and a SubclassWrapperPipelineResult type and conditionally calls runSubclassWrapperPipeline only when a candidate is found, otherwise returning an empty result. Also updated comments to explain the optimization. subclass-wrapper.ts now imports isStaticMethod from ast-helpers.js (small import cleanup/usage update).
Add shared AST predicates in packages/proton/src/ast/ast-helpers.ts to centralize small structural checks used by the analyzer, callsite matcher, wrapper detector, and subclass resolver. Introduce a WeakMap cache in packages/proton/src/ast/utils.ts so createImportedTypeNameResolver is memoized per SourceFile to avoid repeated import-scans during transform() work. Update packages/proton/README.md with a Chinese translation and various clarifications/documentation tweaks.
Group rendered subclass overrides per class and emit them as position-tagged insertions instead of a single trailing string. Add SubclassOverrideInsertion and applyOverrideInsertions to splice overrides immediately after each subclass declaration (sorted in reverse to keep offsets valid). Update runSubclassWrapperPipeline to collect per-subclass rendered parts and return insertions + extra root names, and update the plugin transform to apply these insertions before producing final code. Update tests to expect insertions and verify placement and runtime behavior. This ensures user code sees overrides right after the class body so subsequent top-level calls dispatch to the override rather than an inherited abstract implementation.
Introduce per-subclass static-method wrapper support: detect wrapper-pattern methods in the AST, resolve concrete types via a TypeChecker, render per-subclass overrides, and append them into transformed output. Adds static-wrapper detector, subclass-override codegen, a typecheck file pipeline, a lazy ts.Program cache, and the subclass-wrapper resolver. Integrates the pipeline into the Vite plugin (including program cache invalidation on HMR) and threads extra root type names into registry selection so generated overrides get required codecs. Remove the runtime-map fallback feature and related types/logic (runtime-map module, dynamic runtime generation, and public API surface), simplifying runtime stubs and protobuf.d.ts. Update README and analyzer to document and support the new flow; add tests and fixtures for subclass-wrapper behavior.
Enable @typescript-eslint/no-explicit-any as an error in the ESLint config to tighten typing across the codebase, and add an override to disable this rule for test files. Also apply non-functional cleanup in packages/proton/src/runtime.ts: reorder/imports and adjust formatting/indentation and string alignment for readability — no behavioral changes.
`resolveModulePath` used to skip any specifier not starting with '.'
which meant proton's vite plugin only saw proto definitions that lived
in the same package as the call site. Trying to move definitions into
a sibling workspace package (e.g. `@snowluma/proto-defs`) caused those
imports to silently fail — the call-site type lookup found nothing
and the protobuf_encode<T> replacement never fired.
Add a bare-specifier branch that delegates to `ts.resolveModuleName`
with `moduleResolution: Bundler`. That covers monorepo workspaces,
npm packages, package.json `exports`, and tsconfig path aliases with
zero extra user configuration.
The relative-path branch is preserved byte-for-byte so existing
single-package consumers see identical behaviour. Resolution is
constrained to `.ts` source files (not `.d.ts`) because proton walks
interface AST to collect `pb<>` field tags, and generated declaration
files typically strip those generic arguments.
Per-process `ModuleResolutionCache` keeps the hot path (HMR rebuild)
from re-scanning node_modules every call.
Tests: new `cross-package.test.ts` builds a fake monorepo under
`os.tmpdir()` and exercises smoke / transform / round-trip / negative
paths. 99/99 proton tests pass; SnowLuma core regression unchanged
(587 baseline ⇄ 587 with change).
Add consistent indent rule to ESLint and normalize formatting/indentation across multiple files. Refactor RequestUtil: improve cookie extraction, redirect handling, and HttpGetJson/HttpGetText request flow; fix cookie string builder formatting. Fix uploadGroupNoticeImage promise/error handling and network request flow. Also apply minor style/refactor tweaks in bridge web APIs, onebot handlers, message-store SQL alignment, and Proton AST collector utilities to improve readability and correctness.
Add a repository .editorconfig and a root eslint.config.ts to standardize formatting and linting. Migrate packages/webui ESLint config to TypeScript (eslint.config.ts) with project-specific rules and tsconfigRootDir handling, and remove the old JS config. Update package.json to run ESLint directly (add lint and lint:fix) and add required ESLint/dev dependencies; also add jiti to packages/webui/package.json. Include a small formatting tweak to tsconfig.base.json.
Add support for resolving types re-exported with `export * from '...'`. Introduces extractExportAllSpecifiers and a new exportAllTypeSources map on ParsedFileEntry, populates it while parsing (recursively following export-all chains using the cache and an inProgress set to avoid cycles), and falls back to exportAllTypeSources when resolving imported type names. This allows types and generic templates exported through export-all declarations to be discovered and resolved.
Closes the OIDB phase of the proton migration. Every consumer that
talked to the OIDB service through the old runtime
`makeOidbBaseSchema()` factory now goes through the compile-time
generic `OidbBase<T>` instead — proton monomorphizes the codec per
call-site instantiation.
@snowluma/proton — two analyzer fixes uncovered by the migration:
1. Skip queuing call-site type-args that reference an *enclosing
generic function's* type parameter for monomorphization. Before
this, a wrapper like
export function encodeOidbEnv<T>(env: OidbBase<T>): Uint8Array {
return protobuf_encode<OidbBase<T>>(env);
}
would cause the analyzer to mint a literal `OidbBase__T` registry
entry with `body.typeName = 'T'`, which the post-walk wire-type
guard then tripped on. The enclosing-scope type params are tracked
on a stack as the walker descends into FunctionDeclaration,
FunctionExpression, ArrowFunction, MethodDeclaration nodes; matching
call sites are skipped (the wrapper-binding path is still
responsible for materialising concrete codecs at downstream call
sites).
2. Register *explicitly* empty interfaces (`interface X {}`) as
zero-field proto messages. OIDB has a handful of these
('this call has no meaningful response body' placeholders —
Oidb0xeb7Resp, Oidb0xcd4Resp, Oidb0x112aResp, Oidb0x8a0Resp,
Oidb0xf16Resp, OidbClientKeyReq). Proto3 wire allows zero-field
messages, and downstream code does `protobuf_decode<OidbBase<X>>`
where X is one of these — the wire-type guard would otherwise trip
on the missing `X` registration. Interfaces that have members but
no pb-marked fields are still NOT registered.
@snowluma/core — the OIDB-layer refactor itself:
bridge-oidb.ts ditches the runtime ProtoSchema factory and exposes:
- makeOidbEnvelope<T>(oidbCmd, subCmd, body, isUid) pure helper
- encodeOidbEnv<T>(env) pass-through
- decodeOidbEnv<T>(bytes) pass-through
- runOidb(bridge, cmd, envelopeBytes) non-generic raw
`encodeOidbEnv` / `decodeOidbEnv` are pure pass-through generic
wrappers — proton wrapper-binds them and substitutes every call site
with the inlined `protobuf_encode_OidbBase__X` / `protobuf_decode_…`
function. `runOidb` is non-generic (proton leaves it alone) so it can
be mocked in tests. `makeOidbEnvelope` is plain TS — also mockable for
introspection.
56 `runOidb` call sites across 11 files migrated to the new API:
- bridge-contacts.ts, actions/extras.ts, actions/friend.ts,
actions/group-admin.ts, actions/group-file.ts,
actions/group-message.ts, actions/interaction.ts,
actions/misc.ts, actions/profile.ts, web-actions/cookies.ts,
highway/pipeline.ts
pipeline.ts also drops `makeOidbBaseSchema(NTV2UploadRichMediaReqSchema)`
and the residual `protoEncode({...}, EncodableMediaMsgInfoSchema)` —
both go through proton now.
Tests:
- 8 test files migrated from `vi.mocked(decodeOidbEnv).mockReturnValueOnce`
to `vi.mocked(runOidb).mockResolvedValueOnce(protobuf_encode<OidbBase<X>>({body: …}))`
— the proton substitution means the old mock layer is bypassed at
build time, so the only mockable point is the bytes layer at `runOidb`.
- 2 new parity tests pinning `OidbBase<T>` ↔ legacy
`makeOidbBaseSchema(InnerSchema)` byte equivalence.
Verification:
- @snowluma/proton: 95/95 tests pass, tsc --noEmit clean
- @snowluma/core: 605/605 tests pass (network-manager.test.ts
fails to load due to a missing darwin-arm64 native binary in this
environment — pre-existing), tsc --noEmit clean
Covers quick start, primitive types, generic templates, cross-file
wrapper bindings, the proto3 default-value divergence vs the legacy
runtime encoder, build-time diagnostics, and the optional runtime-map
fallback. Also lists every edge case fixed on top of upstream with a
pointer to the corresponding regression test.
Migrates a self-contained subset of bridge/proto/longmsg.ts to the new
@snowluma/proton compile-time DSL form and verifies wire-format parity
against the legacy runtime ProtoSchema encoder. End-to-end proof that
the toolchain works in core's vitest pipeline.
What's covered:
- LongMsgUid / LongMsgSettings / SendLongMsgInfo / SendLongMsgReq /
SendLongMsgResp / SendLongMsgRespResult / RecvLongMsgInfo /
RecvLongMsgReq / RecvLongMsgResp / RecvLongMsgRespResult
- The three schemas that pull in PushMsgBody (LongMsgContent /
LongMsgAction / LongMsgResult) stay on the legacy API for now, since
migrating them requires also migrating message.ts and element.ts.
Toolchain changes:
- @snowluma/proton gets a tsup build step. The ./vite subpath now
points at the built dist/index.js so it is loadable as plain Node
ESM from vite.config.ts / vitest.config.ts (the source-only .ts
layout can't be consumed from outside the Vite transform pipeline).
The . (runtime) subpath stays at TS source — it's only ever
imported from user code, which IS Vite-transformed.
- prepare script ensures dist/ exists after pnpm install.
- @snowluma/core depends on @snowluma/proton (workspace:*) and adds
protobufVitePlugin() to its vitest config so test files get
transformed.
Documented divergence:
proton follows proto3 semantics: fields equal to their default value
(0 / false / "" / empty bytes) are NOT emitted on the wire. The legacy
runtime encoder always emits them. Both forms are round-trippable
through a conforming proto3 reader. The parity test pins this
behaviour explicitly.
8/8 parity tests pass.
Vendors SnowLuma/protobuf-dsl into the monorepo as @snowluma/proton, with
edge-case fixes applied on top of the upstream source. Reference copy of
the unmodified upstream is kept under dev/protobuf-dsl/.
Edge cases fixed (each with a regression test):
- Aliased pb / pb_repeated marker imports (`import { pb as P }`)
— collector now resolves the marker name through the import resolver
- Concrete interface with a generic-instantiated message field
(`wrapped: pb<5, Wrapper<uint_32>>`) — field typeName now resolves
to the mangled form, and the inner instantiation is queued for
monomorphization
- Generic template with a generic-instantiated field
(`interface Outer<U> { x: pb<5, Wrapper<U>> }`) — the inner
instantiation is now re-substituted and re-monomorphized at outer
instantiation time
- Wrapper-binding synthetic SourceFile was empty-text — getText()
returned garbage, breaking mangled name computation downstream
- Cross-file forwarded wrappers (chain → imported base) were not
detected — parseFileForDefinitions now resolves imports recursively
(value imports only, with cycle protection)
- matchForwardedKnownWrapper did not propagate typePattern — chain
treated <X> as the encoded type instead of Wrapper<X>
- Synthetic-SF WeakMap was private to analyzer.ts — replacer could
not see it. Moved to utils.ts so typeNodeToMangledName picks it up
automatically.
Defensive guard: any field whose typeName resolves to neither a primitive
nor a known message now throws at analyze() time, instead of silently
leaking the WireType.Varint placeholder into codegen.
95/95 tests pass; tsc --noEmit clean.