Add-ons that keep tables of their own
本頁內容尚未翻譯。
Most add-ons draw something for an app: an invoice, a shipping label. Some keep data of their own: a stock list, gift cards. From Adminium 0.3.18 such an add-on declares its tables, pages and roles in its manifest, in the same words an app uses, and Adminium installs them the way it installs an app. This page says what that means for you as the owner of the server. The fields themselves are in the manifest reference.
What it may declare
Section titled “What it may declare”- Tables, always under the add-on’s own prefix: the add-on
stock-kitmakesstock_kit_items, neveritems. A table of yours that already has one of those names is never renamed or changed: the check says so and the install stops. - Pages in the dashboard, generated ones and screens of its own code, in a section of the sidebar that carries the add-on’s name.
- Roles that open those pages and tables. Whoever installs the add-on is given its first role, so they can open what they just installed. Nobody else gets one until you assign it.
- Lists of choices for its columns, document layouts for its rows, emails it sends from a table of its own, and a few starting rows (units of measure, reasons, its one settings row).
- Public entries, described below.
One database
Section titled “One database”All of an add-on’s tables go in one database, chosen at install and kept on its record. With one database connected, nobody is asked. With several, the install dialog asks “Which database?” and shows what will be made there before you confirm. An add-on cannot be moved to another database later; uninstall it and install it again where you want it.
The check, before anything is made
Section titled “The check, before anything is made”Studio › Add-ons shows, before you confirm: the tables it creates, the pages and roles it adds, its lists, whether its tables start with rows, and what it would open to the public. The install makes exactly that. If the database changed between the check and the install, the install stops and asks you to check again.
An install that stops part way (a full disk, a lost connection) undoes nothing and says at which step it stopped. Press Install again: it carries on from there, and nothing is made twice.
Its pages are a screen, not a wall
Section titled “Its pages are a screen, not a wall”A page of the add-on’s own code is shown only to someone whose role holds that page. That hides the screen. What actually protects the data is the same thing that protects every table in Adminium: a person reads or writes one of the add-on’s tables only with a role that grants that table, whichever screen or API they come through.
The page’s code is handed out the same way: only to someone who may open the page, and to nobody while the add-on is switched off for the dashboard. Three rules keep that true:
- A page is built into a file of its own. A file a slot loads is served to everybody signed in, so an add-on whose page and slot name the same file is refused.
- A page’s name opens it, so no two add-ons installed together may share one. An add-on whose page
is named under another installed add-on’s key (
inventorynaminginventory-pro-stockbesideinventory-pro) is refused withADD_ON_PAGE_REF_TAKEN. - A version that drops a page takes the page back from the add-on’s roles, and uninstalling takes it back from every role, your own included.
The data kit
Section titled “The data kit”A page of the add-on’s own code does not fetch its tables itself. It imports
@adminium/add-on-data, which the build aliases to a shim, and gets the dashboard’s own parts and
hooks: the signed-in reader’s grants, the dashboard’s cache, its look in light, dark and
right-to-left. A manifest whose pages use it says "hostApi": 2.
import { Card, DataTable, StatusPill, useAccess, useRecords, useWrite } from '@adminium/add-on-data';
export default function Transfers() { const { rows, loading } = useRecords('transfers', { sort: [{ column: 'id', direction: 'desc' }] }); const { canCreate } = useAccess(); // …}Tables are named by the add-on’s own short names (transfers, never inventory_transfers).
Nothing a page does through the kit is something its reader could not do on a generated page: a
read or a write the role does not grant is refused the same way.
| Hook | What it gives |
|---|---|
useRecords(table, options?) |
A filtered, sorted page of rows, and whether more exist. The server filters and pages. |
useRecord(table, key) |
One row. |
useRead() |
list and get as promises, for a read made when something happens (a scan, a run over a sheet’s lines) and not while the page is drawn. |
useWrite(table) |
create, update, remove, and createEach / updateEach for up to 500 rows, one save each. updateEach takes {from}, the state the rows were seen in, and answers what a ledger said of each row. |
useTreeWrite(table) |
A row with the rows under it in one save, and a dryRun of it. |
useStateMove(table) |
Makes one of the table’s declared actions on a row. |
useAccess() |
canRead, canCreate, canUpdate, canMove, has(feature), and currency: the currency of the database your tables are in, or null when the owner set none. |
useLookUp() |
Finds a row by a typed or scanned code, through the add-on’s lookUp. |
useWords(id) |
Asks the add-on’s stock words about up to 60 rows. |
useDocument() |
Draws a document for a row and opens it, or prints it. |
useExport() |
Starts an export of a table’s rows and downloads it. |
The parts are Card, Grid, Stack, Sheet (with SheetHeader, SheetBody, SheetFooter),
StickyBar, Divider, Skeleton, DataTable, Stat, KeyValueList, StatusPill,
ProgressBar, Pagination, MonoText, Field, Input, NumberInput, Textarea, DateInput,
Select, Combobox, Switch, Checkbox, RadioGroup, RadioCard, ToggleChip, InputGroup,
Menu, MenuItem, ConfirmModal and Link.
Three things to know:
- Lay a page out with the parts. A class of your own is in no stylesheet the dashboard loads. A part carries no words: pass every label from the add-on’s own strings.
- Money and decimals are text in a row, so no figure is rounded on the way.
- An older Adminium has no kit. Importing the shim there throws, and the dashboard shows “This
page needs a newer Adminium.” in place of the page.
compatibility.minAdminiumVersionkeeps the add-on from being installed there in the first place.
Printing from a page
Section titled “Printing from a page”useDocument().open(kind, table, key, options?) draws one of the add-on’s
documents for a row of one of its tables and opens it;
{ "print": true } opens the print dialog, and paper picks one of the papers the kind lists
(a4, letter, receipt-80mm, a6). The reader must be able to read everything the document
reads. A document that prints a money code
is kept nowhere, so it is not in the documents register: pass the once ticket the create
answered to print the code that was shown once.
Starting rows
Section titled “Starting rows”An add-on may ship rows its tables hold from the first second. They are written once, at install, into a table that is empty, in the language of the person installing. They are your rows from then on: change or delete them freely. Installing the add-on again over tables you kept adds none of them a second time, and removing sample data never touches them.
Updating
Section titled “Updating”An update may add tables, columns and indexes to the add-on’s own tables. Studio shows what it adds first when it changes a table or would open something new to the public. While an update runs the add-on does nothing; when it stops part way, press Update again.
What you changed is kept: a page you edited, a rule you changed, a role you narrowed.
Removing
Section titled “Removing”Uninstalling removes its pages, roles, rules, email templates, public entries and keys. Its tables stay, with every row, unless you tick “Also delete its tables” and type its key, which needs Super Admin. A page you edited stays as an ordinary page of your own.
A key you made by hand that reads one of its public entries loses that entry too: it does not go on serving a table the add-on left behind.
It cannot be removed while an app still hands rows to it or uses it for a feature that is switched on; the dialog says which.
A ledger, and code that decides
Section titled “A ledger, and code that decides”An add-on may keep a ledger: tables Adminium writes for it inside the save of another table’s row, from rows the add-on’s own code answers. The add-on thinks; Adminium reads and writes.
The code is one file, a classic script with no import and no require, at most 512 KiB,
that sets module.exports = { rows }. rows(input) is called with the lines to plan, the rows
Adminium read for it (the action’s reads), the add-on’s one settings row, and the moment as
text (now, today, zone). It answers the rows to insert or update, any refusals, and — in
words mode — what is left of each line.
- It is pure. No clock, no randomness, no network, no timers:
Date,Intl,Math.random,fetch,setTimeout,evalandFunctionare not there. The same input gives the same answer. - It is quick. A call is stopped at 250 ms and the save refused; a call over 50 ms is logged. The server waits on it, so a slow answer slows every request.
- It is checked. Every row it answers must be for a table and column the ledger’s
writeslist, must hang under a row one of its reads returned, and is prepared, capped and settled by Adminium like any row. An answer that fails a check fails the save (POSTING_REFUSED {reason: "planner-failed"}) and leaves one audit row,ledger.refused, that says which check. - This is hardening, not a sandbox. It stops mistakes, not an attacker. So the file runs only
when its package is one this server can vouch for: bundled with the release, or downloaded from
the catalogue. A package uploaded by hand installs, and every save that would ask its code is
refused
add-on-unavailableuntil that version is released. On a developer’s own server,ADMINIUM_ADD_ON_DEV_TRUSTnames the add-ons whose code runs as it is.
The contracts package ships a conformance suite, postingRowsConformance, that an add-on’s own
tests run over its case table: every answer inside writes, a give-back that nets to zero, two
calls that agree.
Rules, tabs and stock words
Section titled “Rules, tabs and stock words”Three more things are declared, not coded:
- Rules it ships (
automations): installed with the add-on and shown on the rules page under “From your add-ons”, where the owner switches them or edits a copy. - A tab on another table’s record (
addOn.recordTabs): the add-on’s rows for a dish, a product or an order, listed on that record’s own page. - Stock words (
addOn.words): “in”, “low” or “out” for each row a page asks about, answered by the ledger’s code with nothing written. Answer each line by itself: a page asks about many rows at once, and one row’s answer must not depend on the others in the same question.
What it opens to the public
Section titled “What it opens to the public”An add-on may ask for two kinds of public access. Neither is opened by installing it.
- Entries served through an app’s public key. A gift-card add-on may let a customer read a card’s balance by typing its code on the shop’s own page. Such an entry is put on the public key of an app that names the add-on, and only when you tick Allow public access — and only if you may manage API keys. Left unticked, the add-on is installed and its entries wait.
- One link key of its own. Whoever holds a row’s link opens that one row and can only read it. It is made with the same tick.
The entries leave an app’s key the moment the add-on stops being there for that app: when you switch it off for the app, uninstall it, or update it to a version that drops the entry. Switching it back on does not put them back by itself; Studio asks.
See An app’s public access for how this looks from the app’s side.