Overview

Collection factories sit on the atom namespace: atom.list, atom.advancedList, atom.stack, atom.queue, atom.Map, and atom.Set (capitalized to avoid clashing with instance .set / .map).

atom.list is for ordered UI rows with unique readonly keys (default id). Raw .set/.update throw — use setList, add, updateItem, etc. Primitives must go through createList first.

advancedList wraps a list with search, filters, sort, and pagination (static local pipeline or remote load). Map/Set are reactive wrappers around the native collections.

Which collection?

API
Shape
Prefer when
atom.array T[] General arrays — see typed factories.
atom.list Keyed rows Dynamic UI lists needing stable unique ids.
advancedList Controller Search / filter / sort / page (or remote load).
atom.stack / .queue LIFO / FIFO Undo stacks, job queues.
atom.Map / .Set Native Map/Set Keyed bags / unique membership with reactive reads.

atom.list — keyed rows

Unique readonly id stamped on add. updateItem mutates a row; delete by key.

import { html, atom, renderApp } from 'mates';

const App = () => {
const todos = atom.list([
{ name: 'Write docs', done: false },
{ name: 'Ship', done: false },
]);

return () => html`
<div class="m-col m-gap">
<ul class="muted">
${todos.render((row) => html`
<li class="m-flex m-items-center m-justify-center m-gap-sm" style="justify-content:space-between;gap:8px">
<label>
<input type="checkbox" .checked=${row.done}
@change=${() => todos.updateItem(row.id, (r) => { r.done = !r.done; })} />
${row.name}
</label>
<button class="btn-ghost" @click=${() => todos.delete(row.id)}>×</button>
</li>
`)}
</ul>
<div class="m-flex m-items-center m-justify-center m-gap-sm">
<button class="btn-primary" @click=${() => todos.add({ name: 'New', done: false })}>
add
</button>
<button @click=${() => todos.clear()}>clear</button>
</div>
<p class="muted m-t-5">size=${todos.size} · keyName=${todos.keyName}</p>
</div>
`;
};
renderApp(App, document.getElementById('app'));

stack + queue

LIFO push/pop vs FIFO enqueue/dequeue.

import { html, atom, renderApp } from 'mates';

const App = () => {
const stack = atom.stack(['a', 'b']);
const queue = atom.queue(['x', 'y']);

return () => html`
<div class="m-col m-gap">
<div class="m-grid m-grid-cols-3 m-gap">
<div class="card card-body center">
<span class="overline">stack (LIFO)</span>
<span class="card-title">[${stack.toArray().join(', ')}]</span>
</div>
<div class="card card-body center">
<span class="overline">queue (FIFO)</span>
<span class="card-title">[${queue.toArray().join(', ')}]</span>
</div>
</div>
<div class="m-flex m-items-center m-justify-center m-gap-sm">
<button class="btn-primary" @click=${() => stack.push(String(stack.size + 1))}>push</button>
<button @click=${() => stack.pop()}>pop</button>
<button class="btn-primary" @click=${() => queue.enqueue(String(queue.size + 1))}>enqueue</button>
<button @click=${() => queue.dequeue()}>dequeue</button>
</div>
</div>
`;
};
renderApp(App, document.getElementById('app'));

Map + Set

Native-shaped reactive collections. Reads (size, iteration, get/has) track.

import { html, atom, renderApp } from 'mates';

const App = () => {
const scores = atom.Map([['Ada', 10], ['Grace', 8]]);
const tags = atom.Set(['docs', 'state']);

return () => html`
<div class="m-col m-gap">
<p class="muted">
Map: ${[...scores.entries()].map(([k, v]) => k + '=' + v).join(', ')}
</p>
<p class="muted">Set: ${[...tags].join(', ')}</p>
<div class="m-flex m-items-center m-justify-center m-gap-sm">
<button class="btn-primary" @click=${() =>
scores.set('Ada', (scores.get('Ada') ?? 0) + 1)
}>Ada +1</button>
<button @click=${() => tags.add('atom')}>add tag</button>
<button @click=${() => tags.delete('docs')}>delete docs</button>
</div>
<p class="muted m-t-5">map.size=${scores.size} · set.size=${tags.size}</p>
</div>
`;
};
renderApp(App, document.getElementById('app'));