import type { AuthUser, MockServerConfig, OnMessageHook, ResourceItem } from '../types.ts'; const NAMED = [ 'tables', 'views', 'queries', 'requestReplies', 'eventHandlers', 'eventSchemas', ] as const; function mergeUsers(a: AuthUser[] = [], b: AuthUser[] = []): AuthUser[] { const users = new Map(a.map((user) => [user.username, user])); for (const user of b) users.set(user.username, user); return [...users.values()]; } function mergeResources(a: ResourceItem[] = [], b: ResourceItem[] = []): ResourceItem[] { const resources = new Map(); for (const resource of [...a, ...b]) { resources.set(`${resource.RESOURCE_TYPE}:${resource.RESOURCE_NAME}`, resource); } return [...resources.values()]; } // Both hooks, in order: the second sees what the first returned, and a // message the first handled (false) goes no further. function chainHooks(first: OnMessageHook, second: OnMessageHook): OnMessageHook { return (message, ctx) => { const hooked = first(message, ctx); if (hooked === false) return false; return second(hooked ?? message, ctx) ?? hooked; }; } function mergeTwo(base: MockServerConfig, over: MockServerConfig): MockServerConfig { const merged: MockServerConfig = { ...base }; for (const [key, value] of Object.entries(over)) { if (value !== undefined) (merged as Record)[key] = value; } for (const key of NAMED) { if (base[key] && over[key]) merged[key] = { ...base[key], ...over[key] } as never; } if (base.auth && over.auth) { merged.auth = { ...base.auth, ...over.auth, users: mergeUsers(base.auth.users, over.auth.users), }; } if (base.additionalResources && over.additionalResources) { merged.additionalResources = mergeResources(base.additionalResources, over.additionalResources); } // The later config's routes are tried first. if (base.httpRoutes && over.httpRoutes) { merged.httpRoutes = [...over.httpRoutes, ...base.httpRoutes]; } if (base.onMessage && over.onMessage) { merged.onMessage = chainHooks(base.onMessage, over.onMessage); } return merged; } /** * Merges mock-server configs into one, later configs over earlier ones: * `createMockServer(mergeConfigs(platformPreset(), appConfig))`. * * - tables, views, queries, requestReplies, eventHandlers, eventSchemas: by * name, a later entry replacing an earlier one whole. * - auth.users: by username, a later user replacing an earlier one; the other * auth settings, setting by setting. * - additionalResources: both lists, without repeats. * - httpRoutes: both, the later config's tried first. onMessage: both, in order. * - Anything else (resources, fidelity, defaultEventHandler, productName, ...): * the later config's, when it sets one. */ export function mergeConfigs(...configs: MockServerConfig[]): MockServerConfig { return configs.reduce((merged, config) => mergeTwo(merged, config), {}); }