Ports
Cross-cutting ports for infrastructure the core depends on, who implements each one and which fake stands in for it in tests.
Import from @workspace/training/application/ports. These are the interfaces the application layer depends on for infrastructure that is not a module's own repository: queueing, events, email, the auth provider, object storage, session tokens and webhook verification. Every export here is a type except DEFAULT_SESSION_TOKEN_TTL_MS. Implementations live in capability-named adapter packages and are injected at the composition root of each app, see Adapter packages. A module's own repository ports live under that module's ports/ folder and are documented on its page.
| Port | Implemented by | Fake |
|---|---|---|
Queue | createPgBossQueue in @workspace/queue/drivers/pg-boss | createFakeQueue in @workspace/training/testing |
DomainEventBus | createEventBus in @workspace/shared/event-bus (mitt) | none; an object with a recorded emit |
EmailSender | createResendEmailSender in @workspace/emails | none; an object with a recorded send |
AdBannersStorage | createSupabaseAdBannersStorage in @workspace/ads/admin | none |
BrandAssetsStorage | createSupabaseBrandAssetsStorage in @workspace/ads/brand-assets/supabase-storage | none |
SessionTokenService | createHmacSessionTokenService in @workspace/auth/server | none |
WebhookVerifier | createClerkWebhookVerifier in @workspace/auth/server, createStripeWebhookVerifier in @workspace/payment/server | signing helpers in @workspace/auth/server/testing and @workspace/payment/server/testing drive the real verifiers |
Queue
Durable message queueing for background work such as webhook delivery. The EventIngestionService of Events is its consumer inside the core: it publishes { id } jobs on its process topic. The API worker registers the handlers and the sweep cron.
| Method | Signature | Description |
|---|---|---|
send | <T>(topic: string, data: T) => Promise<void> | Publish one message. |
work | <T>(topic: string, handler: (msg: QueueMessage<T>) => Promise<void>, opts?: QueueRetryOptions) => Promise<void> | Register a handler for a topic, with optional retry options. |
schedule | (topic: string, cron: string) => Promise<void> | Register a recurring job on topic (cron syntax, UTC). Consume it via work. |
stop | () => Promise<void> | Stop the driver. |
QueueMessage
Prop
Type
QueueRetryOptions
Prop
Type
Topic names must satisfy the driver: pg-boss accepts alphanumerics, _, -, . and /, never a colon. The events module publishes its defaults as DEFAULT_INGESTION_TOPICS.
type ProcessJob = { id: string };
const PROCESS_TOPIC = "webhook.process";
const RETRY: QueueRetryOptions = {
maxAttempts: 8,
initialDelayMs: 60_000,
backoff: "exponential",
};
export function enqueueProcessing(queue: Queue, eventId: string): Promise<void> {
return queue.send<ProcessJob>(PROCESS_TOPIC, { id: eventId });
}
export function startProcessing(
queue: Queue,
process: (eventId: string) => Promise<void>
): Promise<void> {
return queue.work<ProcessJob>(
PROCESS_TOPIC,
async (msg: QueueMessage<ProcessJob>) => {
// msg.id is the queue's job id; msg.retryCount is 0 on the first delivery
await process(msg.data.id);
},
RETRY
);
}
export async function scheduleSweep(queue: Queue): Promise<void> {
await queue.schedule("webhook.sweep-pending", "*/2 * * * *"); // UTC cron
await queue.work("webhook.sweep-pending", async () => {
// consumed like any other topic
});
}DomainEventBus
Emits domain events; subscribers react asynchronously. createAccountsService takes one and emits account:created when ensureAccount creates a row, see Accounts. The API builds its bus with createEventBus from @workspace/shared/event-bus over a ServerEvents map whose entries match DomainEvent.
| Method | Signature | Description |
|---|---|---|
emit | (event: DomainEvent) => void | Fire-and-forget. |
DomainEvent is the closed union of the events the core emits:
type | payload |
|---|---|
"account:created" | { userId: string; email: string } |
"account:updated" | { userId: string } |
export function announceAccount(
eventBus: DomainEventBus,
userId: string,
email: string
): void {
eventBus.emit({ type: "account:created", payload: { userId, email } });
}EmailSender
Renders an email message and hands it to the provider (Resend today). Its only consumer is the EmailsService of Emails, on the worker side; nothing else sends an email. The adapter is null when no API key is configured, and the service then marks every email skipped.
| Method | Signature | Description |
|---|---|---|
send | (input: SendEmailInput) => Promise<SendEmailResult> | Render and send one message to its one recipient. The idempotency key goes to the provider, which refuses a second email under the same key. |
SendEmailInput
Prop
Type
SendEmailResult
Prop
Type
AdBannersStorage
The object store holding sponsor banner assets, consumed by the ad banners service of Ads. The Supabase Storage implementation lives in @workspace/ads.
| Method | Signature | Description |
|---|---|---|
listForSponsor | (sponsorSlug: string) => Promise<AdBanner[]> | Banners uploaded for a sponsor slug. Returns an empty array when the sponsor has no folder yet; never throws for "not found". |
BrandAssetsStorage
The object store holding a deal owner's visuals, consumed by the brand assets service of Ads. The API mints a signed upload URL, the browser writes the object, and on confirm the API reads the stored bytes back through this port.
| Method | Signature | Description |
|---|---|---|
createUploadUrl | (storageKey: string) => Promise<BrandAssetUploadTicket> | Mint a signed upload ticket for a key. |
readImage | (storageKey: string) => Promise<StoredBrandAssetImage | null> | The stored image's size, decoded content type and dimensions; null when no object is stored at the key. |
publicUrl | (storageKey: string) => string | Public URL of a stored key. |
BrandAssetUploadTicket
Prop
Type
SessionTokenService
Minting and verification of signed anonymous client session tokens, used by the API's telemetry session guard and its session token handlers. Verification failures throw SessionTokenVerificationError from @workspace/training, see Errors.
| Method | Signature | Description |
|---|---|---|
mint | () => MintedSessionToken | Mint a fresh signed session token. |
verify | (token: string) => VerifiedSessionToken | Verify signature and expiry; returns the session id or throws a SessionTokenVerificationError. |
renew | (previousToken: string) => MintedSessionToken | Re-sign the sid of an authentic previous token with a fresh issuedAt, ignoring expiry; throws a SessionTokenVerificationError when the token is malformed or its signature is invalid. |
| Export | Definition |
|---|---|
DEFAULT_SESSION_TOKEN_TTL_MS | days(1) from @workspace/shared/time-units: the 24 hour default lifetime of a client session token. |
MintedSessionToken
Prop
Type
VerifiedSessionToken
Prop
Type
WebhookVerifier
Webhook signature verification, the first step of EventIngestionService.ingest in Events. WebhookVerifier<TProvider extends string = string> lets an adapter narrow the provider tag to its own literal ("clerk", "stripe") so the composition root can require an exact match at the wiring site. The admin app, which only publishes replays, wires a verifier whose verify throws.
| Method | Signature | Description |
|---|---|---|
verify | (request: Request) => Promise<IngestedEvent<TProvider>> | Verify the webhook signature and return the parsed event. |
IngestedEvent
A verified webhook event, before persistence. The domain treats provider as opaque; the default string suits readers of a persisted event, whose column has no CHECK, so an unknown value stays lookup-safe. The persisted shape, PersistedIngestedEvent, belongs to the events module. The makeIngestedEvent factory builds one with neutral provider and event type defaults, see Factories.
Prop
Type
Queries
Query objects, criteria, projections, aggregations, pagination and the generic read and write repository ports every module binds.
Accounts
Accounts (person or organisation), public handles, owners, user roles, preferences, onboarding completeness, and the AccountsService fed by the identity provider.