montycat 1.2.3
montycat: ^1.2.3 copied to clipboard
Self-hosted vector database + NoSQL with built-in AI semantic search — the Dart & Flutter client for Montycat. A Rust-powered Pinecone alternative for RAG & AI agents.
1.2.3 - 2026-09-02 #
- Added exported
SearchMode,searchKeys, andsearchValues, with optional metadata filters. ExistingsemanticSearch*methods are deprecated semantic-only wrappers with unchanged signatures and behavior. Unsupported older engines reject keyword and hybrid commands. Current engines normalize hybrid RRF scores to[0, 1].
Documentation #
- Clarify score semantics: hybrid RRF is bounded to
[0, 1], while raw BM25 keyword scores are unbounded and should only be compared within the same query and search mode.
1.2.2 - 2026-08-15 #
- Add
ResultOrderand optional ordering to key-range, bulk, and lookup reads.
1.2.1 - 2026-08-12 #
Changed #
- Applied Dart analyzer and formatter cleanups across the public exports, engine, keyspace, pooling, transport, and test code. This release contains no API or runtime behavior changes.
1.2.0 - 2026-08-02 #
Opt-in connection pooling. Additive — upgrading needs no code changes, and behavior is unchanged until you enable it.
Added #
-
Opt-in connection pooling. Every request previously opened a socket, sent one request, read one response, and closed. Reuse removes the handshake from every call after the first:
import 'package:montycat/montycat.dart'; final engine = Engine( host: '127.0.0.1', port: 21210, username: 'USER', password: '12345', store: 'Company', pool: const PoolConfig(), // the only new argument; omit for today's behavior ); customers.connectEngine(engine); await customers.insertValue(value: customer.serialize()); // unchanged await closeAllPools(); // before exitPools live in a library-level registry keyed by
(host, port, useTls), so every keyspace pointing at one server shares a single pool. That matters more here than in the other clients: keyspace state is per-instance, so a Flutter app building a keyspace per screen or per rebuild would otherwise create a pool per instance, and instance lifetime is not something this client controls. The key is read at request time rather than cached atconnectEnginetime, becauseuseTlsis mutable after construction — caching it would let a TLS engine reuse a plaintext connection.Disabled by default: an idle pooled connection still holds one of the engine's connection permits, so the bound is conservative (
maxIdle: 8, 30sidleTimeout). Subscriptions are never pooled.On mobile, prefer a short
idleTimeout. iOS and Android kill sockets when an app is backgrounded, so every pooled connection is dead on resume and the cost of discovering that is a user-visible round trip. Apps that already observe connectivity should callcloseAllPools()when the network changes — Wi-Fi to cellular invalidates every pooled connection — rather than waiting to find out one failed request at a time.Exported
PoolConfigandcloseAllPoolsfrompackage:montycat/montycat.dart. -
Precomputed vectors. Vectors produced elsewhere — another model, a batch pipeline, an existing embedding store — can now be supplied directly, and the server skips embedding entirely. Requires a Montycat Semantic server 1.3.0 or newer.
Writes take an optional
vector, applied after the write succeeds:await keyspace.insertValue( value: {'text': 'a document'}, vector: myEmbedding, // List<double>, omit for server-side embedding );Available on
insertValue,insertCustomKeyValue, andupdateValuefor bothKeyspaceInMemoryandKeyspacePersistent.insertBulktakesList<List<double>> vectors, paired withbulkValuesby position;updateBulktakesMap<String, List<double>> vectorsfor numeric keys andcustomVectorsfor custom keys.Search takes an optional query
vector, which bypasses text embedding. The query string may be empty when one is supplied:await keyspace.semanticSearchGetValues('', vector: myQueryEmbedding);Available on
semanticSearchGetKeys,semanticSearchGetKeysWhere,semanticSearchGetValues, andsemanticSearchGetValuesWhere.Dimensions must match the keyspace's enrolled model; the server validates before anything reaches the index. A supplied vector is not overwritten by background embedding — a later ordinary write to the same item clears that protection and re-embeds from text.
Fixed #
-
Every non-ASCII character was corrupted on the way out. The wire encoder ended in
jsonEncode(queryDict).codeUnits, which yields UTF-16 code units thatUint8List.fromListtruncates to their low byte.Приветleft as@825B,caféas invalid UTF-8,🐱as=1— unrecoverable server-side, not reversible mojibake.Worse, a character whose low byte is
0x0Aemitted the newline that frames a request, splitting it mid-JSON.U+030Ais the combining ring above, so decomposedå— the normal form for text originating on macOS — desynchronised the protocol.It affected every data operation: inserts, updates, gets, deletes, bulk operations, WHERE lookups, and semantic search. Schema, keyspace, and governance calls were always correct; they encode elsewhere.
Now
utf8.encode, matching the rest of this client and the Python, Node, and Rust clients. Byte-identical for ASCII, so the wire is unchanged for data that already worked. Data written by an earlier version in a non-Latin script is corrupt at rest and cannot be recovered by upgrading.
Changed #
-
The response framing chain is now built once per connection rather than per request. The
Socket→utf8.decoder→LineSplitterchain was previously constructed insidesendDataand discarded when the call returned.LineSplitterholds a partial trailing line in its own internal buffer, so tearing the chain down between requests would drop it. That was harmless while every connection was closed immediately; on a pooled connection it corrupts the next response. Pooled connections keep the whole chain, and a single persistent listener buffers frames.The unpooled path is unchanged.
1.1.3 - 2026-07-31 #
Adds a way to read the server's real semantic configuration, and a safe way to change an enrolled keyspace's embedding model. Additive — upgrading from 1.1.2 requires no code changes.
Added #
Engine.getSemanticStatus({store, keyspace})returns the server's actual semantic settings rather than what the caller assumed: the DB-wide switch and default model, plus each enrolled keyspace's model, dimensions, field, storage type, and whether a backfill is still pending.Engine.reembedSemanticSearch({model, field, store, keyspace})atomically drops one keyspace's vectors, records the new configuration, and starts a complete backfill. It reports the previous model alongside the new one, so a caller can confirm what it replaced.SemanticStatus,SemanticKeyspaceStatus, andSemanticReembedResultare exported frompackage:montycat/montycat.dart. The two new methods return these types instead of an untyped map, and throwStateErrorwhen the server reports failure.SemanticStatus.keyspace(store, keyspace)looks up one entry without building the'store/keyspace'key by hand.
Changed #
- Documented that
enableSemanticSearchleaves an already-enrolled keyspace alone, and rejects an explicitly different model or field. It was never a way to switch models;reembedSemanticSearchis. Behavior is unchanged — only the documentation was misleading. - Corrected the
disableSemanticSearchdocs:dropVectorsis not "required before switching to a different embedding model". UsereembedSemanticSearch, which does not leave the keyspace unsearchable in between.
1.1.2 - 2026-07-29 #
Fixes misrouted requests whose payload contains the word subscribe.
Upgrade from 1.1.1 is recommended.
Fixed #
-
A request whose value contained the substring
subscribereturned aSubscriptionHandleinstead of the response envelope, and leaked its socket. Subscription mode was detected by searching the serialized request forsubscribe, so a call likeinsertValue(value: {'note': 'please subscribe'})took the streaming branch: it resolved to a handle rather than{status, payload, error}, so readingres['status']threw, and the socket was never closed. Any record mentioning the word was affected,unsubscribeincluded.A request is now a subscription because the caller supplied a
callback. That was always the real distinction:subscribeis the only method that passes one. Intent is no longer inferred from user data.Present in every release before this one. The Rust and Python clients carry the same defect — where it hangs instead of mistyping — and are fixed in their matching releases; the Node client was already correct.
-
Note for anyone calling
subscribe()without acallback: that now performs a single request/response and returns the parsed envelope, rather than returning a handle that never fired. Such a call did nothing useful before.
1.1.1 - 2026-07-29 #
Documentation, tests, and CI only — no library code changed, so upgrading from 1.1.0 is optional.
Added #
- README sections for behavior that was previously undocumented: response shape
(
{status, payload, error}and u128 keys arriving as strings), real-time subscriptions withSubscriptionHandleand theport + 1subscription port, TLS viauseTls, and owner/access management withcreateOwner,grantTo,revokeFrom, andPermission. - Socket-level transport tests covering request framing, response envelope
parsing, u128 key preservation, subscription delivery and
stop(), and connection failures being returned rather than thrown. ci.ymlworkflow running analyze,dart pub publish --dry-run, and the test suite on Linux, macOS, and Windows against the stable and beta SDKs.- Changelog link in the README.
Changed #
- The publish workflow now runs
dart analyze --fatal-infosanddart testbefore publishing to pub.dev.
Fixed #
- The README installation snippet pinned
^1.0.10, a version that predates the governance APIs documented further down the same file.
Added #
- Data-mesh governance policy APIs on
Engine:- inspection:
policyView,policyHistory,policyExplain,policyExport - mutation:
policyGrant,policyRevoke,policyDeny,policyRemoveDenial - dry runs:
policyPreviewGrant,policyPreviewRevoke - manifests:
policyValidate,policyPlan,policyApply
- inspection:
PolicyCapability,PolicyKeyspaceType,SemanticModel, andPolicyFormat, exported from the package root.- Keyspace-scoped semantic enrollment and removal through
keyspaceonenableSemanticSearchanddisableSemanticSearch.
Changed #
- Governance qualifiers are validated client-side before sending a command:
- semantic models apply to
provisionKeyspaceandmanageSemantic - storage types apply to
provisionKeyspace,removeKeyspace,manageSchema,manageAccess, andmanageSemantic;manageSnapshotsis always in-memory
- semantic models apply to
provisionKeyspaceis treated as a store-level capability, so its policy commands omitkeyspace.
1.0.1 #
- Initial release.
1.0.2 #
- Fixed Dart Pub compliance issues.
1.0.3 #
- Published to pub.dev.
1.0.4 #
- Added support for nullable fields using built-in schema mechanics.
1.0.5 #
- Cache value fix
- Populate errors directly if connection is unsuccesfull
1.0.6 #
- Fixed empty string insertion
- Added documentation
- Added changelog
- Added GitHub link
- Fixed bulk custom keys conversion
- Added retrieval by volume in getBulk() function for both types of keyspaces
1.0.7 #
- Fixed connection timeout
1.0.8 #
- Fixed bulk write
- Fixed bulk read
1.0.9 #
- Stateless query refactor: removed shared
commandandlimitOutputmutable fields fromKVbase class; both are now passed as explicit local parameters toconvertToBinaryQuery, eliminating state-related bugs in concurrent usage. subscribeAPI unified: moved fromKeyspacePersistent-only into theKVbase class, making it available on both keyspace types. AddedsubscriptionPortparameter to override the default port. Added validation to reject providing bothkeyandcustomKeysimultaneously.listAllDependingKeys: added validation to reject providing bothkeyandcustomKeysimultaneously.get_value: removed mutual-exclusivity restriction betweenwithPointersandpointersMetadata— both can now be used together.createKeyspace/updateCacheAndCompression:cacheandcompressionmoved from class-level fields to method parameters;updateCacheAndCompressionno longer throws when called on a non-persistent keyspace.
1.0.10 #
- Added AI semantic (vector) search support for the Montycat Semantic server edition:
enableSemanticSearch({model, field, store})/disableSemanticSearch({dropVectors, store})— toggle semantic search DB-wide (or scoped to a single store) and choose the embedding model (minilm,bge-small(default),bge-base,e5-small).semanticSearchGetValues(query, {limit, minScore})— retrieve records ranked by meaning, each returned with its key, similarity score, and value.semanticSearchGetKeys(query, {limit, minScore})— lighter key-and-score results; useminScoreto drop weak matches.
1.0.11 #
- Semantic search response fields renamed to the dunder envelope used everywhere else in the API. Each hit from
semanticSearchGetValuesis now{__key__, __score__, __value__}(was{key, score, value});semanticSearchGetKeysreturns{__key__, __score__}. This matches the__key__/__value__wrapperlookupValuesWhere(keyIncluded: true)already returns. Wire-breaking for code that read the oldkey/score/valuefield names.
1.0.12 #
- Added hybrid semantic search with
semanticSearchGetKeysWhereandsemanticSearchGetValuesWhere. - Hybrid search applies metadata criteria as a hard AND pre-filter using the
same field, timestamp, and pointer criteria as
lookupKeysWhere; results remain ranked by cosine similarity. - Added optional
minScorefiltering to hybrid search.