@rsc-kit/press 0.30.49 → 0.30.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -97,7 +97,8 @@ A theme is an object of components: `Shell`, `Brand`, `TenantSwitcher`, `Nav`, `
97
97
 
98
98
  ```ts
99
99
  interface DataSource<Row, Values> {
100
- list(query: ListQuery): Promise<{ rows: Row[]; total: number }>
100
+ // Counted, numbered pages; or by cursor, Previous and Next: query.cursor is the one handed out.
101
+ list(query: ListQuery): Promise<{ rows: Row[]; total: number } | { rows: Row[]; next: string | null; prev?: string | null }>
101
102
  find(id: string): Promise<Row | null>
102
103
  create(values: Values): Promise<Row>
103
104
  update(id: string, values: Values): Promise<Row>
@@ -18,14 +18,17 @@ export async function saveRecord(path, name, id, form) {
18
18
  if (id !== null && !current)
19
19
  throw new Error(`[admin] no ${name} ${id} here`);
20
20
  // What the record had is what a file field may keep without uploading again.
21
- const parsed = await parse(resource, form, { current: current, tenant: context.tenant });
21
+ // A backend's create and update may take different fields: a password
22
+ // required to create is optional to edit.
23
+ const target = id === null ? resource : { schema: resource.editSchema, fields: resource.editFields };
24
+ const parsed = await parse(target, form, { current: current, tenant: context.tenant });
22
25
  if (!parsed.ok)
23
26
  return fieldErrors(parsed.errors);
24
27
  // A relation's value is an id the browser sent, so it is looked up as the
25
28
  // visitor: one from another tenant, or none at all, is not one of theirs.
26
29
  const values = parsed.values;
27
30
  const unrelated = {};
28
- for (const field of resource.fields) {
31
+ for (const field of target.fields) {
29
32
  const value = values[field.name];
30
33
  if (!field.relation || value === undefined || value === null || value === '')
31
34
  continue;
@@ -36,7 +39,7 @@ export async function saveRecord(path, name, id, form) {
36
39
  if (Object.keys(unrelated).length > 0)
37
40
  return fieldErrors(unrelated);
38
41
  const row = id === null ? await resource.source.create(parsed.values, context) : await resource.source.update(id, parsed.values, context);
39
- await tellAll(panelAt(path), { panel: path, resource: name, id: String(row[resource.key]), change: id === null ? 'created' : 'updated', values: parsed.values, tenant: context.tenant }, context);
42
+ await tellAll(panelAt(path), { panel: path, resource: name, id: id ?? String(row[resource.key] ?? ''), change: id === null ? 'created' : 'updated', values: parsed.values, tenant: context.tenant }, context);
40
43
  // The form is replaced by the list the redirect lands on, so the word
41
44
  // that it worked rides along to that page.
42
45
  await flash(`${resource.singular} ${id === null ? 'created' : 'saved'}.`);
@@ -1 +1 @@
1
- {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/admin/actions.ts"],"names":[],"mappings":"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","sourcesContent":["'use server'\n\nimport { fieldErrors } from '@rsc-kit/core/action'\nimport { flash } from '@rsc-kit/core/flash'\nimport { redirect } from '@rsc-kit/core/redirect'\nimport { revalidate } from '@rsc-kit/core/revalidate'\nimport { digestOf, fetchManifest, newInstallation } from '../installable.js'\nimport { allowOf, allowedResource, contextOf, panelAt, resourcesFor, tellAll } from './registry.js'\nimport { bulkToast, parse } from '../resource.js'\n\n/**\n * One save for every resource, bound by the page:\n * saveRecord.bind(null, '/admin', 'products', id). The resource's own schema\n * is the check, and the panel's authorize runs first, on every call - an\n * action is reachable without the page that rendered it.\n */\nexport async function saveRecord(path: string, name: string, id: string | null, form: FormData) {\n const { resource, context } = await allowedResource(path, name)\n const current = id === null ? null : await resource.source.find(id, context)\n\n if (id !== null && !current) throw new Error(`[admin] no ${name} ${id} here`)\n\n // What the record had is what a file field may keep without uploading again.\n const parsed = await parse(resource, form, { current: current as Record<string, unknown> | null, tenant: context.tenant })\n\n if (!parsed.ok) return fieldErrors(parsed.errors)\n\n // A relation's value is an id the browser sent, so it is looked up as the\n // visitor: one from another tenant, or none at all, is not one of theirs.\n const values = parsed.values as Record<string, unknown>\n const unrelated: Record<string, string> = {}\n\n for (const field of resource.fields) {\n const value = values[field.name]\n\n if (!field.relation || value === undefined || value === null || value === '') continue\n\n const related = (await resourcesFor(panelAt(path), context)).find((candidate) => candidate.name === field.relation)!\n\n if (!(await related.source.find(String(value), context))) unrelated[field.name] = `Choose one of the listed ${related.label.toLowerCase()}.`\n }\n\n if (Object.keys(unrelated).length > 0) return fieldErrors(unrelated)\n\n const row = id === null ? await resource.source.create(parsed.values, context) : await resource.source.update(id, parsed.values, context)\n\n await tellAll(\n panelAt(path),\n { panel: path, resource: name, id: String(row[resource.key]), change: id === null ? 'created' : 'updated', values: parsed.values, tenant: context.tenant },\n context,\n )\n\n // The form is replaced by the list the redirect lands on, so the word\n // that it worked rides along to that page.\n await flash(`${resource.singular} ${id === null ? 'created' : 'saved'}.`)\n\n redirect(`${path}/${name}` as never)\n}\n\n/** Deleting from the table refreshes the table, and nothing else on the page. */\nexport async function deleteRecord(path: string, name: string, id: string) {\n const { resource, context } = await allowedResource(path, name)\n\n if (!(await resource.source.find(id, context))) return\n\n await resource.source.delete(id, context)\n await tellAll(panelAt(path), { panel: path, resource: name, id, change: 'deleted', tenant: context.tenant }, context)\n await flash(`${resource.singular} deleted.`)\n revalidate('admin-table' as never)\n}\n\n/**\n * The rows ticked in the table, deleted or handed to one of the resource's\n * bulk actions. Each id is found as the visitor first, so one from another\n * tenant, or already gone, is left out rather than acted on.\n */\nexport async function bulkRecords(path: string, name: string, action: string, ids: string[]) {\n const { resource, context } = await allowedResource(path, name)\n const bulk = action === 'delete' ? null : resource.bulkActions.find((candidate) => candidate.name === action)\n\n if (bulk === undefined) throw new Error(`[admin] ${name} has no bulk action ${action}`)\n\n const found = await Promise.all([...new Set(ids.map(String))].map(async (id) => [id, await resource.source.find(id, context)] as const))\n const rows = found.filter(([, row]) => row !== null && row !== undefined)\n const noun = (count: number) => (count === 1 ? resource.singular : resource.label).toLowerCase()\n\n if (bulk === null) {\n for (const [id] of rows) {\n await resource.source.delete(id, context)\n await tellAll(panelAt(path), { panel: path, resource: name, id, change: 'deleted', tenant: context.tenant }, context)\n }\n\n await flash(bulkToast({ label: 'Delete', done: 'deleted' }, rows.length, noun(rows.length)))\n } else {\n const said = await bulk.run(rows.map(([, row]) => row), context)\n\n await flash(typeof said === 'string' ? said : bulkToast(bulk, rows.length, noun(rows.length)))\n }\n\n revalidate('admin-table' as never)\n}\n\n/** The switcher's choice, checked against the visitor's own tenants, then the dashboard. */\nexport async function switchTenant(path: string, id: string) {\n const panel = panelAt(path)\n\n if (!(await panel.authorize()) || !panel.tenancy) throw new Error('[admin] not allowed')\n\n const theirs = await panel.tenancy.tenants()\n\n if (!theirs.some((tenant) => tenant.id === id)) throw new Error('[admin] not one of this visitor\\'s tenants')\n\n await panel.tenancy.switch(id)\n // The team's name and nav are in the layout, which a redirect keeps as it\n // was unless the action says what it changed.\n revalidate('all' as never)\n redirect(path as never)\n}\n\nexport async function signOut(path: string) {\n const panel = panelAt(path)\n\n await panel.signOut?.()\n redirect('/' as never)\n}\n\n/** The panel, once this visitor may install and uninstall there. */\nasync function managed(path: string) {\n const panel = panelAt(path)\n\n if (!panel.installs || !(await panel.authorize())) throw new Error('[admin] not allowed')\n if (panel.installs.manage && !(await panel.installs.manage())) throw new Error('[admin] not allowed to manage plugins')\n\n const context = await contextOf(panel)\n\n if (!context) throw new Error('[admin] no tenant chosen, or not one of this visitor\\'s')\n\n return { panel, installs: panel.installs, context }\n}\n\n/** A manifest url, fetched and checked, then shown for review. Nothing is installed yet. */\nexport async function reviewPlugin(path: string, form: FormData) {\n const { panel } = await managed(path)\n const url = String(form.get('manifest') ?? '').trim()\n\n try {\n await fetchManifest(url, allowOf(panel), panel.resources.map((resource) => resource.name))\n } catch (error) {\n return fieldErrors({ manifest: error instanceof Error ? error.message : 'The manifest could not be read.' })\n }\n\n redirect(`${path}/plugins?review=${encodeURIComponent(url)}` as never)\n}\n\n/**\n * Install what was reviewed. The manifest is fetched again and must match the\n * digest the review page showed: a plugin that changed its scopes between the\n * review and the click is refused, not installed with scopes nobody read.\n */\nexport async function installPlugin(path: string, url: string, digest: string) {\n const { panel, installs, context } = await managed(path)\n const host = panel.resources.map((resource) => resource.name)\n const manifest = await fetchManifest(url, allowOf(panel), host)\n\n if ((await digestOf(manifest)) !== digest) {\n await flash('The plugin changed since you reviewed it. Review it again.', 'error')\n redirect(`${path}/plugins?review=${encodeURIComponent(url)}` as never)\n }\n\n const installed = await installs.store.list(context.tenant)\n const taken = new Set([...host, ...installed.flatMap((installation) => installation.manifest.resources.map((resource) => resource.name))])\n\n if (installed.some((installation) => installation.manifest.name === manifest.name)) {\n await flash(`${manifest.label} is already installed.`, 'error')\n redirect(`${path}/plugins` as never)\n }\n\n const clash = manifest.resources.find((resource) => taken.has(resource.name))\n\n if (clash) {\n await flash(`Another plugin already adds ${clash.name}.`, 'error')\n redirect(`${path}/plugins` as never)\n }\n\n await installs.store.add(context.tenant, newInstallation(url, manifest))\n await flash(`${manifest.label} installed. Copy its secret into the plugin.`)\n // Re-rendered in place, not redirected: its resources join the sidebar,\n // which is in a layout a redirect keeps mounted from before the install.\n // The review it came from closes itself once the manifest is installed.\n revalidate('all' as never)\n}\n\n/** Uninstalled: its secret goes with it, so every call it could have answered stops at once. */\nexport async function uninstallPlugin(path: string, id: string) {\n const { installs, context } = await managed(path)\n const installation = (await installs.store.list(context.tenant)).find((candidate) => candidate.id === id)\n\n if (!installation) return\n\n await installs.store.remove(context.tenant, id)\n await flash(`${installation.manifest.label} uninstalled.`)\n revalidate('all' as never)\n}\n\n/** The secret was copied into the plugin; the plugins page stops showing it. */\nexport async function secretCopied(path: string, id: string) {\n const { installs, context } = await managed(path)\n\n await installs.store.seen(context.tenant, id)\n redirect(`${path}/plugins` as never)\n}\n"]}
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/admin/actions.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACnG,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAEjD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,IAAY,EAAE,EAAiB,EAAE,IAAc;IAC5F,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAC/D,MAAM,OAAO,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IAE5E,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,IAAI,EAAE,OAAO,CAAC,CAAA;IAE7E,6EAA6E;IAC7E,sEAAsE;IACtE,0CAA0C;IAC1C,MAAM,MAAM,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,UAAU,EAAE,CAAA;IACpG,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,OAAyC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAExH,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAEjD,0EAA0E;IAC1E,0EAA0E;IAC1E,MAAM,MAAM,GAAG,MAAM,CAAC,MAAiC,CAAA;IACvD,MAAM,SAAS,GAA2B,EAAE,CAAA;IAE5C,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAEhC,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;YAAE,SAAQ;QAEtF,MAAM,OAAO,GAAG,CAAC,MAAM,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,KAAK,CAAC,QAAQ,CAAE,CAAA;QAEpH,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;YAAE,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,4BAA4B,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,CAAA;IAC9I,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,WAAW,CAAC,SAAS,CAAC,CAAA;IAEpE,MAAM,GAAG,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAEzI,MAAM,OAAO,CACX,OAAO,CAAC,IAAI,CAAC,EACb,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,EACtK,OAAO,CACR,CAAA;IAED,sEAAsE;IACtE,2CAA2C;IAC3C,MAAM,KAAK,CAAC,GAAG,QAAQ,CAAC,QAAQ,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAA;IAEzE,QAAQ,CAAC,GAAG,IAAI,IAAI,IAAI,EAAW,CAAC,CAAA;AACtC,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,IAAY,EAAE,EAAU;IACvE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAE/D,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAAE,OAAM;IAEtD,MAAM,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IACzC,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAA;IACrH,MAAM,KAAK,CAAC,GAAG,QAAQ,CAAC,QAAQ,WAAW,CAAC,CAAA;IAC5C,UAAU,CAAC,aAAsB,CAAC,CAAA;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,IAAY,EAAE,MAAc,EAAE,GAAa;IACzF,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAC/D,MAAM,IAAI,GAAG,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,MAAM,CAAC,CAAA;IAE7G,IAAI,IAAI,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,uBAAuB,MAAM,EAAE,CAAC,CAAA;IAEvF,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,CAAU,CAAC,CAAC,CAAA;IACxI,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,CAAC,CAAA;IACzE,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAA;IAEhG,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,KAAK,MAAM,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;YACxB,MAAM,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;YACzC,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAA;QACvH,CAAC;QAED,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IAC9F,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAA;QAEhE,MAAM,KAAK,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IAChG,CAAC;IAED,UAAU,CAAC,aAAsB,CAAC,CAAA;AACpC,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,EAAU;IACzD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAA;IAExF,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAA;IAE5C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;IAE7G,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC9B,0EAA0E;IAC1E,8CAA8C;IAC9C,UAAU,CAAC,KAAc,CAAC,CAAA;IAC1B,QAAQ,CAAC,IAAa,CAAC,CAAA;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAY;IACxC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3B,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,CAAA;IACvB,QAAQ,CAAC,GAAY,CAAC,CAAA;AACxB,CAAC;AAED,oEAAoE;AACpE,KAAK,UAAU,OAAO,CAAC,IAAY;IACjC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3B,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAA;IACzF,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAA;IAEvH,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,CAAA;IAEtC,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;IAExF,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;AACrD,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,IAAc;IAC7D,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAErD,IAAI,CAAC;QACH,MAAM,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;IAC5F,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAiC,EAAE,CAAC,CAAA;IAC9G,CAAC;IAED,QAAQ,CAAC,GAAG,IAAI,mBAAmB,kBAAkB,CAAC,GAAG,CAAC,EAAW,CAAC,CAAA;AACxE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY,EAAE,GAAW,EAAE,MAAc;IAC3E,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC7D,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAA;IAE/D,IAAI,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC;QAC1C,MAAM,KAAK,CAAC,4DAA4D,EAAE,OAAO,CAAC,CAAA;QAClF,QAAQ,CAAC,GAAG,IAAI,mBAAmB,kBAAkB,CAAC,GAAG,CAAC,EAAW,CAAC,CAAA;IACxE,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3D,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAE1I,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnF,MAAM,KAAK,CAAC,GAAG,QAAQ,CAAC,KAAK,wBAAwB,EAAE,OAAO,CAAC,CAAA;QAC/D,QAAQ,CAAC,GAAG,IAAI,UAAmB,CAAC,CAAA;IACtC,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;IAE7E,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,KAAK,CAAC,+BAA+B,KAAK,CAAC,IAAI,GAAG,EAAE,OAAO,CAAC,CAAA;QAClE,QAAQ,CAAC,GAAG,IAAI,UAAmB,CAAC,CAAA;IACtC,CAAC;IAED,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAA;IACxE,MAAM,KAAK,CAAC,GAAG,QAAQ,CAAC,KAAK,8CAA8C,CAAC,CAAA;IAC5E,wEAAwE;IACxE,yEAAyE;IACzE,wEAAwE;IACxE,UAAU,CAAC,KAAc,CAAC,CAAA;AAC5B,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY,EAAE,EAAU;IAC5D,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD,MAAM,YAAY,GAAG,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IAEzG,IAAI,CAAC,YAAY;QAAE,OAAM;IAEzB,MAAM,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC/C,MAAM,KAAK,CAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,KAAK,eAAe,CAAC,CAAA;IAC1D,UAAU,CAAC,KAAc,CAAC,CAAA;AAC5B,CAAC;AAED,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,EAAU;IACzD,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjD,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC7C,QAAQ,CAAC,GAAG,IAAI,UAAmB,CAAC,CAAA;AACtC,CAAC","sourcesContent":["'use server'\n\nimport { fieldErrors } from '@rsc-kit/core/action'\nimport { flash } from '@rsc-kit/core/flash'\nimport { redirect } from '@rsc-kit/core/redirect'\nimport { revalidate } from '@rsc-kit/core/revalidate'\nimport { digestOf, fetchManifest, newInstallation } from '../installable.js'\nimport { allowOf, allowedResource, contextOf, panelAt, resourcesFor, tellAll } from './registry.js'\nimport { bulkToast, parse } from '../resource.js'\n\n/**\n * One save for every resource, bound by the page:\n * saveRecord.bind(null, '/admin', 'products', id). The resource's own schema\n * is the check, and the panel's authorize runs first, on every call - an\n * action is reachable without the page that rendered it.\n */\nexport async function saveRecord(path: string, name: string, id: string | null, form: FormData) {\n const { resource, context } = await allowedResource(path, name)\n const current = id === null ? null : await resource.source.find(id, context)\n\n if (id !== null && !current) throw new Error(`[admin] no ${name} ${id} here`)\n\n // What the record had is what a file field may keep without uploading again.\n // A backend's create and update may take different fields: a password\n // required to create is optional to edit.\n const target = id === null ? resource : { schema: resource.editSchema, fields: resource.editFields }\n const parsed = await parse(target, form, { current: current as Record<string, unknown> | null, tenant: context.tenant })\n\n if (!parsed.ok) return fieldErrors(parsed.errors)\n\n // A relation's value is an id the browser sent, so it is looked up as the\n // visitor: one from another tenant, or none at all, is not one of theirs.\n const values = parsed.values as Record<string, unknown>\n const unrelated: Record<string, string> = {}\n\n for (const field of target.fields) {\n const value = values[field.name]\n\n if (!field.relation || value === undefined || value === null || value === '') continue\n\n const related = (await resourcesFor(panelAt(path), context)).find((candidate) => candidate.name === field.relation)!\n\n if (!(await related.source.find(String(value), context))) unrelated[field.name] = `Choose one of the listed ${related.label.toLowerCase()}.`\n }\n\n if (Object.keys(unrelated).length > 0) return fieldErrors(unrelated)\n\n const row = id === null ? await resource.source.create(parsed.values, context) : await resource.source.update(id, parsed.values, context)\n\n await tellAll(\n panelAt(path),\n { panel: path, resource: name, id: id ?? String(row[resource.key] ?? ''), change: id === null ? 'created' : 'updated', values: parsed.values, tenant: context.tenant },\n context,\n )\n\n // The form is replaced by the list the redirect lands on, so the word\n // that it worked rides along to that page.\n await flash(`${resource.singular} ${id === null ? 'created' : 'saved'}.`)\n\n redirect(`${path}/${name}` as never)\n}\n\n/** Deleting from the table refreshes the table, and nothing else on the page. */\nexport async function deleteRecord(path: string, name: string, id: string) {\n const { resource, context } = await allowedResource(path, name)\n\n if (!(await resource.source.find(id, context))) return\n\n await resource.source.delete(id, context)\n await tellAll(panelAt(path), { panel: path, resource: name, id, change: 'deleted', tenant: context.tenant }, context)\n await flash(`${resource.singular} deleted.`)\n revalidate('admin-table' as never)\n}\n\n/**\n * The rows ticked in the table, deleted or handed to one of the resource's\n * bulk actions. Each id is found as the visitor first, so one from another\n * tenant, or already gone, is left out rather than acted on.\n */\nexport async function bulkRecords(path: string, name: string, action: string, ids: string[]) {\n const { resource, context } = await allowedResource(path, name)\n const bulk = action === 'delete' ? null : resource.bulkActions.find((candidate) => candidate.name === action)\n\n if (bulk === undefined) throw new Error(`[admin] ${name} has no bulk action ${action}`)\n\n const found = await Promise.all([...new Set(ids.map(String))].map(async (id) => [id, await resource.source.find(id, context)] as const))\n const rows = found.filter(([, row]) => row !== null && row !== undefined)\n const noun = (count: number) => (count === 1 ? resource.singular : resource.label).toLowerCase()\n\n if (bulk === null) {\n for (const [id] of rows) {\n await resource.source.delete(id, context)\n await tellAll(panelAt(path), { panel: path, resource: name, id, change: 'deleted', tenant: context.tenant }, context)\n }\n\n await flash(bulkToast({ label: 'Delete', done: 'deleted' }, rows.length, noun(rows.length)))\n } else {\n const said = await bulk.run(rows.map(([, row]) => row), context)\n\n await flash(typeof said === 'string' ? said : bulkToast(bulk, rows.length, noun(rows.length)))\n }\n\n revalidate('admin-table' as never)\n}\n\n/** The switcher's choice, checked against the visitor's own tenants, then the dashboard. */\nexport async function switchTenant(path: string, id: string) {\n const panel = panelAt(path)\n\n if (!(await panel.authorize()) || !panel.tenancy) throw new Error('[admin] not allowed')\n\n const theirs = await panel.tenancy.tenants()\n\n if (!theirs.some((tenant) => tenant.id === id)) throw new Error('[admin] not one of this visitor\\'s tenants')\n\n await panel.tenancy.switch(id)\n // The team's name and nav are in the layout, which a redirect keeps as it\n // was unless the action says what it changed.\n revalidate('all' as never)\n redirect(path as never)\n}\n\nexport async function signOut(path: string) {\n const panel = panelAt(path)\n\n await panel.signOut?.()\n redirect('/' as never)\n}\n\n/** The panel, once this visitor may install and uninstall there. */\nasync function managed(path: string) {\n const panel = panelAt(path)\n\n if (!panel.installs || !(await panel.authorize())) throw new Error('[admin] not allowed')\n if (panel.installs.manage && !(await panel.installs.manage())) throw new Error('[admin] not allowed to manage plugins')\n\n const context = await contextOf(panel)\n\n if (!context) throw new Error('[admin] no tenant chosen, or not one of this visitor\\'s')\n\n return { panel, installs: panel.installs, context }\n}\n\n/** A manifest url, fetched and checked, then shown for review. Nothing is installed yet. */\nexport async function reviewPlugin(path: string, form: FormData) {\n const { panel } = await managed(path)\n const url = String(form.get('manifest') ?? '').trim()\n\n try {\n await fetchManifest(url, allowOf(panel), panel.resources.map((resource) => resource.name))\n } catch (error) {\n return fieldErrors({ manifest: error instanceof Error ? error.message : 'The manifest could not be read.' })\n }\n\n redirect(`${path}/plugins?review=${encodeURIComponent(url)}` as never)\n}\n\n/**\n * Install what was reviewed. The manifest is fetched again and must match the\n * digest the review page showed: a plugin that changed its scopes between the\n * review and the click is refused, not installed with scopes nobody read.\n */\nexport async function installPlugin(path: string, url: string, digest: string) {\n const { panel, installs, context } = await managed(path)\n const host = panel.resources.map((resource) => resource.name)\n const manifest = await fetchManifest(url, allowOf(panel), host)\n\n if ((await digestOf(manifest)) !== digest) {\n await flash('The plugin changed since you reviewed it. Review it again.', 'error')\n redirect(`${path}/plugins?review=${encodeURIComponent(url)}` as never)\n }\n\n const installed = await installs.store.list(context.tenant)\n const taken = new Set([...host, ...installed.flatMap((installation) => installation.manifest.resources.map((resource) => resource.name))])\n\n if (installed.some((installation) => installation.manifest.name === manifest.name)) {\n await flash(`${manifest.label} is already installed.`, 'error')\n redirect(`${path}/plugins` as never)\n }\n\n const clash = manifest.resources.find((resource) => taken.has(resource.name))\n\n if (clash) {\n await flash(`Another plugin already adds ${clash.name}.`, 'error')\n redirect(`${path}/plugins` as never)\n }\n\n await installs.store.add(context.tenant, newInstallation(url, manifest))\n await flash(`${manifest.label} installed. Copy its secret into the plugin.`)\n // Re-rendered in place, not redirected: its resources join the sidebar,\n // which is in a layout a redirect keeps mounted from before the install.\n // The review it came from closes itself once the manifest is installed.\n revalidate('all' as never)\n}\n\n/** Uninstalled: its secret goes with it, so every call it could have answered stops at once. */\nexport async function uninstallPlugin(path: string, id: string) {\n const { installs, context } = await managed(path)\n const installation = (await installs.store.list(context.tenant)).find((candidate) => candidate.id === id)\n\n if (!installation) return\n\n await installs.store.remove(context.tenant, id)\n await flash(`${installation.manifest.label} uninstalled.`)\n revalidate('all' as never)\n}\n\n/** The secret was copied into the plugin; the plugins page stops showing it. */\nexport async function secretCopied(path: string, id: string) {\n const { installs, context } = await managed(path)\n\n await installs.store.seen(context.tenant, id)\n redirect(`${path}/plugins` as never)\n}\n"]}
@@ -7,6 +7,7 @@ import { bulkRecords, deleteRecord, installPlugin, reviewPlugin, saveRecord, sec
7
7
  import { digestOf, fetchManifest, RESERVED, scopeWords } from '../installable.js';
8
8
  import { allowOf, contextOf, registerPanel, resourcesFor } from './registry.js';
9
9
  import { humanize } from '../resource.js';
10
+ import { cursorHref } from './url.js';
10
11
  export { listSearch, pluginsSearch, recordParams, resourceParams } from './url.js';
11
12
  /**
12
13
  * A panel: its resources, and the pages that show them, already bound to it.
@@ -54,7 +55,7 @@ export function definePanel(definition) {
54
55
  // resource is known, so a misspelt one fails at startup and not on the
55
56
  // first form that needs its choices.
56
57
  for (const resource of resources) {
57
- const named = [...resource.fields.map((field) => field.relation), ...resource.columns.map((column) => column.relation)];
58
+ const named = [...resource.relations, ...resource.columns.map((column) => column.relation)];
58
59
  for (const relation of named) {
59
60
  if (relation !== undefined && !names.includes(relation)) {
60
61
  throw new Error(`[admin] ${resource.name} relates to ${relation}, which is not a resource of this panel`);
@@ -86,10 +87,10 @@ export function definePanel(definition) {
86
87
  return found;
87
88
  };
88
89
  }
89
- /** The form's fields, each relation given its choices. */
90
- async function fieldsFor(resource, context) {
90
+ /** The form's fields, each relation given its choices: the create form's, or the edit form's. */
91
+ async function fieldsFor(resource, context, editing = false) {
91
92
  const of = choices(context, await resourcesFor(handle, context));
92
- return Promise.all(resource.fields.map(async (field) => (field.relation ? { ...field, options: await of(field.relation) } : field)));
93
+ return Promise.all((editing ? resource.editFields : resource.fields).map(async (field) => (field.relation ? { ...field, options: await of(field.relation) } : field)));
93
94
  }
94
95
  async function enter(name) {
95
96
  await connection();
@@ -205,14 +206,29 @@ export function definePanel(definition) {
205
206
  async function TableBody({ params, searchParams }) {
206
207
  const { resource, context } = await enter((await params).resource);
207
208
  const search = await searchParams;
208
- const { q, sort, dir, page, ...rest } = search;
209
+ const { q, sort, dir, page, cursor, ...rest } = search;
209
210
  const filters = Object.fromEntries(Object.entries(rest).flatMap(([key, value]) => typeof value === 'string' && value !== '' && resource.filters.some((filter) => filter.key === key) ? [[key, value]] : []));
210
211
  const sortable = resource.columns.some((column) => column.sortable && column.key === sort) ? sort : undefined;
211
212
  const server = resource.mode === 'server';
212
213
  const searchable = resource.columns.filter((column) => column.searchable).map((column) => column.key);
213
- const { rows, total } = await resource.source.list(server
214
- ? { ...(q ? { q } : {}), ...(sortable ? { sort: sortable } : {}), dir, page, perPage: resource.perPage, filters, searchable }
214
+ const listed = await resource.source.list(server
215
+ ? {
216
+ ...(q ? { q } : {}),
217
+ ...(sortable ? { sort: sortable } : {}),
218
+ dir,
219
+ page,
220
+ ...(cursor ? { cursor } : {}),
221
+ perPage: resource.perPage,
222
+ filters,
223
+ searchable,
224
+ }
215
225
  : { dir: 'asc', page: 1, perPage: null, filters: {}, searchable }, context);
226
+ const { rows } = listed;
227
+ const total = typeof listed.total === 'number' ? listed.total : null;
228
+ // A page of a list paged by cursor: the pager asks for the cursor it was
229
+ // handed, with the search, filters and sort the visitor is on.
230
+ const cursorUrl = (at) => at ? cursorHref(to(resource.name), { ...(q ? { q } : {}), ...(sortable ? { sort: sortable } : {}), dir, filters }, at) : null;
231
+ const cursors = server && 'next' in listed ? { prev: cursorUrl(listed.prev), next: cursorUrl(listed.next) } : undefined;
216
232
  // A relation column shows the related row's title, and is searched and
217
233
  // sorted by it in the browser too: what the visitor reads is what they type.
218
234
  const of = choices(context, await resourcesFor(handle, context));
@@ -240,7 +256,7 @@ export function definePanel(definition) {
240
256
  sortable: canSort,
241
257
  searchable: canSearch,
242
258
  visible,
243
- })), rows: drawn, total: total, perPage: resource.perPage, query: { q: q ?? '', ...(sortable ? { sort: sortable } : {}), dir, page, filters }, filters: resource.filters, noun: { one: resource.singular, many: resource.label }, ...(resource.readOnly
259
+ })), rows: drawn, total: total, ...(cursors ? { cursors } : {}), perPage: resource.perPage, query: { q: q ?? '', ...(sortable ? { sort: sortable } : {}), dir, page, filters }, filters: resource.filters, noun: { one: resource.singular, many: resource.label }, ...(resource.readOnly
244
260
  ? {}
245
261
  : {
246
262
  bulk: {
@@ -269,7 +285,7 @@ export function definePanel(definition) {
269
285
  const record = resource.readOnly ? null : await resource.source.find(id, context);
270
286
  if (!record)
271
287
  notFound();
272
- return (_jsx(theme.Form, { title: String(record[resource.title]), description: `Edit this ${resource.singular.toLowerCase()}.`, action: saveRecord.bind(null, path, resource.name, id), fields: await fieldsFor(resource, context), values: record, submit: "Save changes", cancel: to(resource.name) }));
288
+ return (_jsx(theme.Form, { title: String(record[resource.title]), description: `Edit this ${resource.singular.toLowerCase()}.`, action: saveRecord.bind(null, path, resource.name, id), fields: await fieldsFor(resource, context, true), values: record, submit: "Save changes", cancel: to(resource.name) }));
273
289
  }
274
290
  /** The installed plugins, a manifest under review, and the form to start one. */
275
291
  async function PluginsBody({ searchParams }) {
@@ -1 +1 @@
1
- {"version":3,"file":"panel.js","sourceRoot":"","sources":["../../src/admin/panel.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Suspense } from 'react'\nimport type { ComponentType, ReactNode } from 'react'\nimport { notFound } from '@rsc-kit/core/not-found'\nimport { connection } from '@rsc-kit/core/request'\nimport { section } from '@rsc-kit/core/section'\nimport {\n bulkRecords,\n deleteRecord,\n installPlugin,\n reviewPlugin,\n saveRecord,\n secretCopied,\n signOut,\n switchTenant,\n uninstallPlugin,\n} from './actions.js'\nimport { digestOf, fetchManifest, RESERVED, scopeWords } from '../installable.js'\nimport type { Installs } from '../installable.js'\nimport type { PanelPlugin } from '../plugin.js'\nimport { allowOf, contextOf, registerPanel, resourcesFor } from './registry.js'\nimport type { PanelHandle, Tenancy } from './registry.js'\nimport { humanize } from '../resource.js'\nimport type { Column, Field, Option, Resource, SourceContext } from '../resource.js'\nimport type { AdminTheme, NavItem, PanelUser, PluginsProps, TableRowData } from './theme.js'\nimport type { ListSearch, PluginsSearch } from './url.js'\n\nexport { listSearch, pluginsSearch, recordParams, resourceParams } from './url.js'\n\nexport interface PanelDefinition {\n /** Where the panel's routes are: the folder under src/app it is mounted in. */\n path?: string\n title?: string\n /** The sidebar's mark beside the title, in a panel without tenants. */\n logo?: ComponentType<{ className?: string }>\n resources: Resource[]\n /**\n * What draws the panel: import { shadcn } from '@rsc-kit/press/shadcn', or\n * any object that fulfils AdminTheme. The panel decides what is shown and\n * who may see it; the theme only draws.\n */\n theme: AdminTheme\n /**\n * Whether this request may use the panel. Runs on every page and on every\n * save, delete and switch - an action is reachable without the page that\n * rendered it, so a guard on the pages alone would not be one. A refusal is\n * a 404. The panel has no login of its own: this asks the app's.\n */\n authorize?: () => boolean | Promise<boolean>\n /** Who is signed in, for the sidebar's footer. */\n user?: () => PanelUser | null | Promise<PanelUser | null>\n /** Sign the visitor out. Runs in a server action; the visitor lands on /. */\n signOut?: () => void | Promise<void>\n /** Whose records the panel shows: a switcher in the sidebar, and a tenant on every data source call. */\n tenancy?: Tenancy\n /** Packages that add resources, dashboard widgets and reactions to changes. */\n plugins?: PanelPlugin[]\n /**\n * Plugins a person installs from the panel while it runs, with no rebuild:\n * a manifest url, reviewed, then installed for the current tenant. Mounted\n * as src/app/admin/plugins/page.tsx. See installable.ts for what they can\n * and cannot reach.\n */\n installs?: Installs\n /** The light/dark switch in the header. On unless the app has its own. */\n modeToggle?: boolean\n}\n\n/** A type, not an interface: section() takes props that are a record, and an interface has no index signature. */\ntype Props<P, S = never> = {\n params: Promise<P>\n searchParams: Promise<S>\n}\n\n/**\n * A panel: its resources, and the pages that show them, already bound to it.\n * The app mounts each in a route file of its own, because the engine finds\n * routes, url schemas and sections by their files:\n *\n * src/app/admin/middleware.ts export default panel.middleware\n * src/app/admin/layout.tsx export default panel.Layout\n * src/app/admin/page.tsx export default panel.Dashboard\n * src/app/admin/[resource]/page.tsx export const params = resourceParams\n * export const searchParams = listSearch\n * export default panel.Index\n * src/app/admin/[resource]/admin-table.section.tsx export default panel.Table\n * src/app/admin/[resource]/new/page.tsx export const params = resourceParams\n * export default panel.Create\n * src/app/admin/[resource]/[id]/page.tsx export const params = recordParams\n * export default panel.Edit\n */\nexport function definePanel(definition: PanelDefinition) {\n const path = definition.path ?? '/admin'\n const title = definition.title ?? 'Admin'\n const plugins = definition.plugins ?? []\n const resources = [...definition.resources, ...plugins.flatMap((plugin) => plugin.resources ?? [])]\n const authorize = definition.authorize ?? (() => true)\n const tenancy = definition.tenancy ?? null\n const installs = definition.installs ?? null\n const theme = definition.theme\n /** The panel's own resources and its plugins'; any other is one a tenant installed. */\n const own = new Set(resources.map((resource) => resource.name))\n\n if (!/^(\\/[a-z0-9-]+)+$/.test(path)) {\n throw new Error(`[admin] a panel's path is where its routes are, with no trailing slash: ${JSON.stringify(path)}`)\n }\n\n const names = resources.map((resource) => resource.name)\n const twice = names.find((name, index) => names.indexOf(name) !== index)\n\n if (twice) throw new Error(`[admin] two resources are named ${twice}; a name is a url segment`)\n\n const reserved = installs ? names.find((name) => RESERVED.includes(name)) : undefined\n\n if (reserved) throw new Error(`[admin] a panel that takes installs has a plugins page, so no resource may be named ${reserved}`)\n\n const pluginNames = plugins.map((plugin) => plugin.name)\n const pluginTwice = pluginNames.find((name, index) => pluginNames.indexOf(name) !== index)\n\n if (pluginTwice) throw new Error(`[admin] the plugin ${pluginTwice} is installed twice`)\n\n // A relation names a resource by its url segment. Checked here, once every\n // resource is known, so a misspelt one fails at startup and not on the\n // first form that needs its choices.\n for (const resource of resources) {\n const named = [...resource.fields.map((field) => field.relation), ...resource.columns.map((column) => column.relation)]\n\n for (const relation of named) {\n if (relation !== undefined && !names.includes(relation)) {\n throw new Error(`[admin] ${resource.name} relates to ${relation}, which is not a resource of this panel`)\n }\n }\n }\n\n const handle: PanelHandle = { path, resources, authorize, plugins, tenancy, signOut: definition.signOut ?? null, installs }\n\n registerPanel(handle)\n\n /** A url of the panel. Typed routes are the app's; the panel's are its own. */\n const to = (...parts: string[]) => [path, ...parts].join('/')\n\n /**\n * A related resource's rows as choices: its key saved, its title shown.\n * Every row, scoped to the visitor's tenant like any list - so a belongsTo\n * is for a resource with a select's worth of rows: categories, teams,\n * plans. Memoised per render, so a field and a column naming the same\n * resource list it once.\n */\n function choices(context: SourceContext, resources: Resource[]) {\n const listed = new Map<string, Promise<Option[]>>()\n\n return (name: string): Promise<Option[]> => {\n let found = listed.get(name)\n\n if (!found) {\n const related = resources.find((resource) => resource.name === name)!\n\n found = related.source\n .list({ dir: 'asc', page: 1, perPage: null, filters: {}, searchable: [] }, context)\n .then(({ rows }) => rows.map((row) => ({ value: String(row[related.key]), label: String(row[related.title]) })))\n listed.set(name, found)\n }\n\n return found\n }\n }\n\n /** The form's fields, each relation given its choices. */\n async function fieldsFor(resource: Resource, context: SourceContext): Promise<Field[]> {\n const of = choices(context, await resourcesFor(handle, context))\n\n return Promise.all(resource.fields.map(async (field) => (field.relation ? { ...field, options: await of(field.relation) } : field)))\n }\n\n /**\n * The panel's door: a refusal is a 404, so the panel's existence is not\n * shown. Per request, always - authorize is about this visitor and the rows\n * are live - so the build stores the skeleton and never runs either.\n */\n async function enter(): Promise<SourceContext>\n async function enter(name: string): Promise<{ resource: Resource; context: SourceContext }>\n async function enter(name?: string) {\n await connection()\n\n if (!(await authorize())) notFound()\n\n const context = (await contextOf(handle)) ?? notFound()\n\n if (name === undefined) return context\n\n return { resource: (await resourcesFor(handle, context)).find((resource) => resource.name === name) ?? notFound(), context }\n }\n\n /**\n * The panel's guard, mounted as src/app/admin/middleware.ts. It runs before\n * anything is sent, so its refusals are real 404s: a visitor authorize turns\n * away, no tenant or another's, a resource the panel does not have, a record\n * that is not there - or not this tenant's. The pages check again, inside\n * their boundaries, where a refusal can no longer change the status.\n */\n async function middleware(params: Record<string, string | undefined> = {}) {\n if (!(await authorize())) notFound()\n\n const context = (await contextOf(handle)) ?? notFound()\n\n if (params.resource === undefined) return\n\n const resource = (await resourcesFor(handle, context)).find((candidate) => candidate.name === params.resource) ?? notFound()\n\n // An edit page changes a record. A read-only resource's new page cannot\n // be told apart here - it has the list's params - so it refuses itself.\n if (resource.readOnly && params.id !== undefined) notFound()\n\n if (params.id !== undefined && !(await resource.source.find(params.id, context))) notFound()\n }\n\n /** The sidebar's header: the tenant switcher, or the panel's name. */\n async function Tenants() {\n await connection()\n\n const context = (await authorize()) && tenancy ? await contextOf(handle) : null\n\n if (!tenancy || !context?.tenant) return brand()\n\n return (\n <theme.TenantSwitcher\n tenants={await tenancy.tenants()}\n current={context.tenant}\n label={tenancy.label ?? 'Teams'}\n choose={switchTenant.bind(null, path)}\n />\n )\n }\n\n function brand() {\n const Logo = definition.logo\n\n return <theme.Brand href={path} title={title} {...(Logo ? { logo: <Logo /> } : {})} />\n }\n\n async function User() {\n await connection()\n\n const user = (await authorize()) ? await definition.user?.() : null\n\n if (!user) return null\n\n return <theme.UserMenu user={user} {...(definition.signOut ? { signOut: signOut.bind(null, path) } : {})} />\n }\n\n /** A resource's own icon, drawn; the theme draws a default for one without. */\n function iconOf(resource: Resource) {\n const Icon = resource.icon\n\n return Icon ? { icon: <Icon /> } : {}\n }\n\n /** The sidebar's links: the panel's resources, then the tenant's installed ones, then Plugins. */\n function navOf(shown: Resource[]): NavItem[] {\n return [\n { href: path, label: 'Dashboard', kind: 'dashboard', exact: true },\n ...shown.map((resource): NavItem => ({\n href: to(resource.name),\n label: resource.label,\n kind: own.has(resource.name) ? 'resource' : 'installed',\n ...iconOf(resource),\n })),\n ...(installs ? [{ href: to('plugins'), label: 'Plugins', kind: 'plugins' } as const] : []),\n ]\n }\n\n /** Installed resources are this tenant's and this request's, so the nav waits for them; the panel's own show meanwhile. */\n async function InstalledNav() {\n await connection()\n\n const context = (await authorize()) ? await contextOf(handle) : null\n\n return <theme.Nav items={navOf(context ? await resourcesFor(handle, context) : resources)} />\n }\n\n function crumbsOf(shown: Resource[]) {\n return (\n <theme.Breadcrumbs\n root={path}\n title={title}\n labels={{ ...(installs ? { plugins: 'Plugins' } : {}), ...Object.fromEntries(shown.map((resource) => [resource.name, resource.label])) }}\n />\n )\n }\n\n async function InstalledCrumbs() {\n await connection()\n\n const context = (await authorize()) ? await contextOf(handle) : null\n\n return crumbsOf(context ? await resourcesFor(handle, context) : resources)\n }\n\n function Layout({ children }: { children: ReactNode }) {\n return (\n <theme.Shell\n title={title}\n modeToggle={definition.modeToggle !== false}\n brand={\n tenancy ? (\n <Suspense fallback={<theme.Loading kind=\"menu\" />}>\n <Tenants />\n </Suspense>\n ) : (\n brand()\n )\n }\n nav={\n installs ? (\n <Suspense fallback={<theme.Nav items={navOf(resources)} />}>\n <InstalledNav />\n </Suspense>\n ) : (\n <theme.Nav items={navOf(resources)} />\n )\n }\n user={\n definition.user ? (\n <Suspense fallback={<theme.Loading kind=\"menu\" />}>\n <User />\n </Suspense>\n ) : null\n }\n crumbs={\n installs ? (\n <Suspense fallback={crumbsOf(resources)}>\n <InstalledCrumbs />\n </Suspense>\n ) : (\n crumbsOf(resources)\n )\n }\n >\n {children}\n </theme.Shell>\n )\n }\n\n async function DashboardBody() {\n const context = await enter()\n const counts = await Promise.all(\n (await resourcesFor(handle, context)).map(async (resource) => {\n // An installed plugin is somebody else's server: one that is down\n // leaves its card blank, not the dashboard broken.\n const total = await resource.source\n .list({ dir: 'asc', page: 1, perPage: 1, filters: {}, searchable: [] }, context)\n .then(({ total: counted }) => counted as number | null)\n .catch((error: unknown) => {\n console.error(`[admin] counting ${resource.name} failed`, error)\n\n return null\n })\n\n return { name: resource.name, label: resource.label, href: to(resource.name), total, ...iconOf(resource) }\n }),\n )\n\n return (\n <theme.Dashboard\n description={context.tenant ? `Everything in ${context.tenant.name}.` : 'Everything in the panel, at a glance.'}\n counts={counts}\n widgets={plugins.flatMap((plugin) =>\n (plugin.widgets ?? []).map(({ title: widgetTitle, description, component: Widget }, index) => ({\n key: `${plugin.name}:${index}`,\n title: widgetTitle,\n ...(description ? { description } : {}),\n content: <Widget tenant={context.tenant} />,\n })),\n )}\n />\n )\n }\n\n /**\n * The table, as a section: a delete refreshes it and nothing else. A\n * refresh renders it with the page's params and searchParams, and nothing\n * else - which is why it is bound to the panel here rather than handed one\n * as a prop.\n */\n async function TableBody({ params, searchParams }: Props<{ resource: string }, ListSearch>) {\n const { resource, context } = await enter((await params).resource)\n const search = await searchParams\n const { q, sort, dir, page, ...rest } = search\n const filters: Record<string, string> = Object.fromEntries(\n Object.entries(rest).flatMap(([key, value]) =>\n typeof value === 'string' && value !== '' && resource.filters.some((filter) => filter.key === key) ? [[key, value]] : [],\n ),\n )\n const sortable = resource.columns.some((column) => column.sortable && column.key === sort) ? sort : undefined\n const server = resource.mode === 'server'\n const searchable = resource.columns.filter((column) => column.searchable).map((column) => column.key)\n const { rows, total } = await resource.source.list(\n server\n ? { ...(q ? { q } : {}), ...(sortable ? { sort: sortable } : {}), dir, page, perPage: resource.perPage, filters, searchable }\n : { dir: 'asc', page: 1, perPage: null, filters: {}, searchable },\n context,\n )\n\n // A relation column shows the related row's title, and is searched and\n // sorted by it in the browser too: what the visitor reads is what they type.\n const of = choices(context, await resourcesFor(handle, context))\n const titles = new Map(\n await Promise.all(\n resource.columns\n .filter((column) => column.relation !== undefined)\n .map(async (column) => [column.key, new Map((await of(column.relation!)).map((option) => [option.value, option.label]))] as const),\n ),\n )\n const shown = (column: Column, value: unknown) => {\n const title = value === null || value === undefined ? undefined : titles.get(column.key)?.get(String(value))\n\n return title ?? value\n }\n\n const drawn: TableRowData[] = await Promise.all(\n rows.map(async (row) => {\n const id = String(row[resource.key])\n\n return {\n id,\n cells: Object.fromEntries(\n await Promise.all(resource.columns.map(async (column) => [column.key, await draw(theme, column, shown(column, row[column.key]), row)] as const)),\n ),\n values: Object.fromEntries(resource.columns.map((column) => [column.key, plain(shown(column, row[column.key]))])),\n ...(resource.readOnly\n ? {}\n : { edit: to(resource.name, encodeURIComponent(id)), remove: deleteRecord.bind(null, path, resource.name, id) }),\n }\n }),\n )\n\n return (\n <theme.Table\n mode={resource.mode}\n href={to(resource.name)}\n columns={resource.columns.map(({ key, label, sortable: canSort, searchable: canSearch, visible }) => ({\n key,\n label,\n sortable: canSort,\n searchable: canSearch,\n visible,\n }))}\n rows={drawn}\n total={total}\n perPage={resource.perPage}\n query={{ q: q ?? '', ...(sortable ? { sort: sortable } : {}), dir, page, filters }}\n filters={resource.filters}\n noun={{ one: resource.singular, many: resource.label }}\n {...(resource.readOnly\n ? {}\n : {\n bulk: {\n actions: [\n ...resource.bulkActions.map(({ name, label, destructive }) => ({ name, label, destructive: destructive ?? false })),\n { name: 'delete', label: 'Delete', destructive: true },\n ],\n run: bulkRecords.bind(null, path, resource.name),\n },\n })}\n />\n )\n }\n\n const Table = section('admin-table', TableBody)\n\n async function IndexBody({ params, searchParams }: Props<{ resource: string }, ListSearch>) {\n const { resource } = await enter((await params).resource)\n\n return (\n <theme.Index\n title={resource.label}\n {...(resource.description ? { description: resource.description } : {})}\n {...(resource.readOnly ? {} : { create: { href: to(resource.name, 'new'), label: `New ${resource.singular.toLowerCase()}` } })}\n table={<Table params={params} searchParams={searchParams} />}\n />\n )\n }\n\n async function CreateBody({ params }: Props<{ resource: string }>) {\n const { resource, context } = await enter((await params).resource)\n\n if (resource.readOnly) notFound()\n\n return (\n <theme.Form\n title={`New ${resource.singular.toLowerCase()}`}\n description={`Add a ${resource.singular.toLowerCase()} to ${resource.label.toLowerCase()}.`}\n action={saveRecord.bind(null, path, resource.name, null)}\n fields={await fieldsFor(resource, context)}\n values={{}}\n submit={`Create ${resource.singular.toLowerCase()}`}\n cancel={to(resource.name)}\n />\n )\n }\n\n async function EditBody({ params }: Props<{ resource: string; id: string }>) {\n const { resource: name, id } = await params\n const { resource, context } = await enter(name)\n const record = resource.readOnly ? null : await resource.source.find(id, context)\n\n if (!record) notFound()\n\n return (\n <theme.Form\n title={String(record[resource.title])}\n description={`Edit this ${resource.singular.toLowerCase()}.`}\n action={saveRecord.bind(null, path, resource.name, id)}\n fields={await fieldsFor(resource, context)}\n values={record}\n submit=\"Save changes\"\n cancel={to(resource.name)}\n />\n )\n }\n\n /** The installed plugins, a manifest under review, and the form to start one. */\n async function PluginsBody({ searchParams }: { searchParams: Promise<PluginsSearch> }) {\n const context = await enter()\n\n if (!installs) notFound()\n\n const allowed = !installs.manage || (await installs.manage())\n const { review } = await searchParams\n const installed = await installs.store.list(context.tenant)\n const labelOf = (name: string) => resources.find((resource) => resource.name === name)?.label ?? name\n let reviewing: PluginsProps['reviewing'] = null\n\n // Once installed, the review is over: the install re-renders this url in place, ?review= and all.\n if (review && allowed && !installed.some((installation) => installation.manifestUrl === review)) {\n try {\n const manifest = await fetchManifest(review, allowOf(handle), resources.map((resource) => resource.name))\n\n reviewing = {\n label: manifest.label,\n ...(manifest.version ? { version: manifest.version } : {}),\n description: manifest.description ?? new URL(manifest.endpoint).host,\n adds: manifest.resources.map((resource) => resource.label ?? humanize(resource.name)),\n can: manifest.scopes.length === 0 ? [] : scopeWords(manifest, labelOf),\n endpoint: manifest.endpoint,\n install: installPlugin.bind(null, path, review, await digestOf(manifest)),\n cancel: to('plugins'),\n }\n } catch (error) {\n reviewing = { problem: error instanceof Error ? error.message : 'The manifest could not be read.' }\n }\n }\n\n return (\n <theme.Plugins\n description={`Installed for ${context.tenant ? context.tenant.name : 'this panel'}. A plugin runs on its own server; it sees what you grant it here and nothing else.`}\n allowed={allowed}\n review={reviewPlugin.bind(null, path)}\n reviewing={reviewing}\n installed={installed.map((installation) => ({\n id: installation.id,\n label: installation.manifest.label,\n description: installation.manifest.description ?? new URL(installation.manifest.endpoint).host,\n resources: installation.manifest.resources.map((resource) => resource.label ?? humanize(resource.name)),\n scopes: installation.manifest.scopes,\n ...(allowed ? { uninstall: uninstallPlugin.bind(null, path, installation.id) } : {}),\n ...(allowed && !installation.secretSeen\n ? { secret: { installation: installation.id, secret: installation.secret, copied: secretCopied.bind(null, path, installation.id) } }\n : {}),\n }))}\n />\n )\n }\n\n // The pages read their url above everything they show, so each waits inside\n // a boundary of its own: the build stores the theme's Loading as the page's\n // shell, and a page that awaited params above every boundary would fail it.\n // A panel cannot put a loading.tsx beside the app's route files; this is that.\n function Dashboard() {\n return (\n <Suspense fallback={<theme.Loading kind=\"dashboard\" />}>\n <DashboardBody />\n </Suspense>\n )\n }\n\n function Index(props: Props<{ resource: string }, ListSearch>) {\n return (\n <Suspense fallback={<theme.Loading kind=\"table\" />}>\n <IndexBody {...props} />\n </Suspense>\n )\n }\n\n function Create(props: Props<{ resource: string }>) {\n return (\n <Suspense fallback={<theme.Loading kind=\"form\" />}>\n <CreateBody {...props} />\n </Suspense>\n )\n }\n\n function Edit(props: Props<{ resource: string; id: string }>) {\n return (\n <Suspense fallback={<theme.Loading kind=\"form\" />}>\n <EditBody {...props} />\n </Suspense>\n )\n }\n\n function Plugins(props: { searchParams: Promise<PluginsSearch> }) {\n return (\n <Suspense fallback={<theme.Loading kind=\"form\" />}>\n <PluginsBody {...props} />\n </Suspense>\n )\n }\n\n return { path, resources, middleware, Layout, Dashboard, Index, Table, Create, Edit, Plugins }\n}\n\nexport type Panel = ReturnType<typeof definePanel>\n\n/** A cell: the column's own cell(), the theme's drawing of its format, or the value as text. */\nasync function draw(theme: AdminTheme, column: Column, value: unknown, row: unknown): Promise<ReactNode> {\n if (column.cell) return column.cell(value, row)\n if (column.format === 'text' || column.format === 'relation') return String(value ?? '')\n\n return <theme.Cell format={column.format} value={value} />\n}\n\n/** A value the browser can sort and search by. */\nfunction plain(value: unknown): string | number | boolean | null {\n if (value === null || value === undefined) return null\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'string') return value\n\n return String(value)\n}\n"]}
1
+ {"version":3,"file":"panel.js","sourceRoot":"","sources":["../../src/admin/panel.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Suspense } from 'react'\nimport type { ComponentType, ReactNode } from 'react'\nimport { notFound } from '@rsc-kit/core/not-found'\nimport { connection } from '@rsc-kit/core/request'\nimport { section } from '@rsc-kit/core/section'\nimport {\n bulkRecords,\n deleteRecord,\n installPlugin,\n reviewPlugin,\n saveRecord,\n secretCopied,\n signOut,\n switchTenant,\n uninstallPlugin,\n} from './actions.js'\nimport { digestOf, fetchManifest, RESERVED, scopeWords } from '../installable.js'\nimport type { Installs } from '../installable.js'\nimport type { PanelPlugin } from '../plugin.js'\nimport { allowOf, contextOf, registerPanel, resourcesFor } from './registry.js'\nimport type { PanelHandle, Tenancy } from './registry.js'\nimport { humanize } from '../resource.js'\nimport type { Column, Field, Option, Resource, SourceContext } from '../resource.js'\nimport type { AdminTheme, NavItem, PanelUser, PluginsProps, TableRowData } from './theme.js'\nimport type { ListSearch, PluginsSearch } from './url.js'\n\nimport { cursorHref } from './url.js'\nexport { listSearch, pluginsSearch, recordParams, resourceParams } from './url.js'\n\nexport interface PanelDefinition {\n /** Where the panel's routes are: the folder under src/app it is mounted in. */\n path?: string\n title?: string\n /** The sidebar's mark beside the title, in a panel without tenants. */\n logo?: ComponentType<{ className?: string }>\n resources: Resource[]\n /**\n * What draws the panel: import { shadcn } from '@rsc-kit/press/shadcn', or\n * any object that fulfils AdminTheme. The panel decides what is shown and\n * who may see it; the theme only draws.\n */\n theme: AdminTheme\n /**\n * Whether this request may use the panel. Runs on every page and on every\n * save, delete and switch - an action is reachable without the page that\n * rendered it, so a guard on the pages alone would not be one. A refusal is\n * a 404. The panel has no login of its own: this asks the app's.\n */\n authorize?: () => boolean | Promise<boolean>\n /** Who is signed in, for the sidebar's footer. */\n user?: () => PanelUser | null | Promise<PanelUser | null>\n /** Sign the visitor out. Runs in a server action; the visitor lands on /. */\n signOut?: () => void | Promise<void>\n /** Whose records the panel shows: a switcher in the sidebar, and a tenant on every data source call. */\n tenancy?: Tenancy\n /** Packages that add resources, dashboard widgets and reactions to changes. */\n plugins?: PanelPlugin[]\n /**\n * Plugins a person installs from the panel while it runs, with no rebuild:\n * a manifest url, reviewed, then installed for the current tenant. Mounted\n * as src/app/admin/plugins/page.tsx. See installable.ts for what they can\n * and cannot reach.\n */\n installs?: Installs\n /** The light/dark switch in the header. On unless the app has its own. */\n modeToggle?: boolean\n}\n\n/** A type, not an interface: section() takes props that are a record, and an interface has no index signature. */\ntype Props<P, S = never> = {\n params: Promise<P>\n searchParams: Promise<S>\n}\n\n/**\n * A panel: its resources, and the pages that show them, already bound to it.\n * The app mounts each in a route file of its own, because the engine finds\n * routes, url schemas and sections by their files:\n *\n * src/app/admin/middleware.ts export default panel.middleware\n * src/app/admin/layout.tsx export default panel.Layout\n * src/app/admin/page.tsx export default panel.Dashboard\n * src/app/admin/[resource]/page.tsx export const params = resourceParams\n * export const searchParams = listSearch\n * export default panel.Index\n * src/app/admin/[resource]/admin-table.section.tsx export default panel.Table\n * src/app/admin/[resource]/new/page.tsx export const params = resourceParams\n * export default panel.Create\n * src/app/admin/[resource]/[id]/page.tsx export const params = recordParams\n * export default panel.Edit\n */\nexport function definePanel(definition: PanelDefinition) {\n const path = definition.path ?? '/admin'\n const title = definition.title ?? 'Admin'\n const plugins = definition.plugins ?? []\n const resources = [...definition.resources, ...plugins.flatMap((plugin) => plugin.resources ?? [])]\n const authorize = definition.authorize ?? (() => true)\n const tenancy = definition.tenancy ?? null\n const installs = definition.installs ?? null\n const theme = definition.theme\n /** The panel's own resources and its plugins'; any other is one a tenant installed. */\n const own = new Set(resources.map((resource) => resource.name))\n\n if (!/^(\\/[a-z0-9-]+)+$/.test(path)) {\n throw new Error(`[admin] a panel's path is where its routes are, with no trailing slash: ${JSON.stringify(path)}`)\n }\n\n const names = resources.map((resource) => resource.name)\n const twice = names.find((name, index) => names.indexOf(name) !== index)\n\n if (twice) throw new Error(`[admin] two resources are named ${twice}; a name is a url segment`)\n\n const reserved = installs ? names.find((name) => RESERVED.includes(name)) : undefined\n\n if (reserved) throw new Error(`[admin] a panel that takes installs has a plugins page, so no resource may be named ${reserved}`)\n\n const pluginNames = plugins.map((plugin) => plugin.name)\n const pluginTwice = pluginNames.find((name, index) => pluginNames.indexOf(name) !== index)\n\n if (pluginTwice) throw new Error(`[admin] the plugin ${pluginTwice} is installed twice`)\n\n // A relation names a resource by its url segment. Checked here, once every\n // resource is known, so a misspelt one fails at startup and not on the\n // first form that needs its choices.\n for (const resource of resources) {\n const named = [...resource.relations, ...resource.columns.map((column) => column.relation)]\n\n for (const relation of named) {\n if (relation !== undefined && !names.includes(relation)) {\n throw new Error(`[admin] ${resource.name} relates to ${relation}, which is not a resource of this panel`)\n }\n }\n }\n\n const handle: PanelHandle = { path, resources, authorize, plugins, tenancy, signOut: definition.signOut ?? null, installs }\n\n registerPanel(handle)\n\n /** A url of the panel. Typed routes are the app's; the panel's are its own. */\n const to = (...parts: string[]) => [path, ...parts].join('/')\n\n /**\n * A related resource's rows as choices: its key saved, its title shown.\n * Every row, scoped to the visitor's tenant like any list - so a belongsTo\n * is for a resource with a select's worth of rows: categories, teams,\n * plans. Memoised per render, so a field and a column naming the same\n * resource list it once.\n */\n function choices(context: SourceContext, resources: Resource[]) {\n const listed = new Map<string, Promise<Option[]>>()\n\n return (name: string): Promise<Option[]> => {\n let found = listed.get(name)\n\n if (!found) {\n const related = resources.find((resource) => resource.name === name)!\n\n found = related.source\n .list({ dir: 'asc', page: 1, perPage: null, filters: {}, searchable: [] }, context)\n .then(({ rows }) => rows.map((row) => ({ value: String(row[related.key]), label: String(row[related.title]) })))\n listed.set(name, found)\n }\n\n return found\n }\n }\n\n /** The form's fields, each relation given its choices: the create form's, or the edit form's. */\n async function fieldsFor(resource: Resource, context: SourceContext, editing = false): Promise<Field[]> {\n const of = choices(context, await resourcesFor(handle, context))\n\n return Promise.all((editing ? resource.editFields : resource.fields).map(async (field) => (field.relation ? { ...field, options: await of(field.relation) } : field)))\n }\n\n /**\n * The panel's door: a refusal is a 404, so the panel's existence is not\n * shown. Per request, always - authorize is about this visitor and the rows\n * are live - so the build stores the skeleton and never runs either.\n */\n async function enter(): Promise<SourceContext>\n async function enter(name: string): Promise<{ resource: Resource; context: SourceContext }>\n async function enter(name?: string) {\n await connection()\n\n if (!(await authorize())) notFound()\n\n const context = (await contextOf(handle)) ?? notFound()\n\n if (name === undefined) return context\n\n return { resource: (await resourcesFor(handle, context)).find((resource) => resource.name === name) ?? notFound(), context }\n }\n\n /**\n * The panel's guard, mounted as src/app/admin/middleware.ts. It runs before\n * anything is sent, so its refusals are real 404s: a visitor authorize turns\n * away, no tenant or another's, a resource the panel does not have, a record\n * that is not there - or not this tenant's. The pages check again, inside\n * their boundaries, where a refusal can no longer change the status.\n */\n async function middleware(params: Record<string, string | undefined> = {}) {\n if (!(await authorize())) notFound()\n\n const context = (await contextOf(handle)) ?? notFound()\n\n if (params.resource === undefined) return\n\n const resource = (await resourcesFor(handle, context)).find((candidate) => candidate.name === params.resource) ?? notFound()\n\n // An edit page changes a record. A read-only resource's new page cannot\n // be told apart here - it has the list's params - so it refuses itself.\n if (resource.readOnly && params.id !== undefined) notFound()\n\n if (params.id !== undefined && !(await resource.source.find(params.id, context))) notFound()\n }\n\n /** The sidebar's header: the tenant switcher, or the panel's name. */\n async function Tenants() {\n await connection()\n\n const context = (await authorize()) && tenancy ? await contextOf(handle) : null\n\n if (!tenancy || !context?.tenant) return brand()\n\n return (\n <theme.TenantSwitcher\n tenants={await tenancy.tenants()}\n current={context.tenant}\n label={tenancy.label ?? 'Teams'}\n choose={switchTenant.bind(null, path)}\n />\n )\n }\n\n function brand() {\n const Logo = definition.logo\n\n return <theme.Brand href={path} title={title} {...(Logo ? { logo: <Logo /> } : {})} />\n }\n\n async function User() {\n await connection()\n\n const user = (await authorize()) ? await definition.user?.() : null\n\n if (!user) return null\n\n return <theme.UserMenu user={user} {...(definition.signOut ? { signOut: signOut.bind(null, path) } : {})} />\n }\n\n /** A resource's own icon, drawn; the theme draws a default for one without. */\n function iconOf(resource: Resource) {\n const Icon = resource.icon\n\n return Icon ? { icon: <Icon /> } : {}\n }\n\n /** The sidebar's links: the panel's resources, then the tenant's installed ones, then Plugins. */\n function navOf(shown: Resource[]): NavItem[] {\n return [\n { href: path, label: 'Dashboard', kind: 'dashboard', exact: true },\n ...shown.map((resource): NavItem => ({\n href: to(resource.name),\n label: resource.label,\n kind: own.has(resource.name) ? 'resource' : 'installed',\n ...iconOf(resource),\n })),\n ...(installs ? [{ href: to('plugins'), label: 'Plugins', kind: 'plugins' } as const] : []),\n ]\n }\n\n /** Installed resources are this tenant's and this request's, so the nav waits for them; the panel's own show meanwhile. */\n async function InstalledNav() {\n await connection()\n\n const context = (await authorize()) ? await contextOf(handle) : null\n\n return <theme.Nav items={navOf(context ? await resourcesFor(handle, context) : resources)} />\n }\n\n function crumbsOf(shown: Resource[]) {\n return (\n <theme.Breadcrumbs\n root={path}\n title={title}\n labels={{ ...(installs ? { plugins: 'Plugins' } : {}), ...Object.fromEntries(shown.map((resource) => [resource.name, resource.label])) }}\n />\n )\n }\n\n async function InstalledCrumbs() {\n await connection()\n\n const context = (await authorize()) ? await contextOf(handle) : null\n\n return crumbsOf(context ? await resourcesFor(handle, context) : resources)\n }\n\n function Layout({ children }: { children: ReactNode }) {\n return (\n <theme.Shell\n title={title}\n modeToggle={definition.modeToggle !== false}\n brand={\n tenancy ? (\n <Suspense fallback={<theme.Loading kind=\"menu\" />}>\n <Tenants />\n </Suspense>\n ) : (\n brand()\n )\n }\n nav={\n installs ? (\n <Suspense fallback={<theme.Nav items={navOf(resources)} />}>\n <InstalledNav />\n </Suspense>\n ) : (\n <theme.Nav items={navOf(resources)} />\n )\n }\n user={\n definition.user ? (\n <Suspense fallback={<theme.Loading kind=\"menu\" />}>\n <User />\n </Suspense>\n ) : null\n }\n crumbs={\n installs ? (\n <Suspense fallback={crumbsOf(resources)}>\n <InstalledCrumbs />\n </Suspense>\n ) : (\n crumbsOf(resources)\n )\n }\n >\n {children}\n </theme.Shell>\n )\n }\n\n async function DashboardBody() {\n const context = await enter()\n const counts = await Promise.all(\n (await resourcesFor(handle, context)).map(async (resource) => {\n // An installed plugin is somebody else's server: one that is down\n // leaves its card blank, not the dashboard broken.\n const total = await resource.source\n .list({ dir: 'asc', page: 1, perPage: 1, filters: {}, searchable: [] }, context)\n .then(({ total: counted }) => counted as number | null)\n .catch((error: unknown) => {\n console.error(`[admin] counting ${resource.name} failed`, error)\n\n return null\n })\n\n return { name: resource.name, label: resource.label, href: to(resource.name), total, ...iconOf(resource) }\n }),\n )\n\n return (\n <theme.Dashboard\n description={context.tenant ? `Everything in ${context.tenant.name}.` : 'Everything in the panel, at a glance.'}\n counts={counts}\n widgets={plugins.flatMap((plugin) =>\n (plugin.widgets ?? []).map(({ title: widgetTitle, description, component: Widget }, index) => ({\n key: `${plugin.name}:${index}`,\n title: widgetTitle,\n ...(description ? { description } : {}),\n content: <Widget tenant={context.tenant} />,\n })),\n )}\n />\n )\n }\n\n /**\n * The table, as a section: a delete refreshes it and nothing else. A\n * refresh renders it with the page's params and searchParams, and nothing\n * else - which is why it is bound to the panel here rather than handed one\n * as a prop.\n */\n async function TableBody({ params, searchParams }: Props<{ resource: string }, ListSearch>) {\n const { resource, context } = await enter((await params).resource)\n const search = await searchParams\n const { q, sort, dir, page, cursor, ...rest } = search\n const filters: Record<string, string> = Object.fromEntries(\n Object.entries(rest).flatMap(([key, value]) =>\n typeof value === 'string' && value !== '' && resource.filters.some((filter) => filter.key === key) ? [[key, value]] : [],\n ),\n )\n const sortable = resource.columns.some((column) => column.sortable && column.key === sort) ? sort : undefined\n const server = resource.mode === 'server'\n const searchable = resource.columns.filter((column) => column.searchable).map((column) => column.key)\n const listed = await resource.source.list(\n server\n ? {\n ...(q ? { q } : {}),\n ...(sortable ? { sort: sortable } : {}),\n dir,\n page,\n ...(cursor ? { cursor } : {}),\n perPage: resource.perPage,\n filters,\n searchable,\n }\n : { dir: 'asc', page: 1, perPage: null, filters: {}, searchable },\n context,\n )\n const { rows } = listed\n const total = typeof listed.total === 'number' ? listed.total : null\n // A page of a list paged by cursor: the pager asks for the cursor it was\n // handed, with the search, filters and sort the visitor is on.\n const cursorUrl = (at: string | null | undefined) =>\n at ? cursorHref(to(resource.name), { ...(q ? { q } : {}), ...(sortable ? { sort: sortable } : {}), dir, filters }, at) : null\n const cursors = server && 'next' in listed ? { prev: cursorUrl(listed.prev), next: cursorUrl(listed.next) } : undefined\n\n // A relation column shows the related row's title, and is searched and\n // sorted by it in the browser too: what the visitor reads is what they type.\n const of = choices(context, await resourcesFor(handle, context))\n const titles = new Map(\n await Promise.all(\n resource.columns\n .filter((column) => column.relation !== undefined)\n .map(async (column) => [column.key, new Map((await of(column.relation!)).map((option) => [option.value, option.label]))] as const),\n ),\n )\n const shown = (column: Column, value: unknown) => {\n const title = value === null || value === undefined ? undefined : titles.get(column.key)?.get(String(value))\n\n return title ?? value\n }\n\n const drawn: TableRowData[] = await Promise.all(\n rows.map(async (row) => {\n const id = String(row[resource.key])\n\n return {\n id,\n cells: Object.fromEntries(\n await Promise.all(resource.columns.map(async (column) => [column.key, await draw(theme, column, shown(column, row[column.key]), row)] as const)),\n ),\n values: Object.fromEntries(resource.columns.map((column) => [column.key, plain(shown(column, row[column.key]))])),\n ...(resource.readOnly\n ? {}\n : { edit: to(resource.name, encodeURIComponent(id)), remove: deleteRecord.bind(null, path, resource.name, id) }),\n }\n }),\n )\n\n return (\n <theme.Table\n mode={resource.mode}\n href={to(resource.name)}\n columns={resource.columns.map(({ key, label, sortable: canSort, searchable: canSearch, visible }) => ({\n key,\n label,\n sortable: canSort,\n searchable: canSearch,\n visible,\n }))}\n rows={drawn}\n total={total}\n {...(cursors ? { cursors } : {})}\n perPage={resource.perPage}\n query={{ q: q ?? '', ...(sortable ? { sort: sortable } : {}), dir, page, filters }}\n filters={resource.filters}\n noun={{ one: resource.singular, many: resource.label }}\n {...(resource.readOnly\n ? {}\n : {\n bulk: {\n actions: [\n ...resource.bulkActions.map(({ name, label, destructive }) => ({ name, label, destructive: destructive ?? false })),\n { name: 'delete', label: 'Delete', destructive: true },\n ],\n run: bulkRecords.bind(null, path, resource.name),\n },\n })}\n />\n )\n }\n\n const Table = section('admin-table', TableBody)\n\n async function IndexBody({ params, searchParams }: Props<{ resource: string }, ListSearch>) {\n const { resource } = await enter((await params).resource)\n\n return (\n <theme.Index\n title={resource.label}\n {...(resource.description ? { description: resource.description } : {})}\n {...(resource.readOnly ? {} : { create: { href: to(resource.name, 'new'), label: `New ${resource.singular.toLowerCase()}` } })}\n table={<Table params={params} searchParams={searchParams} />}\n />\n )\n }\n\n async function CreateBody({ params }: Props<{ resource: string }>) {\n const { resource, context } = await enter((await params).resource)\n\n if (resource.readOnly) notFound()\n\n return (\n <theme.Form\n title={`New ${resource.singular.toLowerCase()}`}\n description={`Add a ${resource.singular.toLowerCase()} to ${resource.label.toLowerCase()}.`}\n action={saveRecord.bind(null, path, resource.name, null)}\n fields={await fieldsFor(resource, context)}\n values={{}}\n submit={`Create ${resource.singular.toLowerCase()}`}\n cancel={to(resource.name)}\n />\n )\n }\n\n async function EditBody({ params }: Props<{ resource: string; id: string }>) {\n const { resource: name, id } = await params\n const { resource, context } = await enter(name)\n const record = resource.readOnly ? null : await resource.source.find(id, context)\n\n if (!record) notFound()\n\n return (\n <theme.Form\n title={String(record[resource.title])}\n description={`Edit this ${resource.singular.toLowerCase()}.`}\n action={saveRecord.bind(null, path, resource.name, id)}\n fields={await fieldsFor(resource, context, true)}\n values={record}\n submit=\"Save changes\"\n cancel={to(resource.name)}\n />\n )\n }\n\n /** The installed plugins, a manifest under review, and the form to start one. */\n async function PluginsBody({ searchParams }: { searchParams: Promise<PluginsSearch> }) {\n const context = await enter()\n\n if (!installs) notFound()\n\n const allowed = !installs.manage || (await installs.manage())\n const { review } = await searchParams\n const installed = await installs.store.list(context.tenant)\n const labelOf = (name: string) => resources.find((resource) => resource.name === name)?.label ?? name\n let reviewing: PluginsProps['reviewing'] = null\n\n // Once installed, the review is over: the install re-renders this url in place, ?review= and all.\n if (review && allowed && !installed.some((installation) => installation.manifestUrl === review)) {\n try {\n const manifest = await fetchManifest(review, allowOf(handle), resources.map((resource) => resource.name))\n\n reviewing = {\n label: manifest.label,\n ...(manifest.version ? { version: manifest.version } : {}),\n description: manifest.description ?? new URL(manifest.endpoint).host,\n adds: manifest.resources.map((resource) => resource.label ?? humanize(resource.name)),\n can: manifest.scopes.length === 0 ? [] : scopeWords(manifest, labelOf),\n endpoint: manifest.endpoint,\n install: installPlugin.bind(null, path, review, await digestOf(manifest)),\n cancel: to('plugins'),\n }\n } catch (error) {\n reviewing = { problem: error instanceof Error ? error.message : 'The manifest could not be read.' }\n }\n }\n\n return (\n <theme.Plugins\n description={`Installed for ${context.tenant ? context.tenant.name : 'this panel'}. A plugin runs on its own server; it sees what you grant it here and nothing else.`}\n allowed={allowed}\n review={reviewPlugin.bind(null, path)}\n reviewing={reviewing}\n installed={installed.map((installation) => ({\n id: installation.id,\n label: installation.manifest.label,\n description: installation.manifest.description ?? new URL(installation.manifest.endpoint).host,\n resources: installation.manifest.resources.map((resource) => resource.label ?? humanize(resource.name)),\n scopes: installation.manifest.scopes,\n ...(allowed ? { uninstall: uninstallPlugin.bind(null, path, installation.id) } : {}),\n ...(allowed && !installation.secretSeen\n ? { secret: { installation: installation.id, secret: installation.secret, copied: secretCopied.bind(null, path, installation.id) } }\n : {}),\n }))}\n />\n )\n }\n\n // The pages read their url above everything they show, so each waits inside\n // a boundary of its own: the build stores the theme's Loading as the page's\n // shell, and a page that awaited params above every boundary would fail it.\n // A panel cannot put a loading.tsx beside the app's route files; this is that.\n function Dashboard() {\n return (\n <Suspense fallback={<theme.Loading kind=\"dashboard\" />}>\n <DashboardBody />\n </Suspense>\n )\n }\n\n function Index(props: Props<{ resource: string }, ListSearch>) {\n return (\n <Suspense fallback={<theme.Loading kind=\"table\" />}>\n <IndexBody {...props} />\n </Suspense>\n )\n }\n\n function Create(props: Props<{ resource: string }>) {\n return (\n <Suspense fallback={<theme.Loading kind=\"form\" />}>\n <CreateBody {...props} />\n </Suspense>\n )\n }\n\n function Edit(props: Props<{ resource: string; id: string }>) {\n return (\n <Suspense fallback={<theme.Loading kind=\"form\" />}>\n <EditBody {...props} />\n </Suspense>\n )\n }\n\n function Plugins(props: { searchParams: Promise<PluginsSearch> }) {\n return (\n <Suspense fallback={<theme.Loading kind=\"form\" />}>\n <PluginsBody {...props} />\n </Suspense>\n )\n }\n\n return { path, resources, middleware, Layout, Dashboard, Index, Table, Create, Edit, Plugins }\n}\n\nexport type Panel = ReturnType<typeof definePanel>\n\n/** A cell: the column's own cell(), the theme's drawing of its format, or the value as text. */\nasync function draw(theme: AdminTheme, column: Column, value: unknown, row: unknown): Promise<ReactNode> {\n if (column.cell) return column.cell(value, row)\n if (column.format === 'text' || column.format === 'relation') return String(value ?? '')\n\n return <theme.Cell format={column.format} value={value} />\n}\n\n/** A value the browser can sort and search by. */\nfunction plain(value: unknown): string | number | boolean | null {\n if (value === null || value === undefined) return null\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'string') return value\n\n return String(value)\n}\n"]}
@@ -176,7 +176,16 @@ export interface TableProps {
176
176
  href: string;
177
177
  columns: TableColumn[];
178
178
  rows: TableRowData[];
179
- total: number;
179
+ /** Every row the query matches, or null from a source that pages by cursor and does not count. */
180
+ total: number | null;
181
+ /**
182
+ * A list paged by cursor: the urls of the pages either side, null at an
183
+ * end. Present, the pager follows these and there are no page numbers.
184
+ */
185
+ cursors?: {
186
+ prev: string | null;
187
+ next: string | null;
188
+ };
180
189
  perPage: number;
181
190
  query: TableQuery;
182
191
  filters: Filter[];
@@ -1 +1 @@
1
- {"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/admin/theme.ts"],"names":[],"mappings":"","sourcesContent":["import type { ComponentType, ReactNode } from 'react'\nimport type { Field, Filter, Format, TableMode, Tenant } from '../resource.js'\n\n/**\n * What a theme draws for an admin panel. The panel decides everything else:\n * who may see a page, which rows, which tenant, what a button does. A theme\n * is handed the result as props and draws it.\n *\n * Every prop is data, a server action already bound to its record, or an\n * element already drawn - never a component. A theme's component may be a\n * client component, and a component cannot cross to the browser; the\n * element it draws can.\n *\n * The panel owns the Suspense boundaries, because it knows which parts read\n * the request; a theme draws what shows meanwhile, as `Loading`.\n */\nexport interface AdminTheme {\n /** The frame every page sits in: navigation, header, the page itself. */\n Shell: ComponentType<ShellProps>\n /** The panel's name and mark, where a panel with tenants has its switcher. */\n Brand: ComponentType<BrandProps>\n TenantSwitcher: ComponentType<TenantSwitcherProps>\n /** The panel's links, lit up for the page the visitor is on - which only the browser knows. */\n Nav: ComponentType<{ items: NavItem[] }>\n /** Where the visitor is, read from the url: Admin / Products / Edit. */\n Breadcrumbs: ComponentType<BreadcrumbsProps>\n UserMenu: ComponentType<UserMenuProps>\n\n Dashboard: ComponentType<DashboardProps>\n /** A resource's list page around its table. */\n Index: ComponentType<IndexProps>\n /** A resource's rows: searching, sorting, paging, filters and bulk actions, in either mode. */\n Table: ComponentType<TableProps>\n /** A create or edit page, and its form. */\n Form: ComponentType<FormProps>\n Plugins: ComponentType<PluginsProps>\n\n /** A value in a column with a format: money, a badge, yes or no, a date. A column's own cell() is drawn instead. */\n Cell: ComponentType<{ format: Exclude<Format, 'text' | 'relation'>; value: unknown }>\n /** What shows while a page, or a part of the shell, waits for the request. */\n Loading: ComponentType<{ kind: 'dashboard' | 'table' | 'form' | 'menu' }>\n}\n\nexport interface ShellProps {\n title: string\n /** The Brand, or the TenantSwitcher. */\n brand: ReactNode\n /** The Nav. */\n nav: ReactNode\n /** The UserMenu, in a panel that knows who is signed in. */\n user: ReactNode | null\n /** The Breadcrumbs. */\n crumbs: ReactNode\n /** The light and dark switch: off when the app has its own. */\n modeToggle: boolean\n children: ReactNode\n}\n\nexport interface BrandProps {\n href: string\n title: string\n /** The app's mark, drawn; absent when the panel was given none. */\n logo?: ReactNode\n}\n\nexport interface TenantSwitcherProps {\n tenants: Tenant[]\n current: Tenant\n /** What the tenants are called: Teams. */\n label: string\n choose: (id: string) => Promise<unknown>\n}\n\nexport interface NavItem {\n href: string\n label: string\n /**\n * What the link leads to, for the theme's default icon. `installed` is a\n * resource a plugin installed while the panel ran.\n */\n kind: 'dashboard' | 'resource' | 'installed' | 'plugins'\n /** The resource's own icon, drawn. */\n icon?: ReactNode\n /** Exact for the dashboard, so it is not lit up on every page below it. */\n exact?: boolean\n}\n\nexport interface BreadcrumbsProps {\n /** The panel's path. */\n root: string\n title: string\n /** Each first segment's label: { products: 'Products' }. */\n labels: Record<string, string>\n}\n\nexport interface PanelUser {\n name: string\n email?: string\n avatar?: string\n}\n\nexport interface UserMenuProps {\n user: PanelUser\n signOut?: () => Promise<unknown>\n}\n\nexport interface DashboardProps {\n description: string\n counts: {\n name: string\n label: string\n href: string\n /** Null when the source could not be counted: an installed plugin's server that is down. */\n total: number | null\n icon?: ReactNode\n }[]\n /** Each plugin widget, already rendered for this tenant. */\n widgets: { key: string; title: string; description?: string; content: ReactNode }[]\n}\n\nexport interface IndexProps {\n title: string\n description?: string\n /** The new-record link, absent for a read-only resource. */\n create?: { href: string; label: string }\n /** The Table, as a section the panel refreshes on its own. */\n table: ReactNode\n}\n\n/** A column as the browser needs it: what to call it, and what it may do. The cells come drawn. */\nexport interface TableColumn {\n key: string\n label: string\n sortable: boolean\n searchable: boolean\n visible: boolean\n}\n\n/**\n * A row, drawn on the server: each cell is already the element it shows -\n * a money format, a badge, a column's own cell() - and `values` are the raw\n * values the browser sorts and searches by in a client-mode table.\n */\nexport interface TableRowData {\n id: string\n cells: Record<string, ReactNode>\n values: Record<string, string | number | boolean | null>\n edit?: string\n remove?: () => Promise<unknown>\n}\n\n/** A bulk action as the table offers it; the work is the server's. */\nexport interface TableBulkAction {\n name: string\n label: string\n destructive: boolean\n}\n\n/** What a server-mode table is showing: the url's state, as the page read it. */\nexport interface TableQuery {\n q: string\n sort?: string\n dir: 'asc' | 'desc'\n page: number\n filters: Record<string, string>\n}\n\nexport interface TableProps {\n /**\n * server the url is the state. Typing, a filter, a header and the pager\n * each visit a url, the page asks the source for one page, and a\n * link works with no javascript and can be shared.\n * client every row arrived at once; search, filters, sort and the pager\n * happen in the browser, with no request.\n */\n mode: TableMode\n /** The list's url, which a server-mode table visits with its state. */\n href: string\n columns: TableColumn[]\n rows: TableRowData[]\n total: number\n perPage: number\n query: TableQuery\n filters: Filter[]\n noun: { one: string; many: string }\n /** Ticking rows, and what can be done to them. Absent for a read-only resource. */\n bulk?: { actions: TableBulkAction[]; run: (action: string, ids: string[]) => Promise<unknown> }\n}\n\nexport interface FormProps {\n title: string\n description: string\n /** Saves the record. A refusal is a 422 whose messages belong under their fields. */\n action: (form: FormData) => Promise<unknown>\n /** Each field, a relation's with its choices. */\n fields: Field[]\n /** The record's values; empty on a create page. */\n values: Record<string, unknown>\n /** The submit button's words: Create product, Save changes. */\n submit: string\n /** Where cancelling goes. */\n cancel: string\n}\n\nexport interface PluginsProps {\n description: string\n /** Whether this visitor may install and uninstall; one who may not sees what is installed. */\n allowed: boolean\n /** Reads a manifest url, sent as the field `manifest`, and puts it under review. */\n review: (form: FormData) => Promise<unknown>\n /** A manifest under review, or why it cannot be installed. */\n reviewing: null | { problem: string } | PluginReview\n installed: InstalledPlugin[]\n}\n\nexport interface PluginReview {\n label: string\n version?: string\n /** Its description, or its server's host. */\n description: string\n /** The labels of the resources it adds. */\n adds: string[]\n /** What it may reach, in words; empty when it reaches nothing beyond its own records. */\n can: string[]\n endpoint: string\n install: () => Promise<unknown>\n /** Where cancelling the review goes. */\n cancel: string\n}\n\nexport interface InstalledPlugin {\n id: string\n label: string\n description: string\n /** The labels of the resources it added. */\n resources: string[]\n scopes: string[]\n /** Absent for a visitor who may not uninstall. */\n uninstall?: () => Promise<unknown>\n /**\n * What the plugin needs to tell this panel's calls from anyone else's,\n * shown until someone confirms they copied it.\n */\n secret?: { installation: string; secret: string; copied: () => Promise<unknown> }\n}\n"]}
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/admin/theme.ts"],"names":[],"mappings":"","sourcesContent":["import type { ComponentType, ReactNode } from 'react'\nimport type { Field, Filter, Format, TableMode, Tenant } from '../resource.js'\n\n/**\n * What a theme draws for an admin panel. The panel decides everything else:\n * who may see a page, which rows, which tenant, what a button does. A theme\n * is handed the result as props and draws it.\n *\n * Every prop is data, a server action already bound to its record, or an\n * element already drawn - never a component. A theme's component may be a\n * client component, and a component cannot cross to the browser; the\n * element it draws can.\n *\n * The panel owns the Suspense boundaries, because it knows which parts read\n * the request; a theme draws what shows meanwhile, as `Loading`.\n */\nexport interface AdminTheme {\n /** The frame every page sits in: navigation, header, the page itself. */\n Shell: ComponentType<ShellProps>\n /** The panel's name and mark, where a panel with tenants has its switcher. */\n Brand: ComponentType<BrandProps>\n TenantSwitcher: ComponentType<TenantSwitcherProps>\n /** The panel's links, lit up for the page the visitor is on - which only the browser knows. */\n Nav: ComponentType<{ items: NavItem[] }>\n /** Where the visitor is, read from the url: Admin / Products / Edit. */\n Breadcrumbs: ComponentType<BreadcrumbsProps>\n UserMenu: ComponentType<UserMenuProps>\n\n Dashboard: ComponentType<DashboardProps>\n /** A resource's list page around its table. */\n Index: ComponentType<IndexProps>\n /** A resource's rows: searching, sorting, paging, filters and bulk actions, in either mode. */\n Table: ComponentType<TableProps>\n /** A create or edit page, and its form. */\n Form: ComponentType<FormProps>\n Plugins: ComponentType<PluginsProps>\n\n /** A value in a column with a format: money, a badge, yes or no, a date. A column's own cell() is drawn instead. */\n Cell: ComponentType<{ format: Exclude<Format, 'text' | 'relation'>; value: unknown }>\n /** What shows while a page, or a part of the shell, waits for the request. */\n Loading: ComponentType<{ kind: 'dashboard' | 'table' | 'form' | 'menu' }>\n}\n\nexport interface ShellProps {\n title: string\n /** The Brand, or the TenantSwitcher. */\n brand: ReactNode\n /** The Nav. */\n nav: ReactNode\n /** The UserMenu, in a panel that knows who is signed in. */\n user: ReactNode | null\n /** The Breadcrumbs. */\n crumbs: ReactNode\n /** The light and dark switch: off when the app has its own. */\n modeToggle: boolean\n children: ReactNode\n}\n\nexport interface BrandProps {\n href: string\n title: string\n /** The app's mark, drawn; absent when the panel was given none. */\n logo?: ReactNode\n}\n\nexport interface TenantSwitcherProps {\n tenants: Tenant[]\n current: Tenant\n /** What the tenants are called: Teams. */\n label: string\n choose: (id: string) => Promise<unknown>\n}\n\nexport interface NavItem {\n href: string\n label: string\n /**\n * What the link leads to, for the theme's default icon. `installed` is a\n * resource a plugin installed while the panel ran.\n */\n kind: 'dashboard' | 'resource' | 'installed' | 'plugins'\n /** The resource's own icon, drawn. */\n icon?: ReactNode\n /** Exact for the dashboard, so it is not lit up on every page below it. */\n exact?: boolean\n}\n\nexport interface BreadcrumbsProps {\n /** The panel's path. */\n root: string\n title: string\n /** Each first segment's label: { products: 'Products' }. */\n labels: Record<string, string>\n}\n\nexport interface PanelUser {\n name: string\n email?: string\n avatar?: string\n}\n\nexport interface UserMenuProps {\n user: PanelUser\n signOut?: () => Promise<unknown>\n}\n\nexport interface DashboardProps {\n description: string\n counts: {\n name: string\n label: string\n href: string\n /** Null when the source could not be counted: an installed plugin's server that is down. */\n total: number | null\n icon?: ReactNode\n }[]\n /** Each plugin widget, already rendered for this tenant. */\n widgets: { key: string; title: string; description?: string; content: ReactNode }[]\n}\n\nexport interface IndexProps {\n title: string\n description?: string\n /** The new-record link, absent for a read-only resource. */\n create?: { href: string; label: string }\n /** The Table, as a section the panel refreshes on its own. */\n table: ReactNode\n}\n\n/** A column as the browser needs it: what to call it, and what it may do. The cells come drawn. */\nexport interface TableColumn {\n key: string\n label: string\n sortable: boolean\n searchable: boolean\n visible: boolean\n}\n\n/**\n * A row, drawn on the server: each cell is already the element it shows -\n * a money format, a badge, a column's own cell() - and `values` are the raw\n * values the browser sorts and searches by in a client-mode table.\n */\nexport interface TableRowData {\n id: string\n cells: Record<string, ReactNode>\n values: Record<string, string | number | boolean | null>\n edit?: string\n remove?: () => Promise<unknown>\n}\n\n/** A bulk action as the table offers it; the work is the server's. */\nexport interface TableBulkAction {\n name: string\n label: string\n destructive: boolean\n}\n\n/** What a server-mode table is showing: the url's state, as the page read it. */\nexport interface TableQuery {\n q: string\n sort?: string\n dir: 'asc' | 'desc'\n page: number\n filters: Record<string, string>\n}\n\nexport interface TableProps {\n /**\n * server the url is the state. Typing, a filter, a header and the pager\n * each visit a url, the page asks the source for one page, and a\n * link works with no javascript and can be shared.\n * client every row arrived at once; search, filters, sort and the pager\n * happen in the browser, with no request.\n */\n mode: TableMode\n /** The list's url, which a server-mode table visits with its state. */\n href: string\n columns: TableColumn[]\n rows: TableRowData[]\n /** Every row the query matches, or null from a source that pages by cursor and does not count. */\n total: number | null\n /**\n * A list paged by cursor: the urls of the pages either side, null at an\n * end. Present, the pager follows these and there are no page numbers.\n */\n cursors?: { prev: string | null; next: string | null }\n perPage: number\n query: TableQuery\n filters: Filter[]\n noun: { one: string; many: string }\n /** Ticking rows, and what can be done to them. Absent for a read-only resource. */\n bulk?: { actions: TableBulkAction[]; run: (action: string, ids: string[]) => Promise<unknown> }\n}\n\nexport interface FormProps {\n title: string\n description: string\n /** Saves the record. A refusal is a 422 whose messages belong under their fields. */\n action: (form: FormData) => Promise<unknown>\n /** Each field, a relation's with its choices. */\n fields: Field[]\n /** The record's values; empty on a create page. */\n values: Record<string, unknown>\n /** The submit button's words: Create product, Save changes. */\n submit: string\n /** Where cancelling goes. */\n cancel: string\n}\n\nexport interface PluginsProps {\n description: string\n /** Whether this visitor may install and uninstall; one who may not sees what is installed. */\n allowed: boolean\n /** Reads a manifest url, sent as the field `manifest`, and puts it under review. */\n review: (form: FormData) => Promise<unknown>\n /** A manifest under review, or why it cannot be installed. */\n reviewing: null | { problem: string } | PluginReview\n installed: InstalledPlugin[]\n}\n\nexport interface PluginReview {\n label: string\n version?: string\n /** Its description, or its server's host. */\n description: string\n /** The labels of the resources it adds. */\n adds: string[]\n /** What it may reach, in words; empty when it reaches nothing beyond its own records. */\n can: string[]\n endpoint: string\n install: () => Promise<unknown>\n /** Where cancelling the review goes. */\n cancel: string\n}\n\nexport interface InstalledPlugin {\n id: string\n label: string\n description: string\n /** The labels of the resources it added. */\n resources: string[]\n scopes: string[]\n /** Absent for a visitor who may not uninstall. */\n uninstall?: () => Promise<unknown>\n /**\n * What the plugin needs to tell this panel's calls from anyone else's,\n * shown until someone confirms they copied it.\n */\n secret?: { installation: string; secret: string; copied: () => Promise<unknown> }\n}\n"]}
@@ -9,11 +9,23 @@ export type ListSearch = {
9
9
  sort?: string;
10
10
  dir: 'asc' | 'desc';
11
11
  page: number;
12
+ /** Where a list paged by cursor is. */
13
+ cursor?: string;
12
14
  /** A filter: any other key. */
13
15
  [filter: string]: string | number | undefined;
14
16
  };
15
17
  /** A resource's list page reads these from its url. A filter is any other key. */
16
18
  export declare const listSearch: StandardSchemaV1<Record<string, unknown>, ListSearch>;
19
+ /**
20
+ * The list's url at a cursor, keeping the search, sort and filters the visitor
21
+ * is on - a cursor is only good for the query that handed it out.
22
+ */
23
+ export declare function cursorHref(base: string, query: {
24
+ q?: string;
25
+ sort?: string;
26
+ dir: 'asc' | 'desc';
27
+ filters: Record<string, string>;
28
+ }, cursor: string): string;
17
29
  export declare const resourceParams: StandardSchemaV1<Record<string, unknown>, Record<"resource", string>>;
18
30
  export declare const recordParams: StandardSchemaV1<Record<string, unknown>, Record<"id" | "resource", string>>;
19
31
  export type PluginsSearch = {
package/dist/admin/url.js CHANGED
@@ -29,6 +29,24 @@ export const listSearch = schema((value) => {
29
29
  }
30
30
  return { value: out };
31
31
  });
32
+ /**
33
+ * The list's url at a cursor, keeping the search, sort and filters the visitor
34
+ * is on - a cursor is only good for the query that handed it out.
35
+ */
36
+ export function cursorHref(base, query, cursor) {
37
+ const search = new URLSearchParams();
38
+ if (query.q)
39
+ search.set('q', query.q);
40
+ if (query.sort)
41
+ search.set('sort', query.sort);
42
+ if (query.sort && query.dir === 'desc')
43
+ search.set('dir', 'desc');
44
+ for (const [key, value] of Object.entries(query.filters))
45
+ if (value)
46
+ search.set(key, value);
47
+ search.set('cursor', cursor);
48
+ return `${base}?${search}`;
49
+ }
32
50
  const params = (...keys) => schema((value) => {
33
51
  const missing = keys.filter((key) => text(value[key]) === undefined);
34
52
  if (missing.length)
@@ -1 +1 @@
1
- {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/admin/url.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The url schemas a panel's route files export. Standard Schemas written by\n * hand, so the panel depends on no validation library: the engine reads a\n * page's params and searchParams through `~standard`, whichever made it.\n */\nimport type { StandardSchemaV1 } from '@rsc-kit/core/standardSchema'\n\ntype Result<T> = { value: T } | { issues: { message: string; path: string[] }[] }\n\nfunction schema<T>(validate: (value: Record<string, unknown>) => Result<T>): StandardSchemaV1<Record<string, unknown>, T> {\n return {\n '~standard': {\n version: 1,\n vendor: '@rsc-kit/press',\n validate: (value) =>\n typeof value === 'object' && value !== null ? validate(value as Record<string, unknown>) : { issues: [{ message: 'Expected an object', path: [] }] },\n types: undefined as never,\n },\n }\n}\n\nconst text = (value: unknown) => (typeof value === 'string' ? value : Array.isArray(value) && typeof value[0] === 'string' ? value[0] : undefined)\n\nexport type ListSearch = {\n q?: string\n sort?: string\n dir: 'asc' | 'desc'\n page: number\n /** A filter: any other key. */\n [filter: string]: string | number | undefined\n}\n\n/** A resource's list page reads these from its url. A filter is any other key. */\nexport const listSearch = schema<ListSearch>((value) => {\n const out: ListSearch = { dir: 'asc', page: 1 }\n\n for (const [key, raw] of Object.entries(value)) {\n const given = text(raw)\n\n if (given === undefined) continue\n\n if (key === 'dir') out.dir = given === 'desc' ? 'desc' : 'asc'\n else if (key === 'page') {\n const page = Number(given)\n\n // A page that is not one is the first, rather than a 400: an old link\n // or a hand-edited url should still show the table.\n out.page = Number.isInteger(page) && page >= 1 ? page : 1\n } else out[key] = given\n }\n\n return { value: out }\n})\n\nconst params = <K extends string>(...keys: K[]) =>\n schema<Record<K, string>>((value) => {\n const missing = keys.filter((key) => text(value[key]) === undefined)\n\n if (missing.length) return { issues: missing.map((key) => ({ message: 'Required', path: [key] })) }\n\n return { value: Object.fromEntries(keys.map((key) => [key, text(value[key])!])) as Record<K, string> }\n })\n\nexport const resourceParams = params('resource')\n\nexport const recordParams = params('resource', 'id')\n\nexport type PluginsSearch = {\n /** A manifest url under review. */\n review?: string\n}\n\n/** The plugins page reads the manifest it is reviewing from its url. */\nexport const pluginsSearch = schema<PluginsSearch>((value) => {\n const review = text(value.review)\n\n return { value: review ? { review } : {} }\n})\n"]}
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/admin/url.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The url schemas a panel's route files export. Standard Schemas written by\n * hand, so the panel depends on no validation library: the engine reads a\n * page's params and searchParams through `~standard`, whichever made it.\n */\nimport type { StandardSchemaV1 } from '@rsc-kit/core/standardSchema'\n\ntype Result<T> = { value: T } | { issues: { message: string; path: string[] }[] }\n\nfunction schema<T>(validate: (value: Record<string, unknown>) => Result<T>): StandardSchemaV1<Record<string, unknown>, T> {\n return {\n '~standard': {\n version: 1,\n vendor: '@rsc-kit/press',\n validate: (value) =>\n typeof value === 'object' && value !== null ? validate(value as Record<string, unknown>) : { issues: [{ message: 'Expected an object', path: [] }] },\n types: undefined as never,\n },\n }\n}\n\nconst text = (value: unknown) => (typeof value === 'string' ? value : Array.isArray(value) && typeof value[0] === 'string' ? value[0] : undefined)\n\nexport type ListSearch = {\n q?: string\n sort?: string\n dir: 'asc' | 'desc'\n page: number\n /** Where a list paged by cursor is. */\n cursor?: string\n /** A filter: any other key. */\n [filter: string]: string | number | undefined\n}\n\n/** A resource's list page reads these from its url. A filter is any other key. */\nexport const listSearch = schema<ListSearch>((value) => {\n const out: ListSearch = { dir: 'asc', page: 1 }\n\n for (const [key, raw] of Object.entries(value)) {\n const given = text(raw)\n\n if (given === undefined) continue\n\n if (key === 'dir') out.dir = given === 'desc' ? 'desc' : 'asc'\n else if (key === 'page') {\n const page = Number(given)\n\n // A page that is not one is the first, rather than a 400: an old link\n // or a hand-edited url should still show the table.\n out.page = Number.isInteger(page) && page >= 1 ? page : 1\n } else out[key] = given\n }\n\n return { value: out }\n})\n\n/**\n * The list's url at a cursor, keeping the search, sort and filters the visitor\n * is on - a cursor is only good for the query that handed it out.\n */\nexport function cursorHref(\n base: string,\n query: { q?: string; sort?: string; dir: 'asc' | 'desc'; filters: Record<string, string> },\n cursor: string,\n): string {\n const search = new URLSearchParams()\n\n if (query.q) search.set('q', query.q)\n if (query.sort) search.set('sort', query.sort)\n if (query.sort && query.dir === 'desc') search.set('dir', 'desc')\n for (const [key, value] of Object.entries(query.filters)) if (value) search.set(key, value)\n search.set('cursor', cursor)\n\n return `${base}?${search}`\n}\n\nconst params = <K extends string>(...keys: K[]) =>\n schema<Record<K, string>>((value) => {\n const missing = keys.filter((key) => text(value[key]) === undefined)\n\n if (missing.length) return { issues: missing.map((key) => ({ message: 'Required', path: [key] })) }\n\n return { value: Object.fromEntries(keys.map((key) => [key, text(value[key])!])) as Record<K, string> }\n })\n\nexport const resourceParams = params('resource')\n\nexport const recordParams = params('resource', 'id')\n\nexport type PluginsSearch = {\n /** A manifest url under review. */\n review?: string\n}\n\n/** The plugins page reads the manifest it is reviewing from its url. */\nexport const pluginsSearch = schema<PluginsSearch>((value) => {\n const review = text(value.review)\n\n return { value: review ? { review } : {} }\n})\n"]}