// @pdsjs/sites - lexicon schemas for the app platform // // Record validation fails open for unresolvable collections, so these gate // nothing at write time. They document the shapes, drive the CLI, and can be // bundled into a resolver or published later. @pdsjs/sites has no dependency // on a lexicon library, so the type is structural. /** * A deployed static site. The record key is the site name: a lowercase DNS * label that binds the site to `.`; the deployment's * configured apex name binds to the apex itself. */ export const devPdsjsSiteDeploy = { lexicon: 1, id: 'dev.pdsjs.site.deploy', defs: { main: { type: 'record', description: 'A static site served from this repo. The record key is the site name, a lowercase DNS label.', key: 'any', record: { type: 'object', required: ['files', 'createdAt'], properties: { files: { type: 'array', description: 'Site contents. Paths are unique, relative, slash-separated, with no leading slash.', items: { type: 'ref', ref: '#file' }, }, fallback: { type: 'string', description: 'Path served with status 200 for any request path with no match (single-page-app routing). Must resolve within files, possibly via base.', }, base: { type: 'ref', ref: 'com.atproto.repo.strongRef', description: "Another dev.pdsjs.site.deploy record in this repo whose files merge beneath this record's; this record wins on path conflicts. One hop only: a base may not itself declare a base.", }, createdAt: { type: 'string', format: 'datetime' }, }, }, }, file: { type: 'object', required: ['path', 'blob'], properties: { path: { type: 'string', maxLength: 1024 }, blob: { type: 'blob', maxSize: 5242880 }, contentType: { type: 'string', description: 'Overrides the stored blob mime type when serving this path.', }, cacheControl: { type: 'string', description: 'Overrides the default Cache-Control header for this path.', }, originTemplate: { type: 'boolean', description: 'Serve this file with the token __PDSJS_ORIGIN__ replaced by the origin the request arrived on, for a file whose content names the site it is served from, such as an OAuth client registration.', }, }, }, }, }; /** * An installable app, published in its developer's repo. Installing copies * the referenced site's blobs into the installer's own repo, so the app * keeps working if the developer's repo disappears. */ export const devPdsjsAppManifest = { lexicon: 1, id: 'dev.pdsjs.app.manifest', defs: { main: { type: 'record', key: 'any', record: { type: 'object', required: ['name', 'createdAt'], properties: { name: { type: 'string', maxLength: 64 }, description: { type: 'string', maxLength: 1024 }, icon: { type: 'blob', accept: ['image/*'], maxSize: 262144 }, site: { type: 'ref', ref: 'com.atproto.repo.strongRef', description: "The dev.pdsjs.site.deploy record in this repo holding the app's frontend. Its record key is an artifact address with no naming meaning for installs.", }, suggestedSite: { type: 'string', description: 'Default subdomain label offered at install. The installer never uses the pinned record key for naming.', }, scopes: { type: 'array', items: { type: 'string' }, description: 'OAuth scopes the app requests at install, in the host PDS scope grammar.', }, primitives: { type: 'array', items: { type: 'string', enum: ['serve', 'grant', 'query', 'react', 'schedule'], }, }, external: { type: 'boolean', description: "True when the app talks to services other than the user's own PDS; surfaced prominently on the install sheet.", }, queries: { type: 'unknown', description: 'Query definitions the app reads from its host: an array of {name, collection, where?, sort?}. The installer writes each as a dev.pdsjs.query.def record.', }, settings: { type: 'unknown', description: 'Settings the app exposes: an array of {key, label, type: "boolean"|"string"|"select", default?, options?}. Declared data, never code; the install record snapshots this schema with the chosen values.', }, createdAt: { type: 'string', format: 'datetime' }, }, }, }, }, }; /** * An installed app, written to the installing user's repo. The manifest ref * pins the reviewed version by CID; an updated manifest requires re-consent. */ export const devPdsjsAppInstall = { lexicon: 1, id: 'dev.pdsjs.app.install', defs: { main: { type: 'record', key: 'tid', record: { type: 'object', required: ['manifest', 'createdAt'], properties: { name: { type: 'string', maxLength: 64, description: "The manifest's display name at install, snapshotted like the settings schema.", }, manifest: { type: 'ref', ref: 'com.atproto.repo.strongRef', description: "The developer's dev.pdsjs.app.manifest record, pinned by CID.", }, grantedScopes: { type: 'array', items: { type: 'string' } }, site: { type: 'string', description: "Local site name: the dev.pdsjs.site.deploy rkey holding this install's copied frontend.", }, config: { type: 'unknown', description: 'Chosen values for the settings the manifest declares.', }, settings: { type: 'unknown', description: "Snapshot of the manifest's settings schema at install, so a settings editor needs no cross-server read.", }, icon: { type: 'blob', accept: ['image/*'], maxSize: 262144, description: "The manifest's icon, copied into this repo at install.", }, createdAt: { type: 'string', format: 'datetime' }, }, }, }, }, }; /** * A palette an account publishes for the pages it serves. The record key * names the theme, so an account may hold several and a reader may pick * between them. * * The colours are the semantic names a page paints with, not the elements * they paint: background, foreground, key, destructive and so on. A page * that knows a name it is not given falls back to its own. */ export const devPdsjsAppTheme = { lexicon: 1, id: 'dev.pdsjs.app.theme', defs: { main: { type: 'record', key: 'any', record: { type: 'object', required: ['name', 'scheme', 'colors'], properties: { name: { type: 'string', maxLength: 64, description: 'What to call this palette in a theme picker.', }, scheme: { type: 'string', knownValues: ['light', 'dark'], description: 'Which way round the palette runs. A page tells the browser, so scrollbars and form controls match.', }, colors: { type: 'unknown', description: 'Colour per semantic name. A name the page does not know is ignored, and a name left out keeps the page default.', }, createdAt: { type: 'string', format: 'datetime' }, }, }, }, }, }; /** * A named live view over one collection of this repo. The record key names * the query; dev.pdsjs.query.run serves its rows. */ export const devPdsjsQueryDef = { lexicon: 1, id: 'dev.pdsjs.query.def', defs: { main: { type: 'record', key: 'any', record: { type: 'object', required: ['collection', 'createdAt'], properties: { collection: { type: 'string', format: 'nsid', description: 'The collection this query reads.', }, where: { type: 'unknown', description: 'Field path to required value, equality only. A record matches when every named field equals its value.', }, sort: { type: 'object', properties: { field: { type: 'string', description: 'Field path to sort by.' }, direction: { type: 'string', enum: ['asc', 'desc'] }, }, }, createdAt: { type: 'string', format: 'datetime' }, }, }, }, }, }; /** The collection a site manifest lives in. */ export const SITE_COLLECTION = 'dev.pdsjs.site.deploy'; /** The collection query definitions live in. */ export const QUERY_COLLECTION = 'dev.pdsjs.query.def'; /** The collection published palettes live in. */ export const THEME_COLLECTION = 'dev.pdsjs.app.theme'; export const siteLexicons = [ devPdsjsSiteDeploy, devPdsjsAppManifest, devPdsjsAppInstall, devPdsjsAppTheme, devPdsjsQueryDef, ];