Skip to content

Commit be85356

Browse files
committed
docs(api): document federation jsonld helpers
1 parent 5271560 commit be85356

2 files changed

Lines changed: 140 additions & 0 deletions

File tree

packages/api/src/http.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,21 @@ const binaryResponse = (data: Uint8Array, contentType: string, status = 200) =>
275275
)
276276
)
277277

278+
/**
279+
* Serializes a federation JSON-LD document with the ForgeFed response content type.
280+
*
281+
* @param data - JSON-LD payload that satisfies the JSON.stringify serializability precondition.
282+
* @param status - HTTP status code assigned to the response.
283+
* @returns Effect that yields an HTTP text response containing the serialized JSON-LD document.
284+
*
285+
* @pure false
286+
* @effect Delegates response allocation to textResponse and preserves no-store HTTP headers.
287+
* @invariant successful responses always use federationJsonLdResponseContentType.
288+
* @precondition data is JSON.stringify-serializable and status is a valid HTTP status code.
289+
* @postcondition response body equals JSON.stringify(data) and response status equals status.
290+
* @complexity O(n) time and O(n) space where n is the serialized JSON-LD payload size.
291+
* @throws TypeError when data violates the JSON.stringify serializability precondition.
292+
*/
278293
const jsonLdResponse = (data: unknown, status: number) =>
279294
textResponse(JSON.stringify(data), federationJsonLdResponseContentType, status)
280295

@@ -595,34 +610,99 @@ export const federationExchangeStatusResponse = () =>
595610
return yield* _(jsonResponse(makeFederationExchangeStatus(context), 200))
596611
}).pipe(Effect.catchAll(errorResponse))
597612

613+
/**
614+
* Builds the federation actor JSON-LD HTTP handler.
615+
*
616+
* @returns Effect that yields the local ActivityPub actor document response.
617+
*
618+
* @pure false
619+
* @effect Reads HttpServerRequest, resolves federation context, renders makeFederationActorDocument, serializes with jsonLdResponse, and maps failures through errorResponse.
620+
* @invariant successful responses contain the actor id derived from the resolved federation context.
621+
* @precondition request headers or configured env provide a non-empty public origin.
622+
* @postcondition successful responses contain a JSON-LD Person document with HTTP 200.
623+
* @complexity O(1) time and O(1) space for document construction, excluding serialization size.
624+
* @throws Never; failures are represented through the Effect error channel and converted by errorResponse.
625+
*/
598626
export const federationActorDocumentResponse = () =>
599627
Effect.gen(function*(_) {
600628
const request = yield* _(HttpServerRequest.HttpServerRequest)
601629
const context = yield* _(resolveFederationContext(request))
602630
return yield* _(jsonLdResponse(makeFederationActorDocument(context), 200))
603631
}).pipe(Effect.catchAll(errorResponse))
604632

633+
/**
634+
* Builds the federation outbox JSON-LD HTTP handler.
635+
*
636+
* @returns Effect that yields the local ActivityPub outbox collection response.
637+
*
638+
* @pure false
639+
* @effect Reads HttpServerRequest, resolves federation context, renders makeFederationOutboxCollection, serializes with jsonLdResponse, and maps failures through errorResponse.
640+
* @invariant successful responses contain the outbox id derived from the resolved federation context.
641+
* @precondition request headers or configured env provide a non-empty public origin.
642+
* @postcondition successful responses contain a JSON-LD OrderedCollection document with HTTP 200.
643+
* @complexity O(1) time and O(1) space for document construction, excluding serialization size.
644+
* @throws Never; failures are represented through the Effect error channel and converted by errorResponse.
645+
*/
605646
export const federationOutboxDocumentResponse = () =>
606647
Effect.gen(function*(_) {
607648
const request = yield* _(HttpServerRequest.HttpServerRequest)
608649
const context = yield* _(resolveFederationContext(request))
609650
return yield* _(jsonLdResponse(makeFederationOutboxCollection(context), 200))
610651
}).pipe(Effect.catchAll(errorResponse))
611652

