Creating a New Module
This guide walks through creating a new module for the Neemias ecosystem. Modules extend the core with premium functionality (BSL-licensed) and are activated via license keys.
Prerequisites
- Node.js 24+, pnpm 12+ (o repositório pina
pnpm@12.3.1) - Write access to the
barateza/neemiasmonorepo (modules live inmodules/packages/) - Understanding of the plugin registry (
@neemias/plugin-registry) - Read ADR-0014: Open Core Architecture
Overview
A module is a Plugin that self-registers at import time. The core iterates all registered plugins and wires them into the Worker, React app, Dexie DB, i18n, and permission system.
modules/packages/<module-name>/
├── package.json
├── src/
│ ├── index.ts # Plugin registration (entry point)
│ ├── routes/ # Worker API handlers
│ ├── pages/ # React components (optional — can stay in core)
│ ├── db/ # Dexie repositories (optional)
│ ├── services/ # Business logic (optional)
│ ├── i18n/ # Translations (optional)
│ └── schemas.ts # Module-specific schemasStep 1 — Create the package
cd modules
mkdir -p packages/<module-name>/src/{routes,pages,db,services,i18n}Create package.json:
{
"name": "@neemias/<module-name>",
"version": "1.0.0",
"private": true,
"type": "module",
"exports": { ".": "./src/index.ts" },
"peerDependencies": {
"@neemias/plugin-registry": "*",
"@neemias/schemas": "*"
}
}Step 2 — Define the plugin
Create src/index.ts:
import { registerPlugin } from "@neemias/plugin-registry";
import type { Plugin } from "@neemias/plugin-registry";
export const myPlugin: Plugin = {
id: "my-module", // used for license entitlement check
name: "My Module",
version: "1.0.0",
minCoreVersion: "1.0.0",
registerWorkerRoutes: (route, mw) => {
route("GET", "/api/v1/my-resource", [mw.requireAuth()], handleList);
route(
"POST",
"/api/v1/my-resource",
[mw.requireAuth(), mw.requireRole(["ADMIN"])],
handleCreate,
);
},
registerReactRoutes: () => [{ path: "/my-module", lazy: () => import("./pages/MyPage") }],
registerI18n: () => ({
myModule: {
title: "My Module",
add: "Add item",
},
}),
registerPermissions: () => ({
"myModule.manage": ["ADMIN"],
}),
registerMigrations: () => [{ version: 1, sql: "CREATE TABLE IF NOT EXISTS my_table (...)" }],
registerDexieStores: (db) => {
// Dexie version registration — use the existing DB instance
// db.version(N).stores({ ...existing, myTable: "..." });
},
};
registerPlugin(myPlugin);Step 3 — Implement Worker routes
Worker handlers are pipeline-compatible functions. Vendor parseBody, json, nowISO, and randomUUID in your module (or import from @neemias/schemas).
// src/routes/myResource.ts
import { HttpError } from "@neemias/schemas";
import type { AuthPrincipal } from "@neemias/schemas";
export async function handleList(
request: Request,
env: { DB: D1Database },
_ctx: ExecutionContext,
_principal: AuthPrincipal,
_corrId: string,
): Promise<Response> {
const db = env.DB;
const result = await db.prepare("SELECT * FROM my_table").all();
return json({ items: result.results });
}Use env.DB directly — do NOT import getDB() from the core. Modules must be self-contained.
Step 4 — Wire into the core
In the monorepo root (barateza/neemias):
workers/src/index.ts — add one side-effect import (the module's registerPlugin() runs at import time), exactly like @neemias/nucleus:
import "@neemias/<module-name>";App side — the app consumes registered plugins through getPlugins() from @neemias/plugin-registry (app/src/app/routes.tsx for React routes, app/src/app/utils/i18n.ts for translations). If your module registers React routes or i18n namespaces, make sure it is imported somewhere in the app bundle. Nucleus currently wires only the Worker side — use its import in workers/src/index.ts as the template.
Step 5 — Test
Core without module
# Module NOT imported → 0 routes registered
pnpm --dir workers test
# Verify: no /api/v1/<module> routes in createRouter() outputCore with module
# Module imported → routes + i18n + permissions registered
# Write a plugin integration test following workers/src/__tests__/router.test.tsChecklist
Reference
- Full example:
modules/packages/nucleus/— first extracted module - Plugin interface:
neemias/packages/plugin-registry/src/index.ts - ADR:
neemias/docs/architecture/adr/ADR-0014.md