Add isNotFoundError and improve getDialogue error handling#96
Add isNotFoundError and improve getDialogue error handling#96devkobby24 wants to merge 7 commits into
Conversation
isNotFoundError identifies the API's structured "resource does not exist" response. It requires the type as well as the status, so a 404 raised by the gateway — which carries no structured body and arrives as server_error — stays an error rather than being read as an authoritative not-found. dialogueNotFound builds the message for an unresolvable dialogue. A namespace is part of a dialogue's storage key, so the API resolves an omitted one to its default; the resulting miss cannot distinguish "does not exist" from "you dropped the namespace", and naming the namespace to pass is the only actionable signal available. The API's requestId, code and details are carried through when there was a cause, so the error stays traceable in support requests.
A 404 from an unscoped lookup could mean the dialogue does not exist, or that the caller dropped the namespace that list handed them. Returning null collapsed both into a value that crashed downstream as "Cannot read properties of null (reading 'loadMessages')" — the failure reported in #94 and #90, and the exact line in the featured in-product sample. It now throws, naming the namespace searched and the one to pass. Both miss paths are covered: the API's 404 and a null body, which previously diverged. Reverses d95af9a, which predates both reports that showed the null was itself the confusing part. BREAKING CHANGE: returns Promise<Dialogue> rather than Promise<Dialogue | null>.
Pins the message for both a scoped and an unscoped miss, that an empty namespace is treated as unscoped (the request layer only sends a non-empty one), and that the API's requestId and code survive onto the thrown error.
Follows getDialogue no longer returning null, and documents that the namespace a dialogue was created with must be passed — without it the lookup is scoped to the default namespace and will not find it.
Throwing removes the null that existence checks relied on, so callers need a way to tell a missing dialogue from a real failure without matching on message strings.
getDialogue now throws, so the null branch is unreachable. withErrorHandler already reports a DialogueDBError and exits non-zero, so the CLI prints the namespace hint and requestId rather than a bare "not found".
|
Claude finished @devkobby24's task in 2m 21s —— View job Review: Add
|
|
Claude finished @devkobby24's task in 2m 22s —— View job Review: Add
|
What
getDialoguethrows a descriptiveDialogueDBErrorinstead of returningnullwhen a dialogue can't be resolved.
Fixes #94, fixes #90 — the same bug, reported twice.
Why
A dialogue's namespace is part of its storage key, so the API resolves an omitted
namespace to
DEFAULTrather than searching across namespaces. A 404 couldn't bedistinguished from "you dropped the namespace
listDialogueshanded you", andreturning
nullcollapsed both into a value that detonated one line later:That's the crash in both reports — and in the featured in-product sample, so it's
the first thing a new developer copies.
Now:
The API's
requestId,codeanddetailsare carried through. Both miss pathsare covered — the API's 404 and a null body, which previously diverged.
This reverses d95af9a
Greg's Feb commit ("enforce get dialogue to return null instead of 404") was
deliberate — but it predates #90 (June) and #94 (July) by four months, so it was
made without the evidence that the
nullwas itself the confusing part.Breaking
getDialoguereturnsPromise<Dialogue>rather thanPromise<Dialogue | null>.Existence checks should catch and test
isNotFoundError, now exported from thepackage root:
Version: this must be 3.0.0, and the automation will not do it
package.jsonis untouched in this PR — but the version can't be left to therelease automation, because its default is wrong rather than absent.
draft-main-pr.ymlonly ever increments PATCH (line 174, "Bump version number(patch increment)"). There is no major or minor path in it:
package.jsonis2.0.1and the latest tag isv2.0.1, so2.0.1 <= 2.0.1and this publishes as v2.0.2 — a breaking change delivered as a patch. Every
external consumer on
^2.0.1picks it up on their next install and gets anunhandled throw where they had a
nullcheck, without anyone choosing toupgrade.
Internal repos are insulated:
mcp,core-backendand theexamplesare allpinned
^1.0.x, which can't resolve 2.x let alone 3.x. It's npm consumers whoare exposed.
The same comparison is the escape hatch:
Set
package.jsonto3.0.0and3.0.0 > 2.0.1, so the workflow steps back andcuts
v3.0.0. The automation is built to handle routine patches and defer to adeliberately-set version — this is the case it defers for.
Action required before this reaches
main: setpackage.jsonto3.0.0,either here or on the release PR. Left alone, it ships as 2.0.2.
Follow-ups filed
Promise<Dialogue | null>in two places, anddocs/guide/examples.mdships the bugif (!dialogue)branches go dead (deferred; pinned^1.0.1)