Skip to content

Commit 1700485

Browse files
committed
feat(api): add ActivityPub schema parity proof
1 parent 9691b4c commit 1700485

7 files changed

Lines changed: 548 additions & 66 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import * as Schema from "effect/Schema"
2+
3+
import {
4+
activityStreamsJsonLdContext,
5+
forgeFedJsonLdContext,
6+
securityJsonLdContext
7+
} from "./contracts.js"
8+
9+
export type JsonPrimitive = boolean | number | string | null
10+
export type JsonValue = JsonPrimitive | JsonObject | ReadonlyArray<JsonValue>
11+
export type JsonObject = Readonly<{ [key: string]: JsonValue }>
12+
13+
export const JsonValueSchema: Schema.Schema<JsonValue> = Schema.suspend(() =>
14+
Schema.Union(
15+
Schema.Null,
16+
Schema.Boolean,
17+
Schema.Number,
18+
Schema.String,
19+
Schema.Array(JsonValueSchema),
20+
Schema.Record({ key: Schema.String, value: JsonValueSchema })
21+
)
22+
)
23+
24+
const OptionalString = Schema.optional(Schema.String)
25+
const OptionalBoolean = Schema.optional(Schema.Boolean)
26+
const JsonObjectSchema = Schema.Record({ key: Schema.String, value: JsonValueSchema })
27+
const JsonLdContextEntrySchema = Schema.Union(Schema.String, JsonObjectSchema)
28+
29+
export const ActivityForgeFedJsonLdContextSchema = Schema.Tuple(
30+
Schema.Literal(activityStreamsJsonLdContext),
31+
Schema.Literal(forgeFedJsonLdContext)
32+
)
33+
34+
export const ActorJsonLdContextSchema = Schema.Tuple(
35+
Schema.Literal(activityStreamsJsonLdContext),
36+
Schema.Literal(securityJsonLdContext),
37+
Schema.Literal(forgeFedJsonLdContext)
38+
)
39+
40+
export const JsonLdContextSchema = Schema.Union(
41+
Schema.String,
42+
JsonObjectSchema,
43+
Schema.Array(JsonLdContextEntrySchema)
44+
)
45+
46+
export const ForgeFedTicketSourceSchema = Schema.Struct({
47+
content: OptionalString,
48+
mediaType: OptionalString
49+
})
50+
51+
export const ForgeFedTicketSchema = Schema.Struct({
52+
id: Schema.String,
53+
attributedTo: Schema.String,
54+
summary: Schema.String,
55+
content: Schema.String,
56+
mediaType: OptionalString,
57+
source: Schema.optional(Schema.Union(Schema.String, ForgeFedTicketSourceSchema)),
58+
published: OptionalString,
59+
updated: OptionalString,
60+
url: OptionalString,
61+
context: OptionalString,
62+
workType: OptionalString,
63+
attachment: Schema.optional(Schema.Array(Schema.Unknown)),
64+
raw: Schema.optional(Schema.Unknown)
65+
})
66+
67+
export const ActivityPubPublicKeySchema = Schema.Struct({
68+
id: Schema.String,
69+
owner: Schema.String,
70+
publicKeyPem: Schema.String
71+
})
72+
73+
const ActivityPubEndpointsSchema = Schema.Struct({
74+
sharedInbox: OptionalString
75+
})
76+
77+
const ActivityPubInteractionPolicySchema = Schema.Record({
78+
key: Schema.String,
79+
value: JsonValueSchema
80+
})
81+
82+
export const ActivityPubPersonSchema = Schema.Struct({
83+
"@context": JsonLdContextSchema,
84+
type: Schema.Literal("Person"),
85+
id: Schema.String,
86+
name: Schema.String,
87+
preferredUsername: Schema.String,
88+
summary: Schema.String,
89+
inbox: Schema.String,
90+
outbox: Schema.String,
91+
followers: Schema.String,
92+
following: Schema.String,
93+
liked: OptionalString,
94+
publicKey: Schema.optional(ActivityPubPublicKeySchema),
95+
endpoints: Schema.optional(ActivityPubEndpointsSchema),
96+
webfinger: OptionalString,
97+
featured: OptionalString,
98+
featuredTags: OptionalString,
99+
url: OptionalString,
100+
manuallyApprovesFollowers: OptionalBoolean,
101+
discoverable: OptionalBoolean,
102+
indexable: OptionalBoolean,
103+
published: OptionalString,
104+
memorial: OptionalBoolean,
105+
showFeatured: OptionalBoolean,
106+
showMedia: OptionalBoolean,
107+
showRepliesInMedia: OptionalBoolean,
108+
interactionPolicy: Schema.optional(ActivityPubInteractionPolicySchema),
109+
featuredCollections: OptionalString,
110+
tag: Schema.optional(Schema.Array(JsonValueSchema)),
111+
attachment: Schema.optional(Schema.Array(JsonValueSchema))
112+
})
113+
114+
export const ActivityPubFollowActivitySchema = Schema.Struct({
115+
"@context": ActivityForgeFedJsonLdContextSchema,
116+
id: Schema.String,
117+
type: Schema.Literal("Follow"),
118+
actor: Schema.String,
119+
object: Schema.String,
120+
to: Schema.optional(Schema.Array(Schema.String)),
121+
capability: OptionalString
122+
})
123+
124+
export const ActivityPubOrderedCollectionSchema = Schema.Struct({
125+
"@context": JsonLdContextSchema,
126+
type: Schema.Literal("OrderedCollection"),
127+
id: Schema.String,
128+
totalItems: Schema.Number,
129+
first: OptionalString,
130+
last: OptionalString,
131+
current: OptionalString,
132+
orderedItems: Schema.optionalWith(Schema.Array(Schema.Unknown), { default: () => [] })
133+
})
134+
135+
export const ActivityPubOrderedCollectionPageSchema = Schema.Struct({
136+
"@context": JsonLdContextSchema,
137+
type: Schema.Literal("OrderedCollectionPage"),
138+
id: Schema.String,
139+
totalItems: Schema.Number,
140+
partOf: Schema.String,
141+
next: OptionalString,
142+
prev: OptionalString,
143+
orderedItems: Schema.Array(Schema.Unknown)
144+
})

