wip: media composition root
This commit is contained in:
parent
2973f5074b
commit
d88e0de1bc
@ -245,3 +245,6 @@ EVOO_COCKPIT_CORS_ORIGIN=
|
|||||||
# MEDIA_WORKER_TOKEN=REPLACE_WITH_RANDOM_TOKEN
|
# MEDIA_WORKER_TOKEN=REPLACE_WITH_RANDOM_TOKEN
|
||||||
# Private platform profiles that override the shipped ones. Keep outside the repo.
|
# Private platform profiles that override the shipped ones. Keep outside the repo.
|
||||||
# MEDIA_PROFILES_DIR=~/.evoo/media-profiles
|
# MEDIA_PROFILES_DIR=~/.evoo/media-profiles
|
||||||
|
# Media jobs per worker process: model inference (GPU) and ffmpeg work (CPU).
|
||||||
|
# MEDIA_GPU_CONCURRENCY=1
|
||||||
|
# MEDIA_CPU_CONCURRENCY=2
|
||||||
|
|||||||
@ -27,6 +27,8 @@ import {
|
|||||||
createJobReconciler,
|
createJobReconciler,
|
||||||
createMessagingContextLoader,
|
createMessagingContextLoader,
|
||||||
createMessagingTeamChatSender,
|
createMessagingTeamChatSender,
|
||||||
|
createMediaJobHandlers,
|
||||||
|
createMediaServices,
|
||||||
createProfileConsolidationScheduler,
|
createProfileConsolidationScheduler,
|
||||||
createRunExecutor,
|
createRunExecutor,
|
||||||
createRunSandbox,
|
createRunSandbox,
|
||||||
@ -110,6 +112,7 @@ import {
|
|||||||
TEAM_CHAT_STARTUP_SHUTDOWN_MS,
|
TEAM_CHAT_STARTUP_SHUTDOWN_MS,
|
||||||
} from "./team-chat-startup.js";
|
} from "./team-chat-startup.js";
|
||||||
import { mountTradingBotStatus } from "./trading-bot-proxy.js";
|
import { mountTradingBotStatus } from "./trading-bot-proxy.js";
|
||||||
|
import { mountMediaHttpRoutes } from "./media-http.js";
|
||||||
import { mountVoiceHttpRoutes } from "./voice.js";
|
import { mountVoiceHttpRoutes } from "./voice.js";
|
||||||
import { mountWebhookHttpRoutes } from "./webhook.js";
|
import { mountWebhookHttpRoutes } from "./webhook.js";
|
||||||
|
|
||||||
@ -414,6 +417,18 @@ export async function createApp(
|
|||||||
shutdownSignal: shutdown.signal,
|
shutdownSignal: shutdown.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mediaServices = createMediaServices({
|
||||||
|
config: env.media,
|
||||||
|
prisma,
|
||||||
|
jobs,
|
||||||
|
artifacts,
|
||||||
|
runtime,
|
||||||
|
realtime,
|
||||||
|
resolveModel: (scope) => executor.resolveModel(scope),
|
||||||
|
});
|
||||||
|
const mediaJobs = mediaServices
|
||||||
|
? createMediaJobHandlers(mediaServices, { signal: shutdown.signal })
|
||||||
|
: null;
|
||||||
const jobHandlers = createBackgroundJobHandlers({
|
const jobHandlers = createBackgroundJobHandlers({
|
||||||
executor,
|
executor,
|
||||||
prisma,
|
prisma,
|
||||||
@ -421,6 +436,7 @@ export async function createApp(
|
|||||||
home,
|
home,
|
||||||
jobs,
|
jobs,
|
||||||
events,
|
events,
|
||||||
|
...(mediaJobs ? { media: mediaJobs.handlers } : {}),
|
||||||
workerId: "api",
|
workerId: "api",
|
||||||
runtime,
|
runtime,
|
||||||
secretStore: secrets,
|
secretStore: secrets,
|
||||||
@ -438,6 +454,9 @@ export async function createApp(
|
|||||||
});
|
});
|
||||||
await graphileWorkerHost.start(jobHandlers);
|
await graphileWorkerHost.start(jobHandlers);
|
||||||
}
|
}
|
||||||
|
if (mediaJobs) {
|
||||||
|
void mediaJobs.resume().catch((error) => getLogger().error("media ingest resume failed", error));
|
||||||
|
}
|
||||||
const reconciler = inMemoryJobs
|
const reconciler = inMemoryJobs
|
||||||
? createJobReconciler({
|
? createJobReconciler({
|
||||||
prisma,
|
prisma,
|
||||||
@ -469,6 +488,7 @@ export async function createApp(
|
|||||||
remoteConnectors,
|
remoteConnectors,
|
||||||
artifacts,
|
artifacts,
|
||||||
dataDir: env.dataDir,
|
dataDir: env.dataDir,
|
||||||
|
mediaServices,
|
||||||
messaging: {
|
messaging: {
|
||||||
enabled: Boolean(messaging),
|
enabled: Boolean(messaging),
|
||||||
providers: messaging?.platforms().map((platform) => platform.provider) ?? [],
|
providers: messaging?.platforms().map((platform) => platform.provider) ?? [],
|
||||||
@ -583,6 +603,13 @@ export async function createApp(
|
|||||||
if (actor) enrichLogContext({ "user.id": actor.userId, "space.id": actor.spaceId });
|
if (actor) enrichLogContext({ "user.id": actor.userId, "space.id": actor.spaceId });
|
||||||
return actor;
|
return actor;
|
||||||
});
|
});
|
||||||
|
mountMediaHttpRoutes(app, { services: mediaServices }, async (c) => {
|
||||||
|
const session = await auth.api.getSession({ headers: sessionHeaders(c.req.raw) });
|
||||||
|
if (!session?.user) return null;
|
||||||
|
return requireMembership(prisma, session.user.id, c.req.header("x-rakazo-space-id")).catch(
|
||||||
|
() => null,
|
||||||
|
);
|
||||||
|
});
|
||||||
mountWebhookHttpRoutes(app, { prisma, secrets, events, jobs });
|
mountWebhookHttpRoutes(app, { prisma, secrets, events, jobs });
|
||||||
// Shared with stop so a shutdown during retry delays does not restart polling.
|
// Shared with stop so a shutdown during retry delays does not restart polling.
|
||||||
let messagingStopped = false;
|
let messagingStopped = false;
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
|
import type { MediaConfig } from "@rakazo/adapters";
|
||||||
import {
|
import {
|
||||||
|
mediaConfigFromEnv,
|
||||||
resolveCloudAgentProvider,
|
resolveCloudAgentProvider,
|
||||||
resolveDeploymentModel,
|
resolveDeploymentModel,
|
||||||
resolveSandboxProvider,
|
resolveSandboxProvider,
|
||||||
@ -15,6 +17,8 @@ export { resolveCloudAgentProvider, resolveSandboxProvider } from "@rakazo/adapt
|
|||||||
|
|
||||||
export interface AppEnv {
|
export interface AppEnv {
|
||||||
nodeEnv: string;
|
nodeEnv: string;
|
||||||
|
/** Media Studio; disabled unless a sidecar (or the scripted emulator) is configured. */
|
||||||
|
media: MediaConfig;
|
||||||
desktopStackToken?: string;
|
desktopStackToken?: string;
|
||||||
databaseUrl: string;
|
databaseUrl: string;
|
||||||
realtimeDatabaseUrl: string;
|
realtimeDatabaseUrl: string;
|
||||||
@ -106,6 +110,7 @@ export function loadEnv(source: NodeJS.ProcessEnv = process.env): AppEnv {
|
|||||||
const updaterToken = optional(source.RAKAZO_UPDATER_TOKEN);
|
const updaterToken = optional(source.RAKAZO_UPDATER_TOKEN);
|
||||||
return {
|
return {
|
||||||
nodeEnv: source.NODE_ENV ?? "",
|
nodeEnv: source.NODE_ENV ?? "",
|
||||||
|
media: mediaConfigFromEnv(source),
|
||||||
databaseUrl: required(source, "DATABASE_URL"),
|
databaseUrl: required(source, "DATABASE_URL"),
|
||||||
realtimeDatabaseUrl: source.REALTIME_DATABASE_URL ?? required(source, "DATABASE_URL"),
|
realtimeDatabaseUrl: source.REALTIME_DATABASE_URL ?? required(source, "DATABASE_URL"),
|
||||||
desktopStackToken: optional(source.RAKAZO_DESKTOP_STACK_TOKEN),
|
desktopStackToken: optional(source.RAKAZO_DESKTOP_STACK_TOKEN),
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import type {
|
|||||||
ConnectorRegistry,
|
ConnectorRegistry,
|
||||||
EncryptedSecretStore,
|
EncryptedSecretStore,
|
||||||
IntegrationProviderSettings,
|
IntegrationProviderSettings,
|
||||||
|
MediaServices,
|
||||||
MemoryProviderResolver,
|
MemoryProviderResolver,
|
||||||
PiOAuthLogins,
|
PiOAuthLogins,
|
||||||
RemoteConnectorDependencies,
|
RemoteConnectorDependencies,
|
||||||
@ -464,6 +465,8 @@ export interface RouterDeps {
|
|||||||
remoteConnectors?: RemoteConnectorDependencies;
|
remoteConnectors?: RemoteConnectorDependencies;
|
||||||
artifacts: ArtifactStore;
|
artifacts: ArtifactStore;
|
||||||
dataDir: string;
|
dataDir: string;
|
||||||
|
/** Null when no media sidecar (or scripted emulator) is configured; Media Studio then stays hidden. */
|
||||||
|
mediaServices?: MediaServices | null;
|
||||||
/** Present when the external messaging surface is enabled. */
|
/** Present when the external messaging surface is enabled. */
|
||||||
messaging?: { enabled: boolean; providers: string[]; openSignup: boolean };
|
messaging?: { enabled: boolean; providers: string[]; openSignup: boolean };
|
||||||
env: {
|
env: {
|
||||||
@ -527,7 +530,11 @@ export function createRouter(deps: RouterDeps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return os.router({
|
return os.router({
|
||||||
media: createMediaRouter({ prisma: deps.prisma, dataDir: deps.dataDir }),
|
media: createMediaRouter({
|
||||||
|
prisma: deps.prisma,
|
||||||
|
services: deps.mediaServices ?? null,
|
||||||
|
dataDir: deps.dataDir,
|
||||||
|
}),
|
||||||
aiConsent: {
|
aiConsent: {
|
||||||
status: authed.aiConsent.status.handler(({ context, input }) =>
|
status: authed.aiConsent.status.handler(({ context, input }) =>
|
||||||
aiConsentStatus(deps, context.actor, input),
|
aiConsentStatus(deps, context.actor, input),
|
||||||
|
|||||||
@ -12,6 +12,8 @@ import {
|
|||||||
createJobReconciler,
|
createJobReconciler,
|
||||||
createMessagingContextLoader,
|
createMessagingContextLoader,
|
||||||
createPostgresReconciliationLeadership,
|
createPostgresReconciliationLeadership,
|
||||||
|
createMediaJobHandlers,
|
||||||
|
createMediaServices,
|
||||||
createProfileConsolidationScheduler,
|
createProfileConsolidationScheduler,
|
||||||
createRunExecutor,
|
createRunExecutor,
|
||||||
createRunSandbox,
|
createRunSandbox,
|
||||||
@ -29,6 +31,7 @@ import {
|
|||||||
isPipedreamEnabled,
|
isPipedreamEnabled,
|
||||||
LocalAgentHomeStore,
|
LocalAgentHomeStore,
|
||||||
LocalArtifactStore,
|
LocalArtifactStore,
|
||||||
|
mediaConfigFromEnv,
|
||||||
McpConnector,
|
McpConnector,
|
||||||
McpOAuthBroker,
|
McpOAuthBroker,
|
||||||
messagingEnvFromProcess,
|
messagingEnvFromProcess,
|
||||||
@ -201,6 +204,25 @@ async function main() {
|
|||||||
cloudAgent,
|
cloudAgent,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mediaAbort = new AbortController();
|
||||||
|
const mediaServices = createMediaServices({
|
||||||
|
config: mediaConfigFromEnv(process.env),
|
||||||
|
prisma,
|
||||||
|
jobs,
|
||||||
|
artifacts,
|
||||||
|
runtime,
|
||||||
|
realtime,
|
||||||
|
resolveModel: (scope) => executor.resolveModel(scope),
|
||||||
|
});
|
||||||
|
const mediaJobs = mediaServices
|
||||||
|
? createMediaJobHandlers(mediaServices, {
|
||||||
|
signal: mediaAbort.signal,
|
||||||
|
limits: {
|
||||||
|
gpu: parsePositiveInteger(process.env.MEDIA_GPU_CONCURRENCY, 1),
|
||||||
|
cpu: parsePositiveInteger(process.env.MEDIA_CPU_CONCURRENCY, 2),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: null;
|
||||||
const jobHandlers = createBackgroundJobHandlers({
|
const jobHandlers = createBackgroundJobHandlers({
|
||||||
executor,
|
executor,
|
||||||
prisma,
|
prisma,
|
||||||
@ -208,6 +230,7 @@ async function main() {
|
|||||||
home,
|
home,
|
||||||
jobs,
|
jobs,
|
||||||
events,
|
events,
|
||||||
|
...(mediaJobs ? { media: mediaJobs.handlers } : {}),
|
||||||
workerId: process.pid.toString(),
|
workerId: process.pid.toString(),
|
||||||
runtime,
|
runtime,
|
||||||
secretStore: secrets,
|
secretStore: secrets,
|
||||||
@ -235,6 +258,9 @@ async function main() {
|
|||||||
await new Promise((resolve) => setTimeout(resolve, databaseCapacityBackoffMs(attempt)));
|
await new Promise((resolve) => setTimeout(resolve, databaseCapacityBackoffMs(attempt)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mediaJobs) {
|
||||||
|
void mediaJobs.resume().catch((error) => logger.error("media ingest resume failed", error));
|
||||||
|
}
|
||||||
const reconciler = createJobReconciler({
|
const reconciler = createJobReconciler({
|
||||||
prisma,
|
prisma,
|
||||||
jobs,
|
jobs,
|
||||||
@ -253,6 +279,7 @@ async function main() {
|
|||||||
stopping = true;
|
stopping = true;
|
||||||
try {
|
try {
|
||||||
await reconciler.stop();
|
await reconciler.stop();
|
||||||
|
mediaAbort.abort();
|
||||||
await jobHost.stop();
|
await jobHost.stop();
|
||||||
await jobs.close();
|
await jobs.close();
|
||||||
await realtime.close();
|
await realtime.close();
|
||||||
|
|||||||
@ -85,6 +85,8 @@ export * from "./media-factory.js";
|
|||||||
export * from "./media-ffmpeg-renderer.js";
|
export * from "./media-ffmpeg-renderer.js";
|
||||||
export * from "./media-folder-source.js";
|
export * from "./media-folder-source.js";
|
||||||
export * from "./media-folder-source-probe.js";
|
export * from "./media-folder-source-probe.js";
|
||||||
|
export * from "./media-ingest.js";
|
||||||
|
export { defaultMediaJobLimits, MediaJobLimiter, type MediaJobLimits } from "./media-jobs.js";
|
||||||
export * from "./media-marketing.js";
|
export * from "./media-marketing.js";
|
||||||
export * from "./media-marketing-copy.js";
|
export * from "./media-marketing-copy.js";
|
||||||
export * from "./media-marketing-files.js";
|
export * from "./media-marketing-files.js";
|
||||||
@ -103,6 +105,7 @@ export {
|
|||||||
} from "./media-motion-renderer.js";
|
} from "./media-motion-renderer.js";
|
||||||
export * from "./media-publish.js";
|
export * from "./media-publish.js";
|
||||||
export * from "./media-publisher-bundle.js";
|
export * from "./media-publisher-bundle.js";
|
||||||
|
export * from "./media-services.js";
|
||||||
export {
|
export {
|
||||||
createMediaRenderHandler,
|
createMediaRenderHandler,
|
||||||
createMediaRenderService,
|
createMediaRenderService,
|
||||||
|
|||||||
136
packages/adapters/src/media-services.ts
Normal file
136
packages/adapters/src/media-services.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import type {
|
||||||
|
AgentRunModel,
|
||||||
|
AgentRuntime,
|
||||||
|
ArtifactStore,
|
||||||
|
JobPublisher,
|
||||||
|
MediaJobHandlers,
|
||||||
|
RealtimeFanout,
|
||||||
|
} from "@rakazo/adapter-kit";
|
||||||
|
import type { ModelTier } from "@rakazo/contracts";
|
||||||
|
import type { PrismaClient } from "@rakazo/db";
|
||||||
|
import { createMediaRepos } from "@rakazo/db";
|
||||||
|
import { getLogger } from "@rakazo/logging";
|
||||||
|
import type { ProfileSet } from "@rakazo/media";
|
||||||
|
import { mergeProfiles } from "@rakazo/media";
|
||||||
|
import { BUILTIN_PROFILES_DIR, loadProfilesFromDir } from "@rakazo/media/node";
|
||||||
|
import type { MediaServiceDeps, MediaServices } from "./media-context.js";
|
||||||
|
import { createMediaModelClient, mediaTopic } from "./media-context.js";
|
||||||
|
import {
|
||||||
|
createMediaInferenceProvider,
|
||||||
|
createMediaSourceProvider,
|
||||||
|
type MediaConfig,
|
||||||
|
mediaEmulatorEnabled,
|
||||||
|
} from "./media-factory.js";
|
||||||
|
import { FfmpegMediaRenderer } from "./media-ffmpeg-renderer.js";
|
||||||
|
import {
|
||||||
|
createMediaIngestHandlers,
|
||||||
|
createMediaIngestService,
|
||||||
|
resumeMediaIngest,
|
||||||
|
} from "./media-ingest.js";
|
||||||
|
import type { MediaJobLimits } from "./media-jobs.js";
|
||||||
|
import { createMediaMarketingJobHandlers, createMediaMarketingService } from "./media-marketing.js";
|
||||||
|
import { ResvgMotionGraphicsRenderer } from "./media-motion-renderer.js";
|
||||||
|
import { createMediaPrivacyService } from "./media-privacy.js";
|
||||||
|
import type { MediaUploadReminder } from "./media-publish.js";
|
||||||
|
import { BundleMediaPublisher } from "./media-publisher-bundle.js";
|
||||||
|
import { createMediaRenderHandler, createMediaRenderService } from "./media-render.js";
|
||||||
|
|
||||||
|
const PROFILE_CACHE_MS = 60_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composition root for Media Studio, shared by the API and the worker. Returns
|
||||||
|
* null when no sidecar (or scripted emulator) is configured; media then stays
|
||||||
|
* hidden in every client.
|
||||||
|
*/
|
||||||
|
export function createMediaServices(input: {
|
||||||
|
config: MediaConfig;
|
||||||
|
prisma: PrismaClient;
|
||||||
|
jobs: JobPublisher;
|
||||||
|
artifacts: ArtifactStore;
|
||||||
|
runtime: AgentRuntime;
|
||||||
|
realtime: RealtimeFanout | null;
|
||||||
|
resolveModel: (scope: {
|
||||||
|
userId: string;
|
||||||
|
spaceId: string;
|
||||||
|
tier?: ModelTier;
|
||||||
|
}) => Promise<AgentRunModel>;
|
||||||
|
env?: NodeJS.ProcessEnv;
|
||||||
|
}): MediaServices | null {
|
||||||
|
const env = input.env ?? process.env;
|
||||||
|
if (!input.config.enabled) return null;
|
||||||
|
const inference = createMediaInferenceProvider(env);
|
||||||
|
if (!inference) return null;
|
||||||
|
const sources: MediaServiceDeps["sources"] = { folder: createMediaSourceProvider("folder", env) };
|
||||||
|
if (mediaEmulatorEnabled(env)) sources.emulator = createMediaSourceProvider("emulator", env);
|
||||||
|
|
||||||
|
let cached: { at: number; set: ProfileSet } | null = null;
|
||||||
|
const profiles = async (): Promise<ProfileSet> => {
|
||||||
|
if (cached && Date.now() - cached.at < PROFILE_CACHE_MS) return cached.set;
|
||||||
|
const [builtin, own] = await Promise.all([
|
||||||
|
loadProfilesFromDir(BUILTIN_PROFILES_DIR),
|
||||||
|
loadProfilesFromDir(input.config.profilesDir),
|
||||||
|
]);
|
||||||
|
const set = mergeProfiles(builtin.profiles, own.profiles);
|
||||||
|
set.errors = [...builtin.errors, ...own.errors];
|
||||||
|
for (const error of set.errors) getLogger().warn("media profile ignored", error);
|
||||||
|
cached = { at: Date.now(), set };
|
||||||
|
return set;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deps: MediaServiceDeps = {
|
||||||
|
prisma: input.prisma,
|
||||||
|
repos: createMediaRepos(input.prisma),
|
||||||
|
jobs: input.jobs,
|
||||||
|
inference,
|
||||||
|
renderer: new FfmpegMediaRenderer({ root: input.config.mediaRoot }),
|
||||||
|
motion: new ResvgMotionGraphicsRenderer(),
|
||||||
|
publisher: new BundleMediaPublisher(),
|
||||||
|
shortener: null,
|
||||||
|
sources,
|
||||||
|
mediaRoot: input.config.mediaRoot,
|
||||||
|
profiles,
|
||||||
|
artifacts: input.artifacts,
|
||||||
|
model: createMediaModelClient({
|
||||||
|
prisma: input.prisma,
|
||||||
|
runtime: input.runtime,
|
||||||
|
resolveModel: input.resolveModel,
|
||||||
|
}),
|
||||||
|
publish: async (spaceId, event) => {
|
||||||
|
await input.realtime?.publish(mediaTopic(spaceId), JSON.stringify(event));
|
||||||
|
},
|
||||||
|
now: () => new Date(),
|
||||||
|
};
|
||||||
|
const privacy = createMediaPrivacyService(deps);
|
||||||
|
return {
|
||||||
|
deps,
|
||||||
|
ingest: createMediaIngestService(deps),
|
||||||
|
privacy,
|
||||||
|
render: createMediaRenderService(deps),
|
||||||
|
marketing: createMediaMarketingService(deps),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every media job handler for one job host. Any process that runs a job host
|
||||||
|
* (worker, or the API in single-process mode) must register these, otherwise
|
||||||
|
* it would acknowledge media jobs without running them.
|
||||||
|
*/
|
||||||
|
export function createMediaJobHandlers(
|
||||||
|
services: MediaServices,
|
||||||
|
options: { signal?: AbortSignal; limits?: MediaJobLimits; remind?: MediaUploadReminder } = {},
|
||||||
|
): { handlers: MediaJobHandlers; resume: () => Promise<number> } {
|
||||||
|
const ingestOptions = {
|
||||||
|
...(options.signal ? { signal: options.signal } : {}),
|
||||||
|
...(options.limits ? { limits: options.limits } : {}),
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
handlers: {
|
||||||
|
...createMediaIngestHandlers(services.deps, ingestOptions),
|
||||||
|
"media.render": createMediaRenderHandler(services.deps, services.privacy),
|
||||||
|
...createMediaMarketingJobHandlers(services.deps, services.marketing, {
|
||||||
|
...(options.remind ? { remind: options.remind } : {}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
resume: () => resumeMediaIngest(services.deps, options.limits),
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -117,6 +117,12 @@ async function main() {
|
|||||||
"packages/adapters/src/cloud-agent.postgres.test.ts",
|
"packages/adapters/src/cloud-agent.postgres.test.ts",
|
||||||
"packages/adapters/src/builtin-memory-provider.postgres.test.ts",
|
"packages/adapters/src/builtin-memory-provider.postgres.test.ts",
|
||||||
"packages/adapters/src/profile-consolidation.postgres.test.ts",
|
"packages/adapters/src/profile-consolidation.postgres.test.ts",
|
||||||
|
"packages/db/src/media.postgres.test.ts",
|
||||||
|
"packages/db/src/media-isolation.postgres.test.ts",
|
||||||
|
"packages/db/src/media-production.postgres.test.ts",
|
||||||
|
"packages/adapters/src/media-ingest.postgres.test.ts",
|
||||||
|
"packages/adapters/src/media-marketing.postgres.test.ts",
|
||||||
|
"apps/api/src/media.postgres.test.ts",
|
||||||
];
|
];
|
||||||
// Each app reconciles all durable work in its database, including intentionally
|
// Each app reconciles all durable work in its database, including intentionally
|
||||||
// unfinished fixture runs. Clone the pristine migrated schema so one suite
|
// unfinished fixture runs. Clone the pristine migrated schema so one suite
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user