feat(assets): the typed /assets service - #136
Merged
Merged
Conversation
The api grew a typed `/assets` family — the last node type without one. Every call is the shared `/resources` pipeline with the `ASSET` discriminator pinned server-side, so the two paths cannot drift apart on ACLs, naming policy or status codes. What differs is the shape that comes back: `Asset` rather than the polymorphic `Node`, so `geolocation` and `is_root` are reachable without a match. `AssetsService` covers the whole surface — create, get_by_id, by_ids, list, filter, search, update, delete — in the async client, the blocking mirror and the Python bindings (sync and async). `filter` and `search` reuse `ResourceFilterForm` and `SearchAndFilterForm<ResourceFilter>` rather than minting a second Rust spelling of one wire shape: the api declares the same Java types on both endpoint families. A `nodeType` in the body is *replaced* with `["asset"]`, not merged — entries OR together, so merging would widen a request to `/assets` into a mixed query. The Python `filter` therefore has no `node_type` keyword at all; offering a parameter the server silently discards is worse than omitting it. Behaviours worth knowing, each pinned by a test: - A 404 from `get_by_id` does not mean the id is free. A node of another type, and an asset you may not read, are both reported as missing. - Unlike `/resources/create`, the caller never sets the `ASSET` label — the endpoint deserializes into the api's `Asset`, whose constructor forces it in. - `delete` answers 204 with no body, so the wrapper is always empty. - `update` echoes a typed `Node`, and `geolocation` is the one update field that means anything on exactly one node type. `PyAsset` already existed in the bindings' `nodes.rs` — it is what `PyNode` dispatches an `ASSET` row to — so this adds the service pair only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: jgjesdal <jostein@intellistream.ai>
olavgg
approved these changes
Sep 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The api grew a typed
/assetsfamily — the last node type without one. Every call is the shared/resourcespipeline with theASSETdiscriminator pinned server-side, so the two paths cannot drift apart on ACLs, naming policy or status codes. What differs is the shape that comes back:Assetrather than the polymorphicNode, sogeolocationandis_rootare reachable without a match.AssetsServicecovers the whole surface — create, get_by_id, by_ids, list, filter, search, update, delete — in the async client, the blocking mirror and the Python bindings (sync and async).filterandsearchreuseResourceFilterFormandSearchAndFilterForm<ResourceFilter>rather than minting a second Rust spelling of one wire shape: the api declares the same Java types on both endpoint families. AnodeTypein the body is replaced with["asset"], not merged — entries OR together, so merging would widen a request to/assetsinto a mixed query. The Pythonfiltertherefore has nonode_typekeyword at all; offering a parameter the server silently discards is worse than omitting it.Behaviours worth knowing, each pinned by a test:
get_by_iddoes not mean the id is free. A node of another type, and an asset you may not read, are both reported as missing./resources/create, the caller never sets theASSETlabel — the endpoint deserializes into the api'sAsset, whose constructor forces it in.deleteanswers 204 with no body, so the wrapper is always empty.updateechoes a typedNode, andgeolocationis the one update field that means anything on exactly one node type.PyAssetalready existed in the bindings'nodes.rs— it is whatPyNodedispatches anASSETrow to — so this adds the service pair only.