packages/api/src/api/contracts.ts

Lines changed: 25 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
import type * as Schema from "effect/Schema"
2+
3+
import type {
4+
ActivityForgeFedJsonLdContextSchema,
5+
ActivityPubFollowActivitySchema,
6+
ActivityPubOrderedCollectionPageSchema,
7+
ActivityPubOrderedCollectionSchema,
8+
ActivityPubPersonSchema,
9+
ActivityPubPublicKeySchema,
10+
ActorJsonLdContextSchema,
11+
ForgeFedTicketSchema,
12+
ForgeFedTicketSourceSchema
13+
} from "./activitypub-schema.js"
14+
115
export type ProjectStatus = "running" | "stopped" | "unknown"
216

317
export type AgentProvider = "codex" | "opencode" | "claude" | "grok" | "custom"
@@ -556,29 +570,12 @@ export const federationJsonLdContentType =
556570
export const federationJsonLdResponseContentType =
557571
`${federationJsonLdContentType}; charset=utf-8` as const
558572

559-
export type ActivityForgeFedJsonLdContext = typeof activityForgeFedJsonLdContext
560-
export type ActorJsonLdContext = typeof actorJsonLdContext
573+
export type ActivityForgeFedJsonLdContext = Schema.Schema.Type<typeof ActivityForgeFedJsonLdContextSchema>
574+
export type ActorJsonLdContext = Schema.Schema.Type<typeof ActorJsonLdContextSchema>
561575

