@kernhq/module-quire
v0.7.3
Published
Kern Quire: collaborative documents, spaces and page trees
Readme
Module template
Copy this to start a Kern module. It is a working module: a Note entity with list, create and
delete, its own Postgres schema, row-level security, permissions, events, and a test that refuses to
let the contract and the router drift apart.
The fastest way to copy it correctly is not to copy it by hand:
cd repos/modules
pnpm new-module crm # generates this package *and* its half in the appRead the rest of this file if you are doing it by hand, or if you want to know what the generator wrote.
What is in here
| File | What it is |
|---|---|
| src/contract.ts | Zod models, the oRPC contract, events, permission keys, capabilities. Imported by both halves, so no Node code. |
| src/server/schema.ts | Drizzle tables in mod_<id>. |
| src/server/_impl.ts | The router. Separate from index.ts so the test can walk it without a kernel. |
| src/server/index.ts | defineServerModule — schema, migrations, router, subscriptions. |
| src/client/index.ts | The typed API client and module logic. Ships as source. |
| src/module.test.ts | Contract-to-router parity and the authorisation guard. Keep it. |
| migrations/0000_init.sql | Generated by pnpm db:generate. |
| migrations/0001_rls.sql | Hand-written. Never generated. |
Copying it by hand
Each of these has been got wrong before.
package.json— setnameto@kernhq/module-<id>, and delete"private": true. A private package is skipped silently by changesets: the commit lands, CI is green, and nothing publishes.filesmust cover every directory./clientreaches —src/clientandsrc/contract. The client ships as source, so a re-export the tarball omits breaks the consumer and nothing local notices, because the workspace resolves the file the package does not ship.pnpm check:packcatches it.- The id agrees in four places:
MODULE_ID,moduleSchema('<id>'),schemaFilterindrizzle.config.ts, and every permission and event prefix. - Version comes from the package, never a literal:
packageVersion(import.meta.url). A literal is not bumped by a release — chat once shipped as 0.2.0 while telling every admin it was 0.1.0, and that literal is whatworkspace_modules.installed_versionrecorded. - Write the RLS migration.
pnpm db:generatewill not. Copy0001_rls.sqland change the table names;rlsPolicySqlfrom@kernhq/kernelemits the same text. - Host it. A module nothing imports is invisible: its tests pass, it publishes, and every call
404s. Add it to
featureModulesin thecorerepo'ssrc/service.ts, or to whichever service should hold it. - Register the client in the app's
src/lib/modules/registry.ts.
What a module can contribute
The server half declares tables, migrations, a router, procedures other modules call through
kernel.call(), jobs, subscriptions, search indexers and lifecycle hooks.
The client half — the manifest in the app — declares nav, commands, settingsPages, widgets
for the dashboard, and sidebar for the column beside the rail. Read the kern-widget skill before
writing a widget, and kern-module for the whole sequence.
Before you call it done
pnpm typecheck && pnpm lint && pnpm test && pnpm build
pnpm check:pack # the tarball contains what ./client imports
pnpm check:versions # the manifest version matches package.jsonThen use it through the interface, signed in, with the module enabled for a workspace. A module that has never served a request is not finished, whatever the type-checker says.
