How Applications in the same document find each other and communicate in real time.
|
|
'chart' or 'data-table' — and get back whatever Applications in the document are currently providing it.window.charmiq.advertise('chart', { // RPC methods — return values or Promises setData: (data) => { chart.update(data); return { success: true, count: data.length }; }, getSelectedPoint: () => { return chart.getSelection(); }, // Observable streams — method names ending with $ selection$: () => { return selectionSubject.asObservable(); }, });
$ return Observables. This distinction matters for consumers — $ methods are subscribed to, not awaited.window.charmiq.discover('chart').subscribe(async providers => { if (providers.length === 0) { updateUI('no chart available'); return; } const chart = providers[0]; // Call an RPC method await chart.setData([ { x: 1, y: 10 }, { x: 2, y: 20 }, ]); // Subscribe to a stream chart.selection$().subscribe(point => { highlightRow(point.id); }); });
discover() returns an Observable that emits the current list of providers immediately, then again whenever the list changes. Always handle the empty case — the provider might not exist yet.discover() observable handles the full lifecycle:advertise(). The discover subscription fires with the updated provider list.let chartProxy; window.charmiq.discover('chart').subscribe(providers => { chartProxy = providers[0] || undefined; updateConnectionStatus(chartProxy ? 'connected' : 'disconnected'); }); // Later, when you need to call: if (chartProxy) { await chartProxy.setData(newData); }
postMessage, which means it must be serializable. The structured clone algorithm handles: primitives, plain objects, arrays, Dates, typed arrays. It does not handle: functions, DOM nodes, class instances with methods, Symbols.// CORRECT — returns data window.charmiq.advertise('math', { add: (a, b) => a + b, // ✅ returns a number computeAsync: async () => result, // ✅ Promise resolves to data }); // INCORRECT — returns a function widgetAPI.advertise('math', { getAdder: () => (a, b) => a + b, // ❌ functions can't be cloned (DataCloneError) });
const dataSubject = new rxjs.BehaviorSubject([]); // Incoming data from an external source (MCP, form input, etc.) window.charmiq.appContent.onChange$("[name='ingest']").subscribe(change => { const records = change.content .split('\n') .filter(l => l.trim()) .map(l => JSON.parse(l)); dataSubject.next(records); }); // Advertise to other Applications window.charmiq.advertise('data-source', { getData: () => dataSubject.getValue(), data$: () => dataSubject.asObservable(), });
window.charmiq.discover('data-source').pipe( rxjs.switchMap(providers => providers[0] ? providers[0].data$() : rxjs.EMPTY ) ).subscribe(async records => { const results = await runAnalysis(records); await window.charmiq.appContent.set(JSON.stringify(results), "[name='output']"); });
window.charmiq.appContent.onChange$("[name='output']").subscribe(change => { const results = JSON.parse(change.content || '{}'); renderChart(results); });
charmiq.command Namespacecharmiq.* namespace support multiple providers simultaneously — unlike custom capabilities, which are single-provider by design. The charmiq.command capability is used for command registration: any Application can advertise handlers for document-level commands, and the parent editor can invoke specific Applications by node ID.window.charmiq.advertise('charmiq.command', { exportData: () => { /* ... */ }, refresh: () => { /* ... */ }, clear: () => { /* ... */ }, });
discover() subscription fires immediately, which means it may fire with an empty array before any provider has loaded. Your consumer code must handle this gracefully.rxjs.switchMap for stream composition. When you want to subscribe to a provider's stream but only when a provider exists, switchMap handles the switching cleanly:window.charmiq.discover('data-source').pipe( rxjs.switchMap(providers => providers[0]?.data$() ?? rxjs.EMPTY) ).subscribe(handleData);
'chart' is clearer than 'visualization-tool'. A consumer that knows what it's looking for finds it faster.postMessage on every call. For high-frequency updates or large datasets, write to app-content and have the consumer watch that instead.window.charmiq methods.