UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

498 lines (446 loc) 14.2 kB
/** * Static provider package metadata declared in package.json under * `beignet.provider`. * * This metadata is for docs and tooling. It must stay side-effect-free and * does not change provider setup, ordering, or runtime port merging behavior. */ export type ProviderPackagePortMetadata = { name: string; type: string; }; export type ProviderPackageRegistrationSeverity = "warning" | "hint"; export type ProviderPackageRegistrationMetadata = { required?: boolean; severity?: ProviderPackageRegistrationSeverity; tokens?: readonly string[]; }; export type ProviderPackageVariantMetadata = { name: string; displayName?: string; env?: readonly string[]; requiredEnv?: readonly string[]; requiredEnvAlternatives?: readonly (readonly string[])[]; requiredTables?: readonly string[]; registration?: ProviderPackageRegistrationMetadata; }; export type ProviderPackageMetadata = { displayName?: string; ports?: readonly string[]; appPorts?: readonly ProviderPackagePortMetadata[]; env?: readonly string[]; requiredEnv?: readonly string[]; requiredEnvAlternatives?: readonly (readonly string[])[]; requiredTables?: readonly string[]; registration?: ProviderPackageRegistrationMetadata; variants?: readonly ProviderPackageVariantMetadata[]; watchers?: readonly string[]; }; export type ProviderPackageMetadataIssue = { path: string; message: string; }; export type ProviderPackageMetadataParseResult = | { success: true; metadata: ProviderPackageMetadata } | { success: false; issues: readonly ProviderPackageMetadataIssue[] }; export function parseProviderPackageMetadata( input: unknown, ): ProviderPackageMetadataParseResult { if (!isRecord(input)) { return { success: false, issues: [ { path: "beignet.provider", message: "must be an object", }, ], }; } const issues: ProviderPackageMetadataIssue[] = []; const metadata: ProviderPackageMetadata = {}; if (input.displayName !== undefined) { if (typeof input.displayName !== "string") { issues.push({ path: "beignet.provider.displayName", message: "must be a string", }); } else { metadata.displayName = input.displayName; } } const ports = parseStringArray(input.ports, "beignet.provider.ports", issues); if (ports !== undefined) metadata.ports = ports; const env = parseStringArray(input.env, "beignet.provider.env", issues); if (env !== undefined) metadata.env = env; const requiredEnv = parseStringArray( input.requiredEnv, "beignet.provider.requiredEnv", issues, ); if (requiredEnv !== undefined) { metadata.requiredEnv = requiredEnv; validateRequiredEnvMembers( requiredEnv, env, "beignet.provider.requiredEnv", "beignet.provider.env", issues, ); } const requiredEnvAlternatives = parseStringArrayArray( input.requiredEnvAlternatives, "beignet.provider.requiredEnvAlternatives", issues, ); if (requiredEnvAlternatives !== undefined) { metadata.requiredEnvAlternatives = requiredEnvAlternatives; for (const alternative of requiredEnvAlternatives) { validateRequiredEnvMembers( alternative, env, "beignet.provider.requiredEnvAlternatives", "beignet.provider.env", issues, ); } } if ( input.requiredEnv !== undefined && input.requiredEnvAlternatives !== undefined ) { issues.push({ path: "beignet.provider.requiredEnvAlternatives", message: "must not be set alongside beignet.provider.requiredEnv", }); } const requiredTables = parseStringArray( input.requiredTables, "beignet.provider.requiredTables", issues, ); if (requiredTables !== undefined) metadata.requiredTables = requiredTables; const watchers = parseStringArray( input.watchers, "beignet.provider.watchers", issues, ); if (watchers !== undefined) metadata.watchers = watchers; const appPorts = parseAppPorts(input.appPorts, issues); if (appPorts !== undefined) metadata.appPorts = appPorts; const registration = parseRegistration( input.registration, issues, "beignet.provider.registration", ); if (registration !== undefined) metadata.registration = registration; const variants = parseVariants(input.variants, issues); if (variants !== undefined) metadata.variants = variants; if (input.variants !== undefined) { if (input.requiredEnv !== undefined) { issues.push({ path: "beignet.provider.requiredEnv", message: "must not be set when beignet.provider.variants is present; declare requiredEnv on each variant instead", }); } if (input.requiredEnvAlternatives !== undefined) { issues.push({ path: "beignet.provider.requiredEnvAlternatives", message: "must not be set when beignet.provider.variants is present; declare requiredEnvAlternatives on each variant instead", }); } if (input.registration !== undefined) { issues.push({ path: "beignet.provider.registration", message: "must not be set when beignet.provider.variants is present; declare registration on each variant instead", }); } if (input.requiredTables !== undefined) { issues.push({ path: "beignet.provider.requiredTables", message: "must not be set when beignet.provider.variants is present; declare requiredTables on each variant instead", }); } } if (issues.length > 0) { return { success: false, issues }; } return { success: true, metadata }; } function parseStringArray( value: unknown, path: string, issues: ProviderPackageMetadataIssue[], ): string[] | undefined { if (value === undefined) return undefined; if (!Array.isArray(value)) { issues.push({ path, message: "must be an array of strings" }); return undefined; } const parsed: string[] = []; for (const [index, entry] of value.entries()) { if (typeof entry !== "string") { issues.push({ path: `${path}[${index}]`, message: "must be a string", }); continue; } parsed.push(entry); } return parsed; } function parseStringArrayArray( value: unknown, path: string, issues: ProviderPackageMetadataIssue[], ): string[][] | undefined { if (value === undefined) return undefined; if (!Array.isArray(value)) { issues.push({ path, message: "must be an array of string arrays" }); return undefined; } if (value.length === 0) { issues.push({ path, message: "must include at least one alternative when present", }); return undefined; } const parsed: string[][] = []; for (const [index, entry] of value.entries()) { const entryPath = `${path}[${index}]`; const alternative = parseStringArray(entry, entryPath, issues); if (alternative === undefined) continue; if (alternative.length === 0) { issues.push({ path: entryPath, message: "must include at least one environment variable", }); continue; } parsed.push(alternative); } return parsed; } function validateRequiredEnvMembers( requiredEnv: readonly string[], env: readonly string[] | undefined, path: string, envPath: string, issues: ProviderPackageMetadataIssue[], ): void { const envSet = new Set(env ?? []); for (const envVar of requiredEnv) { if (!envSet.has(envVar)) { issues.push({ path, message: `${envVar} must also be listed in ${envPath}`, }); } } } function parseAppPorts( value: unknown, issues: ProviderPackageMetadataIssue[], ): ProviderPackagePortMetadata[] | undefined { if (value === undefined) return undefined; const path = "beignet.provider.appPorts"; if (!Array.isArray(value)) { issues.push({ path, message: "must be an array of port metadata objects" }); return undefined; } const parsed: ProviderPackagePortMetadata[] = []; for (const [index, entry] of value.entries()) { const entryPath = `${path}[${index}]`; if (!isRecord(entry)) { issues.push({ path: entryPath, message: "must be an object" }); continue; } if (typeof entry.name !== "string") { issues.push({ path: `${entryPath}.name`, message: "must be a string" }); } if (typeof entry.type !== "string") { issues.push({ path: `${entryPath}.type`, message: "must be a string" }); } if (typeof entry.name === "string" && typeof entry.type === "string") { parsed.push({ name: entry.name, type: entry.type }); } } return parsed; } function parseVariants( value: unknown, issues: ProviderPackageMetadataIssue[], ): ProviderPackageVariantMetadata[] | undefined { if (value === undefined) return undefined; const path = "beignet.provider.variants"; if (!Array.isArray(value)) { issues.push({ path, message: "must be an array of variant objects" }); return undefined; } if (value.length === 0) { issues.push({ path, message: "must include at least one variant when present", }); return undefined; } const parsed: ProviderPackageVariantMetadata[] = []; const seenNames = new Set<string>(); for (const [index, entry] of value.entries()) { const entryPath = `${path}[${index}]`; if (!isRecord(entry)) { issues.push({ path: entryPath, message: "must be an object" }); continue; } let name: string | undefined; if (typeof entry.name !== "string" || entry.name.length === 0) { issues.push({ path: `${entryPath}.name`, message: "must be a non-empty string", }); } else if (seenNames.has(entry.name)) { issues.push({ path: `${entryPath}.name`, message: `duplicates variant name "${entry.name}"`, }); } else { seenNames.add(entry.name); name = entry.name; } let displayName: string | undefined; if (entry.displayName !== undefined) { if (typeof entry.displayName !== "string") { issues.push({ path: `${entryPath}.displayName`, message: "must be a string", }); } else { displayName = entry.displayName; } } const env = parseStringArray(entry.env, `${entryPath}.env`, issues); const requiredEnv = parseStringArray( entry.requiredEnv, `${entryPath}.requiredEnv`, issues, ); if (requiredEnv !== undefined) { validateRequiredEnvMembers( requiredEnv, env, `${entryPath}.requiredEnv`, `${entryPath}.env`, issues, ); } const requiredEnvAlternatives = parseStringArrayArray( entry.requiredEnvAlternatives, `${entryPath}.requiredEnvAlternatives`, issues, ); if (requiredEnvAlternatives !== undefined) { for (const alternative of requiredEnvAlternatives) { validateRequiredEnvMembers( alternative, env, `${entryPath}.requiredEnvAlternatives`, `${entryPath}.env`, issues, ); } } if ( entry.requiredEnv !== undefined && entry.requiredEnvAlternatives !== undefined ) { issues.push({ path: `${entryPath}.requiredEnvAlternatives`, message: `must not be set alongside ${entryPath}.requiredEnv`, }); } const requiredTables = parseStringArray( entry.requiredTables, `${entryPath}.requiredTables`, issues, ); const registration = parseRegistration( entry.registration, issues, `${entryPath}.registration`, ); if (name === undefined) continue; const variant: ProviderPackageVariantMetadata = { name }; if (displayName !== undefined) variant.displayName = displayName; if (env !== undefined) variant.env = env; if (requiredEnv !== undefined) variant.requiredEnv = requiredEnv; if (requiredEnvAlternatives !== undefined) { variant.requiredEnvAlternatives = requiredEnvAlternatives; } if (requiredTables !== undefined) variant.requiredTables = requiredTables; if (registration !== undefined) variant.registration = registration; parsed.push(variant); } return parsed; } function parseRegistration( value: unknown, issues: ProviderPackageMetadataIssue[], path: string, ): ProviderPackageRegistrationMetadata | undefined { if (value === undefined) return undefined; if (!isRecord(value)) { issues.push({ path, message: "must be an object" }); return undefined; } const registration: ProviderPackageRegistrationMetadata = {}; if (value.required !== undefined) { if (typeof value.required !== "boolean") { issues.push({ path: `${path}.required`, message: "must be a boolean" }); } else { registration.required = value.required; } } if (value.severity !== undefined) { if (value.severity !== "warning" && value.severity !== "hint") { issues.push({ path: `${path}.severity`, message: 'must be "warning" or "hint"', }); } else { registration.severity = value.severity; } } if (registration.required === true && registration.severity === "hint") { issues.push({ path: `${path}.severity`, message: 'must not be "hint" when registration is required', }); } const tokens = parseStringArray(value.tokens, `${path}.tokens`, issues); if (tokens !== undefined) registration.tokens = tokens; if ((tokens?.length ?? 0) === 0) { if (registration.required === true) { issues.push({ path: `${path}.tokens`, message: "must include at least one token when registration is required", }); } else if (registration.severity !== undefined) { issues.push({ path: `${path}.tokens`, message: "must include at least one token when registration severity is set", }); } } return registration; } function isRecord(value: unknown): value is Record<string, unknown> { return typeof value === "object" && value !== null && !Array.isArray(value); }