Skip to content

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

  1. 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.
  2. Re-read saved-query results from response.data, not response.result.rows. The wire shape is now { data, meta }.
  3. Rename client.runsclient.functionRuns. The old name still works but emits a one-shot deprecation warning per process.
  4. Replace api.searchRecords() in compute functions with api.queryRecords(slug, { text: { query, fields }, where, page }). The stub is gone in 6.0 and will throw at runtime.
  5. 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.
  6. 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.

const r = await client.savedQueries.execute(
  'orders',
  'q-uuid',
);

// r.data was { rows, rowCount, fields }
const rows = r.data.rows;
const total = r.data.rowCount;
const r = await client.savedQueries.execute(
  'orders',
  'q-uuid',
);

// r.data IS the row array
const rows = r.data;
const total = r.meta.total;
const hasMore = r.meta.hasMore;

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

const run = await client.runs.get('run-id');
const failures = await client.runs.query({
  where: { 'data.status': { eq: 'failed' } },
});
const run = await client.functionRuns.get('run-id');
const failures = await client.functionRuns.query({
  where: { 'data.status': { eq: 'failed' } },
});

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[].

{
  "resource": "orders",
  "where": { "data.status": { "eq": "paid" } },
  "join": {
    "foreignSlug": "customers",
    "localField": "data.customerId",
    "foreignField": "id",
    "select": ["firstName", "lastName"]
  }
}
{
  "resource": "orders",
  "where": { "data.status": { "eq": "paid" } },
  "joins": [
    {
      "foreignSlug": "customers",
      "localField": "data.customerId",
      "foreignField": "id",
      "joinType": "inner",
      "alias": "customer",
      "select": ["data.firstName", "data.lastName"]
    }
  ]
}

New affordances in 6.0:

  • Up to JOINS_MAX_LENGTH joins per saved query. JOINS_MAX_LENGTH is exported from the SDK.
  • OUTER joinsjoinType: 'left' | 'right' | 'full'. Right and full are gated behind the DATA_QUERY_OUTER_JOINS_ENABLED environment flag during the initial production rollout (default false).
  • Phantom-row semantics — under outer joins, missing-foreign rows surface as _joined[alias] === null; missing-primary rows (under right/full) surface with every primary column null and detection via row.id === null.
  • Identifier slots are literal-only. foreignSlug, localField, foreignField, alias, joinType reject ${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.

{
  "resource": "orders",
  "where": {
    "isActive": { "eq": true },
    "customerEmail": { "contains": "@" }
  },
  "sort": [{ "field": "totalAmount", "direction": "desc" }],
  "select": { "fields": ["orderNumber", "status", "totalAmount"] }
}
{
  "resource": "orders",
  "where": {
    "data.isActive": { "eq": true },
    "data.customerEmail": { "contains": "@" }
  },
  "sort": [{ "field": "data.totalAmount", "direction": "desc" }],
  "select": {
    "fields": ["data.orderNumber", "status", "data.totalAmount"]
  }
}

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.

const matches = await api.searchRecords('orders', {
  query: 'urgent',
  fields: ['data.title', 'data.notes'],
  page: 1,
  pageSize: 20,
});

for (const m of matches.items) {
  api.log({ id: m.id });
}
const matches = await api.queryRecords<Order>('orders', {
  text: { query: 'urgent', fields: ['data.title', 'data.notes'] },
  where: { 'data.status': { eq: 'open' } },
  page: { limit: 20 },
});

for (const m of matches.data) {
  api.log({ id: m.id });
}

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:

const r = await api.queryRecords('orders', {
  filter: { status: 'paid' },
  search: 'urgent',
  page: 1,
  pageSize: 50,
  sort: '-createdAt',
});

for (const item of r.items) { /* ... */ }
console.log(r.total, r.page, r.pageSize);
const r = await api.queryRecords<Order>('orders', {
  resource: 'orders',
  where: { 'data.status': { eq: 'paid' } },
  text: { query: 'urgent' },
  page: { limit: 50 },
  sort: [{ field: 'createdAt', direction: 'desc' }],
});

for (const item of r.data) { /* ... */ }
console.log(r.meta.total, r.meta.limit);

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.