A Dependency Bump Changed the Shape of My Vectors

The commit is titled feat(deps): consolidate dependency updates. Sounds like a Tuesday. Bump some carets, watch CI go green, move on.

What it actually did was cut every embedding in the database in half and rename the table they live in.

The one line that did it

-    "@logan/libsql-search": "jsr:^0.1.3",
+    "@logan/libsql-search": "jsr:^0.7.1",

That’s a jump across 14 published versions. I checked the JSR registry rather than trusting my memory of it: between 0.1.3 and 0.7.1 there are 0.1.4, 0.1.5, 0.1.6, 0.2.0 through 0.2.4, 0.3.0, 0.4.0, 0.5.0, 0.6.0, and 0.7.0. The package is at 0.11.1 now, so I was already well behind when I started.

Everything downstream of that line is the interesting part, and none of it appears in the commit message.

The embedding runtime got swapped underneath me

The library generates embeddings locally, no API key required. In 0.1.x it did that with @xenova/transformers. In 0.7.x it doesn’t.

I didn’t take the changelog’s word for this. I checked what’s actually installed:

$ grep -c "xenova" pnpm-lock.yaml
0
$ grep -n "onnxruntime" pnpm-lock.yaml | head -3
2455:  [email protected]:
2461:  [email protected]:

Zero references to the old runtime. [email protected] in its place, and nothing under node_modules/@xenova on disk. The inference engine changed.

There’s a second place that tell shows up, and it’s one I’d never thought to read as a signal before. pnpm keeps an allowlist of packages permitted to run install scripts, because native modules need to compile:

-onlyBuiltDependencies:
-  - sharp
-  - "@xenova/transformers"
+allowBuilds:
+  esbuild: true
+  onnxruntime-node: true
+  protobufjs: true
+  sharp: true

Two things happened in that hunk. pnpm 10 to 11 renamed the field, which is why it’s a rewrite rather than an edit. And the membership changed: @xenova/transformers out, onnxruntime-node and protobufjs in. Your native build allowlist is a map of every package that compiles C++ on your machine. When entries appear and disappear there, something real moved.

768 became 384

This is the part with actual consequences.

-      embedding F32_BLOB(768),

The new local model emits 384-dimension vectors instead of 768. Those are not interchangeable. A 384-float vector cannot be compared against a 768-float vector, so every embedding computed before this commit became unusable the moment it landed.

Which is why the table name is now pinned to the dimension:

export const SEARCH_TABLE_NAME = 'articles_local_384';
export const LOCAL_EMBEDDING_DIMENSIONS = 384;

That two-line file is the best decision in the whole commit. The old code had 768 written into scripts/init-db.ts as raw DDL, and separately into the indexer, and separately into the search query. Three places, no shared constant, and nothing that would fail loudly if one drifted. Now the dimension lives in one module and the table it belongs to carries the number in its name, so a mismatched index can’t quietly overwrite a good one. It creates a new table instead.

The hand-written schema went away with it. scripts/init-db.ts lost 37 lines and gained 13: a CREATE TABLE, a libsql_vector_idx index, two more indexes, and their log lines all collapsed into one call.

await createTable(client, SEARCH_TABLE_NAME, LOCAL_EMBEDDING_DIMENSIONS);

I’m of two minds about that. Less schema I maintain is good. But the vector index definition is now something I have to go read the library source to see, and if it changes in 0.12 I won’t find out from my own repo.

Also smuggled in

Since I was already reading the diff instead of the title:

  • TypeScript 6 to 7. A major version, in a commit that says “dependency updates.” tsc --version confirms 7.0.2 is what’s installed.
  • baseUrl deleted from tsconfig.json. One line, no comment. The paths mapping for @/* still works because modern resolution doesn’t need it, but ignoreDeprecations: "6.0" is still sitting in there pointing at the previous major.
  • pnpm 10.20.0 to 11.23.0, and Node’s floor moved from 22.12.0 to 22.13.0.

The one that actually left

I removed winston in the same window, and the skeptical move is to assume it’s still there. Optional and transitive dependencies survive manifest deletions constantly, so “removed from package.json” and “gone” are different claims.

This time they matched. Zero lockfile references, nothing on disk, no imports anywhere in source. The reason is that logan-logger went 1.1.16 to 2.5.1, and version 2.5.1’s own package.json declares no dependencies at all. Winston was its transport layer, and dropping it took the whole subtree out.

So the removal was clean. I’m mentioning it because I expected it not to be, and reporting only the findings that confirm your suspicions is how you end up with a blog full of nonsense.

The caret did its job

Worth being fair about what went wrong here, because it isn’t semver.

^0.1.3 does not resolve to 0.7.1. For 0.x releases the caret pins to the minor, so ^0.1.3 stays inside 0.1.x. Getting to 0.7.1 required editing that string by hand. No install silently pulled a breaking change in, and no lockfile refresh could have.

The upgrade was deliberate. The migration note even made it into the repo’s CLAUDE.md, saying to run db:init before index when coming from the legacy 768-dimension table.

What failed is the commit message. feat(deps): consolidate dependency updates is a true statement that hides a data migration, a runtime swap, and a compiler major. Six months from now, bisecting a search quality regression, that title is going to send me straight past the commit that caused it.

Things I’m doing differently:

  1. git diff <base>..HEAD -- pnpm-lock.yaml before believing any manifest. Additions, removals, and version changes are three separate stories.
  2. Read the native build allowlist as a diff. allowBuilds in pnpm 11, onlyBuiltDependencies in 10. Changes there mean a compiled dependency moved.
  3. Put the dimension in the table name. articles_local_384 cannot be corrupted by a 768-dimension writer. articles can.
  4. Don’t bundle a compiler major with anything. TypeScript 6 to 7 deserved its own commit and its own revert point.
  5. If a dep bump invalidates stored data, the commit message says so. feat(deps)!: with a BREAKING CHANGE footer costs nothing and is the only thing future-me will actually see.

One thing I have not done and won’t pretend otherwise: I haven’t measured whether 384-dimension search results are worse than the 768-dimension ones were. Half the floats for a corpus this small is likely a fine trade, and it’s faster, but “likely fine” is a guess and I should go get a number.

Sources

I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].

Astro Typescript Dependencies Tooling Embeddings