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 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 - wrong

A full review across all three connectors closed this and a dozen other gaps - see What changed and Details.

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 left 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 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

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. 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 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 - wrong

    Two or arrays 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 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. 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. 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 = 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

See also ​