Changelog - 2026-10-07
A list answers extras only when they are asked for
FeatureIn one line. A list route offers figures beside its rows. The client names the ones it wants, and the server computes exactly those.
// Server
const body = await this.respond({
context,
format: ResponseFormats.ARRAY,
payload: { count: data.length, data },
range,
extra: {
counts: { isDefault: true, compute: () => this.productService.counts({ where }) },
facets: {
keys: ['status', 'category', 'saleChannel', 'tag'],
compute: ({ keys }) => this.productService.facets({ where, dimensions: keys }),
},
},
});
// Client
const { data, range, extra } = await products.find({
filter,
options: { shouldQueryRange: true, extra: { facets: ['status', 'category'] } },
});
extra.facets.status; // keys typed from what was asked forWhat changed
Before, a list with facets or counts built its own body, like { facets, data }. Every request computed the figures, needed or not. The body was an object even when the client asked for the bare array, so the http connector refused it.
The route offers extras in respond({ extra }), in three kinds:
| Kind | Declared as | Runs |
|---|---|---|
| Plain | () => value | When the client names it |
| Default | { isDefault: true, compute } | Always, unless the client switches it off |
| Group | { keys: [...], compute: ({ keys }) => value } | When the client names it with keys; compute gets exactly those keys, so one aggregate pass serves them all. A group is never a default |
The client names them in x-request-extra:
| Entry | Meaning |
|---|---|
summary | A plain extra |
facets(status,tag) | A group, with the keys it wants |
-counts | A default switched off |
-* | Every default switched off |
The response:
| Request | Body |
|---|---|
| Nothing asked, and no default | Unchanged: { count, data }, or the bare array with x-request-count: false |
| Anything computed | { data, extra }, plus count unless x-request-count: false. The response carries x-response-extra naming what was computed |
An unknown name or key, a group with no keys, keys on a plain extra or on a -name, or a malformed entry | 400, naming what the route offers |
A group named with no keys is a 400, not "every key". A client cannot trigger every dimension by accident. Names and keys are only matched against what the route declares, never evaluated.
The connector:
find({ options: { extra: { facets: ['status'], counts: false, summary: true } } })sendsfacets(status),-counts,summary.- The result's
extrais typed by what was asked for. Every entry is optional, since a route that offers none, or an older server, answers nothing. - With a range,
findanswers{ data, range, extra }; without one,{ data, extra }. Asked for,extrais always there, empty when nothing came back. read({ extra })andwrite({ extra })take the same request object and returnextra.- The connector unwraps
{ data, extra }only when the response carriesx-response-extra. A row that happens to havedataandextracolumns is never misread. count(byContent-Rangeor bycountPath),existsWithandfindOnesend-*, so a default is not computed for a read that throws it away. They always ask for the rows alone, replacing anyx-request-extrathe call set by hand.- The header builder and the reader live in
@venizia/ignis-kernel/repository:HttpExtraRequest.toHeader({ extra, isEveryDefaultOff }), andHttpResponseReader.readExtra({ body, headers }). A client that reads the server without the connector uses the same code.readExtrareturnsextraand the body as the route answers it without extras: the{ count, data }envelope when the count was asked for in the body, the rows otherwise (from kernel 0.2.1-5; 0.2.1-4 droppedcount).
To read extras without rows, send limit: 0.
CORS. HTTP.CorsHeaders.ALLOW and HTTP.CorsHeaders.EXPOSE list every header IGNIS adds to a request and to a response, x-request-extra and x-response-extra included. Spread them into a CORS configuration that lists headers by hand; then a header IGNIS adds later needs no change there.
Who is affected
- A route that hand-builds a
{ facets, data }-style body. Move the figures intorespond({ extra }): cheap, always-shown figures as defaults; per-dimension figures as a group; the rest plain. Clients readextra.facetsinstead offacets. - A route that adds a DEFAULT extra. Its list body becomes
{ data, extra }for every client. A connector older than this release reads only a bare array or{ count, data }, so it fails on that route; upgrade the client first. - A server that lists its CORS headers by hand. Add
...HTTP.CorsHeaders.ALLOWand...HTTP.CorsHeaders.EXPOSE, or at leastx-request-extraandx-response-extra. Otherwise the browser preflight fails. - A subclass of
HttpRepositorythat overridesfind.findnow has five overloads, one per combination of range andextra, plus one for a range orextradecided at run time. An override that declares only the old two fails with TS2416. Declare the same five overloads in the subclass. - Everyone else. No change. Routes that offer no extras ignore
x-request-extra, andrespondandfindwithoutextrabehave as before.
Files: packages/kernel/src/base/controllers/rest/extras.ts, packages/kernel/src/base/controllers/rest/base.ts, packages/connectors/src/http/datasource.ts, packages/connectors/src/http/repository.ts