Wardian docs

Keeping data

A suite app keeps data in one of two places, kept by Wardian in its data folder. Choose by size and shape.

UseForCapabilityExample
ctx.storesmall settings and state: the last inputs, a list of habitsstoragehabit-tracker
ctx.cap('db')data: rows to page, sort, filter or sharedbcsv-explorer

A page app has neither. It is sandboxed and keeps nothing between visits. To keep data in a page, let the user download it and load it again. Or make it a suite.

Small state: storage#

Declare storage in the part's caps, in suite.json and in Kernel.register:

{ "name": "inputs", "slot": "aside", "caps": ["storage"] }

Then read and write by key:

const saved = ctx.store.get('loan') || { amount: 300000, rate: 6, years: 30 };
ctx.store.set('loan', { amount, rate, years });

Wardian keeps the data in DATA_DIR/state/, so a restart, another browser or cleared site data does not lose it (ADR-2610071055).

Tables: db#

A part that declares db gets the package's own SQLite database, kept in DATA_DIR/db/<app>.sqlite (ADR-2610071219).

const db = await ctx.cap('db');

await db.insertRows({
  table: 'runs', columns: ['n', 'x'],
  rows: [[1, 980], [2, 1900]],
  create: true,                 // make the table if it is missing
});

const page = await db.page({
  table: 'runs', offset: 0, limit: 100,
  orderBy: 'x', desc: true,
  where: 'n > ?', params: [1],
});
// page = { columns, rows, total, offset }

const { columns, rows } = await db.query({ sql: 'SELECT n, avg(x) FROM runs GROUP BY n' });
MethodDoes
insertRows({table, columns, rows, create, replace})adds rows; create makes the table if it is missing, replace drops it and makes it again with these columns
page({table, offset, limit, orderBy, desc, where, params})one page of rows and the total; limit at most 1,000; read-only
query({sql, params})one SQL statement; at most 1,000 rows back
tables()the tables in this database
readPage({package, ...page})a page of another package's table, read-only, after the user allows it
searchInto({search, earliest, latest, table})loads a Splunk search into a table, up to 1,000,000 rows; needs splunk too

Show large tables a page at a time#

Never load every row into the frame. Ask for the page the viewer sees:

async function show(offset) {
  const { rows, total } = await db.page({ table: 'data', offset, limit: 100, orderBy: sortCol, desc });
  render(rows);
  status.textContent = `${offset + 1}–${offset + rows.length} of ${total.toLocaleString()}`;
}

csv-explorer pages 100,000 rows this way, and its sort, filter and export all run in SQL.

Always use placeholders#

Put user input in params, never in the SQL text:

// Right
db.page({ table: 'data', where: 'city LIKE ?', params: [`%${text}%`] });
// Wrong: the user's text becomes SQL
db.page({ table: 'data', where: `city LIKE '%${text}%'` });

Table and column names may hold only letters, digits and _, and cannot be placeholders. Check a column name against columns from the table before you use it in orderBy or in SQL.

What Wardian refuses#

Apps write their own SQL, so Wardian keeps each one inside its own file:

Share a large table with another package#

A channel message holds at most 256 KB. To hand a large table to another package, send a dataset reference instead of the rows:

ctx.channel('splunk.table').send({
  dataset: { package: 'splunk-table', table: 'search', total, columns, fields },
});

The receiver declares db and reads the pages it needs:

const page = await db.readPage({ package: msg.dataset.package, table: msg.dataset.table, offset: 0, limit: 1000 });

The user is asked once whether the receiver may read that package's tables. The Splunk table app and the USL lab work this way. See Apps that talk to each other.

Take the data with you#

Download on an app's page saves the app as a .wardian file. Tick Include my data to add its storage, its layout and its tables. Wardian lists what goes in first. Keys, accounts, permission answers and history never go in. See Share, import and remove apps.

This page is docs/site/data.md in the repository. Something wrong or missing? Change that file.