@@ -11,7 +11,7 @@ import * as Schema from "effect/Schema"
1111import { renderError , type AppError } from "@effect-template/lib/usecases/errors"
1212
1313import { ApiAuthRequiredError , ApiBadRequestError , ApiConflictError , ApiInternalError , ApiNotFoundError , describeUnknown } from "./api/errors.js"
14- import type { ApplyProjectRequest } from "./api/contracts.js"
14+ import { federationJsonLdResponseContentType , type ApplyProjectRequest } from "./api/contracts.js"
1515import {
1616 AuthMenuRequestSchema ,
1717 AuthTerminalSessionRequestSchema ,
@@ -275,8 +275,23 @@ const binaryResponse = (data: Uint8Array, contentType: string, status = 200) =>
275275 )
276276 )
277277
278- const activityJsonResponse = ( data : unknown , status : number ) =>
279- textResponse ( JSON . stringify ( data ) , "application/activity+json; charset=utf-8" , status )
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+ */
293+ const jsonLdResponse = ( data : unknown , status : number ) =>
294+ textResponse ( JSON . stringify ( data ) , federationJsonLdResponseContentType , status )
280295
281296const parseQueryInt = ( url : string , key : string , fallback : number ) : number => {
282297 const parsed = Number ( new URL ( url , "http://localhost" ) . searchParams . get ( key ) ?? "" )
@@ -595,6 +610,106 @@ 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+ */
626+ export const federationActorDocumentResponse = ( ) =>
627+ Effect . gen ( function * ( _ ) {
628+ const request = yield * _ ( HttpServerRequest . HttpServerRequest )
629+ const context = yield * _ ( resolveFederationContext ( request ) )
630+ return yield * _ ( jsonLdResponse ( makeFederationActorDocument ( context ) , 200 ) )
631+ } ) . pipe ( Effect . catchAll ( errorResponse ) )
632+
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+ */
646+ export const federationOutboxDocumentResponse = ( ) =>
647+ Effect . gen ( function * ( _ ) {
648+ const request = yield * _ ( HttpServerRequest . HttpServerRequest )
649+ const context = yield * _ ( resolveFederationContext ( request ) )
650+ return yield * _ ( jsonLdResponse ( makeFederationOutboxCollection ( context ) , 200 ) )
651+ } ) . pipe ( Effect . catchAll ( errorResponse ) )
652+
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+ */
666+ export const federationFollowersDocumentResponse = ( ) =>
667+ Effect . gen ( function * ( _ ) {
668+ const request = yield * _ ( HttpServerRequest . HttpServerRequest )
669+ const context = yield * _ ( resolveFederationContext ( request ) )
670+ return yield * _ ( jsonLdResponse ( makeFederationFollowersCollection ( context ) , 200 ) )
671+ } ) . pipe ( Effect . catchAll ( errorResponse ) )
672+
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+ */
686+ export const federationFollowingDocumentResponse = ( ) =>
687+ Effect . gen ( function * ( _ ) {
688+ const request = yield * _ ( HttpServerRequest . HttpServerRequest )
689+ const context = yield * _ ( resolveFederationContext ( request ) )
690+ return yield * _ ( jsonLdResponse ( makeFederationFollowingCollection ( context ) , 200 ) )
691+ } ) . pipe ( Effect . catchAll ( errorResponse ) )
692+
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+ */
706+ export const federationLikedDocumentResponse = ( ) =>
707+ Effect . gen ( function * ( _ ) {
708+ const request = yield * _ ( HttpServerRequest . HttpServerRequest )
709+ const context = yield * _ ( resolveFederationContext ( request ) )
710+ return yield * _ ( jsonLdResponse ( makeFederationLikedCollection ( context ) , 200 ) )
711+ } ) . pipe ( Effect . catchAll ( errorResponse ) )
712+
598713const terminalWebSocketUpgradeResponse = Effect . gen ( function * ( _ ) {
599714 const request = yield * _ ( HttpServerRequest . HttpServerRequest )
600715 const upgrade = readHeader ( request , "upgrade" ) ?. toLowerCase ( )
@@ -859,43 +974,23 @@ export const makeRouter = () => {
859974 ) ,
860975 HttpRouter . get (
861976 "/federation/actor" ,
862- Effect . gen ( function * ( _ ) {
863- const request = yield * _ ( HttpServerRequest . HttpServerRequest )
864- const context = yield * _ ( resolveFederationContext ( request ) )
865- return yield * _ ( activityJsonResponse ( makeFederationActorDocument ( context ) , 200 ) )
866- } ) . pipe ( Effect . catchAll ( errorResponse ) )
977+ federationActorDocumentResponse ( )
867978 ) ,
868979 HttpRouter . get (
869980 "/federation/outbox" ,
870- Effect . gen ( function * ( _ ) {
871- const request = yield * _ ( HttpServerRequest . HttpServerRequest )
872- const context = yield * _ ( resolveFederationContext ( request ) )
873- return yield * _ ( activityJsonResponse ( makeFederationOutboxCollection ( context ) , 200 ) )
874- } ) . pipe ( Effect . catchAll ( errorResponse ) )
981+ federationOutboxDocumentResponse ( )
875982 ) ,
876983 HttpRouter . get (
877984 "/federation/followers" ,
878- Effect . gen ( function * ( _ ) {
879- const request = yield * _ ( HttpServerRequest . HttpServerRequest )
880- const context = yield * _ ( resolveFederationContext ( request ) )
881- return yield * _ ( activityJsonResponse ( makeFederationFollowersCollection ( context ) , 200 ) )
882- } ) . pipe ( Effect . catchAll ( errorResponse ) )
985+ federationFollowersDocumentResponse ( )
883986 ) ,
884987 HttpRouter . get (
885988 "/federation/following" ,
886- Effect . gen ( function * ( _ ) {
887- const request = yield * _ ( HttpServerRequest . HttpServerRequest )
888- const context = yield * _ ( resolveFederationContext ( request ) )
889- return yield * _ ( activityJsonResponse ( makeFederationFollowingCollection ( context ) , 200 ) )
890- } ) . pipe ( Effect . catchAll ( errorResponse ) )
989+ federationFollowingDocumentResponse ( )
891990 ) ,
892991 HttpRouter . get (
893992 "/federation/liked" ,
894- Effect . gen ( function * ( _ ) {
895- const request = yield * _ ( HttpServerRequest . HttpServerRequest )
896- const context = yield * _ ( resolveFederationContext ( request ) )
897- return yield * _ ( activityJsonResponse ( makeFederationLikedCollection ( context ) , 200 ) )
898- } ) . pipe ( Effect . catchAll ( errorResponse ) )
993+ federationLikedDocumentResponse ( )
899994 ) ,
900995 HttpRouter . get (
901996 "/federation/status" ,
0 commit comments