Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,18 @@ Slice 2b (the plugin object, hooks, capabilities and the runtime):

- `DunePlugin`, `PluginApi`, `MountApi`, `HookRegistry`, `HookContext`,
`HookHandler`, `HookEvent` and the event payloads, `ResponseTransformContext`,
`ContentEditorPlugin`, `PublicRouteRegistration` and the admin registrations
(`AdminPageRegistration`, `AdminRouteRegistration`, `AdminBarAction`,
`AdminBarActionContext`). `HookHandler`, `setup`, `mount` and
`PublicRouteRegistration`. `HookHandler`, `setup`, `mount` and
`transformResponse` are bivariant: core hands handlers a wider context than
this package declares.
- Extension points belong to the plugin that reads them (#127). The admin's
`AdminPageRegistration`, `AdminRouteRegistration` and `ContentEditorPlugin`
(and the keys `admin.pages`, `admin.routes`, `admin.editor`) are declared by
`@dune/plugin-admin`; the bar's `BarAction`, `BarActionContext` and
`bar.actions` by `@dune/plugin-inline-edit`. They extend `Capabilities` and
`Contributions` by declaration merging, which this package supports and keys
by the consuming plugin's name (`admin.*`, `bar.*`); unprefixed names are the
contract's. `ResponseTransformContext` carries `capabilities` (the reader
`MountApi` offers) instead of `adminPrefix` and `barActions`.
- Capabilities: `Capabilities` (slots), `Contributions`, `CapabilityContext`,
`CapabilityFactory`, `CapabilityRegistrar`, `CapabilityReader`,
`CapabilityProvider`, `RequestAuthenticator`, `ScheduledActionExecutor`.
Expand Down
44 changes: 26 additions & 18 deletions src/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@
* @module
*/

import type { AdminBarAction, AdminBarActionContext } from "./hooks.ts";
import type {
AdminPageRegistration,
AdminRouteRegistration,
ContentEditorPlugin,
} from "./hooks.ts";
import type { DuneConfig } from "./config.ts";
import type { HistoryEngine } from "./history.ts";
import type { HookRegistry } from "./hooks.ts";
Expand Down Expand Up @@ -75,7 +69,25 @@ export interface AdminPrefix {
readonly prefix: string;
}

