Skip to content

v0.27.0

Choose a tag to compare

@angel-ip angel-ip released this 02 Oct 22:07
ecef981

@kanzo-tech/ui · @kanzo-tech/graph · @kanzo-tech/mosaic

The Mosaic stack is ^0.32.0, and npm install @kanzo-tech/ui works. mosaic-core 0.29 published
a workspace:^ peer range that npm refuses to install; every @uwdata/mosaic-* and vgplot range
here — peers and the optional analytics peers — is now ^0.32.0. DuckDB-WASM stays at
1.33.1-dev57.0, the release mosaic-core 0.32 pins. Raise your own @uwdata/* installs to 0.32:

npm i @uwdata/mosaic-core@^0.32.0 @uwdata/mosaic-sql@^0.32.0 @uwdata/vgplot@^0.32.0 @uwdata/mosaic-plot@^0.32.0

What changes for your code with 0.32:

  • A failed query still reaches onFailure as it was thrown. mosaic-core now hands a client's
    queryError a QueryError wrapping the original, with the SQL appended to its message. The charts,
    the inputs, ChartStat and the graph unwrap it, so onFailure receives the original error with its
    code and message intact. A MosaicClient of your own does the same with queryFailure(error), new
    on @kanzo-tech/mosaic and @kanzo-tech/ui/analytics.
  • coordinator.query(sql, { type: "json" }) answers an Arrow table — the type option is gone.
    Read rows with table.toArray().
  • A connector of your own answers Arrow as IPC bytes, which mosaic-core decodes; one that returned
    a decoded table fails with "IPC data batch was not a Uint8Array".
  • clausePoints(fields, []) is a clause that drops every row, not a retraction. Pass undefined to
    retract one. ChartFilter does so when the last value is unticked.

@kanzo-tech/graph

GraphSelect, and a selection source that names no host. SelectionSource is the canvas's own
gestures — "marquee", "lasso", "node" — and "external" for a selection you set through
select. "order" and "ask" are gone, and select(vertices) without a source is now
"external" (it was "node").

If you passed "order", "ask" or any source of your own to select, pass "external" and keep
the label: the label is what tells your selections apart.

GraphSelect is the toggle a panel offers — these vertices, on the canvas:

<GraphSelect label="Contracts past due" load={() => idsOf(rule)}>
  …the body you click…
</GraphSelect>

load runs when it is pressed; pressed is the live selection being "external" with this label,
so a lasso elsewhere releases it; a rejected load reaches the root's onFailure as thrown. If you
built this toggle yourself over useGraphContext().select and useGraphState, delete it.

An unbound graph — x and y left to the layout — draws again. Under cosmos.gl 3.4 every such graph
opened blank, whatever its size: the simulation was turned on before the render that uploads
the positions, cosmos.gl dropped that upload, and each layout tick threw Cannot destructure property 'device' of 'texture' from the camera's fitView. The layout now starts after that render, at any size.

The camera reads positions back only once the device is up and they have been rendered, and if a read
fails anyway the graph reports it instead of throwing every frame: onFailure receives a GraphError
with the new code graph/no-positions (the read's error as its cause) and the status is failed. A
host that switches over GraphError["code"] has one more case to show.

@kanzo-tech/ui

Problem draws any thrown value as a Diagnostic. Pass what your catch holds; it reads the
shape every coded error here shares — code (area/kind), title, message, data, cause, and
severity, help and related where present — and nests the causes below it. Your words for a
code come from one function:

<DiagnosticList>
  <Problem copy={(code, data) => registry[code]} error={error}>
    <Button onClick={retry} size="sm" variant="outline">Try again</Button>
  </Problem>
</DiagnosticList>

copy returns { title?, detail?, link?: { label, href }, page? } or undefined; a rewritten
detail keeps the error's own row one level down. Each row's details trigger is named
Details: <title>; translations changes that and the severity words.

If you mapped errors onto Diagnostic parts yourself, you can delete that code.

useQueryRows(query) on @kanzo-tech/ui/analytics: a statement's rows, suspending until they
land.
It reads the provider's coordinator, answers rows as objects keyed by column, and shares one
answer per statement across every component that asks it. A failure is thrown, as it was thrown, to
the nearest error boundary, and the boundary's retry asks again.

const columns = useQueryRows<{ column_name: string }>(`DESCRIBE ${table}`);

It does not follow the crossfilter — it is for a schema, a catalog, a lookup; anything that should
move with the brush stays useChartQuery. If you wrapped coordinator.query in a suspense cache of
your own (TanStack's useSuspenseQuery, say), this replaces it.

@kanzo-tech/mosaic

engine().query answers in a background tab. It went through the coordinator's queue, which
batches Arrow requests behind requestAnimationFrame — and a hidden tab never paints one, so a
corpus opened while the tab was in the background (fossil's open: the secret, the attach, the
views) waited forever with no error, and a host's statement could sit behind a batch of chart
queries. It now goes straight to the connection and is decoded as the coordinator decodes. Charts
still batch as before. A refused statement still rejects with DuckDB's own error. Nothing to change
in your code.

@kanzo-tech/auth

ticketStore bounds every adapter call itself, and accountUrl links Keycloak's account console.

  • Every read, write and delete a ticketStore makes on its adapter now ends at 30 s, rejecting
    as session/silent (data.after: 30000), which relyingParty reports as session/unavailable
    with that as the cause. If your adapter raced its own driver calls against a timeout, delete that:
    an adapter is the three driver calls. Keep the driver's connect timeout and no-offline-queue
    settings, which make a store that is down refuse at once.
  • accountUrl(issuer, page?) returns {issuer}/account, or one of its pages —
    AccountPage: "", "account-security/signing-in", "account-security/device-activity". If you
    built that URL yourself, use this.