@beignet/core
Version:
Core framework primitives for Beignet
498 lines (446 loc) • 14.2 kB
text/typescript
/**
* 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);
}