Skip to content

Feature: Tiled Graph Api Client - #71

Merged
SeijDeLeon merged 9 commits into
stagingfrom
feature/tiled-graph-client
Oct 13, 2026
Merged

SeijDeLeon merged 9 commits into
stagingfrom
feature/tiled-graph-client

Conversation

@alisonscheng

@alisonscheng alisonscheng commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Tiled graph of links support in the Tiled API client

Adds Finch support for Tiled's graph of links - allowing for the creation of entities, links, and namespaces. It goes through the same TiledApiClient, auth, 401 refresh, interceptors and TanStack cache as the rest of src/api/tiled.

Built against main at bluesky/tiled@849c984a.

How Tiled's graph works

  • GraphQL, not REST. It's served at {origin}/api/graphql, outside /api/v1, so it isn't in openapi.json. It's only mounted on catalog-backed servers.
  • Entities have entityType, name, optional uri and JSON properties. They can be bound to a catalog node (nodePathParts), in which case deleting the node deletes the entity. Ids are assigned by the server.
  • Links join two entities through a predicate. Registering a namespace (schema → https://schema.org/) makes CURIEs expand on write and compact on read. This applies to predicates and to the keys of properties. Register namespaces before writing, or terms are stored as literal text.
  • Lists page by limit/offset (default 100), with no total count and no next-page signal. A short page means the end.
  • Errors come back as HTTP 200 with an errors array. Reads never return 401; the server filters what the caller can see.

What's in this PR

Layer Where
Hooks: 6 queries, 9 mutations, incl. raw useTiledGraphQLQuery / useTiledGraphQLMutation for nested selections hooks/graphHooks.ts
Free functions (createTiledGraphEntity, getTiledGraphLinks, tiledGraphQL, …) client/facade.ts
13 client methods and the graphql() transport (maps GraphQL errors to TiledApiError with graphqlCode) client/TiledApiClient.ts
Every GraphQL document; the only file that spells a graph field name graph/operations.ts
Schema snapshot (tests only) and how to regenerate it graph/schema.graphql, graph/README.md
Types types/graph.ts, types/errors.ts
One graph cache root; every graph write refreshes it, and so does deleteNode (bound entities cascade) hooks/queryKeys.ts, hooks/invalidation.ts
Registry (TILED_GRAPH_ENDPOINTS, kept apart from the spec-checked REST registry) endpoints/graphEndpoints.ts
Playground entries for every graph hook, plus a multi-line field for GraphQL documents components/TiledQueryPlayground/
Docs Tiled README.md ("Graph of links"), SKILLS.md, stories/TiledHooks.md

graphql is added as a devDependency only, for schema validation in tests. Nothing shipped imports it, so consumers install nothing new.

How to test

Unit tests

npx vitest run src/testing/tests/Tiled --exclude "**/TiledConnectionStorage.test.ts"
npx tsc --noEmit -p .

TiledGraph.test.tsx (20 tests):

  • checks every document against schema.graphql, and that every schema operation is implemented;
  • tests the transport: origin URL, error mapping, the three-state update (an omitted field is untouched, null clears it), 401 refresh;
  • tests the hooks: idle guards and invalidation.

TiledConnectionStorage and a few non-Tiled suites already fail on main under Node 25 (localStorage.getItem is not a function). This PR doesn't affect them.

Against a real server. The graph isn't in a Tiled release yet, so install upstream main:

  • GraphiQL: open http://localhost:8000/api/graphql and add the header {"Authorization": "Apikey secret"}.

  • Playground: run npm run dev, open the Tiled Queries tab, and connect to http://localhost:8000/api/v1 with key secret. The graph hooks are under "Graph of links".

  • From code:

    await upsertTiledGraphNamespace('schema', 'https://schema.org/');
    const project = await createTiledGraphEntity({ entityType: 'project', name: 'demo-project' });
    const sample = await createTiledGraphEntity({ entityType: 'sample', name: 'S001' });
    await createTiledGraphLink({ subjectId: sample.id, predicate: 'schema:isPartOf', objectId: project.id });
    await getTiledGraphLinks({ objectId: project.id, predicate: 'schema:isPartOf' }); // → 1 link

Planned structure: where vocabularies live

Finch is the generic library that beamline UIs depend on, so it should not hardcode any group's predicates. The plan:

  • Finch (this PR): moves entities and links. It changes only when Tiled's graph API changes.

  • A shared ALS vocabulary repo :

    • one LinkML schema, the source of truth for entity types, predicates (PROV-O, schema.org and the existing forge: namespace) and their rules (e.g. a sample schema:isPartOf exactly one project);
    • generated constants and types, published as an npm package or a PyPI package
    • a small schema-driven writer that checks those rules before calling Finch's createTiledGraphEntity / createTiledGraphLink;
    • SHACL shapes, a JSON-LD context and generated docs for the custom namespaces.
  • UI repos (beamline apps) depend on both. Finch provides the client and hooks; the vocabulary package provides the terms:

    import { createTiledGraphLink } from '@blueskyproject/finch';
    import { PREDICATES } from '@als/vocabulary';
    await createTiledGraphLink({ subjectId: sample.id, predicate: PREDICATES.partOf, objectId: project.id });

Follow-ups

  • Regenerate graph/schema.graphql when #1499 / #1500 merge; the tests will name what changed.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

✅ Test Results

All tests passed!

Complete Test Suite Coverage:

  • ✅ Buttons - Button, ButtonIconOnly, ButtonWithIcon, ButtonCopyToClipboard
  • ✅ Inputs - InputCheckBox, InputNumber, InputEnumBoxRounded, InputStringBoxRounded
  • ✅ Plots - PlotlyHeatmap, PlotlyHeatmapTiled, PlotlyScatter, Histogram, ColormapPicker
  • ✅ Layout - Sidebar, SidebarItem, FinchSidebar, FinchAppLayout, FinchHeader, FinchMainContent, Header, Main, Bento, Paper, Widget
  • ✅ Tiled - TiledComponents, TiledLinePlotMaker
  • ✅ Devices - Camera, Shutter, DeviceControllerBox, DeviceControllerBoxSimple, TableDeviceController, ControllerAbsoluteMove, ControllerRelativeMove, Hexapod, BeamEnergy
  • ✅ Services - QueueServer, SignalMonitorPlots, Experiment
  • ✅ Misc - IFrame, GoogleDoc, SelectDropdown, ComponentViewer, ComponentViewerUtils

Test Environment: Node.js 20.x
Linting: ✅ ESLint passed
Formatting: ✅ Prettier passed
Type Checking: ✅ TypeScript compilation successful
Build: ✅ Production build successful
Tests: ✅ All tests passed

Comment thread src/api/tiled/hooks/graphHooks.ts Outdated

return useTiledQuery({
queryKey: tiledQueryKeys.graph(scope, { op: 'raw', document, variables: vars }),
fetch: (client, request) =>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(From Codex for your review)
[P1] Reject mutation documents in the query hook

The hook forwards any nonblank document to graphql(), including mutations. Pasting a mutation into the playground’s raw-query editor therefore executes a write automatically, bypassing mutation controls. Refetching, polling, or remounting can repeat that write. I reproduced execution twice through the query hook. Validate that the document selects a query operation before sending it.

>,
requestOptions?: TiledRequestOptions,
): UseMutationResult<TResult, TiledHookError, TiledGraphQLMutationVariables, TContext> {
return useTiledMutation({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(From codex for your review)
[P2] Invalidate graph queries after partial mutation failures

This delegates to useTiledMutation, which invalidates only on success. A raw mutation can commit its first field and fail a later field; graphql() then rejects, leaving cached graph reads unchanged despite the committed write. I reproduced the missing invalidation with a resolver-error response. Refresh graph queries on execution failures that may contain partial writes, while preserving the error for callers.

@SeijDeLeon

Copy link
Copy Markdown
Collaborator

Manually tested:

  • Creating entity
  • Getting all entities (using types as well)
  • Creating links btwn two entities
  • Getting all links
  • Getting link via predicate
  • modify existing link
  • delete link
  • create namespace
  • get namespace
  • delete entity
  • update entity to add node path to catalogue entry
  • creating entity with nested catalogue

The only other thing I noticed is no clear way of obtaining the path to a Tiled catalogue node when an entity is NodeBound. This would be needed for going from an entity about a scan to then querying the scan itself from the regular REST api

@github-actions

Copy link
Copy Markdown
Contributor

✅ Test Results

All tests passed!

Complete Test Suite Coverage:

  • ✅ Buttons - Button, ButtonIconOnly, ButtonWithIcon, ButtonCopyToClipboard
  • ✅ Inputs - InputCheckBox, InputNumber, InputEnumBoxRounded, InputStringBoxRounded
  • ✅ Plots - PlotlyHeatmap, PlotlyHeatmapTiled, PlotlyScatter, Histogram, ColormapPicker
  • ✅ Layout - Sidebar, SidebarItem, FinchSidebar, FinchAppLayout, FinchHeader, FinchMainContent, Header, Main, Bento, Paper, Widget
  • ✅ Tiled - TiledComponents, TiledLinePlotMaker
  • ✅ Devices - Camera, Shutter, DeviceControllerBox, DeviceControllerBoxSimple, TableDeviceController, ControllerAbsoluteMove, ControllerRelativeMove, Hexapod, BeamEnergy
  • ✅ Services - QueueServer, SignalMonitorPlots, Experiment
  • ✅ Misc - IFrame, GoogleDoc, SelectDropdown, ComponentViewer, ComponentViewerUtils

Test Environment: Node.js 20.x
Linting: ✅ ESLint passed
Formatting: ✅ Prettier passed
Type Checking: ✅ TypeScript compilation successful
Build: ✅ Production build successful
Tests: ✅ All tests passed

@SeijDeLeon

Copy link
Copy Markdown
Collaborator

Looks good! Tested the bindEntity and verified the uri is exposed as expected.

One last comment is to just export the new hooks as named hooks directly in src/index (useTiledBindGraphEntityMutation and useTiledGraphNodeEntitiesQuery) and also the TiledGraphOperationKind type.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Test Results

All tests passed!

Complete Test Suite Coverage:

  • ✅ Buttons - Button, ButtonIconOnly, ButtonWithIcon, ButtonCopyToClipboard
  • ✅ Inputs - InputCheckBox, InputNumber, InputEnumBoxRounded, InputStringBoxRounded
  • ✅ Plots - PlotlyHeatmap, PlotlyHeatmapTiled, PlotlyScatter, Histogram, ColormapPicker
  • ✅ Layout - Sidebar, SidebarItem, FinchSidebar, FinchAppLayout, FinchHeader, FinchMainContent, Header, Main, Bento, Paper, Widget
  • ✅ Tiled - TiledComponents, TiledLinePlotMaker
  • ✅ Devices - Camera, Shutter, DeviceControllerBox, DeviceControllerBoxSimple, TableDeviceController, ControllerAbsoluteMove, ControllerRelativeMove, Hexapod, BeamEnergy
  • ✅ Services - QueueServer, SignalMonitorPlots, Experiment
  • ✅ Misc - IFrame, GoogleDoc, SelectDropdown, ComponentViewer, ComponentViewerUtils

Test Environment: Node.js 20.x
Linting: ✅ ESLint passed
Formatting: ✅ Prettier passed
Type Checking: ✅ TypeScript compilation successful
Build: ✅ Production build successful
Tests: ✅ All tests passed

@SeijDeLeon SeijDeLeon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm

@SeijDeLeon
SeijDeLeon merged commit 86f8872 into staging Oct 13, 2026
1 check passed
@SeijDeLeon
SeijDeLeon deleted the feature/tiled-graph-client branch October 13, 2026 17:11
SeijDeLeon pushed a commit that referenced this pull request Oct 16, 2026
* install graphql

* Create graph schema, types, and ops document

* Graph client methods and registry

* Add graph hooks, playground entries, and unit tests

* Add to skills and README

* Document constraint to tiled v0.2.19

* Add client methods for binding to node

* Determine operation kinds and invalidate on error

* Export new hooks and TiledGraphOperationKind type
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants