/** * Channel orchestration for dsh-connect: a small, side-effect-free selector * that activates a requested subset of channel adapters. Kept separate from the * cordis entry so it is unit-testable without pulling any channel SDK. * @module dsh-connect/settings/channels */ /** Channel adapters the connect plugin can activate. */ export type ChannelName = "feishu" | "telegram" | "dingtalk" | "web"; /** Config for the connect plugin: which channels + their per-channel config. */ export interface ChannelsConfig { /** Directory holding `bindings.json` / `reminders.json` / the settings document. */ stateDir?: string; /** Optional path to persist web-settings edits (non-secret). */ settingsStatePath?: string; /** Keys applied to every channel that doesn't set its own (e.g. a shared `language`). */ channelDefaults?: Record; /** Channel names to activate; default: all built-in channels. */ channels?: ChannelName[]; feishu?: Record; telegram?: Record; dingtalk?: Record; web?: Record; } /** The only slice of a context the selector touches: a `logger.warn`. */ export interface LoggerLike { logger?: { warn?: (...args: unknown[]) => void; } | undefined; } /** A channel plugin's `apply(ctx, config)`, loosely typed for the selector. */ export type ChannelApply = (ctx: Ctx, config: any) => void; /** Built-in channels, in a stable order (for docs and the default selector). */ export declare const CHANNELS: readonly ChannelName[]; /** * Activate the requested adapters into `ctx` (the `connect` service is * expected to be present via the caller's `inject`). `channels` maps a channel * name to its plugin `apply`. Unknown names are skipped with a warning; a * channel whose `apply` throws is caught so one broken adapter never takes the * others down. * * @returns the channel names that were started. */ export declare function activateChannels(ctx: Ctx, config: ChannelsConfig | null | undefined, channels: Record>): string[]; /** * Where a channel secret actually lives in the channel config: its dotted path, * or the config key itself when it sits at the root. * * Exported because the mapping is otherwise invisible outside this module — a * credential table says `clientId`, the schema says `stream.clientId`, and only * this map knows they are the same key. The completeness test needs exactly that * translation to check every credential against the schema's `role("secret")` * leaves, and re-deriving it there would be a second copy of the map free to * drift from this one. */ export declare function secretConfigPath(channel: ChannelName, configKey: string): string; /** * Read a value at a dotted path (e.g. `"stream.clientId"`), undefined if absent. * * Exported for the settings model: a channel field table addresses nested config * keys the same way this module addresses nested secret keys (`NESTED_SECRET_KEYS`), * so both sides share one implementation rather than growing a second one. */ export declare function readDotPath(obj: Record, path: string): unknown; /** * Write `value` at a dotted path, creating the intermediate objects a missing * segment needs (so `"stream.url"` can be set on a config that has no `stream` * yet). Mutates and returns `target`; the caller owns cloning when it must not * touch its input. * * A path that runs *through* a non-object (a scalar where a branch was * expected) is left untouched rather than overwritten: clobbering a scalar the * user set by hand to make room for a nested key would destroy data to store * data. */ export declare function writeDotPath(target: Record, path: string, value: unknown): Record; /** * Return a copy of `target` with the value at a dotted path removed. A path that * is not there comes back as an unchanged copy. * * Non-mutating, unlike its `readDotPath`/`writeDotPath` siblings, and * deliberately so: its caller is the snapshot's secret filter, which starts from * a *shallow* copy of the live config. A mutating delete would reach through * that copy into the running config and strip the credential the adapter is * still using. Only the objects along the path are cloned; the rest is shared by * reference, which is sound because nothing else is written. * * A branch left empty by removing its last key is kept (`stream: {}`): it is * inert, and pruning would make this helper guess whether the user's own empty * object was meant to be there. */ export declare function withoutDotPath(target: Record, path: string): Record; /** * Extract the build-time secret values (configKey → value) that a channel config * carries for a given secret-key map, reading each key at its *actual* config * location (flat at the channel root, or nested per `NESTED_SECRET_KEYS` — e.g. * dingtalk's `stream.clientId`). Only non-empty string values are returned, so it * doubles as the source for migrating config-file secrets into the credential * store on boot. Read-only: never mutates `config`. */ export declare function extractConfigSecrets(config: Record | undefined, channel: ChannelName, secretKeys: Record): Record; /** * Return a shallow-cloned config whose enabled channels are enriched with the * secrets returned by `getSecrets` (stored secrets win over channel config). * Used by `apply` to feed store-backed credentials into each adapter while * leaving the caller's config object untouched. Secrets are placed at the slot * each adapter expects — flat keys at the channel root, nested keys (dingtalk's * `stream.clientId`/`stream.clientSecret`) under their sub-object. */ export declare function injectSecrets(config: ChannelsConfig, enabled: readonly ChannelName[], getSecrets: (channel: ChannelName) => Promise>): Promise; //# sourceMappingURL=channels.d.ts.map