Guide for AI assistants¶
This page is written for an assistant which writes the files of a website of API Maker. Read it whole before you write a file.
The website¶
- A website is files. Its pages are TSX, rendered on the server by QuickJS : no React, no hooks, no state, no
useEffect. A component is a function of its props which returns JSX ; it may beasync. pages/holds the routes :index.tsxis/,blog/[slug].tsxis/blog/:slug,docs/[...path].tsxtakes several parts,(group)/is no part of the path, a.tsfile is an endpoint (export function GET(ctx))._layout.tsxwraps the pages of its folder (it getschildren),_middleware.tsruns before them,_404.tsxand_error.tsxanswer errors (they geterror).components/,lib/: code the pages import with@/components/Card(@/is the root of the website),./x,../x.data/*.jsonis imported as a module.public/is sent as it is.site.config.ts:export default defineSite({ ... })with values only (no functions, no variables).libraries.json: the UI libraries of the browser.- The only modules a file imports are files of the website,
'am-ssr'and'types'. There is no npm on the server side.
A page, complete¶
import { formatCurrency, notFound } from 'am-ssr';
import type { PageConfig, PageContext, PageProps } from 'am-ssr';
export const config: PageConfig = {
title: (data) => data.product.name, // a value, or (data, ctx) => value
description: (data) => data.product.summary,
access: 'public', // 'public' | 'authenticated' | { groups: ['admin'] }
cache: { maxAge: 60, scope: 'public' }, // only for pages which show nothing of the visitor
};
export async function loader(ctx: PageContext) {
const [product] = await ctx.db.query({
instance: 'mongodb', database: 'shop', table: 'products',
find: { slug: String(ctx.params.slug) }, // a value from the visitor, made a text
limit: 1,
});
if (!product) notFound('No such product.');
return { product };
}
export default function ProductPage({ data }: PageProps<typeof loader>) {
return (
<article>
<h1>{data.product.name}</h1>
<p>{formatCurrency(data.product.price, 'USD')}</p>
<a href="/products">All products</a>
</article>
);
}
Rules¶
- Write addresses from the root of the website :
href="/about",src="/logo.png",action="/login". The server adds the base path (/ssr/<apiPath>/<name>, or nothing behind a domain). Do not write/ssr/<apiPath>/<name>yourself. In a value which is no attribute, usectx.link('/about'). - Static files :
asset('/styles.css')(hash in the address, kept a year). - Every value is escaped. Write
{text}; neverraw()ordangerouslySetInnerHTMLwith what a visitor wrote. - No event attributes :
onclick="..."does not run (CSP). Add a<script>(it gets the nonce by itself) which listens :document.getElementById('x').addEventListener('click', …). - Data for a script :
<script>{const DATA = ${serialize(data)};}</script>. - Forms post to their own page :
<form method="post">gets its CSRF token by itself ; the page exportsaction(ctx); readctx.form; return{ values, errors }withctx.status(422)on a mistake ; on successctx.flash('success', '…')thenredirect('/path', 303). - Scripts which POST send
x-csrf-token: window.__AM_SSR__.csrfand usewindow.__AM_SSR__.base + '/api/...'. - Data :
ctx.db.<method>({ instance, database, table, ... })reaches only the tablessite.config.tslists indata.read/data.write('instance/database/table'or{ table, where, set, requireUser, fields, allow }). When you add a call to a new table, add the table tosite.config.ts. For data of a visitor, add a rulewhere: { owner_id: '$user._id' }rather than filtering in the page. - Never pass an object of the visitor as a query :
find: { status: String(ctx.query.status) }, neverfind: ctx.query. Escape regular expressions. - Who may open a page :
config.access, oraccessof a_layout.tsxfor a whole folder, ordefaultAccessof the website. Do not check it only in the loader. - Sign-in :
await ctx.auth.login({ provider: '<name in auth.providers>', username, password })in an action, insidetry/catch(HttpErrorwithstatus < 500is a wrong password : answer401with one sentence for both cases). Sign-out is aPOSTendpoint callingctx.auth.logout(). - Redirects to another host need
{ external: true }. Anextof the query is a path : checknext.startsWith('/') && !next.startsWith('//'). - Cache :
cache: { maxAge, scope: 'public' }only for pages which show nothing of the visitor (noctx.user, no cookies). Drop withtags+ctx.revalidate('tag')after a change. - Never keep
ctxor a visitor's data in a variable of a module : modules are shared by every visitor. - No Node.js : no
fs,process,Buffer,require('x')of npm,setTimeout. Usectx.fetch(hosts listed indata.fetch),ctx.cache,crypto.randomUUID(),URL,TextEncoder. - Endpoints return an object (JSON), a text,
json(data, { status }),ctx.html(await Component(props))for HTML without the layout, or nothing (204). - Browser libraries (Bootstrap, Tailwind, Chart.js, htmx...) go in
libraries.json({ "id": "bootstrap" }) ; a page may limit them withconfig.libraries: ['chartjs']. Never add a<script src="https://cdn...">by hand : the CSP refuses it. - Types :
PageProps<typeof loader>,LayoutProps,ErrorProps,PageContext,PageConfig,Headfrom'am-ssr'(import type).
The checklist of a new page¶
- The file is in
pages/at the path it answers ; a dynamic part readsctx.params.<name>(a text, or a list for[...]). -
configwithtitle(anddescriptionfor public pages),accesswhen it is not public. - Every table it reads or writes is in
site.config.ts, with a row rule for the data of visitors. - Values of the visitor are made texts or numbers before they reach a query.
- A missing record is
notFound(). - Forms :
method="post", anaction, checks on the server,303after success. - Links are written from the root of the website.
site.config.ts, complete¶
import { defineSite } from 'am-ssr';
export default defineSite({
title: 'My shop',
titleTemplate: '%s · My shop',
description: 'What the website is.',
lang: 'en',
domains: [],
auth: { providers: ['customers'], loginPath: '/login', sessionDays: 30 },
defaultAccess: 'public',
data: {
read: ['mongodb/shop/products', { table: 'mongodb/shop/orders', where: { customer_id: '$user._id' }, requireUser: true }],
write: [{ table: 'mongodb/shop/orders', where: { customer_id: '$user._id' }, requireUser: true, allow: ['create'] }],
fetch: [],
allowSys: false,
},
security: { csp: 'strict' },
seo: { sitemap: true, robots: 'allow' },
redirects: [],
});
Where to look¶
- Starter template : one page per feature, with comments.
- Examples : recipes to copy.
- Reference : every function and every member of
ctx. - The declarations :
am-ssr.d.tsnext to each website in the git repository.