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/blog",
|
||||||
"@repo/core-api",
|
"@repo/core-api",
|
||||||
"@repo/core-cms",
|
"@repo/core-cms",
|
||||||
"@repo/core-events",
|
|
||||||
"@repo/core-shared",
|
"@repo/core-shared",
|
||||||
"@repo/core-trpc",
|
"@repo/core-trpc",
|
||||||
"@repo/core-ui",
|
"@repo/core-ui",
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
"@repo/blog": "workspace:*",
|
"@repo/blog": "workspace:*",
|
||||||
"@repo/core-api": "workspace:*",
|
"@repo/core-api": "workspace:*",
|
||||||
"@repo/core-cms": "workspace:*",
|
"@repo/core-cms": "workspace:*",
|
||||||
"@repo/core-events": "workspace:*",
|
|
||||||
"@repo/core-shared": "workspace:*",
|
"@repo/core-shared": "workspace:*",
|
||||||
"@repo/core-trpc": "workspace:*",
|
"@repo/core-trpc": "workspace:*",
|
||||||
"@repo/core-ui": "workspace:*",
|
"@repo/core-ui": "workspace:*",
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
// Cross-feature proof-of-life: signing up in @repo/auth publishes
|
// Cross-feature event bus proof-of-life (DISABLED — @repo/core-events removed).
|
||||||
// userSignedUpEvent on the shared bus, marketing-pages' subscriber enqueues
|
//
|
||||||
// a send-welcome-email job on the shared queue, and dev-seed's
|
// @repo/core-events is now optional. When absent, ctx.bus is undefined, and
|
||||||
// InMemoryJobQueue.register() dispatches it to the wrapped job — which
|
// bus?.subscribe(...) / bus?.publish(...) calls are no-ops. Cross-feature event
|
||||||
// records the call on the bound RecordingMailerService.
|
// 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 "reflect-metadata";
|
||||||
import { describe, it, expect, beforeEach } from "vitest";
|
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 { MARKETING_PAGES_SYMBOLS } from "@repo/marketing-pages/di/symbols";
|
||||||
import { RecordingMailerService } from "@repo/marketing-pages/services/recording-mailer";
|
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(() => {
|
beforeEach(() => {
|
||||||
__resetBindStateForTests();
|
__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();
|
await bindAllDevSeed();
|
||||||
|
|
||||||
const mailer = marketingPagesContainer.get<RecordingMailerService>(
|
const mailer = marketingPagesContainer.get<RecordingMailerService>(
|
||||||
@@ -33,12 +41,10 @@ describe("e2e: sign-up triggers welcome email via cross-feature event", () => {
|
|||||||
confirmPassword: "secret_password",
|
confirmPassword: "secret_password",
|
||||||
});
|
});
|
||||||
|
|
||||||
// The InMemoryJobQueue dispatches the registered handler on setImmediate;
|
// Without a bus, bus?.subscribe() is a no-op so the event handler never
|
||||||
// wait for microtasks + one setImmediate tick to settle.
|
// fires and the mailer receives nothing.
|
||||||
await new Promise((r) => setImmediate(r));
|
await new Promise((r) => setImmediate(r));
|
||||||
|
|
||||||
expect(mailer.sent).toEqual([
|
expect(mailer.sent).toEqual([]);
|
||||||
{ userId: expect.any(String), email: "testuser@example.local" },
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -52,16 +52,15 @@ describe("bindAllProduction", () => {
|
|||||||
expect(bindProductionBlog).toHaveBeenCalledOnce();
|
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 { bindAllProduction } = await import("./bind-production");
|
||||||
const { bindProductionAuth } = await import("@repo/auth/di/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");
|
const { PayloadJobQueue } = await import("@repo/core-shared/jobs");
|
||||||
|
|
||||||
await bindAllProduction();
|
await bindAllProduction();
|
||||||
|
|
||||||
const ctx = vi.mocked(bindProductionAuth).mock.calls[0]![0];
|
const ctx = vi.mocked(bindProductionAuth).mock.calls[0]![0];
|
||||||
expect(ctx.bus).toBeInstanceOf(PayloadJobsEventBus);
|
expect(ctx.bus).toBeUndefined();
|
||||||
expect(ctx.queue).toBeInstanceOf(PayloadJobQueue);
|
expect(ctx.queue).toBeInstanceOf(PayloadJobQueue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -72,16 +71,15 @@ describe("bindAllDevSeed", () => {
|
|||||||
vi.clearAllMocks();
|
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 { bindAllDevSeed } = await import("./bind-production");
|
||||||
const { bindDevSeedAuth } = await import("@repo/auth/di/bind-dev-seed");
|
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");
|
const { InMemoryJobQueue } = await import("@repo/core-shared/jobs");
|
||||||
|
|
||||||
await bindAllDevSeed();
|
await bindAllDevSeed();
|
||||||
|
|
||||||
const ctx = vi.mocked(bindDevSeedAuth).mock.calls[0]![0];
|
const ctx = vi.mocked(bindDevSeedAuth).mock.calls[0]![0];
|
||||||
expect(ctx.bus).toBeInstanceOf(InMemoryEventBus);
|
expect(ctx.bus).toBeUndefined();
|
||||||
expect(ctx.queue).toBeInstanceOf(InMemoryJobQueue);
|
expect(ctx.queue).toBeInstanceOf(InMemoryJobQueue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ import {
|
|||||||
type ILogger,
|
type ILogger,
|
||||||
} from "@repo/core-shared/instrumentation";
|
} from "@repo/core-shared/instrumentation";
|
||||||
import type { BindProductionContext, BindContext } from "@repo/core-shared/di";
|
import type { BindProductionContext, BindContext } from "@repo/core-shared/di";
|
||||||
import {
|
|
||||||
InMemoryEventBus,
|
|
||||||
PayloadJobsEventBus,
|
|
||||||
type IEventBus,
|
|
||||||
} from "@repo/core-events";
|
|
||||||
import {
|
import {
|
||||||
InMemoryJobQueue,
|
InMemoryJobQueue,
|
||||||
PayloadJobQueue,
|
PayloadJobQueue,
|
||||||
@@ -41,7 +36,6 @@ const sharedContainer = new Container();
|
|||||||
|
|
||||||
let resolvedTracer: ITracer | null = null;
|
let resolvedTracer: ITracer | null = null;
|
||||||
let resolvedLogger: ILogger | null = null;
|
let resolvedLogger: ILogger | null = null;
|
||||||
let resolvedBus: IEventBus | null = null;
|
|
||||||
let resolvedQueue: IJobQueue | null = null;
|
let resolvedQueue: IJobQueue | null = null;
|
||||||
|
|
||||||
/** Rule 0: pick instrumentation backend from DSN env (orthogonal to repo mode). */
|
/** 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
|
* Production-mode job queue: backed by Payload's job system so ad-hoc jobs go
|
||||||
* events fan out via durable Payload tasks (`PayloadJobsEventBus` enqueues
|
* through `PayloadJobQueue.enqueue`. Cached after first resolution.
|
||||||
* `__events.<publisher>.<event>.<consumer>` per subscribed handler) and 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<{
|
async function resolveJobsProduction(): Promise<{ queue: IJobQueue }> {
|
||||||
bus: IEventBus;
|
if (resolvedQueue) return { queue: resolvedQueue };
|
||||||
queue: IJobQueue;
|
|
||||||
}> {
|
|
||||||
if (resolvedBus && resolvedQueue) return { bus: resolvedBus, queue: resolvedQueue };
|
|
||||||
const resolvedConfig = await config;
|
const resolvedConfig = await config;
|
||||||
const payload = await getPayload({ config: resolvedConfig });
|
const payload = await getPayload({ config: resolvedConfig });
|
||||||
const queue = new PayloadJobQueue(payload);
|
const queue = new PayloadJobQueue(payload);
|
||||||
const bus = new PayloadJobsEventBus(queue);
|
|
||||||
resolvedBus = bus;
|
|
||||||
resolvedQueue = queue;
|
resolvedQueue = queue;
|
||||||
return { bus, queue };
|
return { queue };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dev-seed mode: in-process bus + queue. Per-feature binders register their
|
* Dev-seed mode: in-process job queue. Per-feature binders register their job
|
||||||
* job handlers via `queue.register(slug, handler)` and subscribe their event
|
* handlers via `queue.register(slug, handler)` at bind time so dev/test
|
||||||
* handlers via `bus.subscribe(...)` at bind time, so dev/test exercises the
|
* exercises the enqueue path without booting Payload.
|
||||||
* full publish → handler → enqueue path without booting Payload.
|
|
||||||
*/
|
*/
|
||||||
function resolveEventsAndJobsDevSeed(): { bus: IEventBus; queue: IJobQueue } {
|
function resolveJobsDevSeed(): { queue: IJobQueue } {
|
||||||
if (resolvedBus && resolvedQueue) return { bus: resolvedBus, queue: resolvedQueue };
|
if (resolvedQueue) return { queue: resolvedQueue };
|
||||||
const queue = new InMemoryJobQueue();
|
const queue = new InMemoryJobQueue();
|
||||||
const bus = new InMemoryEventBus();
|
|
||||||
resolvedBus = bus;
|
|
||||||
resolvedQueue = queue;
|
resolvedQueue = queue;
|
||||||
return { bus, queue };
|
return { queue };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,14 +89,13 @@ export async function bindAllProduction(): Promise<void> {
|
|||||||
if (bound) return;
|
if (bound) return;
|
||||||
bound = true;
|
bound = true;
|
||||||
const { tracer, logger } = resolveInstrumentation(); // Rule 0
|
const { tracer, logger } = resolveInstrumentation(); // Rule 0
|
||||||
const { bus, queue } = await resolveEventsAndJobsProduction();
|
const { queue } = await resolveJobsProduction();
|
||||||
const resolvedConfig = await config;
|
const resolvedConfig = await config;
|
||||||
|
|
||||||
const ctx: BindProductionContext<IEventBus> = {
|
const ctx: BindProductionContext = {
|
||||||
config: resolvedConfig,
|
config: resolvedConfig,
|
||||||
tracer,
|
tracer,
|
||||||
logger,
|
logger,
|
||||||
bus,
|
|
||||||
queue,
|
queue,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -129,12 +115,11 @@ export async function bindAllDevSeed(): Promise<void> {
|
|||||||
if (bound) return;
|
if (bound) return;
|
||||||
bound = true;
|
bound = true;
|
||||||
const { tracer, logger } = resolveInstrumentation(); // Rule 0
|
const { tracer, logger } = resolveInstrumentation(); // Rule 0
|
||||||
const { bus, queue } = resolveEventsAndJobsDevSeed();
|
const { queue } = resolveJobsDevSeed();
|
||||||
|
|
||||||
const ctx: BindContext<IEventBus> = {
|
const ctx: BindContext = {
|
||||||
tracer,
|
tracer,
|
||||||
logger,
|
logger,
|
||||||
bus,
|
|
||||||
queue,
|
queue,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -157,9 +142,10 @@ export async function bindAllDevSeed(): Promise<void> {
|
|||||||
* Rule 2: NODE_ENV === "production" → real Payload via bindAllProduction
|
* Rule 2: NODE_ENV === "production" → real Payload via bindAllProduction
|
||||||
* Rule 3: otherwise → dev seed (developer-friendly default)
|
* Rule 3: otherwise → dev seed (developer-friendly default)
|
||||||
*
|
*
|
||||||
* When @repo/core-realtime is scaffolded, extend this function to accept
|
* When @repo/core-events is scaffolded via `pnpm turbo gen core-package events`,
|
||||||
* realtime deps (IRealtimeBroadcaster, IRealtimeHandlerRegistry) and pass
|
* extend to construct IEventBus and pass it via ctx.bus to per-feature binders.
|
||||||
* them through to bindAllProduction / bindAllDevSeed.
|
* When @repo/core-realtime is scaffolded, extend to accept realtime deps
|
||||||
|
* (IRealtimeBroadcaster, IRealtimeHandlerRegistry) and pass them through.
|
||||||
*/
|
*/
|
||||||
export async function bindAll(): Promise<void> {
|
export async function bindAll(): Promise<void> {
|
||||||
if (process.env.USE_DEV_SEED === "true") {
|
if (process.env.USE_DEV_SEED === "true") {
|
||||||
@@ -178,7 +164,6 @@ export function __resetBindStateForTests(): void {
|
|||||||
bound = false;
|
bound = false;
|
||||||
resolvedTracer = null;
|
resolvedTracer = null;
|
||||||
resolvedLogger = null;
|
resolvedLogger = null;
|
||||||
resolvedBus = null;
|
|
||||||
resolvedQueue = null;
|
resolvedQueue = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@repo/core-events": "workspace:*",
|
|
||||||
"@repo/core-shared": "workspace:*",
|
"@repo/core-shared": "workspace:*",
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.0.0",
|
||||||
"inversify": "^6.2.0",
|
"inversify": "^6.2.0",
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ContainerModule, type interfaces } from "inversify";
|
import { ContainerModule, type interfaces } from "inversify";
|
||||||
import { InMemoryEventBus } from "@repo/core-events";
|
|
||||||
|
|
||||||
import type { IUsersRepository } from "../application/repositories/users.repository.interface";
|
import type { IUsersRepository } from "../application/repositories/users.repository.interface";
|
||||||
import type { IAuthenticationService } from "../application/services/authentication.service.interface";
|
import type { IAuthenticationService } from "../application/services/authentication.service.interface";
|
||||||
@@ -45,13 +44,13 @@ export const AuthModule = new ContainerModule((bind: interfaces.Bind) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
bind<ISignUpUseCase>(AUTH_SYMBOLS.ISignUpUseCase).toDynamicValue((ctx) =>
|
bind<ISignUpUseCase>(AUTH_SYMBOLS.ISignUpUseCase).toDynamicValue((ctx) =>
|
||||||
// Default fallback uses a fresh InMemoryEventBus — real cross-feature
|
// No default bus — real cross-feature wiring runs through
|
||||||
// wiring runs through bindProductionAuth / bindDevSeedAuth where the
|
// bindProductionAuth / bindDevSeedAuth where bindAll() passes a shared
|
||||||
// bindAll() dispatcher passes a shared bus instance.
|
// bus instance when @repo/core-events is scaffolded.
|
||||||
signUpUseCase(
|
signUpUseCase(
|
||||||
ctx.container.get<IUsersRepository>(AUTH_SYMBOLS.IUsersRepository),
|
ctx.container.get<IUsersRepository>(AUTH_SYMBOLS.IUsersRepository),
|
||||||
ctx.container.get<IAuthenticationService>(AUTH_SYMBOLS.IAuthenticationService),
|
ctx.container.get<IAuthenticationService>(AUTH_SYMBOLS.IAuthenticationService),
|
||||||
new InMemoryEventBus(),
|
undefined,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// packages/auth/src/events/user-signed-up.event.ts
|
// packages/auth/src/events/user-signed-up.event.ts
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { defineEvent } from "@repo/core-events";
|
|
||||||
|
|
||||||
export const userSignedUpEventSchema = z
|
export const userSignedUpEventSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -12,7 +11,9 @@ export const userSignedUpEventSchema = z
|
|||||||
|
|
||||||
export type UserSignedUpEvent = z.infer<typeof userSignedUpEventSchema>;
|
export type UserSignedUpEvent = z.infer<typeof userSignedUpEventSchema>;
|
||||||
|
|
||||||
export const userSignedUpEvent = defineEvent(
|
// Inline event descriptor — core-events is optional. The shape { name, schema }
|
||||||
"auth.user.signed-up",
|
// satisfies EventBusProtocol.publish / subscribe when the bus is present.
|
||||||
userSignedUpEventSchema,
|
export const userSignedUpEvent = {
|
||||||
);
|
name: "auth.user.signed-up" as const,
|
||||||
|
schema: userSignedUpEventSchema,
|
||||||
|
};
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@repo/core-events": "workspace:*",
|
|
||||||
"@repo/core-shared": "workspace:*",
|
"@repo/core-shared": "workspace:*",
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.0.0",
|
||||||
"inversify": "^6.2.0",
|
"inversify": "^6.2.0",
|
||||||
|
|||||||
@@ -111,64 +111,6 @@ export default [
|
|||||||
// Events + jobs rules are added here when @repo/core-events is scaffolded
|
// Events + jobs rules are added here when @repo/core-events is scaffolded
|
||||||
// via `pnpm turbo gen core-package events`.
|
// via `pnpm turbo gen core-package events`.
|
||||||
// <gen:events-rules>
|
// <gen:events-rules>
|
||||||
{
|
|
||||||
files: ["**/*.{ts,tsx,mjs,cjs,js}"],
|
|
||||||
rules: {
|
|
||||||
"no-restricted-syntax": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
selector:
|
|
||||||
"ExportNamedDeclaration[source.value=/\\/events\\/handlers\\//]",
|
|
||||||
message:
|
|
||||||
"Event handlers (events/handlers/*.handler.ts) must not be re-exported. Wire them only inside the consumer feature's bind-production / bind-dev-seed (Rule E1).",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selector:
|
|
||||||
"ExportAllDeclaration[source.value=/\\/events\\/handlers\\//]",
|
|
||||||
message:
|
|
||||||
"Event handlers (events/handlers/*.handler.ts) must not be re-exported. Wire them only inside the consumer feature's bind-production / bind-dev-seed (Rule E1).",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selector:
|
|
||||||
"MemberExpression[object.type='MemberExpression'][object.object.type='Identifier'][object.object.name='payload'][object.property.type='Identifier'][object.property.name='jobs']",
|
|
||||||
message:
|
|
||||||
"Direct `payload.jobs.*` access is not allowed here. Use IJobQueue (from @repo/core-shared/jobs) instead. Allowed only in **/integrations/cms/jobs/** and **/core-shared/src/jobs/**.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// J — `payload.jobs.*` is allowed only in the integration layer.
|
|
||||||
// In these paths, no-restricted-syntax is narrowed to keep E1 active but
|
|
||||||
// drop the payload.jobs check.
|
|
||||||
// Note: "**/core-shared/src/jobs/**" does not match from within a package-local
|
|
||||||
// ESLint run because ESLint resolves globs relative to the config file location.
|
|
||||||
// The pattern is kept for documentation; in practice, the PayloadJobQueue class
|
|
||||||
// uses `this.payload.jobs.*` which the selector already ignores (it only catches
|
|
||||||
// bare `payload.jobs.*`). Any new file added there that does use bare `payload.jobs.*`
|
|
||||||
// would need this allowlist to be expressed as "**/jobs/payload-*" or similar.
|
|
||||||
{
|
|
||||||
files: [
|
|
||||||
"**/integrations/cms/jobs/**",
|
|
||||||
"**/core-shared/src/jobs/**",
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
"no-restricted-syntax": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
selector:
|
|
||||||
"ExportNamedDeclaration[source.value=/\\/events\\/handlers\\//]",
|
|
||||||
message:
|
|
||||||
"Event handlers (events/handlers/*.handler.ts) must not be re-exported. Wire them only inside the consumer feature's bind-production / bind-dev-seed (Rule E1).",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selector:
|
|
||||||
"ExportAllDeclaration[source.value=/\\/events\\/handlers\\//]",
|
|
||||||
message:
|
|
||||||
"Event handlers (events/handlers/*.handler.ts) must not be re-exported. Wire them only inside the consumer feature's bind-production / bind-dev-seed (Rule E1).",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// R2 / R1 (ADR-016) — realtime-specific ESLint rules (no-direct-socket-io,
|
// R2 / R1 (ADR-016) — realtime-specific ESLint rules (no-direct-socket-io,
|
||||||
// no-realtime-handler-reexport) are added here when @repo/core-realtime is
|
// no-realtime-handler-reexport) are added here when @repo/core-realtime is
|
||||||
// scaffolded via `pnpm turbo gen core-package realtime`.
|
// scaffolded via `pnpm turbo gen core-package realtime`.
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
# @repo/core-events
|
|
||||||
|
|
||||||
Owns the cross-feature event bus: `IEventBus`, `defineEvent`, and two implementations (`InMemoryEventBus`, `PayloadJobsEventBus`).
|
|
||||||
|
|
||||||
**Boundary tag:** core. May be imported by feature, core, core-composition, app. May import from core-shared, tooling.
|
|
||||||
|
|
||||||
**Public surface:** `IEventBus`, `EventDescriptor`, `defineEvent`, `EventHandler`, `CORE_EVENTS_SYMBOLS`, both implementations.
|
|
||||||
|
|
||||||
**See:** `docs/decisions/adr-015-events-and-jobs.md` (pending), `docs/guides/events-and-jobs.md` (pending), `docs/superpowers/specs/2026-05-08-events-and-jobs-design.md`.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import baseConfig from "@repo/core-eslint/base";
|
|
||||||
|
|
||||||
export default baseConfig;
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@repo/core-events",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"exports": {
|
|
||||||
".": "./src/index.ts"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc --noEmit",
|
|
||||||
"lint": "eslint .",
|
|
||||||
"typecheck": "tsc --noEmit",
|
|
||||||
"test": "vitest run"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@repo/core-shared": "workspace:*",
|
|
||||||
"zod": "^3.23.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"payload": "^3.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"payload": { "optional": true }
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@repo/core-eslint": "workspace:*",
|
|
||||||
"@repo/core-testing": "workspace:*",
|
|
||||||
"@repo/core-typescript": "workspace:*",
|
|
||||||
"typescript": "^5.8.0",
|
|
||||||
"vitest": "^3.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import type { z } from "zod";
|
|
||||||
import type { EventBusProtocol } from "@repo/core-shared/di/bind-protocols";
|
|
||||||
import type { EventDescriptor } from "./event-descriptor";
|
|
||||||
|
|
||||||
export type EventHandler<T> = (event: T) => Promise<void>;
|
|
||||||
|
|
||||||
export interface IEventBus extends EventBusProtocol {
|
|
||||||
publish<T>(
|
|
||||||
descriptor: EventDescriptor<string, z.ZodType<T>>,
|
|
||||||
payload: T,
|
|
||||||
): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribe a handler. `consumerFeature` is the kebab-case name of the
|
|
||||||
* subscribing feature (e.g., "marketing-pages"). It is unused by
|
|
||||||
* InMemoryEventBus; PayloadJobsEventBus uses it to name the fan-out task
|
|
||||||
* slug deterministically (`__events.<event>.<consumerFeature>`).
|
|
||||||
*/
|
|
||||||
subscribe<T>(
|
|
||||||
descriptor: EventDescriptor<string, z.ZodType<T>>,
|
|
||||||
consumerFeature: string,
|
|
||||||
handler: EventHandler<T>,
|
|
||||||
): void;
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { describe, it, expect } from "vitest";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { defineEvent } from "@/event-descriptor";
|
|
||||||
|
|
||||||
describe("defineEvent", () => {
|
|
||||||
it("returns a descriptor with name and schema", () => {
|
|
||||||
const schema = z.object({ id: z.string() }).strict();
|
|
||||||
const descriptor = defineEvent("test.thing.happened", schema);
|
|
||||||
expect(descriptor.name).toBe("test.thing.happened");
|
|
||||||
expect(descriptor.schema).toBe(schema);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("descriptor.schema parses valid payloads", () => {
|
|
||||||
const schema = z.object({ id: z.string() }).strict();
|
|
||||||
const d = defineEvent("test.evt", schema);
|
|
||||||
expect(() => d.schema.parse({ id: "abc" })).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("descriptor.schema rejects invalid payloads", () => {
|
|
||||||
const schema = z.object({ id: z.string() }).strict();
|
|
||||||
const d = defineEvent("test.evt", schema);
|
|
||||||
expect(() => d.schema.parse({ id: 123 })).toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import type { z } from "zod";
|
|
||||||
|
|
||||||
export type EventDescriptor<TName extends string, TSchema extends z.ZodType> = {
|
|
||||||
readonly name: TName;
|
|
||||||
readonly schema: TSchema;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function defineEvent<TName extends string, TSchema extends z.ZodType>(
|
|
||||||
name: TName,
|
|
||||||
schema: TSchema,
|
|
||||||
): EventDescriptor<TName, TSchema> {
|
|
||||||
return { name, schema };
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { describe, it, expect, vi } from "vitest";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { defineEvent } from "@/event-descriptor";
|
|
||||||
import { InMemoryEventBus } from "@/in-memory-event-bus";
|
|
||||||
|
|
||||||
const evt = defineEvent("test.thing", z.object({ id: z.string() }).strict());
|
|
||||||
|
|
||||||
describe("InMemoryEventBus", () => {
|
|
||||||
it("validates the payload via the descriptor's schema before fanout", async () => {
|
|
||||||
const bus = new InMemoryEventBus();
|
|
||||||
const handler = vi.fn();
|
|
||||||
bus.subscribe(evt, "test-consumer", handler);
|
|
||||||
await expect(bus.publish(evt, { id: 123 } as unknown as { id: string })).rejects.toThrow();
|
|
||||||
expect(handler).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("delivers to all registered handlers in parallel", async () => {
|
|
||||||
const bus = new InMemoryEventBus();
|
|
||||||
const a = vi.fn();
|
|
||||||
const b = vi.fn();
|
|
||||||
bus.subscribe(evt, "consumer-a", a);
|
|
||||||
bus.subscribe(evt, "consumer-b", b);
|
|
||||||
await bus.publish(evt, { id: "x" });
|
|
||||||
expect(a).toHaveBeenCalledWith({ id: "x" });
|
|
||||||
expect(b).toHaveBeenCalledWith({ id: "x" });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("swallows handler errors by default (publisher's publish does not throw)", async () => {
|
|
||||||
const bus = new InMemoryEventBus();
|
|
||||||
bus.subscribe(evt, "boom", async () => {
|
|
||||||
throw new Error("subscriber blew up");
|
|
||||||
});
|
|
||||||
await expect(bus.publish(evt, { id: "x" })).resolves.toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rethrows the first handler error when failFast is true", async () => {
|
|
||||||
const bus = new InMemoryEventBus({ failFast: true });
|
|
||||||
bus.subscribe(evt, "first", async () => {
|
|
||||||
throw new Error("first failure");
|
|
||||||
});
|
|
||||||
bus.subscribe(evt, "second", vi.fn());
|
|
||||||
await expect(bus.publish(evt, { id: "x" })).rejects.toThrow("first failure");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("delivers nothing when no handlers are registered", async () => {
|
|
||||||
const bus = new InMemoryEventBus();
|
|
||||||
await expect(bus.publish(evt, { id: "x" })).resolves.toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import type { z } from "zod";
|
|
||||||
import type { EventDescriptor } from "./event-descriptor";
|
|
||||||
import type { EventHandler, IEventBus } from "./event-bus.interface";
|
|
||||||
|
|
||||||
export type InMemoryEventBusOptions = {
|
|
||||||
/** When true, rethrow the first handler error (default: false — errors swallowed). */
|
|
||||||
failFast?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class InMemoryEventBus implements IEventBus {
|
|
||||||
private readonly handlers = new Map<string, EventHandler<unknown>[]>();
|
|
||||||
|
|
||||||
constructor(private readonly options: InMemoryEventBusOptions = {}) {}
|
|
||||||
|
|
||||||
async publish<T>(
|
|
||||||
descriptor: EventDescriptor<string, z.ZodType<T>>,
|
|
||||||
payload: T,
|
|
||||||
): Promise<void> {
|
|
||||||
descriptor.schema.parse(payload);
|
|
||||||
const subscribers = this.handlers.get(descriptor.name) ?? [];
|
|
||||||
if (subscribers.length === 0) return;
|
|
||||||
const settled = await Promise.allSettled(
|
|
||||||
subscribers.map((h) => h(payload)),
|
|
||||||
);
|
|
||||||
if (this.options.failFast) {
|
|
||||||
const failure = settled.find((s) => s.status === "rejected");
|
|
||||||
// Only the first rejection is rethrown. Other failures are intentionally
|
|
||||||
// dropped — `failFast` is a test-affordance, not a fault-tolerance design.
|
|
||||||
if (failure && failure.status === "rejected") throw failure.reason;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe<T>(
|
|
||||||
descriptor: EventDescriptor<string, z.ZodType<T>>,
|
|
||||||
_consumerFeature: string,
|
|
||||||
handler: EventHandler<T>,
|
|
||||||
): void {
|
|
||||||
const arr = this.handlers.get(descriptor.name) ?? [];
|
|
||||||
arr.push(handler as EventHandler<unknown>);
|
|
||||||
this.handlers.set(descriptor.name, arr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export type { EventDescriptor } from "./event-descriptor";
|
|
||||||
export { defineEvent } from "./event-descriptor";
|
|
||||||
export type { IEventBus, EventHandler } from "./event-bus.interface";
|
|
||||||
export { CORE_EVENTS_SYMBOLS } from "./symbols";
|
|
||||||
export { InMemoryEventBus, type InMemoryEventBusOptions } from "./in-memory-event-bus";
|
|
||||||
export { PayloadJobsEventBus } from "./payload-jobs-event-bus";
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { describe, it, expect, vi } from "vitest";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { defineEvent } from "@/event-descriptor";
|
|
||||||
import { PayloadJobsEventBus } from "@/payload-jobs-event-bus";
|
|
||||||
import type { IJobQueue } from "@repo/core-shared/jobs";
|
|
||||||
|
|
||||||
const evt = defineEvent("auth.user.signed-up", z.object({ userId: z.string() }).strict());
|
|
||||||
|
|
||||||
function recordingQueue(): IJobQueue & { enqueued: { taskSlug: string; input: unknown }[] } {
|
|
||||||
const enqueued: { taskSlug: string; input: unknown }[] = [];
|
|
||||||
const q: IJobQueue = {
|
|
||||||
async enqueue(taskSlug, input) {
|
|
||||||
enqueued.push({ taskSlug, input });
|
|
||||||
return { jobId: `recording-${enqueued.length}` };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return Object.assign(q, { enqueued });
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("PayloadJobsEventBus", () => {
|
|
||||||
it("validates the payload before enqueueing", async () => {
|
|
||||||
const queue = recordingQueue();
|
|
||||||
const bus = new PayloadJobsEventBus(queue);
|
|
||||||
bus.subscribe(evt, "marketing-pages", vi.fn());
|
|
||||||
await expect(
|
|
||||||
bus.publish(evt, { userId: 42 } as unknown as { userId: string }),
|
|
||||||
).rejects.toThrow();
|
|
||||||
expect(queue.enqueued).toHaveLength(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("enqueues one task per subscriber, naming `__events.<event>.<consumer>`", async () => {
|
|
||||||
const queue = recordingQueue();
|
|
||||||
const bus = new PayloadJobsEventBus(queue);
|
|
||||||
bus.subscribe(evt, "marketing-pages", vi.fn());
|
|
||||||
bus.subscribe(evt, "blog", vi.fn());
|
|
||||||
await bus.publish(evt, { userId: "u1" });
|
|
||||||
expect(queue.enqueued).toHaveLength(2);
|
|
||||||
expect(queue.enqueued.map((e) => e.taskSlug).sort()).toEqual([
|
|
||||||
"__events.auth.user.signed-up.blog",
|
|
||||||
"__events.auth.user.signed-up.marketing-pages",
|
|
||||||
]);
|
|
||||||
expect(queue.enqueued[0]!.input).toEqual({ userId: "u1" });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("enqueues nothing when no subscribers are registered", async () => {
|
|
||||||
const queue = recordingQueue();
|
|
||||||
const bus = new PayloadJobsEventBus(queue);
|
|
||||||
await bus.publish(evt, { userId: "u1" });
|
|
||||||
expect(queue.enqueued).toHaveLength(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import type { z } from "zod";
|
|
||||||
import type { IJobQueue } from "@repo/core-shared/jobs";
|
|
||||||
import type { EventDescriptor } from "./event-descriptor";
|
|
||||||
import type { EventHandler, IEventBus } from "./event-bus.interface";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Production-grade bus: for each subscriber, enqueues one Payload task per
|
|
||||||
* `publish()` call. Subscribers register with their consumer-feature name so
|
|
||||||
* fan-out tasks are named deterministically: `__events.<event>.<consumer>`.
|
|
||||||
* The actual handler invocation happens inside Payload's job runner — see the
|
|
||||||
* matching task config generated by `gen event consume` (Task 39).
|
|
||||||
*/
|
|
||||||
export class PayloadJobsEventBus implements IEventBus {
|
|
||||||
private readonly subscribers = new Map<string, string[]>();
|
|
||||||
|
|
||||||
constructor(private readonly queue: IJobQueue) {}
|
|
||||||
|
|
||||||
async publish<T>(
|
|
||||||
descriptor: EventDescriptor<string, z.ZodType<T>>,
|
|
||||||
payload: T,
|
|
||||||
): Promise<void> {
|
|
||||||
descriptor.schema.parse(payload);
|
|
||||||
const consumers = this.subscribers.get(descriptor.name) ?? [];
|
|
||||||
await Promise.all(
|
|
||||||
consumers.map((consumerFeature) =>
|
|
||||||
this.queue.enqueue(
|
|
||||||
`__events.${descriptor.name}.${consumerFeature}`,
|
|
||||||
payload,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe<T>(
|
|
||||||
descriptor: EventDescriptor<string, z.ZodType<T>>,
|
|
||||||
consumerFeature: string,
|
|
||||||
_handler: EventHandler<T>,
|
|
||||||
): void {
|
|
||||||
const arr = this.subscribers.get(descriptor.name) ?? [];
|
|
||||||
if (!arr.includes(consumerFeature)) arr.push(consumerFeature);
|
|
||||||
this.subscribers.set(descriptor.name, arr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export const CORE_EVENTS_SYMBOLS = {
|
|
||||||
IEventBus: Symbol.for("@repo/core-events/IEventBus"),
|
|
||||||
} as const;
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@repo/core-typescript/base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "dist",
|
|
||||||
"rootDir": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["src/**/*"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["//"],
|
|
||||||
"tags": ["core"]
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import path from "node:path";
|
|
||||||
import { mergeConfig } from "vitest/config";
|
|
||||||
import { nodeVitestConfig } from "@repo/core-typescript/vitest.base.node";
|
|
||||||
|
|
||||||
export default mergeConfig(nodeVitestConfig, {
|
|
||||||
resolve: {
|
|
||||||
alias: { "@": path.resolve(__dirname, "./src") },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -23,7 +23,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@repo/auth": "workspace:*",
|
"@repo/auth": "workspace:*",
|
||||||
"@repo/core-events": "workspace:*",
|
|
||||||
"@repo/core-shared": "workspace:*",
|
"@repo/core-shared": "workspace:*",
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.0.0",
|
||||||
"inversify": "^6.2.0",
|
"inversify": "^6.2.0",
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@repo/core-events": "workspace:*",
|
|
||||||
"@repo/core-shared": "workspace:*",
|
"@repo/core-shared": "workspace:*",
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.0.0",
|
||||||
"inversify": "^6.2.0",
|
"inversify": "^6.2.0",
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@repo/core-events": "workspace:*",
|
|
||||||
"@repo/core-shared": "workspace:*",
|
"@repo/core-shared": "workspace:*",
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.0.0",
|
||||||
"inversify": "^6.2.0",
|
"inversify": "^6.2.0",
|
||||||
|
|||||||
46
pnpm-lock.yaml
generated
46
pnpm-lock.yaml
generated
@@ -154,9 +154,6 @@ importers:
|
|||||||
'@repo/core-cms':
|
'@repo/core-cms':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/core-cms
|
version: link:../../packages/core-cms
|
||||||
'@repo/core-events':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../../packages/core-events
|
|
||||||
'@repo/core-shared':
|
'@repo/core-shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/core-shared
|
version: link:../../packages/core-shared
|
||||||
@@ -330,9 +327,6 @@ importers:
|
|||||||
|
|
||||||
packages/auth:
|
packages/auth:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@repo/core-events':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../core-events
|
|
||||||
'@repo/core-shared':
|
'@repo/core-shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../core-shared
|
version: link:../core-shared
|
||||||
@@ -373,9 +367,6 @@ importers:
|
|||||||
|
|
||||||
packages/blog:
|
packages/blog:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@repo/core-events':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../core-events
|
|
||||||
'@repo/core-shared':
|
'@repo/core-shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../core-shared
|
version: link:../core-shared
|
||||||
@@ -531,34 +522,6 @@ importers:
|
|||||||
specifier: ^3.1.0
|
specifier: ^3.1.0
|
||||||
version: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.2)(tsx@4.21.0)
|
version: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.2)(tsx@4.21.0)
|
||||||
|
|
||||||
packages/core-events:
|
|
||||||
dependencies:
|
|
||||||
'@repo/core-shared':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../core-shared
|
|
||||||
payload:
|
|
||||||
specifier: ^3.0.0
|
|
||||||
version: 3.81.0(graphql@16.13.2)(typescript@5.9.3)
|
|
||||||
zod:
|
|
||||||
specifier: ^3.23.0
|
|
||||||
version: 3.25.76
|
|
||||||
devDependencies:
|
|
||||||
'@repo/core-eslint':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../core-eslint
|
|
||||||
'@repo/core-testing':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../core-testing
|
|
||||||
'@repo/core-typescript':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../core-typescript
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.8.0
|
|
||||||
version: 5.9.3
|
|
||||||
vitest:
|
|
||||||
specifier: ^3.0.0
|
|
||||||
version: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.2)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.2)(tsx@4.21.0)
|
|
||||||
|
|
||||||
packages/core-shared:
|
packages/core-shared:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sentry/nextjs':
|
'@sentry/nextjs':
|
||||||
@@ -775,9 +738,6 @@ importers:
|
|||||||
'@repo/auth':
|
'@repo/auth':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../auth
|
version: link:../auth
|
||||||
'@repo/core-events':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../core-events
|
|
||||||
'@repo/core-shared':
|
'@repo/core-shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../core-shared
|
version: link:../core-shared
|
||||||
@@ -818,9 +778,6 @@ importers:
|
|||||||
|
|
||||||
packages/media:
|
packages/media:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@repo/core-events':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../core-events
|
|
||||||
'@repo/core-shared':
|
'@repo/core-shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../core-shared
|
version: link:../core-shared
|
||||||
@@ -858,9 +815,6 @@ importers:
|
|||||||
|
|
||||||
packages/navigation:
|
packages/navigation:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@repo/core-events':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../core-events
|
|
||||||
'@repo/core-shared':
|
'@repo/core-shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../core-shared
|
version: link:../core-shared
|
||||||
|
|||||||
Reference in New Issue
Block a user