Caduceus is a TypeScript library that simplifies real-time data synchronization between your client and server using the Mercure protocol. It provides an elegant way to subscribe to updates on API resources and keep your client-side data in sync with server changes.
Caduceus consists of two main components:
- Mercure - A client for the Mercure protocol that handles the low-level communication with a Mercure hub.
- HydraSynchronizer - A higher-level abstraction designed to synchronize Hydra/JSON-LD resources with real-time updates from a Mercure hub.
- π Real-time data synchronization
- π§ Automatic resource management
- π― Topic-based subscriptions
- π Flexible event handling
- π οΈ Customizable configuration options
import { HydraSynchronizer } from 'caduceus';
// Create a synchronizer connected to your Mercure hub
const synchronizer = new HydraSynchronizer('https://example.com/.well-known/mercure');
// A resource with an @id property (in Hydra/JSON-LD format)
const resource = {
'@id': '/api/books/1',
title: 'The Great Gatsby',
author: 'F. Scott Fitzgerald'
};
// Start synchronizing the resource
synchronizer.sync(resource);
// The resource object will now be automatically updated
// whenever changes are published to the Mercure hub
Important
By default, Caduceus uses the @id
property of the resource to determine the topic for Mercure subscriptions.
Synchronizing too many resources at once may lead to performance issues.
Consider using URI templates or a wildcard topic to reduce the number of subscriptions.
synchronizer.sync(resource, '/api/books/{id}');
// or
synchronizer.sync(resource, '*');
import { HydraSynchronizer } from 'caduceus';
const synchronizer = new HydraSynchronizer('https://example.com/.well-known/mercure');
const book = {
'@id': '/api/books/1',
title: 'The Great Gatsby',
author: 'F. Scott Fitzgerald'
};
// Start synchronizing
synchronizer.sync(book);
// Add a custom event handler
synchronizer.on(book, (updatedData, event) => {
console.log('That book was updated:', updatedData);
// You could trigger UI updates or other side effects here
});
For more control over the subscription process, you can use the Mercure
class directly:
import { Mercure, type MercureMessageEvent } from 'caduceus';
// Create a Mercure client
const mercure = new Mercure('https://example.com/.well-known/mercure');
// Subscribe to specific topics
mercure.subscribe(['/api/books/1', '/api/books/2']);
// Add an event listener
mercure.on('message', async (event: MercureMessageEvent) => {
const data = await event.json();
console.log('Received update:', data);
});
// Connect to the Mercure hub
mercure.connect();
// Later, you can unsubscribe from topics
mercure.unsubscribe('/api/books/2');
Both Mercure
and HydraSynchronizer
accept configuration options:
import { HydraSynchronizer, DefaultEventSourceFactory } from 'caduceus';
const synchronizer = new HydraSynchronizer('https://example.com/.well-known/mercure', {
// Custom event source factory
eventSourceFactory: new DefaultEventSourceFactory(),
// Custom last event ID (for resuming connections)
lastEventId: 'event-id-123',
// Custom resource listener
resourceListener: (resource) => (data) => {
console.log(`Resource ${resource['@id']} updated`);
Object.assign(resource, data);
},
// Custom subscribe options
subscribeOptions: {
append: false, // Replace rather than append topics
},
// Custom event handler
handler: (mercure, listeners) => {
mercure.on('message', async (event) => {
// Custom message handling logic
const data = await event.json();
// ...
});
},
});
The Mercure
class provides a low-level client for a Mercure hub:
constructor(hub: string | URL, options?: Partial<MercureOptions>)
hub
: URL of the Mercure huboptions
: Configuration optionseventSourceFactory
: Factory for creating EventSource instanceslastEventId
: ID of the last event received (for resuming)
subscribe(topic: Topic | Topic[], options?: Partial<SubscribeOptions>): void
- Subscribe to one or more topicsunsubscribe(topic: Topic | Topic[]): void
- Unsubscribe from one or more topicson(type: string, listener: Listener): void
- Add an event listenerconnect(): EventSourceInterface
- Connect to the Mercure hub
An EventSourceFactory
implementation that uses the mercureAuthorization
cookie for authorization when connecting to a Mercure hub.
import { CookieBasedAuthorization, Mercure } from 'caduceus'
const mercure = new Mercure('https://example.com/.well-known/mercure', {
eventSourceFactory: new CookieBasedAuthorization(),
});
An EventSourceFactory
implementation that adds an authorization token as a query parameter when connecting to a Mercure hub.
import { QueryParamAuthorization, Mercure } from 'caduceus'
const token = 'your-jwt-token';
const mercure = new Mercure('https://example.com/.well-known/mercure', {
eventSourceFactory: new QueryParamAuthorization(token),
});
The HydraSynchronizer
class provides a higher-level abstraction for synchronizing resources:
constructor(hub: string | URL, options?: Partial<HydraSynchronizerOptions>)
hub
: URL of the Mercure huboptions
: Configuration optionsresourceListener
: Function to create a listener for a specific resourcesubscribeOptions
: Options for subscribing to topicshandler
: Custom handler for processing events- (plus all options from
MercureOptions
)
sync(resource: ApiResource, topic?: string, subscribeOptions?: Partial<SubscribeOptions>)
- Start synchronizing a resourceon(resource: ApiResource, callback: Listener)
- Add a listener for a specific resourceunsync(resource: ApiResource)
- Stop synchronizing a resource