Automattic / Automattic/wordpress-activitypub
Build the reusable C2S event-sourcing client core (store + reducer + controller)
- Dominant language
- PHP
- Stars
- 579
- Forks
- 92
- Avg merge
- 13h 5m
- Merged PRs (30d)
- 85
Description
## Goal
A **framework-agnostic** ActivityPub C2S client core (`src/c2s-client/`, zero React/WP imports) implementing the event-sourcing projection: **event log → replay reducer → IRI-keyed read model**. Reusable by any conformant C2S server; extractable as a package. This is the bulk of the value and is 100% unit-testable with no network.
## Files
* `types.ts` — AS2 types (`Activity`, `ASObject`, `IRI`, `OrderedCollection(Page)`), store shape, injected interfaces.
* `store.ts` — normalized read model: `objects: Record`, `members: Record`, `links`, `status`; `subscribe()/getSnapshot()` (memoized, `useSyncExternalStore`-compatible). **Owns list membership** (what core-data can't do).
* `reducer.ts` — pure `apply(state, op)`. Two normalizers → a common op `{ kind: 'add'|'update'|'remove', iri, object?, mode }` keyed by the event's `object`/`target` IRI:
* **backfill** keys on the AS2 activity type (`Create`/`Announce`/`Like`/`Update`/`Delete`/`Undo`);
* **live tail** keys on the SSE event type (`Add`/`Update`/`Remove`/`Delete`), using the embedded activity to disambiguate the collapsed `Add`.
* `add` → upsert by IRI, prepend (live)/append (historical); dedupe → update. `update` → shallow-merge. `remove` → drop object+membership (handle bare-IRI/`Tombstone`). Unknown → same state ref.
* `controller.ts` — replays the paged log (`first`→`next`), then (if present) discovers the stream via the `eventStream` property and attaches the live tail. Idempotent folding + **re-read-head consistency** (no reliance on `Last-Event-ID`; best-effort resume only).
* `transport.ts` — interfaces only: `Transport` (paged GET), `StreamFactory` (EventSource-like), `AuthProvider` (inject token via header or `?access_token=`; scope-agnostic). No concrete impls.
* `index.ts`, `README.md` (extractability note), `__tests__/{reducer,controller}.test.ts`.
## Acceptance
* Replaying a scripted log (mock pages + mock stream) yields the correct materialized `getSnapshot().records`; folding is idempotent (overlap/re-read converge); unknown activities are no-ops.
## Tests (the bulk of the value, no network)
* `reducer.test.ts`: add→update same-IRI merge (no dup), remove, Announce/Like link+Undo, ordering (prepend vs append), backfill↔stream dedupe by IRI, partial-update merge, unknown → same ref.
* `controller.test.ts`: paged replay walks `first`→`next`; reconnect re-reads head and converges.
## Notes
Reuses the server it talks to (`includes/rest/{class-outbox-controller,trait-collection,trait-event-stream}.php`) — **no new endpoints**. Auth/scope are a binding concern (Automattic/wordpress-activitypub#3428), not the core.
Contributor guide
Assessment
This issue has not been assessed yet.