Appearance
The SDK
Every screen, view, block and job reaches Sorus through one object, pages. How you get it depends on how the code runs:
js
// In a page Sorus loads in a frame: screens, views, blocks, jobs.
import { connect } from '/__pages/sdk.js'
const pages = await connect()js
// In a module Sorus runs in its own window (signed extensions only).
export function mount(root, pages) {
// draw into root…
return () => {
/* optional: clean up */
}
}Sorus serves /__pages/sdk.js to every extension. connect() waits for Sorus to hand over the connection, and gives up after 10 seconds if the page wasn't opened by Sorus. Calling it twice returns the same pages.
For TypeScript, the types are in pages-sdk/app (PagesApp, AppContext and the rest), in the Sorus repository's packages/sdk.
What pages knows
| Property | |
|---|---|
appId | Your extension's id. |
workspaceId | The workspace it's running in. |
screen | The screen, view or block showing, by its id. |
types | Your types' IDs, by key: types.movie.id, types.movie.fields.year, types.project.options.status.done. See IDs. |
view | For a view: the saved view's name and query. Otherwise null. |
block | For a block: { objectId }. Otherwise null. |
job | For a job: { id }. Otherwise null. |
theme | { scheme, variables }, the colors also set as CSS variables. |
client | The workspace client, below. |
Methods
| Method | |
|---|---|
values(row, key) | A row's field values, by your field keys. |
fields(key, values) | Field entries to save, from values by your field keys. |
navigate(target) | { screen } opens another of your screens; { object } or { page } opens one in Sorus. |
onView(listener) | Called when the saved view a view shows changes. Returns a function that stops. |
onCommand(listener) | Called with each of your commands chosen for this screen. Returns a function that stops. |
assetUrl(assetId) | An address for a file's bytes, for an <img>. |
save(name, content) | Asks the person where to save a file (a Blob or string), or a folder of files ([{ path, data }]). Resolves where they went, or null. |
resize(height) | For a block: asks for a new height. |
fetch(url, init) | Reaches a listed host, or a connection with { connection }. |
storage.get(key), storage.set(key, value) | Small values, kept on this device. |
connections.status(id), connections.connect(id) | A connection's state; Sorus's dialog for making it. |
finish(summary) | Ends a job. |
Reading and writing fields
Field values are tagged by kind: { kind: 'text', value: 'Dune' }, { kind: 'number', value: '1965' }, { kind: 'boolean', value: true }, { kind: 'reference', id }, { kind: 'choice', id }, { kind: 'list', items: [...] }. Numbers travel as strings.
js
const { year, director } = pages.values(row, 'movie')
console.log(year?.value, director?.value)
await pages.client.objects.create({
type: pages.types.movie.id,
name: 'Dune',
fields: pages.fields('movie', {
year: { kind: 'number', value: '2021' },
director: { kind: 'text', value: 'Denis Villeneuve' }
})
})The client
pages.client is a WorkspaceClient: each method is one operation in the workspace, checked before it's sent and refused unless your extension is allowed.
| Group | Methods |
|---|---|
objects | query, get, create, update, upsert, batch, list, links, resolve, referrers |
pages | list, get, create, rename, move, appendText, replaceText |
entries | day, start, stop, log, update, delete |
calendar | list, get, saveEvent, saveTask, move, pin, finish, delete, plan, saveSeries, changeOccurrence |
types | list |
trash | move |
assets | get, link |
workspace | get |
Also:
client.changes(): an async iterator that yields after edits to the workspace, from anywhere. Treat each as a hint to read again.client.upload(blob, name): keeps a file in the workspace and returns its asset, to put in anassetfield.client.download(id)reads one back.client.run(operation, payload): any operation by name.
js
const { rows, next, total } = await pages.client.objects.query({
query: {
type: pages.types.viewing.id,
filters: [{ field: pages.types.viewing.fields.rating, op: 'greaterOrEqual', value: { kind: 'number', value: '4' } }],
sort: { field: pages.types.viewing.fields.watchedOn, descending: true }
},
limit: 100
})A query returns at most 500 rows at a time (100 by default); pass next as after for the following page.
Errors
A refused call rejects with a PagesError. Its code says why: forbidden (not allowed), revision_conflict (the object changed since you read it; read it again), not_found, invalid_payload, and others. Updates take the expectedRevision you read, so you don't overwrite someone else's change.