Published
Edited
Apr 14, 2021
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
tableCSS = html`
<style>
.AtomicCounter {
background-color: WhiteSmoke;
text-align: center;
padding: 6px;
border-radius: 20px;
}

.AtomicCounter > .counter-name {
font-weight: bold;
}
.AtomicCounter > .counter-value {
font-weight: bold;
font-size: 150%;
}
.AtomicCounter > .incrementor {
margin: 6px
}
</style>
`;
Insert cell
Insert cell
async function CounterTopicGUI(topicPath, session) {
type(arguments, ['string', /.*/]);
const controller = await CounterTopic.build(topicPath, session);
const result = html`

<div class="AtomicCounter">
<div class="counter-name">my/counter</div>
<div class="counter-value">${controller.knownValue}</div>
<button class="incrementor">Increment</button>
</div>`;

const buttonElem = result.querySelector("button.incrementor");
const valueElem = result.querySelector(".counter-value");
buttonElem.onclick = () => {
controller.increment().then(value => {
valueElem.innerHTML = value;
});
};

result.controller = controller;
return result;
}
Insert cell
Insert cell
class CounterTopic {
static async build(topicPath, session, topicValue = 0) {
type(arguments, ['string', /.*/, 'number|undefined']);
// Create the topic and set value, if doesn't exist.
try {
await session.topicUpdate.set(
topicPath,
diffusion.datatypes.json(),
topicValue,
{
specification: new diffusion.topics.TopicSpecification(
diffusion.topics.TopicType.JSON
),
constraint: diffusion.updateConstraints().noTopic()
}
)
// Build the object
return new CounterTopic(topicPath, session, topicValue);
} catch(err) {
if (err?.reason.startsWith("Constraint NoTopic")) {
const actualValue = await CounterTopic.fetch(session, topicPath);
return new CounterTopic(topicPath, session, actualValue);
} else {
throw err;
}
}

}

// a private constructor
constructor(topicPath, session, value) {
type(arguments, ['string', /.*/, 'number']);
this.topicPath = topicPath;
this.session = session;
this.value = value;
}

async increment() {
while (true) {
const patch = [
{ op: "test", path: "", value: this.value }, // Test the value has not changed
{ op: "replace", path: "", value: this.value + 1 } // Increment if not
];

// Atomically set the topic to the next known value, or fail
const result = await this.session.topicUpdate.applyJsonPatch(
this.topicPath,
patch
);

// Return if success
if (result.failedOperation === undefined) {
this.value++;
return this.value;
}

// Failed - fetch the new value - and repeat
this.value = await CounterTopic.fetch(this.session, this.topicPath);
}
}

get knownValue() {
return this.value;
}

// private
static async fetch(session, topicPath) {
type(arguments, [/.*/, 'string']);
const fetchResult = await session
.fetchRequest()
.withValues(diffusion.datatypes.json())
.fetch(topicPath);

const topicRows = fetchResult.results();

if (topicRows.length < 1) {
throw Error("Topic not found: " + topicPath);
}
return topicRows[0].value().get();
}
}
Insert cell
Insert cell
session = await diffusion.connect({
host: serverURL,
principal: "myuser",
credentials: "hunter2"
})
Insert cell
Insert cell
import {
diffusion,
serverURL
} from "@martin-cowie/loading-the-diffusion-library-within-observable"
Insert cell
type = require('https://bundle.run/typeof-arguments@5.1.3')
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more