562-
export type ForgeFedTicket = {
563-
readonly id: string
564-
readonly attributedTo: string
565-
readonly summary: string
566-
readonly content: string
567-
readonly mediaType?: string | undefined
568-
readonly source?: string | ForgeFedTicketSource | undefined
569-
readonly published?: string | undefined
570-
readonly updated?: string | undefined
571-
readonly url?: string | undefined
572-
readonly context?: string | undefined
573-
readonly workType?: string | undefined
574-
readonly attachment?: ReadonlyArray<unknown> | undefined
575-
readonly raw?: unknown | undefined
576-
}
576+
export type ForgeFedTicket = Schema.Schema.Type<typeof ForgeFedTicketSchema>
577577

578-
export type ForgeFedTicketSource = {
579-
readonly content?: string | undefined
580-
readonly mediaType?: string | undefined
581-
}
578+
export type ForgeFedTicketSource = Schema.Schema.Type<typeof ForgeFedTicketSourceSchema>
582579

583580
export type FederationIssueStatus =
584581
| "offered"
@@ -618,47 +615,15 @@ export type CreateFollowRequest = {
618615

619616
export type FollowStatus = "pending" | "accepted" | "rejected"
620617

621-
export type ActivityPubFollowActivity = {
622-
readonly "@context": ActivityForgeFedJsonLdContext
623-
readonly id: string
624-
readonly type: "Follow"
625-
readonly actor: string
626-
readonly object: string
627-
readonly to?: ReadonlyArray<string> | undefined
628-
readonly capability?: string | undefined
629-
}
618+
export type ActivityPubFollowActivity = Schema.Schema.Type<typeof ActivityPubFollowActivitySchema>
630619

631-
export type ActivityPubPublicKey = {
632-
readonly id: string
633-
readonly owner: string
634-
readonly publicKeyPem: string
635-
}
620+
export type ActivityPubPublicKey = Schema.Schema.Type<typeof ActivityPubPublicKeySchema>
636621

637-
export type ActivityPubPerson = {
638-
readonly "@context": ActorJsonLdContext
639-
readonly type: "Person"
640-
readonly id: string
641-
readonly name: string
642-
readonly preferredUsername: string
643-
readonly summary: string
644-
readonly inbox: string
645-
readonly outbox: string
646-
readonly followers: string
647-
readonly following: string
648-
readonly liked: string
649-
readonly publicKey?: ActivityPubPublicKey | undefined
650-
readonly endpoints?: {
651-
readonly sharedInbox?: string | undefined
652-
} | undefined
653-
}
654-
655-
export type ActivityPubOrderedCollection = {
656-
readonly "@context": ActivityForgeFedJsonLdContext
657-
readonly type: "OrderedCollection"
658-
readonly id: string
659-
readonly totalItems: number
660-
readonly orderedItems: ReadonlyArray<unknown>
661-
}
622+
export type ActivityPubPerson = Schema.Schema.Type<typeof ActivityPubPersonSchema>
623+
624+
export type ActivityPubOrderedCollection = Schema.Schema.Type<typeof ActivityPubOrderedCollectionSchema>
625+
626+
export type ActivityPubOrderedCollectionPage = Schema.Schema.Type<typeof ActivityPubOrderedCollectionPageSchema>
662627

663628
export type FollowSubscription = {
664629
readonly id: string

packages/api/src/api/schema.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
import * as Schema from "effect/Schema"
22

3+
export {
4+
ActivityForgeFedJsonLdContextSchema,
5+
ActivityPubFollowActivitySchema,
6+
ActivityPubOrderedCollectionPageSchema,
7+
ActivityPubOrderedCollectionSchema,
8+
ActivityPubPersonSchema,
9+
ActivityPubPublicKeySchema,
10+
ActorJsonLdContextSchema,
11+
ForgeFedTicketSchema,
12+
ForgeFedTicketSourceSchema,
13+
JsonLdContextSchema,
14+
JsonValueSchema
15+
} from "./activitypub-schema.js"
16+
317
const OptionalString = Schema.optional(Schema.String)
418
const OptionalBoolean = Schema.optional(Schema.Boolean)
519
const OptionalNullableString = Schema.optional(Schema.NullOr(Schema.String))

0 commit comments

Comments
 (0)