Upgrading from 5.5.1 to 6.0.0¶
6.0 is the major-version cut for the canonical query foundation. This guide covers every breaking change visible to SDK consumers (TypeScript / Node) and compute function authors, with side-by-side before/after. Every legacy surface still exists during the deprecation window — nothing in 5.x stops working overnight.
Released 2026-05-07
Deprecation sunset for legacy saved-query slug routes: 2026-10-29 (RFC 8594).
TL;DR — must-do before 2026-10-29¶
- Bump the SDK:
npm i @centrali-io/centrali-sdk@^6.0.0. The package now exposes the canonical query runtime as a first-class import, so client-side validation runs the same code as the server. - Re-read saved-query results from
response.data, notresponse.result.rows. The wire shape is now{ data, meta }. - Rename
client.runs→client.functionRuns. The old name still works but emits a one-shot deprecation warning per process. - Replace
api.searchRecords()in compute functions withapi.queryRecords(slug, { text: { query, fields }, where, page }). The stub is gone in 6.0 and will throw at runtime. - Prefix bare JSONB field names in any canonical body you send:
where: { isActive: ... }→where: { 'data.isActive': ... }. The 6.0 strict-schema reachability check rejects bare refs that aren't records-table system columns. - If you ever wrote
{{var}}placeholders, switch to${var}. Both still work at runtime; only${var}will be supported after the deprecation window.
No flag-day
Everything below is breaking only in the sense that the old shape is sunsetting. Run 5.5.1 SDK code against 6.0 servers and it keeps working — you'll just see deprecation warnings in stderr until you migrate.
SDK consumers¶
1. Saved-query execute() envelope¶
Pre-6.0 the SDK wrapped saved-query results in a legacy mirror: response.data.rows was the array, response.data.rowCount the total. In 6.0 the SDK calls the canonical POST /saved-queries/:id/execute endpoint, which returns the contract §9 envelope { data, meta } — and the SDK passes that through unchanged. response.data is now the row array.
Joined rows still surface under _joined.<alias> on each row, exactly as before — only the outer envelope changed.
Path change
The SDK now hits /saved-queries/:id/execute (canonical) instead of /saved-queries/slug/:rs/execute/:id (legacy). The first argument (structureSlug) is unused by the wire path; the data service resolves the resource from the saved-query row. We kept the parameter for source compatibility — passing it costs nothing.
2. Function runs: client.runs → client.functionRuns¶
In 5.5.1 we added client.orchestrationRuns. To keep client.runs from being ambiguous, it has been renamed to client.functionRuns in 6.0 (CEN-1227). The old name still works for the deprecation window — first access logs client.runs is deprecated. Use client.functionRuns instead. once per process.
3. client.smartQueries alias¶
client.smartQueries is a deprecated alias for client.savedQueries. Same object, same methods. New code should use savedQueries; existing code keeps working but emits a one-shot deprecation warning. The HTTP routes have also dual-mounted /smart-queries/* as a deprecated alias of /saved-queries/* for the deprecation window.
4. Multi-join joins[] schema¶
The single-join queryDefinition.join shape that shipped in 5.x is replaced by an array of JoinClause entries (CEN-1058). The legacy single-join body is auto-promoted to a one-element joins[] at execute time, so saved queries created before 6.0 keep working. New saved queries authored against the canonical write surface MUST use joins[].
New affordances in 6.0:
- Up to
JOINS_MAX_LENGTHjoins per saved query.JOINS_MAX_LENGTHis exported from the SDK. - OUTER joins —
joinType: 'left' | 'right' | 'full'. Right and full are gated behind theDATA_QUERY_OUTER_JOINS_ENABLEDenvironment flag during the initial production rollout (defaultfalse). - Phantom-row semantics — under outer joins, missing-foreign rows surface as
_joined[alias] === null; missing-primary rows (under right/full) surface with every primary columnnulland detection viarow.id === null. - Identifier slots are literal-only.
foreignSlug,localField,foreignField,alias,joinTypereject${var}placeholders by validator.
5. Typed parameters (${var})¶
5.x bodies often referenced placeholders as {{customerId}}; 6.0's canonical syntax is ${customerId}. {{var}} still parses but logs a deprecation telemetry event server-side and is in the sunset window. Pair placeholders with a variables declaration on the saved-query row to enable typed validation (CEN-1114):
// Save the query with typed declarations
await client.savedQueries.create('orders', {
name: 'Orders by customer',
query: {
resource: 'orders',
where: { 'data.customerId': { eq: '${customerId}' } },
},
variables: {
customerId: { type: 'uuid', required: true },
},
});
// Typed execute — compile-time arg shape enforcement
type Vars = { customerId: string };
const rows = await client.savedQueries.executeTyped<Vars>(
'orders',
'query-id',
{ customerId: '5af4...' },
);
At execute time the server validates each value by JS typeof: "123" is rejected for a number declaration; datetime accepts ISO-8601 strings. There is no coercion.
6. Bare field refs require data. prefix¶
5.x's legacy executor implicitly resolved bare field names like where: { isActive: ... } against data.isActive on JSONB-side properties. 6.0's canonical executor (CEN-1242) enforces strict reachability: bare names must be records-table top-level columns (id, recordSlug, status, workspaceSlug, version, createdAt, updatedAt, createdBy, updatedBy); JSONB-side fields require the data. prefix.
The bare status in the 6.0 example stays bare — it's a top-level column, not a JSONB-side property.
Shadowed names
If your structure declares a property whose name shadows a top-level column (e.g. a status property on a collection that also has the records-table status system column), bare status resolves to the system column. Use data.status to reference the JSONB-side property explicitly.
7. SDK package: modular split + bundled query runtime¶
5.x shipped a single-file SDK; 6.0 ships a modular src/ layout (CEN-1201) with the same public exports. If you imported from a deep path inside node_modules/@centrali-io/centrali-sdk/dist/..., switch to the package root export. Public type names and runtime classes are unchanged.
6.0 also bundles the canonical @centrali/query runtime inside the SDK (CEN-1202) — you get QueryManager, validateQueryDefinition, JOINS_MAX_LENGTH, CANONICAL_OPERATORS, etc. as named exports. Client-side validation now runs the exact same code the server runs.
import {
CentraliSDK,
QueryManager,
validateQueryDefinition,
JOINS_MAX_LENGTH,
} from '@centrali-io/centrali-sdk';
const v = validateQueryDefinition(myBody);
if (!v.ok) console.error(v.errors);
Compute function authors¶
Compute functions consume the same canonical query foundation through the in-sandbox api object. Three changes affect 5.5.1 → 6.0.0 functions.
1. api.searchRecords() removed¶
The api.searchRecords() stub that 5.x kept around as a separate "search vs filter" surface is gone in 6.0 (CEN-1121). The canonical surface is now a text clause inside api.queryRecords — same Meilisearch-backed routing, single envelope.
This is the only hard-breaking change in 6.0
Calls to api.searchRecords() will throw at runtime with a method not found sandbox error. Search every compute function in your workspace before upgrading the SDK or relying on a 6.0 worker pool.
2. api.queryRecords legacy options deprecated¶
api.queryRecords(slug, QueryOptions) — the 5.x shape with { filter, search, page: number, pageSize, sort, dateWindow } — still works in 6.0 but logs a one-shot deprecation warning per worker cold start. The canonical body is preferred:
The legacy items / top-level total / page / pageSize mirror fields are still attached to the result for source compatibility, but accessing them logs a per-read deprecation warning. Read from r.data / r.meta only.
3. api.executeSavedQuery envelope¶
api.executeSavedQuery already returned the canonical { data, meta } shape in 5.x — no source changes needed. We mention it for completeness because the equivalent SDK method changed (see SDK §1).
const result = await api.executeSavedQuery<Order>(
triggerParams.SAVED_QUERY_ID,
{ variables: { customerId: customer.id } },
);
for (const order of result.data) {
api.log({ orderId: order.id });
}
Raw HTTP consumers¶
If you're hitting the data service directly without the SDK, two surface changes matter:
| Endpoint | Change |
|---|---|
POST /workspace/:ws/api/v1/saved-queries/:id/execute | Response is now { data, meta } (was { result: { rows, meta, fields } }). The legacy slug-prefixed route POST /saved-queries/slug/:rs/execute/:id still returns the old shape during the deprecation window. |
POST /workspace/:ws/api/v1/saved-queries (canonical create) | Body uses query (canonical QueryDefinition) and optional typed variables. Still accepts queryDefinition (legacy) on the slug-prefixed route. queryDefinition.joins[] replaces queryDefinition.join. |
POST /workspace/:ws/api/v1/records/query | Already canonical in 5.x. No shape change in 6.0. |
Smart-queries alias /api/v1/smart-queries/* | Dual-mounted as a deprecated alias of /saved-queries/*. Returns RFC 8594 Deprecation + Sunset headers. Migrate before 2026-10-29. |
Every other canonical query surface — records query, function-runs query, orchestration-runs query, files query, audit-log query — already returned { data, meta } in 5.x and is unchanged in 6.0.
Deprecation timeline¶
| Surface | Status in 6.0.0 | Sunset |
|---|---|---|
api.searchRecords() | Removed | 2026-05-07 (now) |
Saved-query { result: { rows } } envelope (canonical path) | Replaced by { data, meta } | 2026-05-07 (now) |
Legacy slug routes /saved-queries/slug/:rs/* | Deprecated, RFC 8594 headers | 2026-10-29 |
Smart-queries alias /smart-queries/* | Deprecated alias | 2026-10-29 |
Legacy {{var}} placeholder syntax | Accepted, telemetry-tracked | 2026-10-29 |
api.queryRecords(slug, QueryOptions) legacy overload | Accepted, deprecation warnings | v1-runtime removal window |
client.runs | Deprecated alias of functionRuns | Next major |
client.smartQueries | Deprecated alias of savedQueries | Next major |
Troubleshooting¶
Field X must be a top-level column or prefixed with 'data.'¶
Your saved-query body has a bare ref that's not a records-table system column. Add the data. prefix in where / sort / select. See SDK §6.
client.runs is deprecated. Use client.functionRuns instead.¶
One-line rename. See SDK §2.
DEPRECATION: api.queryRecords(slug, QueryOptions) is deprecated¶
Switch to the canonical QueryDefinition body. See Compute §2. Your function still works while you migrate.
DEPRECATION: 'result.items' is deprecated — use 'result.data' instead.¶
You're reading from the legacy mirror on a canonical query result. Read result.data / result.meta.total / result.meta.limit. See Compute §2.
method not found: api.searchRecords¶
Hard-breaking change. Replace with api.queryRecords(slug, { text, where, page }). See Compute §1.
Saved query 422s with Required: variables: X¶
Your saved query declares typed parameters and you didn't pass them. Either invoke via executeTyped<Vars>(...) with the values, or pass { variables: { X: ... } } to execute(). See SDK §5.
Saved query 422s with Legacy join with joinWhere cannot be promoted to canonical joins[]¶
The 5.x join + joinWhere single-join shape with a non-empty joinWhere can't auto-promote to joins[] safely (the executor would silently drop the predicate). Re-author the saved query with the explicit joins[] shape and a top-level where. See SDK §4.
Questions? File an issue at github.com/blueinit/centrali with the tag migration:6.0.