brick.ts

Authoring Plugins

Create custom service and resource plugins with scoped routes and typed APIs.

llms.txt

Plugins package modular capabilities into reusable TypeScript descriptors that install onto services or resources.

Authoring a Service Plugin (defineServicePlugin)

A service plugin registers actions and raw HTTP endpoints onto a host service using setup({ service, routes }):

src/plugins/greeting.ts
import { defineServicePlugin, t, type PluginRoutes } from "@brickkit/core";

export interface GreetingOptions {
  salutation?: string;
  routes?: PluginRoutes<"app" | "service">;
}

export function greeting(options: GreetingOptions = {}) {
  const salutation = options.salutation ?? "Hello";

  return defineServicePlugin({
    name: "example/greeting",
    routes: options.routes,
    setup({ service, routes }) {
      // 1. Register an action onto the service mesh
      const hello = routes.action({
        name: "hello",
        method: "GET",
        path: `/${service.name}/hello`,
        input: t.Object({
          name: t.String({ minLength: 1 }),
        }),
        output: t.String(),
        execute: ({ input }) => `${salutation}, ${input.name}!`,
      });

      // 2. Register a raw HTML endpoint (outside the JSON action mesh)
      routes.endpoint({
        name: "welcome",
        method: "GET",
        path: `/${service.name}/welcome.html`,
        handler: async ({ set }) => {
          set.headers["content-type"] = "text/html; charset=utf-8";
          return `<!DOCTYPE html><html><body><h1>${salutation}!</h1></body></html>`;
        },
      });

      // Return a typed API namespace for callers
      return {
        hello,
        salutation,
      };
    },
  });
}

Installing the Service Plugin

Install onto a service using service.use():

src/services/greeter.ts
import { defineService } from "@brickkit/core";
import { greeting } from "../plugins/greeting";

export const greeterService = defineService("greeter");

// Install the plugin
export const api = greeterService.use(greeting({ salutation: "Welcome" }));

// In-process TypeScript calls are fully typed:
const message = await api.hello({ input: { name: "Ada" } });
console.log(message); // "Welcome, Ada!"

Authoring a Resource Plugin (defineResourcePlugin)

A resource plugin attaches behavior to a specific database table and entity:

src/plugins/slugify.ts
import { defineResourcePlugin, eq } from "@brickkit/core";

export function slugLookup(options: { slugColumn: any }) {
  return defineResourcePlugin({
    name: "example/slugLookup",
    setup({ resource, service, routes }) {
      // Return helper methods attached directly onto the resource object
      return {
        findBySlug: async (slug: string, ctx: any) => {
          if (!ctx.db) throw new Error("Database required");
          const rows = await ctx.db
            .select()
            .from(resource.table)
            .where(eq(options.slugColumn, slug))
            .limit(1);
          return rows[0] ?? null;
        },
      };
    },
  });
}

Installing the Resource Plugin

Install onto a resource using resource.use():

src/services/articles.ts
const article = service
  .resource({ name: "article", table: articlesTable, id: articlesTable.id })
  .use(slugLookup({ slugColumn: articlesTable.slug }));

// The returned resource is augmented with findBySlug:
const found = await article.findBySlug("my-post-slug", ctx);

Plugin Design Principles

  1. Synchronous Setup: setup() must execute synchronously. Asynchronous connection initialization or network lookups are prohibited during registration.
  2. Connection Flow: Use ctx.db provided during request handling. Never create persistent connection pools inside plugin constructors.
  3. Collision Avoidance: Resource plugin contributions must not overwrite existing resource properties or prototype methods.
  4. Isolated Descriptors: Reinstalling a plugin with a different descriptor of the same name fails. Reuse descriptors when sharing instances.

Schema

The resource-plugin example expects an application-owned articles table with an identity column and a slug column. Declare the table before registering the resource. A unique slug makes findBySlug unambiguous.

Table Name: articles

FieldTypeDescription
idtextArticle identifier. PK
slugtextSlug used by findBySlug. Unique