refactor: remove core-events from main (scaffoldable via gen core-package events)
- Delete packages/core-events/ (15 files) - Strip @repo/core-events from all 5 feature package.json + apps/web-next/package.json - Strip @repo/core-events from apps/web-next/next.config.mjs transpilePackages - Strip E1 + J no-restricted-syntax blocks from core-eslint/base.js (anchor remains) - Update bind-production.ts: drop bus construction + IEventBus import; rename resolveEventsAndJobsProduction → resolveJobsProduction (queue only), resolveEventsAndJobsDevSeed → resolveJobsDevSeed (queue only); ctx no longer has bus field; BindProductionContext generic arg narrowed - Update bind-production.test.ts: assert ctx.bus is undefined, drop PayloadJobsEventBus/InMemoryEventBus instanceof checks - Update sign-up-welcome-email.test.ts: assert mailer stays empty without bus - Inline userSignedUpEvent in auth (drop defineEvent import from core-events) - Drop InMemoryEventBus fallback from auth/di/module.ts Feature binders' bus?.subscribe/publish calls remain as no-ops. Scaffold @repo/core-events back via: pnpm turbo gen core-package events Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,6 @@ const nextConfig = {
|
||||
"@repo/blog",
|
||||
"@repo/core-api",
|
||||
"@repo/core-cms",
|
||||
"@repo/core-events",
|
||||
"@repo/core-shared",
|
||||
"@repo/core-trpc",
|
||||
"@repo/core-ui",
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"@repo/blog": "workspace:*",
|
||||
"@repo/core-api": "workspace:*",
|
||||
"@repo/core-cms": "workspace:*",
|
||||
"@repo/core-events": "workspace:*",
|
||||
"@repo/core-shared": "workspace:*",
|
||||
"@repo/core-trpc": "workspace:*",
|
||||
"@repo/core-ui": "workspace:*",
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
// Cross-feature proof-of-life: signing up in @repo/auth publishes
|
||||
// userSignedUpEvent on the shared bus, marketing-pages' subscriber enqueues
|
||||
// a send-welcome-email job on the shared queue, and dev-seed's
|
||||
// InMemoryJobQueue.register() dispatches it to the wrapped job — which
|
||||
// records the call on the bound RecordingMailerService.
|
||||
// Cross-feature event bus proof-of-life (DISABLED — @repo/core-events removed).
|
||||
//
|
||||
// @repo/core-events is now optional. When absent, ctx.bus is undefined, and
|
||||
// bus?.subscribe(...) / bus?.publish(...) calls are no-ops. Cross-feature event
|
||||
// fanout does not occur until core-events is scaffolded via:
|
||||
//
|
||||
// pnpm turbo gen core-package events
|
||||
//
|
||||
// After scaffolding, restore this test and re-wire the bus in bind-production.ts
|
||||
// (see the comment in bindAll()). Until then, signing up does NOT trigger a
|
||||
// welcome email — the mailer queue stays empty.
|
||||
//
|
||||
// Replaced with a reduced test that asserts the no-bus behavior.
|
||||
|
||||
import "reflect-metadata";
|
||||
import { describe, it, expect, beforeEach } from "vitest";
|
||||
@@ -14,12 +22,12 @@ import { marketingPagesContainer } from "@repo/marketing-pages/di/container";
|
||||
import { MARKETING_PAGES_SYMBOLS } from "@repo/marketing-pages/di/symbols";
|
||||
import { RecordingMailerService } from "@repo/marketing-pages/services/recording-mailer";
|
||||
|
||||
describe("e2e: sign-up triggers welcome email via cross-feature event", () => {
|
||||
describe("e2e: sign-up with no event bus (core-events not scaffolded)", () => {
|
||||
beforeEach(() => {
|
||||
__resetBindStateForTests();
|
||||
});
|
||||
|
||||
it("delivers a welcome email after a successful sign-up", async () => {
|
||||
it("sign-up succeeds and mailer stays empty (no cross-feature fanout without bus)", async () => {
|
||||
await bindAllDevSeed();
|
||||
|
||||
const mailer = marketingPagesContainer.get<RecordingMailerService>(
|
||||
@@ -33,12 +41,10 @@ describe("e2e: sign-up triggers welcome email via cross-feature event", () => {
|
||||
confirmPassword: "secret_password",
|
||||
});
|
||||
|
||||
// The InMemoryJobQueue dispatches the registered handler on setImmediate;
|
||||
// wait for microtasks + one setImmediate tick to settle.
|
||||
// Without a bus, bus?.subscribe() is a no-op so the event handler never
|
||||
// fires and the mailer receives nothing.
|
||||
await new Promise((r) => setImmediate(r));
|
||||
|
||||
expect(mailer.sent).toEqual([
|
||||
{ userId: expect.any(String), email: "testuser@example.local" },
|
||||
]);
|
||||
expect(mailer.sent).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,16 +52,15 @@ describe("bindAllProduction", () => {
|
||||
expect(bindProductionBlog).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("passes a Payload-backed bus + queue to each per-feature binder", async () => {
|
||||
it("passes a Payload-backed queue to each per-feature binder", async () => {
|
||||
const { bindAllProduction } = await import("./bind-production");
|
||||
const { bindProductionAuth } = await import("@repo/auth/di/bind-production");
|
||||
const { PayloadJobsEventBus } = await import("@repo/core-events");
|
||||
const { PayloadJobQueue } = await import("@repo/core-shared/jobs");
|
||||
|
||||
await bindAllProduction();
|
||||
|
||||
const ctx = vi.mocked(bindProductionAuth).mock.calls[0]![0];
|
||||
expect(ctx.bus).toBeInstanceOf(PayloadJobsEventBus);
|
||||
expect(ctx.bus).toBeUndefined();
|
||||
expect(ctx.queue).toBeInstanceOf(PayloadJobQueue);
|
||||
});
|
||||
});
|
||||
@@ -72,16 +71,15 @@ describe("bindAllDevSeed", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("passes an in-memory bus + queue to each per-feature dev-seed binder", async () => {
|
||||
it("passes an in-memory queue (no bus) to each per-feature dev-seed binder", async () => {
|
||||
const { bindAllDevSeed } = await import("./bind-production");
|
||||
const { bindDevSeedAuth } = await import("@repo/auth/di/bind-dev-seed");
|
||||
const { InMemoryEventBus } = await import("@repo/core-events");
|
||||
const { InMemoryJobQueue } = await import("@repo/core-shared/jobs");
|
||||
|
||||
await bindAllDevSeed();
|
||||
|
||||
const ctx = vi.mocked(bindDevSeedAuth).mock.calls[0]![0];
|
||||
expect(ctx.bus).toBeInstanceOf(InMemoryEventBus);
|
||||
expect(ctx.bus).toBeUndefined();
|
||||
expect(ctx.queue).toBeInstanceOf(InMemoryJobQueue);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,11 +11,6 @@ import {
|
||||
type ILogger,
|
||||
} from "@repo/core-shared/instrumentation";
|
||||
import type { BindProductionContext, BindContext } from "@repo/core-shared/di";
|
||||
import {
|
||||
InMemoryEventBus,
|
||||
PayloadJobsEventBus,
|
||||
type IEventBus,
|
||||
} from "@repo/core-events";
|
||||
import {
|
||||
InMemoryJobQueue,
|
||||
PayloadJobQueue,
|
||||
@@ -41,7 +36,6 @@ const sharedContainer = new Container();
|
||||
|
||||
let resolvedTracer: ITracer | null = null;
|
||||
let resolvedLogger: ILogger | null = null;
|
||||
let resolvedBus: IEventBus | null = null;
|
||||
let resolvedQueue: IJobQueue | null = null;
|
||||
|
||||
/** Rule 0: pick instrumentation backend from DSN env (orthogonal to repo mode). */
|
||||
@@ -59,38 +53,31 @@ function resolveInstrumentation(): { tracer: ITracer; logger: ILogger } {
|
||||
}
|
||||
|
||||
/**
|
||||
* Production-mode event bus + job queue: backed by Payload's job system so
|
||||
* events fan out via durable Payload tasks (`PayloadJobsEventBus` enqueues
|
||||
* `__events.<publisher>.<event>.<consumer>` per subscribed handler) and ad-hoc
|
||||
* jobs go through `PayloadJobQueue.enqueue`. Cached after first resolution.
|
||||
* Production-mode job queue: backed by Payload's job system so ad-hoc jobs go
|
||||
* through `PayloadJobQueue.enqueue`. Cached after first resolution.
|
||||
*
|
||||
* Note: @repo/core-events (IEventBus) is optional — scaffold via
|
||||
* `pnpm turbo gen core-package events` to re-enable cross-feature event fanout.
|
||||
*/
|
||||
async function resolveEventsAndJobsProduction(): Promise<{
|
||||
bus: IEventBus;
|
||||
queue: IJobQueue;
|
||||
}> {
|
||||
if (resolvedBus && resolvedQueue) return { bus: resolvedBus, queue: resolvedQueue };
|
||||
async function resolveJobsProduction(): Promise<{ queue: IJobQueue }> {
|
||||
if (resolvedQueue) return { queue: resolvedQueue };
|
||||
const resolvedConfig = await config;
|
||||
const payload = await getPayload({ config: resolvedConfig });
|
||||
const queue = new PayloadJobQueue(payload);
|
||||
const bus = new PayloadJobsEventBus(queue);
|
||||
resolvedBus = bus;
|
||||
resolvedQueue = queue;
|
||||
return { bus, queue };
|
||||
return { queue };
|
||||
}
|
||||
|
||||
/**
|
||||
* Dev-seed mode: in-process bus + queue. Per-feature binders register their
|
||||
* job handlers via `queue.register(slug, handler)` and subscribe their event
|
||||
* handlers via `bus.subscribe(...)` at bind time, so dev/test exercises the
|
||||
* full publish → handler → enqueue path without booting Payload.
|
||||
* Dev-seed mode: in-process job queue. Per-feature binders register their job
|
||||
* handlers via `queue.register(slug, handler)` at bind time so dev/test
|
||||
* exercises the enqueue path without booting Payload.
|
||||
*/
|
||||
function resolveEventsAndJobsDevSeed(): { bus: IEventBus; queue: IJobQueue } {
|
||||
if (resolvedBus && resolvedQueue) return { bus: resolvedBus, queue: resolvedQueue };
|
||||
function resolveJobsDevSeed(): { queue: IJobQueue } {
|
||||
if (resolvedQueue) return { queue: resolvedQueue };
|
||||
const queue = new InMemoryJobQueue();
|
||||
const bus = new InMemoryEventBus();
|
||||
resolvedBus = bus;
|
||||
resolvedQueue = queue;
|
||||
return { bus, queue };
|
||||
return { queue };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,14 +89,13 @@ export async function bindAllProduction(): Promise<void> {
|
||||
if (bound) return;
|
||||
bound = true;
|
||||
const { tracer, logger } = resolveInstrumentation(); // Rule 0
|
||||
const { bus, queue } = await resolveEventsAndJobsProduction();
|
||||
const { queue } = await resolveJobsProduction();
|
||||
const resolvedConfig = await config;
|
||||
|
||||
const ctx: BindProductionContext<IEventBus> = {
|
||||
const ctx: BindProductionContext = {
|
||||
config: resolvedConfig,
|
||||
tracer,
|
||||
logger,
|
||||
bus,
|
||||
queue,
|
||||
};
|
||||
|
||||
@@ -129,12 +115,11 @@ export async function bindAllDevSeed(): Promise<void> {
|
||||
if (bound) return;
|
||||
bound = true;
|
||||
const { tracer, logger } = resolveInstrumentation(); // Rule 0
|
||||
const { bus, queue } = resolveEventsAndJobsDevSeed();
|
||||
const { queue } = resolveJobsDevSeed();
|
||||
|
||||
const ctx: BindContext<IEventBus> = {
|
||||
const ctx: BindContext = {
|
||||
tracer,
|
||||
logger,
|
||||
bus,
|
||||
queue,
|
||||
};
|
||||
|
||||
@@ -157,9 +142,10 @@ export async function bindAllDevSeed(): Promise<void> {
|
||||
* Rule 2: NODE_ENV === "production" → real Payload via bindAllProduction
|
||||
* Rule 3: otherwise → dev seed (developer-friendly default)
|
||||
*
|
||||
* When @repo/core-realtime is scaffolded, extend this function to accept
|
||||
* realtime deps (IRealtimeBroadcaster, IRealtimeHandlerRegistry) and pass
|
||||
* them through to bindAllProduction / bindAllDevSeed.
|
||||
* When @repo/core-events is scaffolded via `pnpm turbo gen core-package events`,
|
||||
* extend to construct IEventBus and pass it via ctx.bus to per-feature binders.
|
||||
* When @repo/core-realtime is scaffolded, extend to accept realtime deps
|
||||
* (IRealtimeBroadcaster, IRealtimeHandlerRegistry) and pass them through.
|
||||
*/
|
||||
export async function bindAll(): Promise<void> {
|
||||
if (process.env.USE_DEV_SEED === "true") {
|
||||
@@ -178,7 +164,6 @@ export function __resetBindStateForTests(): void {
|
||||
bound = false;
|
||||
resolvedTracer = null;
|
||||
resolvedLogger = null;
|
||||
resolvedBus = null;
|
||||
resolvedQueue = null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user