653+
/**
654+
* Builds the federation followers JSON-LD HTTP handler.
655+
*
656+
* @returns Effect that yields the local ActivityPub followers collection response.
657+
*
658+
* @pure false
659+
* @effect Reads HttpServerRequest, resolves federation context, renders makeFederationFollowersCollection, serializes with jsonLdResponse, and maps failures through errorResponse.
660+
* @invariant successful responses contain the followers id derived from the resolved federation context.
661+
* @precondition request headers or configured env provide a non-empty public origin.
662+
* @postcondition successful responses contain a JSON-LD OrderedCollection document with HTTP 200.
663+
* @complexity O(1) time and O(1) space for document construction, excluding serialization size.
664+
* @throws Never; failures are represented through the Effect error channel and converted by errorResponse.
665+
*/
612666
export const federationFollowersDocumentResponse = () =>
613667
Effect.gen(function*(_) {
614668
const request = yield* _(HttpServerRequest.HttpServerRequest)
615669
const context = yield* _(resolveFederationContext(request))
616670
return yield* _(jsonLdResponse(makeFederationFollowersCollection(context), 200))
617671
}).pipe(Effect.catchAll(errorResponse))
618672

673+
/**
674+
* Builds the federation following JSON-LD HTTP handler.
675+
*
676+
* @returns Effect that yields the local ActivityPub following collection response.
677+
*
678+
* @pure false
679+
* @effect Reads HttpServerRequest, resolves federation context, renders makeFederationFollowingCollection, serializes with jsonLdResponse, and maps failures through errorResponse.
680+
* @invariant successful responses contain the following id derived from the resolved federation context.
681+
* @precondition request headers or configured env provide a non-empty public origin.
682+
* @postcondition successful responses contain a JSON-LD OrderedCollection document with HTTP 200.
683+
* @complexity O(1) time and O(1) space for document construction, excluding serialization size.
684+
* @throws Never; failures are represented through the Effect error channel and converted by errorResponse.
685+
*/
619686
export const federationFollowingDocumentResponse = () =>
620687
Effect.gen(function*(_) {
621688
const request = yield* _(HttpServerRequest.HttpServerRequest)
622689
const context = yield* _(resolveFederationContext(request))
623690
return yield* _(jsonLdResponse(makeFederationFollowingCollection(context), 200))
624691
}).pipe(Effect.catchAll(errorResponse))
625692

