Changelog - 2026-09-19
IGNIS stereotypes and repositories, bulk writes in the body, safer headers
Breaking Change New Feature Bug FixIn one line. ARDOR now builds on @venizia/ignis-kernel: decorated classes register themselves, HTTP repositories come from IGNIS, updateMany/deleteMany send one request with the selector in the body, and an app served over plain http no longer fails every request.
This entry covers everything since 0.1.1-2.
What changed
New
- Classes register by stereotype.
@service(),@component(),@configuration(),@provide()and the rest are exported from@venizia/ardor. A decorated class is bound as a singleton when the application starts, so it no longer needs to be listed inbindingList(). - Resolve by class, not by string.
useService,useProvider,useComponentanduseConfigurationtake{ target: SomeClass }and return that class's type. Each one checks the namespace, souseService({ target: SomeComponent })throws and names both keys. - HTTP repositories.
@venizia/ardor/repositoryre-exportsHttpDataSourceandHttpRepositoryfrom@venizia/ignis-connectors/http. They speak the same filter vocabulary as the data provider and read totals fromContent-Range. When a total is asked for and the server reports none, they throw instead of guessing one. - One stored token for both transports.
readAuthTokenFromStorageis exported (a static ofDefaultNetworkRequestServicesince the hooks entry), andDefaultNetworkRequestServicetakes anauthTokenResolveroption. Pass the same resolver to anHttpDataSourceand both transports send the same token.
Fixed
- Plain-http origins work.
x-request-idcame fromcrypto.randomUUID, which browsers only expose on https and localhost, so an app opened athttp://<lan-ip>failed on every call. It now usesuuidV4from@venizia/ignis-helpers/uuid. updateManyworks against IGNIS. It sentfilter.wherein the query, which a generated IGNIS bulk route does not read, so every call answered400.- Header overrides replace instead of joining. Header names are matched case-insensitively.
X-Request-Countbesidex-request-countused to reach the server as"1, 0".removeHeadersalso removes a header whatever case it was set in. - No
x-auth-provider: undefined. A token that names no provider now sends nox-auth-providerheader at all. localStorageis read only when needed. It used to be read on every request, even when a token had been set withsetAuthToken, so the service crashed anywhere without a DOM.- Form values keep what the caller sent.
0,falseand''are no longer dropped. ADateis sent as ISO-8601 and an object as JSON, not"[object Object]". AFilein a URL-encoded body throws and names the field, instead of arriving as{}. List filters with a falsy value are kept too.
Who is affected
- Every application. Install the new peers:
@venizia/ignis-kerneland@venizia/ignis-helpers, plus the raised floors on@venizia/ignis-inversionand@venizia/ignis-filter. - Applications whose decorated classes were also bound by hand. The stereotype binding is applied first and the explicit one after it, so under the same key the explicit binding wins. Under a different key both exist.
- Applications that call
updateManyordeleteMany. Their backend must acceptwherein the request body. See the breaking changes below. - Code that compared header names to the literal
'Timezone'.HeaderConsts.TIMEZONEis now'timezone'. What goes on the wire is unchanged. - Applications that never import
@venizia/ardor/repository. They do not need@venizia/ignis-connectors; it is an optional peer.
Breaking changes
WARNING
updateMany and deleteMany now need a backend whose bulk routes read where from the body. A route that reads only the query answers 400 and writes nothing. That includes a hand-written override of the bulk handler.
Before:
http
PATCH /products?filter={"where":{"id":{"inq":[...]}}} body: { "status": "ARCHIVED" }
DELETE /products/p-1
DELETE /products/p-2
...one request per idAfter:
http
PATCH /products body: { "status": "ARCHIVED", "where": { "id": { "inq": [...] } } }
DELETE /products body: { "where": { "id": { "inq": [...] } } }- An id list in the URL is capped by the request line.
- The old
deleteManyfired oneDELETEper id. If one failed, the rest had already been deleted and the screen reported a single error. - Both calls now return
{ data: ids }, taken from the rows the server reports as written. They used to return the raw rows. An id that no longer exists is not reported. updateManyrefusesdatathat has its ownwherefield, because the route reads that key as the row selector. Update those records one by one withupdate().
Peers. Every ardor-kernel consumer now installs these:
bash
bun add @venizia/ignis-kernel @venizia/ignis-helpersDetails
ts
import { DefaultNetworkRequestService } from '@venizia/ardor';
import { HttpDataSource, HttpRepository } from '@venizia/ardor/repository';
const dataSource = new HttpDataSource({
baseUrl: 'https://api.example.com',
authTokenResolver: DefaultNetworkRequestService.readAuthTokenFromStorage,
});
const tickets = new HttpRepository<{ id: string; title: string; status: string }>({
dataSource,
resource: 'tickets',
});
// `count` reads the total from Content-Range, and throws if the server reported none.
export const countOpen = () => tickets.count({ where: { status: 'OPEN' } });