/** Slots: at most one active provider each. First-party keys only in 1.0. */
/**
* Slots: at most one active provider each. What core itself consumes, or what
* every plugin may rely on.
*
* Open to declaration merging. A plugin that offers an extension point of its
* own declares the key next to its types, so this contract does not grow with
* every plugin's features:
*
* ```ts
* declare module "@dune/plugin-api" {
* interface Capabilities { "admin.editor": ContentEditorPlugin }
* }
* ```
*
* The rule: **a key is prefixed with the name of the plugin that consumes it**
* (`admin.*` is the admin's, `bar.*` plugin-inline-edit's). Unprefixed
* namespaces (`editor`, `scheduler`, `admin.prefix`) belong to core, and core
* never reads a key it did not declare.
*/
export interface Capabilities {
/** Where the admin is mounted. */
"admin.prefix": AdminPrefix;
Expand All @@ -85,19 +97,15 @@ export interface Capabilities {
"scheduler.executor": ScheduledActionExecutor;
/** Real-time inline editing. */
"editor.inline": InlineEditManager;
/** A replacement page editor. */
"editor.content": ContentEditorPlugin;
}

/** Contribution points: one item from each contributing plugin. */
export interface Contributions {
/** An extra admin page. */
"admin.pages": AdminPageRegistration;
/** A guarded admin route under the admin prefix. */
"admin.routes": AdminRouteRegistration;
/** Buttons for the admin bar. */
"bar.actions": (ctx: AdminBarActionContext) => AdminBarAction[];
}
/**
* Contribution points: one item from each contributing plugin. Open to
* declaration merging under the same rule as {@link Capabilities}. The contract
* declares none: every point belongs to the plugin that reads it.
*/
// deno-lint-ignore no-empty-interface
export interface Contributions {}

/** What a slot factory receives: core infrastructure that exists once every `setup()` has settled. */
export interface CapabilityContext {
Expand Down
74 changes: 7 additions & 67 deletions src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,58 +146,6 @@ export interface PublicRouteRegistration {
island?: string;
}

/** Context passed to a `bar.actions` contribution. */
export interface AdminBarActionContext {
/** Content page matching the current URL, or null for non-content routes. */
page: {
sourcePath: string;
route: string;
title: string | null;
template: string;
} | null;
/** Admin panel URL prefix (e.g. `"/admin"`). */
adminPrefix: string;
}

/** A button contributed to the shared admin bar through the `bar.actions` point. */
export interface AdminBarAction {
/** Unique id; namespace by plugin name, e.g. `"pdf-export:download"`. */
id: string;
label: string;
icon?: string;
/** Renders as a link when set; takes precedence over `onClick`. */
href?: string;
/** Literal JavaScript run on click when `href` is not set. Trusted plugin content. */
onClick?: string;
}

/** A custom admin page contributed through the `admin.pages` point. */
// deno-lint-ignore no-explicit-any
export interface AdminPageRegistration<S = any> {
/** URL path relative to the admin prefix (must start with `/`). */
path: string;
/** Human-readable nav label shown in the admin sidebar. */
label: string;
icon?: string;
/** Admin permission required to view this page. */
permission: string;
/** GET handler for the page. */
handler(ctx: PluginRouteContext<S>): Promise<Response> | Response;
}

/** A guarded admin route contributed through the `admin.routes` point. */
// deno-lint-ignore no-explicit-any
export interface AdminRouteRegistration<S = any> {
/** HTTP method. Anything other than `GET` is subject to the CSRF check. */
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
/** URL path relative to the admin prefix (must start with `/`). */
path: string;
/** Admin permission required, including ones declared through `authzActions`. */
permission: string;
/** Runs only after authentication, CSRF and permission checks pass. */
handler(ctx: PluginRouteContext<S>): Promise<Response> | Response;
}

/**
* Plugin definition. Export it as the default export of your plugin module.
*
Expand Down Expand Up @@ -255,17 +203,6 @@ export interface DunePlugin {
): Promise<Response> | Response;
}

/** Plugin-provided replacement for the built-in block editor (the `editor.content` slot). */
export interface ContentEditorPlugin {
/** Handle `GET /admin/pages/edit?path=...`. */
pageEditorHandler(ctx: PluginRouteContext): Response | Promise<Response>;
/** Optional WebSocket upgrade handler for real-time collaboration. */
wsHandler?: (
req: Request,
user: { id: string; name: string },
) => Response;
}

/** Context passed to {@link DunePlugin.transformResponse}. */
export interface ResponseTransformContext {
req: Request;
Expand All @@ -289,10 +226,13 @@ export interface ResponseTransformContext {
title: string | null;
template: string;
} | null;
/** Admin panel URL prefix (e.g. `"/admin"`). */
adminPrefix: string;
/** Bar buttons contributed through `bar.actions`, in plugin registration order. */
barActions: readonly ((ctx: AdminBarActionContext) => AdminBarAction[])[];
/**
* What plugins provide, as `MountApi.capabilities` offers it: slots such as
* `get("admin.prefix")` and any extension point a plugin declares, such as
* `list("bar.actions")`. Read per request, so a plugin module shared between
* sites keeps no state of its own.
*/
capabilities: import("./capabilities.ts").CapabilityReader;
}

/** What a plugin's `mount()` receives. */
Expand Down
5 changes: 0 additions & 5 deletions src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,6 @@ export type {
ScheduledActionExecutor,
} from "./capabilities.ts";
export type {
AdminBarAction,
AdminBarActionContext,
AdminPageRegistration,
AdminRouteRegistration,
ContentEditorPlugin,
DunePlugin,
FormSubmitPayload,
HookContext,
Expand Down
Loading