Skip to content

/v1/id/_:id/expanded endpoint - #286

Draft
thehabes wants to merge 9 commits into
mainfrom
285-id-expanded
Draft

/v1/id/_:id/expanded endpoint#286
thehabes wants to merge 9 commits into
mainfrom
285-id-expanded

Conversation

@thehabes

@thehabes thehabes commented Aug 7, 2026

Copy link
Copy Markdown
Member

Closes #285.

Summary

Adds GET and POST /v1/id/:_id/expanded — a generic, unauthenticated, browser-cacheable endpoint that returns a RERUM object with the assertions of every current Annotation targeting it already merged in.

Changes

New endpoint

routes/id.js/:_id/expanded registered ahead of /:_id. GET, POST (behind rest.verifyJsonContentType), and HEAD via Express's native GET handling. Everything else is a 405.

controllers/crud.jsidExpanded() plus three helpers:

  • sanitizeExpansionFilters() reduces a POST body to the filter keys this endpoint will honor.
  • assertionsFrom() reads only body and bodyValue off an Annotation, so no other property of an Annotation can leak onto the entity.
  • applyRawExpansion() merges those assertions onto a clone of the entity and re-appends __rerum last.

db-controller.js — exports idExpanded.

GET — the convenience form

Imagined as the plain GET where nothing else is supplied. Recognizes ?generator= and ?creator= only, both matched across the http/https spellings of the URI. ?limit= (default 200) and ?skip= page the Annotation search.

POST — the filtered form

The JSON body is an object of literal MongoDB filter keys, ANDed into the Annotation search — so filtering by generating app means supplying the real property name, __rerum.generatedBy. URL parameters supply no filters here, though ?limit= and ?skip= still page. Not browser-cached.

Because every supplied filter is ANDed in, a filter can only narrow the result set — there is no way to widen it past the entity in the request URI.

Response headers

  • Cache-Control: max-age=86400, must-revalidate — only on the GET, so browsers can cache for 24h. Annotations on an entity are volatile during initial creation and stable afterward, so this trades a hard reload in the rare stale case for not re-running the expand on every visit.
  • Annotations-Gathered — the size of the page you received. When it equals your limit there may be more, and you were expanded from only part of the record's Annotations. This is the one to page off.
  • Annotations-Merged — how many of those could contribute. Annotations with multiple bodies are not counted. A counted Annotation may still assert nothing mergeable, so this is not a count of properties received either.

Location is canonicalized to the _id form, so a slug request advertises the stable URL.

controllers/gog.js

Refactored onto the shared helper, which is where the bulk of the deletions come from. Three behavior changes ride along, all on /gog/id/:_id:

  • hasOwnPropertyObject.hasOwn. An Annotation body asserting hasOwnProperty would otherwise shadow the method and break the merge. This one is load-bearing, not cosmetic.
  • Annotations carrying multiple bodies are now skipped explicitly.
  • /gog/id/:_id is now restricted to GoG-generated data (403 otherwise), and expands only with Annotations from the entity's own generator.

Measured against production before merging: across 3,000 sampled leaf entities generated by a GoG agent, all 24,259 leaf Annotations targeting them are from the GoG production agent, so the generator filter drops nothing today. The prod/dev agent ids moved into named constants at the top of the file.

Documentation

public/API.html gets both forms under GET and POST, including the full merge rules and the ignored-filters table. openapi/contracts/core-provider.openapi.yaml gets /id/{id}/expanded with get, head, and post.

Testing

No tests have been written for the new endpoint yet, this is TODO

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.

/v1/id/:_id/expanded Endpoint

1 participant