Specifies a graph edge, that is, a mapping from the @id
of this subject
to a set of one or more values.
A priority list of preferences
The second subject in this array shows how the first subject (the List) can
be referenced by another subject. When inserting data it might be more
readable to simply nest the list under the interests
property in the outer
subject, fred.
[{
"@id": "fredInterests",
"@list": ["Lounging", "Bowling", "Pool", "Golf", "Poker"]
}, {
"@id": "fred",
"interests": { "@id": "fredInterests" }
}]
A chronology of referenced subjects
{
"@id": "fredAppearsIn",
"@list": [
{ "@type": "Episode", "name": "The Flintstone Flyer" },
{ "@type": "Episode", "name": "Hot Lips Hannigan" },
{ "@type": "Episode", "name": "The Swimming Pool" }
]
}
🚧 This engine does not support use of the
@list
keyword in a JSON-LD Context term definition.
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';
document.addEventListener('domainChanged', async () => {
shoppingList.innerHTML = '';
const { state, genesis } = window.model;
if (genesis) {
// Write some initial shopping items to the state
state.write({
'@id': 'shopping',
'@list': ['bread', 'milk']
});
}
// To use updateSubject for updating the DOM, we use a Javascript object-like
// proxy pattern over the relevant Elements.
const shopping = {
'@id': 'shopping',
'@list': {
// For a List, updateSubject can apply the update to anything with a
// `length` and an Array-like `splice` method.
get length() {
return shoppingList.childElementCount;
},
splice(index, deleteCount, ...items) {
for (let i = 0; i < deleteCount; i++)
shoppingList.children[index]?.remove();
const { el, position } = index < this.length ?
{ el: shoppingList.children[index], position: 'beforebegin' } :
{ el: shoppingList, position: 'beforeend' };
for (let item of items)
el.insertAdjacentHTML(position, `<li>${item}</li>`);
}
}
};
state.read(
async state => updateSubject(shopping, await state.get('shopping')),
update => updateSubject(shopping, update)
);
});
addItem.addEventListener('click', () => {
window.model.state.write({
'@id': 'shopping',
// When writing list items, we can use an object with integer keys instead
// of an array. Here we're inserting at the end of the list.
'@list': { [shoppingList.childElementCount]: itemToAdd.value }
});
});
removeItem.addEventListener('click', () => {
window.model.state.write({
'@delete': {
'@id': 'shopping',
// When deleting list items, we can pattern-match using variables. Here,
// we want to delete the removed item wherever it appears in the list.
'@list': { '?': itemToRemove.value }
}
});
});
<div id="appDiv" hidden>
<h2>Shopping</h2>
<ol id="shoppingList"></ol>
<p>
<input id="itemToAdd" type="text" placeholder="new shopping item"/>
<button id="addItem">+ Add</button>
</p>
<p>
<input id="itemToRemove" type="text" placeholder="shopping item to remove"/>
<button id="removeItem">- Remove</button>
</p>
<hr/>
</div>
Constructor of subjects from subjects: used similarly to e.g. Number
A JSON-LD Context
for the query. In an API, this will frequently be implicit. For example,
using json-rql as the body of a POST
to
http://example.com/my-api/v1/person/query
might have the implicit context
of a Person (possibly found at http://example.com/my-api/v1/person
).
The unique identity of the subject in the domain.
🚧 Subjects strictly need not be identified with an
@id
, but the data of such Subjects cannot be retrieved with a simple Describe query.
An array or indexed-object representation of the list contents. Each "item" in the list can be any of the normal subject property objects, such as strings, numbers, booleans or References to other subjects.
The indexed-object notation is used to insert or delete items at a specific list index, expressed as a number or numeric string. For more explanation, see the m-ld Lists specification.
The type of the subject, as an IRI or set of IRIs. (@type
is actually
shorthand for the RDF property
rdf:type.)
Generated using TypeDoc. Delivered by Vercel. @m-ld/m-ld - v0.10.1-edge.4 Source code licensed MIT. Privacy policy
Used to express an ordered set of data. A List object is reified to a Subject (unlike in JSON-LD) and so it has an @id, which can be set by the user.
Examples:
A priority list of preferences
The second subject in this array shows how the first subject (the List) can be referenced by another subject. When inserting data it might be more readable to simply nest the list under the
interests
property in the outer subject, fred.[{ "@id": "fredInterests", "@list": ["Lounging", "Bowling", "Pool", "Golf", "Poker"] }, { "@id": "fred", "interests": { "@id": "fredInterests" } }]
A chronology of referenced subjects
{ "@id": "fredAppearsIn", "@list": [ { "@type": "Episode", "name": "The Flintstone Flyer" }, { "@type": "Episode", "name": "Hot Lips Hannigan" }, { "@type": "Episode", "name": "The Swimming Pool" } ] }
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'; document.addEventListener('domainChanged', async () => { shoppingList.innerHTML = ''; const { state, genesis } = window.model; if (genesis) { // Write some initial shopping items to the state state.write({ '@id': 'shopping', '@list': ['bread', 'milk'] }); } // To use updateSubject for updating the DOM, we use a Javascript object-like // proxy pattern over the relevant Elements. const shopping = { '@id': 'shopping', '@list': { // For a List, updateSubject can apply the update to anything with a // `length` and an Array-like `splice` method. get length() { return shoppingList.childElementCount; }, splice(index, deleteCount, ...items) { for (let i = 0; i < deleteCount; i++) shoppingList.children[index]?.remove(); const { el, position } = index < this.length ? { el: shoppingList.children[index], position: 'beforebegin' } : { el: shoppingList, position: 'beforeend' }; for (let item of items) el.insertAdjacentHTML(position, `<li>${item}</li>`); } } }; state.read( async state => updateSubject(shopping, await state.get('shopping')), update => updateSubject(shopping, update) ); }); addItem.addEventListener('click', () => { window.model.state.write({ '@id': 'shopping', // When writing list items, we can use an object with integer keys instead // of an array. Here we're inserting at the end of the list. '@list': { [shoppingList.childElementCount]: itemToAdd.value } }); }); removeItem.addEventListener('click', () => { window.model.state.write({ '@delete': { '@id': 'shopping', // When deleting list items, we can pattern-match using variables. Here, // we want to delete the removed item wherever it appears in the list. '@list': { '?': itemToRemove.value } } }); });
<div id="appDiv" hidden> <h2>Shopping</h2> <ol id="shoppingList"></ol> <p> <input id="itemToAdd" type="text" placeholder="new shopping item"/> <button id="addItem">+ Add</button> </p> <p> <input id="itemToRemove" type="text" placeholder="shopping item to remove"/> <button id="removeItem">- Remove</button> </p> <hr/> </div>
m-ld Lists specification
json-rql list