Skip to content

Changelog - 2026-07-11

Connectors Consistency Hardening

Breaking Change Bug Fix Enhancement

In one line. A full review of the connector layer (Postgres, Typesense, Meilisearch) closed a batch of inconsistencies between engines, tightened the public API, and fixed several correctness bugs.

What changed

  • Strict find() / findOne() everywhere. opts and filter are both required on every connector now; the find-all form is the explicit find({ filter: {} }).
  • Engine tuning moved to engineParams. Eleven Typesense-only fields were removed from the neutral search input; engine-specific tuning now travels through engineParams, keyed by the engine's own wire names.
  • Memory connector removed. MemoryDataSource / MemoryRepository had no known consumers and were deleted entirely, including the @venizia/ignis/memory export.
  • Internal barrels are no longer public. The search, Typesense, and Meilisearch internal/ modules are no longer exported from the connector barrels.
  • SwaggerComponent renamed to ApiReferenceComponent. 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, and not now work (previously threw Invalid query operator), including on JSON-path fields.
  • Filter fix. mergeFilter no longer corrupts filters when combining a caller's where with a @model default filter (see Details).
  • Cross-engine alignment. Typesense and Meilisearch now return the same 409 / 404 behavior for duplicate and missing document IDs.
  • Search fixes. multiSearch no longer leaks hidden fields; updateAll / deleteAll on the search connectors are now count-only and no longer silently cap at defaultLimit.
  • Performance. Relation resolution is memoized, WHERE is 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()). Bare find() no longer compiles - see Breaking change 1.
  • Any caller passing Typesense-only tuning fields directly on the search input (numTypos, preset, etc.). Move them into engineParams - see Breaking change 2.
  • Anyone using the memory connector. It is gone - see Breaking change 3.
  • Anyone importing an engine internal barrel. No longer resolvable - see Breaking change 4.
  • Anyone importing SwaggerComponent. Still works via a deprecated alias, but new code should use ApiReferenceComponent - 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

typescript
// worked on the search repositories only
const all = await productSearchRepository.find();

After

typescript
// 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 were removed from 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

typescript
await productSearchRepository.search({
  mode: 'keyword',
  query: 'wireless',
  queryBy: ['name'],
  numTypos: 1,       // camelCase neutral field
  preset: 'listing',
});

After

typescript
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, or 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

typescript
import { TypesenseInternal } from '@venizia/ignis/typesense';

After

typescript
// 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

typescript
import { ISwaggerOptions, SwaggerBindingKeys, SwaggerComponent } from '@venizia/ignis';
this.bind({ key: SwaggerBindingKeys.SWAGGER_OPTIONS }).toValue(options);
this.component(SwaggerComponent);

After

typescript
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

  • mergeFilter no longer corrupts filters. The default-filter merge previously combined arrays index-wise, so a user { id: { inq: [4] } } merged onto a default { id: { inq: [1, 2, 3] } } produced inq: [4, 2, 3], and two or arrays could collapse into one AND-of-both - either could silently widen a default tenant scope. The merge is now top-key level: a user-supplied key replaces the default's value wholesale, and a user value of undefined never overrides a defined default.
  • Cross-engine 409 / 404 alignment. Typesense createDocument on a duplicate id now throws 409 (core.search_engine.already_exists) instead of a misleading 503. Meilisearch updateById on a missing id now throws 404 instead of silently creating a new record.
  • Meilisearch primary-key authority. Update payloads now write the path id last and authoritative, so a patch body carrying the primary key can no longer retarget a write onto a different row.
  • multiSearch no longer leaks hidden fields. Each collection's @model hiddenProperties are now injected into that entry's excludeFields, matching what single-collection search() already did.
  • updateAll / deleteAll on search are count-only. They now return { count, data: null } and never issue an extra read - the previous behavior silently capped the returned data at defaultLimit while reporting a higher count.
  • Meilisearch updateById respects defaultFilter. A document excluded by a @model default filter now reports the same 404 as a genuinely missing one, instead of being updated through the filter.
  • Numeric consistency on Postgres. JSON-path numeric operators now cast the extracted value consistently, fixing a text = integer SQL error; neq correctly never matches a NULL row.

Performance

ScenarioImprovement
Reads without includeRelation resolution runs only when include is present, and is memoized per schema
update / deleteThe WHERE condition is built once and reused, not built twice per call
Meilisearch writesTask polling backs off exponentially (50ms doubling, 1s cap) instead of a fixed 50ms interval
Typesense wire mappingcamelCase-to-wire key mapping is O(1)
Files changed
FileChanges
src/connectors/search/repositories/common/constants.ts11 engine knobs removed from TSearchInput; engineParams added
src/connectors/search/repositories/core/readable.tsStrict find / findOne; search dispatch
src/connectors/search/repositories/core/persistable.tsupdateAll/deleteAll count-only contract; updateById default-filter guard
src/connectors/search/datasources/base.tsmultiSearch injects hidden fields into excludeFields per entry
src/connectors/search/index.tsStop re-exporting ./internal
src/connectors/postgres/repositories/dialect/filter.tsmergeFilter top-key merge; not operator; JSON numeric cast; memoized relations
src/connectors/postgres/repositories/dialect/query.tsexists / notExists operators; NULL-aware neq / ne
src/connectors/postgres/repositories/core/readable.tsStrict find / findOne
src/connectors/postgres/repositories/core/persistable.tsBuild the WHERE once and reuse it
src/connectors/memory/**Removed entirely
src/connectors/meilisearch/connector.tsMissing-id 404; primary-key authority; exponential poll backoff; batch-atomic import
src/connectors/typesense/connector.tsDuplicate-id 409 already_exists (was a misleading 503)
src/connectors/typesense/compiler.tsClear error for a defaultSort naming a nonexistent field