import type { IRouterApi } from '@setu-ts/common';import { registerNotesRoutes } from './notes.controller.ts';/** Registers the notes feature. */export function registerNotesModule(router: IRouterApi): void { registerNotesRoutes(router);}
src/modules/notes/notes.controller.ts
import type { IRouterApi } from '@setu-ts/common';import { tenantNotes } from './notes.service.ts';/** Registers the notes routes; each request reads and writes its own tenant's notes. */export function registerNotesRoutes(router: IRouterApi): void { router.group('/notes', (routes) => { routes.post('/', async (ctx) => { const { text } = await ctx.request.json<{ text: string }>(); return ctx.response.status(201).json(await tenantNotes(ctx).create({ text })); }); routes.get('/', async (ctx) => ctx.response.json(await tenantNotes(ctx).findAll())); });}
src/modules/notes/notes.service.ts
import { CAPABILITIES } from '@setu-ts/common';import type { IMultiTenancyService, IRequestContext, ITenantRepository } from '@setu-ts/common';/** A note, stored in the partition of the tenant that wrote it. */export interface Note { readonly id: string; readonly text: string;}/** Resolves the notes repository for the tenant of the current request. */export function tenantNotes(ctx: IRequestContext): ITenantRepository<Note> { return ctx.services .get<IMultiTenancyService>(CAPABILITIES.MULTI_TENANCY) .getRepository<Note>(ctx, 'notes');}
smoke.ts
import assert from 'node:assert/strict';import { after, before, describe, it } from 'node:test';import type { IKernelApplication } from '@setu-ts/kernel';import { createApp, TENANT_HEADER } from './setu.config.ts';describe('multi-tenant example', () => { let app: IKernelApplication; before(async () => { app = createApp(); await app.start(); }); after(async () => { await app.stop(); }); it("keeps one tenant's writes invisible to another", async () => { const created = await app.inject({ method: 'POST', url: '/notes', headers: { [TENANT_HEADER]: 'tenant-a' }, body: { text: 'only A can read this' }, }); assert.equal(created.statusCode, 201); const tenantA = await app.inject({ method: 'GET', url: '/notes', headers: { [TENANT_HEADER]: 'tenant-a' }, }); const tenantB = await app.inject({ method: 'GET', url: '/notes', headers: { [TENANT_HEADER]: 'tenant-b' }, }); assert.equal(tenantA.json<unknown[]>().length, 1); assert.equal(tenantB.json<unknown[]>().length, 0); });});