The shapes to which domain data must conform
The shapes to which domain data must conform.
Extension declaration. Insert into the domain data to install the
extension. For example (assuming a m-ld clone
object):
clone.write(ShapeConstrained.declare(0, PropertyShape.declare({
path: 'name', count: 1
})));
Note that declaration of shapes will not retrospectively apply constraints to any existing subjects in the domain. It's the app's responsibility to correct existing data, if necessary.
the preferred index into the existing list of extensions (lower value is higher priority).
shape Subjects, or References to pre-existing shapes
Generated using TypeDoc. Delivered by Vercel. @m-ld/m-ld - v0.10.1-edge.4 Source code licensed MIT. Privacy policy
This extension allows an app to declare that the domain data must conform to some defined shapes. A collection of shapes is like a 'schema' or 'object model'.
The extension can be declared in the data using declare, or instantiated and provided to the clone function in the
app
parameter, e.g.api = await clone( new MemoryLevel, MqttRemotes, config, new ShapeConstrained(new PropertyShape({ path: 'name', count: 1 })) );
import { clone, uuid } from 'https://js.m-ld.org/ext/index.mjs'; import { MemoryLevel } from 'https://js.m-ld.org/ext/memory-level.mjs'; import { IoRemotes } from 'https://js.m-ld.org/ext/socket.io.mjs'; // m-ld extensions are loaded using their package identity (@m-ld/m-ld/ext/..). // In a real app, this redirection should be done with an import map. globalThis.require = module => import(module .replace(/@m-ld\/m-ld\/ext\/(\w+)/, 'https://js.m-ld.org/ext/$1.mjs')); globalThis.changeDomain = async function (domain) { const genesis = !domain; if (genesis) domain = `${uuid()}.public.gw.m-ld.org`; if (window.model) { await window.model.state.close(); delete window.model; } const state = await clone(new MemoryLevel(), IoRemotes, { '@id': uuid(), '@domain': domain, genesis, io: { uri: "https://gw.m-ld.org" } }); // Uncomment the next line to log individual updates as they come in //state.follow(update => console.info('UDPATE', JSON.stringify(update))); domainInput.value = domain; appDiv.hidden = false; playgroundAnchor.setAttribute('href', `https://m-ld.org/playground/#domain=${domain}`); // Store the "model" as a global for access by other scripts, and tell them window.model = { domain, state, genesis }; document.dispatchEvent(new Event('domainChanged')); } /** * Utility to populate a template. Returns an object containing the cloned * children of the template, also indexed by tagName and classname. */ globalThis.templated = template => new Proxy({ $: template.content.cloneNode(true) }, { get: (t, p) => t[p] ?? t.$.querySelector(p) ?? t.$.querySelector(`.${p}`) }); document.querySelectorAll('.help').forEach(help => helpDetails.appendChild(templated(help).$));
<div> <a id="playgroundAnchor" target="_blank" title="go to playground">🛝</a> <input id="domainInput" type="text" placeholder="domain name" onfocus="this.select()"/> <button onclick="changeDomain(domainInput.value)">Join</button> <button onclick="changeDomain()">New ⭐️</button> <details id="helpDetails"> <summary>🔢 help...</summary> <p>This live code demo shows how to share live information with <b>m-ld</b>.</p> <p>To get started with a new set of information (a "domain"), click New ⭐️ above. You can then interact with the mini-application below to create some information.</p> <p>To share the information with a duplicate of this page:<ol><li>copy the domain name above</li><li>duplicate the browser tab</li><li>paste the domain name into the new page's domain input</li><li>click Join</li></ol></p> <p>You can also share with the <b>m-ld</b> playground using the 🛝 button.</p> </details> <hr/> </div>
import { updateSubject } from 'https://js.m-ld.org/ext/index.mjs'; import { ShapeConstrained, PropertyShape } from 'https://js.m-ld.org/ext/shacl.mjs'; document.addEventListener('domainChanged', async () => { if (window.model.genesis && false) { // 1️⃣ await window.model.state.write( ShapeConstrained.declare(0, PropertyShape.declare({ path: 'name', count: 1 })) ); } const author = { '@id': 'author', // Naive UI ↔︎ data mapping, don't do this! 2️⃣ set name(name) { nameInput.value = name; }, get name() { return nameInput.value.split(',').filter(v => v); } }; await window.model.state.read( async state => updateSubject(author, await state.get('author')), update => updateSubject(author, update) ); beginEditSession(); }); function beginEditSession() { window.model.state.write(state => new Promise(release => { // 3️⃣ const oldName = nameInput.value; nameInput.readOnly = false; nameInput.focus(); editButton.innerText = 'Enter'; editButton.addEventListener('click', async function enter() { if (nameInput.value) await state.write({ '@update': { '@id': 'author', name: nameInput.value } }); else nameInput.value = oldName; // Revert nameInput.readOnly = true; editButton.innerText = 'Edit'; editButton.removeEventListener('click', enter); release(); }); })); } editButton.addEventListener('click', () => { if (nameInput.readOnly) beginEditSession(); });
<div id="appDiv" hidden> <h2>Author</h2> <label for="nameInput">Name:</label> <input id="nameInput" type="text" readonly/> <button id="editButton">Edit</button> </div> <template class="help"> <p> This example shows how "conflicts" can arise in user sessions, and one way to change the default behaviour of <b>m-ld</b>, using <i>Shapes</i>. </p> <p> In our app, we intend that the "author" subject should have only one "name" property value. Our user interface presents the name, and allows us to change it using the Edit button. However, if another user simultaneously changes the name, it's possible for the author to end up with <i>both</i> entered names. (Try it by following the instructions above to duplicate this tab, and beginning an edit in both tabs.) </p> <ul> <li> 1️⃣ Here we declare that the "name" property should have only one value. When you have explored the behaviour without this fix, change <code>false</code> to <code>true</code> in this line, and try again with a new domain. </li> <li> 2️⃣ Here we are relying on the behaviour of an HTML text input element – if you set its value to an array, it will separate the array values with a comma. This won't work as expected if the name you enter has a comma in it, so a more robust approach would be needed in a real app. </li> <li> 3️⃣ Using a <a href="https://js.m-ld.org/interfaces/meldstatemachine.html#write">"state procedure"</a> allows us to prevent <b>m-ld</b> from accepting remote updates until the returned promise settles. This means that we don't see the effect of a concurrent edit until our editing "session" is finished. </li> </ul> </template>
#nameInput[readonly] { border: none; }
https://www.w3.org/TR/shacl/