693+
/**
694+
* Builds the federation liked JSON-LD HTTP handler.
695+
*
696+
* @returns Effect that yields the local ActivityPub liked collection response.
697+
*
698+
* @pure false
699+
* @effect Reads HttpServerRequest, resolves federation context, renders makeFederationLikedCollection, serializes with jsonLdResponse, and maps failures through errorResponse.
700+
* @invariant successful responses contain the liked collection id derived from the resolved federation context.
701+
* @precondition request headers or configured env provide a non-empty public origin.
702+
* @postcondition successful responses contain a JSON-LD OrderedCollection document with HTTP 200.
703+
* @complexity O(1) time and O(1) space for document construction, excluding serialization size.
704+
* @throws Never; failures are represented through the Effect error channel and converted by errorResponse.
705+
*/
626706
export const federationLikedDocumentResponse = () =>
627707
Effect.gen(function*(_) {
628708
const request = yield* _(HttpServerRequest.HttpServerRequest)

packages/api/src/services/federation.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,20 @@ const isRecord = (value: unknown): value is JsonRecord =>
125125
const asRecord = (value: unknown): JsonRecord | null =>
126126
isRecord(value) ? value : null
127127

128+
/**
129+
* Extracts string JSON-LD context entries from a boundary value.
130+
*
131+
* @param value - Unknown ActivityPub or ForgeFed @context field value.
132+
* @returns Immutable set of string context identifiers; unsupported shapes produce an empty set.
133+
*
134+
* @pure true
135+
* @effect none
136+
* @invariant every returned element is a string from value, and no non-string value is introduced.
137+
* @precondition value may be any boundary value.
138+
* @postcondition result contains value iff value is a string; result contains each string item iff value is an array.
139+
* @complexity O(n) time and O(n) space where n is array length, or O(1) for non-array values.
140+
* @throws Never.
141+
*/
128142
const jsonLdContextValues = (value: unknown): ReadonlySet<string> => {
129143
if (typeof value === "string") {
130144
return new Set([value])
@@ -135,11 +149,41 @@ const jsonLdContextValues = (value: unknown): ReadonlySet<string> => {
135149
return new Set(value.filter((item): item is string => typeof item === "string"))
136150
}
137151

152+
/**
153+
* Checks whether a JSON-LD context value contains both required federation contexts.
154+
*
155+
* @param value - Unknown @context value to validate.
156+
* @returns True when ActivityStreams and ForgeFed context identifiers are both present.
157+
*
158+
* @pure true
159+
* @effect none
160+
* @invariant result is true iff jsonLdContextValues(value) contains both required context URIs.
161+
* @precondition value may be any boundary value.
162+
* @postcondition result does not mutate or normalize the input value.
163+
* @complexity O(n) time and O(n) space where n is array length, or O(1) for non-array values.
164+
* @throws Never.
165+
*/
138166
const hasFederationJsonLdContext = (value: unknown): boolean => {
139167
const contexts = jsonLdContextValues(value)
140168
return contexts.has(activityStreamsJsonLdContext) && contexts.has(forgeFedJsonLdContext)
141169
}
142170

171+
/**
172+
* Requires a top-level federation document to declare or inherit valid JSON-LD contexts.
173+
*
174+
* @param payload - Parsed JSON object representing the federation document being checked.
175+
* @param label - Human-readable document label used in the typed validation error.
176+
* @param inheritedContext - Optional parent @context accepted when payload omits its own @context.
177+
* @returns Effect that succeeds when ActivityStreams and ForgeFed contexts are available.
178+
*
179+
* @pure false
180+
* @effect Constructs a typed ApiBadRequestError in the Effect error channel when validation fails.
181+
* @invariant success implies payload.@context or inheritedContext contains ActivityStreams and ForgeFed contexts.
182+
* @precondition payload is a decoded JSON object at the federation boundary.
183+
* @postcondition failure message identifies the invalid document label.
184+
* @complexity O(n) time and O(n) space where n is the checked context array length.
185+
* @throws Never; validation failures are represented as ApiBadRequestError.
186+
*/
143187
const requireFederationJsonLdContext = (
144188
payload: JsonRecord,
145189
label: string,
@@ -159,6 +203,22 @@ const requireFederationJsonLdContext = (
159203
)
160204
}
161205

206+
/**
207+
* Requires nested federation objects to use valid JSON-LD context only when they override inheritance.
208+
*
209+
* @param payload - Parsed nested JSON object being checked.
210+
* @param label - Human-readable nested object label used in the typed validation error.
211+
* @param inheritedContext - Parent @context available to nested objects that omit @context.
212+
* @returns Effect that succeeds when the nested object inherits context or declares a valid override.
213+
*
214+
* @pure false
215+
* @effect Delegates invalid explicit context failures to requireFederationJsonLdContext.
216+
* @invariant missing nested @context is accepted, explicit nested @context must contain ActivityStreams and ForgeFed contexts.
217+
* @precondition payload is a decoded nested JSON object and inheritedContext is the parent document context.
218+
* @postcondition success preserves JSON-LD inheritance semantics for nested ForgeFed objects.
219+
* @complexity O(n) time and O(n) space where n is the explicit nested context array length, or O(1) if omitted.
220+
* @throws Never; validation failures are represented as ApiBadRequestError.
221+
*/
162222
const requireNestedFederationJsonLdContext = (
163223
payload: JsonRecord,
164224
label: string,

0 commit comments

Comments
 (0)