20139 - Add filterset search, and the clip poster URL - #10
Open
ghultink wants to merge 3 commits into
Open
Conversation
Mirrors bluebillywig/bb-sapi-php-sdk#20. The SDK can list, page and sort media clips but not filter them, so every integration hand-rolls a filter query — and because a wrong one fails silently, they hand-roll it wrong and nobody notices. SAPI ignores a filter it cannot read and still answers HTTP 200 with neither `numfound` nor `items`, which is indistinguishable from an empty library. const filterSet = FilterSet.create() .where('status', 'is', 'published') .where('title', 'contains', 'koert'); await sdk.mediaclip.search(filterSet); Groups are AND-ed, filters within a group OR-ed — the structure OVP6 builds (app/services/filter-set.types.ts), which this mirrors so a filterset moves between the UI, the API and any SDK unchanged. The filterset is SENT, not compiled. SAPI compiles filtersets itself, through the same SearchRequestHelper that serves the OVP, and that is what OVP6 sends (filter-set.service.ts:609). Compiling here would be a second implementation of semantics the server owns, free to drift, with an invisible failure mode. Verified equivalent against a live publication of 5777 clips: filterset and a hand-compiled fq return identical counts (published 4361, title contains "koert" 1, hasInteractivity 868, published AND video 3679). The raw `fq` escape hatch stays for the rare thing a filterset cannot express, with its encoding pinned: `fq[0]=` is accepted, while a plain `fq=` and a nested `fq[][0]=` are both ignored without an error. Also adds Thumbnail.getMediaClipPosterPath(). A clip's `src` is its source media file, so building a poster from it yields a link to a .mov — the service replies "Invalid src mime type: video/quicktime". The route is /mediaclip/{id}/spthumbnail/{w}/{h}.webp, and a draft clip needs a read-only RPC token. 154 tests pass, tsc clean, no new dependencies. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The repo gates on 100% coverage and filter-set.ts landed at 98.5%. toJSON is what makes JSON.stringify(filterSet) produce the wire format, which is how a caller embedding a filterset in a larger body will reach for it, so it is worth a test rather than a deletion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mirrors the fix commit on bb-sapi-php-sdk#20. Three behaviour bugs, all confirmed against the live API, all previously pinned by the tests as if correct — which is why CI was green over broken code. 1. isEmpty/isNotEmpty were silent no-ops. The backend's compiler skips any filter whose value is empty — presence tests included — so a bare isEmpty returned the full unfiltered publication (verified live: 5777 of 5777). OVP6 sends the placeholder '*' with the comment "backend needs a value to work"; strip() now does the same, overriding any caller-supplied value. Live with the placeholder: author isEmpty 5496, isNotEmpty 278. 2. Numeric values were silently DROPPED: hasValue() accepted only strings, so an ingested OVP/Automations filterset with a JSON number lost the filter and returned the full result set. SAPI accepts numbers (views > 100 as a number and as "100" both return 99 live); they are now normalised to decimal strings. 3. Booleans were also dropped — and could not simply be passed through, because the backend mangles a JSON true into "1", which matches nothing (verified live: hasInteractivity true as a boolean returned 0 results; as the string 'true', 868). Booleans now normalise to 'true'/'false'. Also from the review: - toArray() no longer crashes on a malformed group (missing filters array): from() ingests external JSON, and one junk entry should not take the whole filterset down. Non-scalar array members are dropped rather than serialised. - The exported value type is widened to FilterScalar/FilterValue (accepting more is BC-safe); the wire output remains string | string[]. - Documented the server-side quirks a caller inherits: value '0' is dropped by the backend's empty-value guard, '+' becomes a space and '"' is stripped, and an unknown field returns numfound=0 rather than an error. - Version bumped to 1.1.0: the filterset feature is additive. 160 tests pass, 100% coverage, tsc clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Mirrors bb-sapi-php-sdk#20. Same design, same wire format — review that one first if you're reviewing both.
Why
The SDK can list, page and sort media clips, but it cannot filter them.
MediaClip.list()takes onlylimit,offsetandsort, and the query layer isRecord<string, string>fed tosearchParams.set(), which cannot express indexed parameters at all.So every integration hand-rolls a filter query — and a wrong one fails silently: SAPI ignores it and still answers
HTTP 200with a body carrying neithernumfoundnoritems, indistinguishable from an empty library. The TYPO3 extension's library grid showed no videos for exactly this reason.What
Groups are AND-ed, filters within a group OR-ed — the structure OVP6 builds (
app/services/filter-set.types.ts), mirrored here so a filterset moves between the UI, the API and any SDK unchanged.The filterset is sent, not compiled
The part worth reviewing. SAPI compiles filtersets itself, via the same
SearchRequestHelperthat serves the OVP — and that is what OVP6 sends (filter-set.service.ts:609). Compiling in the SDK would be a second implementation of semantics the server owns, free to drift, with a failure mode nobody sees. Multiply by four SDKs and you get four subtly different compilers.Verified equivalent against a live publication of 5777 clips:
statusis publishedtitlecontains "koert"hasInteractivityis trueThe
fqescape hatch, and its trapsearch()still accepts raw filter queries for the rare thing a filterset cannot express. Its encoding is pinned by a test:fq[0]=…— acceptedfq=…repeated, or a nestedfq[][0]=…— ignored, silently, with HTTP 200 and an empty envelopeAlso: the clip poster URL
Thumbnail.getMediaClipPosterPath(). A clip'ssrcis its source media file, so building a poster from it yields a link to a.mov— the service repliesInvalid src mime type: video/quicktime. The route is/mediaclip/{id}/spthumbnail/{w}/{h}.webp, and a draft clip needs a read-only RPC token (never the write key — this URL ends up in page source).Checks
tsc --noEmitcleanNote on browser use
The SDK is otherwise browser-ready — pure ESM,
fetchtransport, no Node built-ins — exceptsrc/util/hotp.ts, which usesnode:cryptoandBuffer. That is also the one part that must never run in a browser, since HOTP minting needs the API secret. A browser entry point that omits the RPC authenticator would be a small change if it's ever wanted; not in this PR.🤖 Generated with Claude Code
Self-review findings — addressed in the latest commit
A thorough adversarial review of this PR (all three SDK PRs plus the TYPO3 consumer) found three behaviour bugs that the original tests pinned as if correct — green CI over broken code:
isEmpty/isNotEmptywere silent no-ops. The backend compiler skips any filter whose value is empty — presence tests included. Verified live: a bareisEmptyreturned the full unfiltered publication (5777/5777). OVP6 sends the placeholder*with the comment "backend needs a value to work"; the wire format now does the same (live:author isEmpty5496,isNotEmpty278).views > 100as number and string both return 99), but dropping or mishandling them varied per SDK.'true'/'false'. The backend mangles a JSONtrueinto"1", which matches nothing — verified live: 0 results as a boolean vs 868 as the string.Plus per-SDK hardening (tolerant ingestion of malformed groups, descriptive errors for unknown operators, documented server-side quirks). Details in the fix commit message.