Section A–A: cut the waitlist feature open.
A feature names what it needs. entrypointOnly hides the database from usecases. A usecase picks its own dependencies by key.
import { defineFeature, entrypointOnly } from '@arki/dot/feature';import { database } from '../../infra/db';import { cache } from '../../infra/kv';import { emailClient } from '../../infra/mail/client';import { limiter } from './infra/signup-limiter';import { store } from './infra/store';import { turnstile } from './infra/turnstile';const waitlistDeps = defineFeature('waitlist') // The store binds `db`, the signup limiter binds `kv`; usecases never see them. .needs({ db: entrypointOnly(database.provides.db), kv: entrypointOnly(cache.provides.kv), // The app's outbound email client; a test fakes the plugin. mailer: emailClient.provides.emailClient, }) .infra(store, limiter, turnstile);rpc · the oRPC contract, mounted by the app's gateway
entrypoints
- files
- procedures.ts, rpc.ts
- sees
- usecases, db, kv
usecases
- files
- signup, counts, markInvited…
- sees
- repos, limiter, turnstile, mailer
infra
- files
- store, repository, signup-limiter, turnstile
- binds
- db, kv
model
- files
- tables, schemas, statuses
- sees
- nothing above
import type { Deps } from '../feature';export const publicCount = ({ repos: { entries } }: Pick<Deps, 'repos'>) => async () => { const [pending, invited, activated] = await Promise.all([ entries.countByStatus('pending'), entries.countByStatus('invited'), entries.countByStatus('activated'), ]); return { total: pending + invited + activated }; };