Changelog - 2026-07-11
Connectors Consistency Hardening
Breaking Change Bug Fix EnhancementIn one line. A full review of the connector layer (Postgres, Typesense, Meilisearch) closed a batch of cross-engine inconsistencies. It also tightened the public API and fixed several correctness bugs.
The problem it solves
Postgres, Typesense, and Meilisearch had drifted independently. find() compiled bare on the search repositories but always required a filter on Postgres, and Typesense-only tuning fields sat on the neutral search input shared by every engine.
The sharpest bug: a same-key user filter could silently widen a default scope instead of narrowing it.
default: { id: { inq: [1, 2, 3] } }
user: { id: { inq: [4] } }
before: { id: { inq: [4, 2, 3] } } // index-wise merge - wrongA full review across all three connectors closed this and a dozen other gaps - see What changed and Details.
What changed
- Strict
find()/findOne()everywhere.optsandfilterare both required on every connector now; the find-all form is the explicitfind({ filter: {} }). - Engine tuning moved to
engineParams. Eleven Typesense-only fields left the neutral search input. Engine-specific tuning now travels throughengineParams, keyed by the engine's own wire names. - Memory connector removed.
MemoryDataSource/MemoryRepositoryhad no known consumers and were deleted entirely, including the@venizia/ignis/memoryexport. - Internal barrels are no longer public. The search, Typesense, and Meilisearch
internal/modules are no longer exported from the connector barrels. SwaggerComponentrenamed toApiReferenceComponent. Swagger UI is one pluggable UI provider among several, so the component now has a vendor-neutral name.- Dead exports removed. The
node:test-based structured testing module (TestPlan,TestCase,TestDescribe,BaseTestPlan) and four unused helper utilities (transformValueOrPromise,getNumberValue,toStringDecimal,parseArrayToRecordWithKey). - New operators on Postgres.
exists,notExists, andnotnow work (previously threwInvalid query operator), including on JSON-path fields. - Filter fix.
mergeFilterno longer corrupts filters when combining a caller'swherewith a@modeldefault filter (see Details). - Cross-engine alignment. Typesense and Meilisearch now return the same
409/404behavior for duplicate and missing document IDs. - Search fixes.
multiSearchno longer leaks hidden fields.updateAll/deleteAllon the search connectors are now count-only, and no longer silently cap atdefaultLimit. - Performance. Relation resolution is memoized,
WHEREis built once per call, and Meilisearch write polling backs off exponentially instead of polling at a fixed interval.
Who is affected
- Every application using search repositories (
find()/findOne()). Barefind()no longer compiles - see Breaking change 1. - Any caller passing Typesense-only tuning fields directly on the search input (
numTypos,preset, etc.). Move them intoengineParams- see Breaking change 2. - Anyone using the memory connector. It is gone - see Breaking change 3.
- Anyone importing an engine
internalbarrel. No longer resolvable - see Breaking change 4. - Anyone importing
SwaggerComponent. Still works via a deprecated alias, but new code should useApiReferenceComponent- see Breaking change 6. - Everyone else. No action needed; the bug fixes and performance work apply automatically.
Breaking changes
1. Strict find() / findOne() signatures
opts and filter are both required on every connector - Postgres and the search family. Bare find() and find({}) are now compile errors. This only changes the search repositories in practice; Postgres already required a filter.
Before
// worked on the search repositories only
const all = await productSearchRepository.find();After
// the find-all form is explicit on every connector
const all = await productSearchRepository.find({ filter: {} });2. Engine-specific search knobs moved to engineParams
Eleven Typesense-only tuning fields left the neutral search input: numTypos, prefix, infix, useCache, cacheTtl, exhaustiveSearch, pinnedHits, hiddenHits, prioritizeExactMatch, dropTokensThreshold, and preset. They now travel through engineParams, keyed by the engine's own wire name (snake_case).
Before
await productSearchRepository.search({
mode: 'keyword',
query: 'wireless',
queryBy: ['name'],
numTypos: 1, // camelCase neutral field
preset: 'listing',
});After
await productSearchRepository.search({
mode: 'keyword',
query: 'wireless',
queryBy: ['name'],
engineParams: { num_typos: 1, preset: 'listing' }, // engine wire names, passed verbatim
});3. Memory connector removed
MemoryDataSource / MemoryRepository and the @venizia/ignis/memory export are gone - code, tests, and all.
What to do: use BasePostgresDataSource / DefaultCRUDRepository against a disposable database for prototyping and tests. Use the search connectors' fakes if your use case was search-shaped.
4. Engine internal/ barrels no longer exported
SearchConnectorInternal, TypesenseInternal, and MeilisearchInternal are no longer part of the public API.
Before
import { TypesenseInternal } from '@venizia/ignis/typesense';After
// Gone from the public surface - there is no specifier that resolves to it.
// These were engine-internal helpers with no compatibility guarantee.5. SwaggerComponent renamed to ApiReferenceComponent
Before
import { ISwaggerOptions, SwaggerBindingKeys, SwaggerComponent } from '@venizia/ignis';
this.bind({ key: SwaggerBindingKeys.SWAGGER_OPTIONS }).toValue(options);
this.component(SwaggerComponent);After
import { ApiReferenceBindingKeys, ApiReferenceComponent, IApiReferenceOptions } from '@venizia/ignis';
this.bind({ key: ApiReferenceBindingKeys.API_REFERENCE_OPTIONS }).toValue(options);
this.component(ApiReferenceComponent);Not urgent - old names still work
The old names remain as deprecated aliases, so existing applications compile and run unchanged.
Details
Bug fixes
mergeFilterno longer corrupts filters. The merge combined arrays index-wise, so a same-key collision could silently widen a default tenant scope:default: { id: { inq: [1, 2, 3] } } user: { id: { inq: [4] } } before: { id: { inq: [4, 2, 3] } } // index-wise merge - wrongTwo
orarrays could collapse into one AND-of-both the same way. The merge is now top-key level: a user-supplied key replaces the default's value wholesale, and a user value ofundefinednever overrides a defined default.Cross-engine
409/404alignment. TypesensecreateDocumenton a duplicate id now throws409(core.search_engine.already_exists) instead of a misleading503. MeilisearchupdateByIdon a missing id now throws404instead of silently creating a new record.Meilisearch primary-key authority. Update payloads now write the path id last and authoritative. A patch body carrying the primary key can no longer retarget a write onto a different row.
multiSearchno longer leaks hidden fields. Each collection's@modelhiddenPropertiesare now injected into that entry'sexcludeFields, matching what single-collectionsearch()already did.updateAll/deleteAllon search are count-only. They now return{ count, data: null }and never issue an extra read. The previous behavior silently capped the returned data atdefaultLimit, while reporting a highercount.Meilisearch
updateByIdrespectsdefaultFilter. A document excluded by a@modeldefault filter now reports the same404as a genuinely missing one. Previously, the filter still let an update through.Numeric consistency on Postgres. JSON-path numeric operators now cast the extracted value consistently, fixing a
text = integerSQL error.neqcorrectly never matches aNULLrow.
Performance
| Scenario | Improvement |
|---|---|
Reads without include | Relation resolution runs only when include is present, and is memoized per schema |
update / delete | The WHERE condition is built once and reused, not built twice per call |
| Meilisearch writes | Task polling backs off exponentially (50ms doubling, 1s cap) instead of a fixed 50ms interval |
| Typesense wire mapping | camelCase-to-wire key mapping is O(1) |
Files changed
| File | Changes |
|---|---|
src/connectors/search/repositories/common/constants.ts | 11 engine knobs removed from TSearchInput; engineParams added |
src/connectors/search/repositories/core/readable.ts | Strict find / findOne; search dispatch |
src/connectors/search/repositories/core/persistable.ts | updateAll/deleteAll count-only contract; updateById default-filter guard |
src/connectors/search/datasources/base.ts | multiSearch injects hidden fields into excludeFields per entry |
src/connectors/search/index.ts | Stop re-exporting ./internal |
src/connectors/postgres/repositories/dialect/filter.ts | mergeFilter top-key merge; not operator; JSON numeric cast; memoized relations |
src/connectors/postgres/repositories/dialect/query.ts | exists / notExists operators; NULL-aware neq / ne |
src/connectors/postgres/repositories/core/readable.ts | Strict find / findOne |
src/connectors/postgres/repositories/core/persistable.ts | Build the WHERE once and reuse it |
src/connectors/memory/** | Removed entirely |
src/connectors/meilisearch/connector.ts | Missing-id 404; primary-key authority; exponential poll backoff; batch-atomic import |
src/connectors/typesense/connector.ts | Duplicate-id 409 already_exists (was a misleading 503) |
src/connectors/typesense/compiler.ts | Clear error for a defaultSort naming a nonexistent field |
See also
- Unified Repository & Connectors Architecture - introduced the connectors this hardening pass reviews
- Typesense Advanced Search - the neutral search input tightened here
- Postgres Driver Seam & Supabase - the same-day Postgres driver work