dimah-formv0.2.0

Plugins

Add endpoints, hooks, error codes, field types, and matching client methods.

A plugin can add HTTP endpoints, lifecycle hooks, error codes, field types, and matching methods on createFormClient.


1. Creating a Server Plugin

Use definePlugin() and createFormEndpoint() from @dimah-form/server to build a server extension:

lib/plugins/analytics-plugin.ts
import {
  createFormEndpoint,
  defineErrorCodes,
  definePlugin,
} from "@dimah-form/server";
import * as z from "zod";

// 1. Declare domain-specific error codes
export const ANALYTICS_ERROR_CODES = defineErrorCodes({
  STATS_UNAVAILABLE: "Analytics data could not be computed",
});

// 2. Define the server plugin
export const analyticsPlugin = () =>
  definePlugin({
    id: "analytics",
    $ERROR_CODES: ANALYTICS_ERROR_CODES,

    // Add custom HTTP endpoints to the dimahForm router
    endpoints: {
      getFormStats: createFormEndpoint(
        "/analytics/stats",
        {
          method: "GET",
          query: z.object({
            formId: z.string(),
          }),
        },
        async ({ query, store, request }) => {
          const list = await store.listResponses({
            formId: query.formId,
            limit: 100,
          });

          const total = list.responses.length;
          const submitted = list.responses.filter(
            (r) => r.status === "submitted",
          ).length;

          return {
            formId: query.formId,
            totalResponses: total,
            completionRate: total > 0 ? (submitted / total) * 100 : 0,
          };
        },
      ),
    },

    // Optional lifecycle hooks contributed by this plugin
    hooks: {
      afterSubmit: async ({ response }) => {
        console.log(`[Analytics] Response ${response.id} submitted.`);
      },
    },
  });

2. Registering on the Server

Pass the plugin to dimahForm({ plugins: [...] }):

lib/form.ts
import { dimahForm, memoryAdapter } from "@dimah-form/server";
import { analyticsPlugin } from "@/lib/plugins/analytics-plugin";
import { forms } from "@/lib/forms";

export const form = dimahForm({
  database: memoryAdapter(),
  forms,
  plugins: [analyticsPlugin()],
});

export type Form = typeof form;

You can now call the plugin endpoint directly through form.api:

const stats = await form.api.getFormStats({
  query: { formId: "feedback" },
});

3. Creating a Companion Client Plugin

To expose typed methods on createFormClient, create a companion client plugin using defineClientPlugin():

lib/plugins/analytics-client.ts
import { defineClientPlugin } from "@dimah-form/react";
import { ANALYTICS_ERROR_CODES } from "./analytics-plugin";

export const analyticsClientPlugin = () =>
  defineClientPlugin({
    id: "analytics",
    $ERROR_CODES: ANALYTICS_ERROR_CODES,
    endpoints: ({ $fetch }) => ({
      getStats: (formId: string) =>
        $fetch<{
          formId: string;
          totalResponses: number;
          completionRate: number;
        }>(`/analytics/stats?formId=${encodeURIComponent(formId)}`, {
          method: "GET",
        }),
    }),
  });

Register the client plugin with createFormClient:

lib/form-client.ts
"use client";

import { createFormClient } from "@dimah-form/react";
import type { Form } from "@/lib/form";
import { analyticsClientPlugin } from "@/lib/plugins/analytics-client";

export const formClient = createFormClient<Form>({
  basePath: "/api/form",
  plugins: [analyticsClientPlugin()],
});

// Call the plugin method from client components
const stats = await formClient.getStats("feedback");

Plugin Options Reference

PropertyTypeDescription
idstringUnique alphanumeric identifier for the plugin.
$ERROR_CODESErrorCodeCatalogCustom error codes declared via defineErrorCodes().
endpointsRecord<string, FormEndpoint>Map of custom route handlers.
hooksDimahFormHooksPre-write and post-write lifecycle hooks.
fieldTypesFieldTypeDefinition[]Custom field validators added by this plugin.
dependsOnstring[]Array of required plugin IDs that must be loaded before this plugin.

Next Steps

On this page