Problem
Six places make the same GRPC call (grpcClient.resources.getResource):
@shm/shared/resource-loader.ts→createResourceLoader— throws on error, no extra workweb/loaders.ts:370→getResource— returnsnot-found, no extra workweb/loaders.ts:407→loadResource— throws on error, adds author metadata etc (see below)notify/loaders.ts→getResource— returnsnot-found, no extra worknotify/loaders.ts→loadResource— throws on error, same as webdesktop/entities.ts:154→loadResource— returnsnot-found, no extra work
This means that we have duplicate or conflicting logic about processing the GRPC data, across many different locations in the code.
It is not clear what is the responsibility of a "loader". Does it fetch for related data like authors? Does it handle redirects or not?
What web loadResource adds (via loadResourcePayload)
Author metadata (name, avatar for each
document.authors)Breadcrumb navigation (metadata for each parent path)
Support documents (docs referenced via embeds/links in content)
Support queries (results from query blocks in content)
Directory results (children of home doc and current doc)
Home document (for site navigation header)
isLatestflag (compares current version to latest)
Why It's Confusing
Same name
loadResourcemeans different things (desktop returns raw, web returns with author metadata)createResourceLoaderthrows on error, but desktop'sloadResourcecatches and returnsnot-foundWeb has both
getResourceANDloadResourcemaking duplicate GRPC callsNo clear naming for abstraction levels
New Pattern: fetch → resolve → load
Note: this is more about "re-organizing" than "refactoring", so it shouldn't be a very dramatic change. But it should hopefully help us understand what functions are responsible for what.
fetchResource (lowest level)
What it does: GRPC call, parse response, return typed result Redirects: Returns {type: 'redirect', redirectTarget} Not found: Returns {type: 'not-found'} Returns: HMResource = document | comment | redirect | not-found
// Desktop, API endpoints, simple lookups
const resource = await fetchResource(id)
if (resource.type === 'document') { /* use resource.document */ }
if (resource.type === 'redirect') { /* caller decides what to do */ }
if (resource.type === 'not-found') { /* caller decides what to do */ }
resolveResource (mid level)
What it does: Fetches and follows redirects until final target Redirects: Follows automatically (recursive) Not found: Throws HMNotFoundError Returns: HMResolvedResource = document | comment (never redirect)
// When you need final target, don't care about redirect chain
const resource = await resolveResource(id)
// resource.type is 'document' or 'comment', never 'redirect'
loadResource (web, highest level)
What it does: Resolves, triggers P2P discovery on not-found, adds:
Author metadata (name, avatar for each author uid)
Breadcrumb navigation (parent path titles)
Support documents (embedded/referenced docs for rendering)
Support queries (query block results)
Directory results (doc children for nav)
Home document + directory (for site header nav)
isLatestflag
Redirects: Follows automatically Not found: Triggers P2P discovery, retries Returns: WebResourcePayload
loadSiteResource (web, wraps loadResource)
What it does: Calls loadResource + adds:
homeMetadata(site name, icon, theme from account root)originHomeId(site root account id)origin(request origin URL)Error fallback (loads home doc even on error so header renders)
Returns: SiteDocumentPayload
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime