1/**
2 * The client half of a provider package.
3 *
4 * Every provider package exports a `ProviderClientDefinition` from its
5 * `./client` entry point. Clients render settings, labels, and badges from
6 * these definitions plus the server's `ServerProvider` snapshots, so a new
7 * provider needs no per-driver branches in web or mobile.
8 *
9 * Definitions are browser- and React Native-safe: plain data and Effect
10 * schemas, no platform UI imports.
11 *
12 * @module provider-core/client
13 */
14import type { ProviderDriverKind } from "@t3tools/contracts";
15import type * as Schema from "effect/Schema";
16
17/**
18 * A provider's instance settings schema. Fields carry `providerSettingsForm`
19 * annotations, which the settings form renders generically.
20 */
21export type ProviderSettingsSchema = {
22 readonly fields: Readonly<Record<string, Schema.Top>>;
23} & Schema.Top;
24
25/** An environment variable the settings UI offers for every instance of a driver. */
26export interface ProviderEnvironmentField {
27 readonly name: string;
28 readonly label: string;
29 readonly description?: string;
30 readonly placeholder?: string;
31 readonly sensitive?: boolean;
32}
33
34/**
35 * A provider glyph as plain data, so web and mobile can each draw it with
36 * their own SVG renderer. Colors are per theme; a path without a fill or
37 * gradient uses the icon's `fill`.
38 */
39export interface ProviderIcon {
40 readonly viewBox: string;
41 readonly fill: { readonly light: string; readonly dark: string };
42 readonly paths: ReadonlyArray<{
43 readonly d: string;
44 readonly fillRule?: "evenodd" | "nonzero";
45 readonly fill?: { readonly light: string; readonly dark: string };
46 /** A brand gradient, the same in both themes. Coordinates are in viewBox units. */
47 readonly gradient?: ProviderIconGradient;
48 }>;
49}
50
51export interface ProviderIconGradient {
52 readonly x1: number;
53 readonly y1: number;
54 readonly x2: number;
55 readonly y2: number;
56 readonly stops: ReadonlyArray<{ readonly offset: number; readonly color: string }>;
57}
58
59export interface ProviderClientDefinition {
60 readonly driverKind: ProviderDriverKind;
61 readonly label: string;
62 /** Omitted for drivers whose glyph a client still draws itself. */
63 readonly icon?: ProviderIcon;
64 readonly settingsSchema: ProviderSettingsSchema;
65 readonly environmentFields?: ReadonlyArray<ProviderEnvironmentField>;
66 /** Whether the driver has a default instance backed by legacy `providers.<kind>` settings. */
67 readonly hasDefaultInstance?: boolean;
68 /**
69 * Short warning badge beside every instance of the driver, such as "Beta".
70 * It marks the driver kind, so built-in and custom instances show it alike.
71 */
72 readonly badgeLabel?: string;
73}
74
75export function defineProviderClient<const Definition extends ProviderClientDefinition>(
76 definition: Definition,
77): Definition {
78 return definition;
79}
80
81/** The provider definitions a client loaded, in presentation order. */
82export interface ProviderClientRegistry {
83 readonly definitions: ReadonlyArray<ProviderClientDefinition>;
84 /** `undefined` for drivers this client does not ship, such as a fork's driver. */
85 readonly get: (
86 driverKind: ProviderDriverKind | undefined,
87 ) => ProviderClientDefinition | undefined;
88}
89
90export function makeProviderClientRegistry(
91 definitions: ReadonlyArray<ProviderClientDefinition>,
92): ProviderClientRegistry {
93 const byDriverKind = new Map<ProviderDriverKind, ProviderClientDefinition>();
94 for (const definition of definitions) {
95 if (byDriverKind.has(definition.driverKind)) {
96 throw new Error(`Provider driver '${definition.driverKind}' is defined more than once.`);
97 }
98 byDriverKind.set(definition.driverKind, definition);
99 }
100 return {
101 definitions,
102 get: (driverKind) => (driverKind === undefined ? undefined : byDriverKind.get(driverKind)),
103 };
104}